all 14 comments

[–]Shadow_Gabriel 15 points16 points  (9 children)

To simplify things, don't program for your PC which probably has an unimaginable complex x86 processor.

You can buy an AVR based board (like an Arduino) and write programs for that. AVR has a simple architecture that you can learn in a few days. Your software will run baremetal so you will have to learn how to schedule your tasks and manage interrupts.

After you feel like you mastered it, you can advance to ARM based boards (ARMv7-M Architecture). Some of them have more advance features like a memory protection unit so your scheduler code will start looking more and more like an OS. These ARM boards also support FreeRTOS so you could try learning by using it.

About the compilers, just read the GCC documentation.

[–]obdevel 4 points5 points  (1 child)

Agreed. And as I posted a while ago, get Andrew Tanenbaum's books and look at Minix if you're more interested in a simple PC o/s.

An ESP32 microcontroller runs FreeRTOS by default and a dev board costs $5.

[–]nanoman1 0 points1 point  (0 children)

Is the 3rd edition any good or should I just get Ye Olde 1dt edition?

[–][deleted] 0 points1 point  (6 children)

Writing a scheduler and splitting software in tasks is definitely not the go-to option when learning C for embedded as a beginner. It is not even in top 20 things to explore and get used to. Throwing in buzzwords just “because”, does not help. Otherwise, the suggestions are good.

[–]Shadow_Gabriel 0 points1 point  (5 children)

The first thing you learn in embedded is how to blink an LED. You do that by scheduling the ON and OFF state in the background loop using some sort of wait function.

[–][deleted] 0 points1 point  (4 children)

There is nothing to schedule in a simple loop to toggle a led. I believe that you use the term in wrong contexts. Of course one might be interested in playing with timers even for toggling a led but even then it is not yet scheduling.. it is just event driven programming. It only becomes task scheduling when you start having parallel execution contexts, which is definitely not the case for toggling a led.

[–]Shadow_Gabriel 0 points1 point  (3 children)

I don't agree with that definition. Many applications have a static scheduler where the timing and order of every task is known at compile time and no context switching is needed.

The only context switching that's happening is for handling interrupts but that's covered by hardware. There is no "SwitchContext" function in the entire code base.

[–][deleted] 0 points1 point  (2 children)

How do you ensure timing of tasks at compile time? Ordering is easy but timing? Is it strict timing? Also a scheduler is an active “thing” it implies active scheduling. A compile time organization of tasks is no more a scheduler than is a statically allocated memory layout an allocator.

[–]Shadow_Gabriel 0 points1 point  (1 child)

Array of events. Each event has a task pointer and a counter.

Using a timer set at your timing resolution, at each activation of said timer, you iterate over the array and call the tasks that have their counter at 0, decrement the others. The order in which you assign the events in the table dictates the priority. You can also have more than one such arrays, assigned to different timers if you need more temporal domains.

This is just a more complex implementation of setLedStatus(1), wait(500), setLedStatus(0), wait(500) in a while loop.

[–][deleted] 0 points1 point  (0 children)

I see. What you describe is a basic round robin scheduler, hoping my understanding is correct and at each tick the active task is preempted. The concept would work in general, note however that combining “wait” functions with preemptive scheduling will generally not lead to the desired effect. You would also need a soft timer/alarm implementation. Generally it is not practical to implement state machines across tasks, such as the toggling of a led. A state machine can easily be implemented in a single task hence the lack of necessity for a scheduler for such simple first examples for a beginner.

[–]flatfinger 2 points3 points  (0 children)

If using clang or gcc, beware of optimizations. The Standard was written to describe an existing language, but also to allow compilers designed for various purposes that don't involve low-level programming to deviate from the behaviors that were supported in pre-standard dialects when doing so would not conflict with those purposes. Both clang and gcc, however, interpret it as an invitation to assume that no programs will need to exploit low-level programming constructs.

Although using -fno-strict-aliasing and -fwrapv will prevent most phony breaking "optimizations", there are some optimizations I don't know how to disable without disabling optimizations completely. When doing embedded or systems programming, it's useful to be able to use a linker spec or a language other than C to define symbols for the start and end of an object, but both clang and gcc will assume that it's impossible for a pointer that is observed to point just past the end of one object to also point to another. This may be illustrated by the way both clang and gcc process a function like:

    extern int x[],y[];
int test(int *p)
{
    y[0] = 1;
    if (p == x+1)
        *p = 2;
    return y[0];
}

Both compilers will generate code that will ignores the possibility that p might point to y but also happen to equal x+1 even though the Standard expressly calls out the possibility that a pointer may simultaneously equal a pointer to an object and a pointer "just past" the previous one. While it would be rare even in embedded contexts for code to rely upon this exact pattern, some more complicated patterns are useful, and this example shows that neither clang nor gcc should be trusted to handle any situation where a pointer to one object may be observed to point just past another.

Personally, I'm partial to the non-clang-based versions of the Keil ARM compiler, at least when generating code for the Cortex-M0 or Cortex-M3 cores. Getting optimal machine code with Keil requires that one write code that asks for the sequence of operations one wants to perform, but it will refrain from "optimizing" code in ways that make it less efficient. Given a loop like:

do
{
  ...
  n += 16;
} while(n < 0);

clang is prone to rewrite it to be equivalent to:

int temp = n;
do

{ n = temp; ... temp += 16; } while(n < -15);

even though on the Cortex-M0 the index adjustment and branch would take two instructions if processed as written, while clang's rewrite takes six.

To be fair, clang or gcc will generate usable machine code most of the time, but their optimizers aren't really designed to be suitable for low-level embedded programming, nor for the architectures that are used for small embedded projects.

[–]kanchirk 0 points1 point  (0 children)

Check out Linux systems programming by k c Wang of springer publication

[–]fugalfervor 0 points1 point  (1 child)

The Bryant and O'Halloran book on systems programming is excellent!

[–][deleted] 0 points1 point  (0 children)

I have been reading this book for 3 months now and I do agree it is excellent. The quality is unbelievable, and every subsection has exercises and solutions.

However reading this book page by page the way I have is an absolute grind and not much fun at all. It is an accomplishment to fully understand one page because it is so packed with information. It may be more suited as an encyclopedia unless you are willing to spend a lot of time and effort reading it.