Modern Embedded Systems Programming: A Comprehensive Guide

All summaries and code snippets in this document are based on the Modern Embedded Systems Programming YouTube playlist by Dr. Miro Samek.

Welcome to this in-depth blog series on Modern Embedded Systems Programming! Whether you’re a student, hobbyist, or professional engineer, this guide distills the essential lessons, code patterns, and best practices from Dr. Miro Samek’s acclaimed YouTube course. Each module is summarized with practical code examples and concludes with actionable insights to help you apply what you’ve learned to real-world embedded projects.


🏛️ Module 1: Foundations of Computer Architecture & C Programming

This foundational module introduces you to the core concepts of how computers and microcontrollers work. You will learn the essential building blocks of the C programming language, which is the lingua franca of embedded systems.

  • #0 Modern Embedded Systems Programming: Getting Started
    https://www.youtube.com/watch?v=hnj-7XwTYRI Summary:
    Introduces the course, the importance of understanding both hardware and software in embedded systems, and the ARM Cortex-M platform. Discusses the course structure, hardware (TivaC LaunchPad, STM32 Nucleo), and tools (IAR EWARM, KEIL MDK, TI CCS). Emphasizes hands-on learning and the relevance of ARM Cortex-M in the industry. No new code introduced in this video.
  • #1 How computers count?
    https://www.youtube.com/watch?v=gQOv8o5lS2k Summary:
    Explains how computers represent and manipulate numbers using binary, hexadecimal, and decimal systems. Covers unsigned and signed integers, two’s complement, and the importance of understanding number systems for embedded programming. Key code concepts:
  // Example: Converting decimal to binary
  int value = 42;
  printf("%d in binary is %08b\n", value, value);
  // Two's complement for negative numbers
  int8_t neg = -42;
  printf("%d as two's complement: 0x%02X\n", neg, (uint8_t)neg);
  • #2 How to change the flow of control through your code
    https://www.youtube.com/watch?v=cZj284kfuE8 Summary:
    Introduces control flow statements in C: if, else, switch, for, while, and do-while. Explains how these constructs allow programs to make decisions and repeat actions, which is essential for embedded systems that must respond to changing inputs. Key code concepts:
  // If-else example
  if (temperature < 0) {
      printf("Frozen\n");
  } else {
      printf("Not frozen\n");
  }

  // For loop example
  for (int i = 0; i < 10; i++) {
      printf("%d\n", i);
  }
  • #3 Variables and Pointers
    https://www.youtube.com/watch?v=o9WpXYBqdPU Summary:
    Explains variables, memory addresses, and pointers in C. Shows how pointers are used to reference and manipulate memory directly, which is crucial for embedded programming (e.g., accessing hardware registers). Key code concepts:
  int a = 10;
  int *ptr = &a; // Pointer to a
  printf("Value: %d, Address: %p\n", *ptr, (void*)ptr);

  // Pointer arithmetic
  int arr[3] = {1, 2, 3};
  int *p = arr;
  printf("Second element: %d\n", *(p + 1));
  • #4 How to control the world outside?
    https://www.youtube.com/watch?v=1Kjh0CAgnl4 Summary:
    Introduces memory-mapped I/O and how microcontrollers interact with the outside world (e.g., turning on LEDs, reading switches) by writing to and reading from specific memory addresses. Explains the concept of registers and how to use pointers to access them. Key code concepts:
  #define GPIO_PORTA_DATA_R (*((volatile unsigned long *)0x400043FC))
  // Set PA5 high
  GPIO_PORTA_DATA_R |= (1 << 5);
  // Set PA5 low
  GPIO_PORTA_DATA_R &= ~(1 << 5);
  • #5 Preprocessor and the “volatile” keyword in C
    https://www.youtube.com/watch?v=5MzilJ2-MGY Summary:
    Explains the C preprocessor (macros, #define, #include, #ifdef) and the critical role of the volatile keyword in embedded programming. volatile tells the compiler not to optimize accesses to a variable, because its value may change at any time (e.g., hardware registers, variables modified by interrupts). Key code concepts:
  // Preprocessor macro
  #define LED_PIN 5
  #define GPIO_PORTA_DATA_R (*((volatile unsigned long *)0x400043FC))

  // Using volatile for hardware register
  volatile int flag = 0;
  void ISR() {
      flag = 1; // Set in interrupt
  }
  int main() {
      while (!flag) {
          // Wait for interrupt
      }
  }

Why use volatile?

  • Prevents the compiler from optimizing out repeated reads/writes to variables that can change outside the program’s flow (e.g., by hardware or interrupts).
  • Essential for correct operation when polling hardware registers or using flags set in ISRs.

Module 1 Conclusion:

In this module, you learned how computers represent data, control program flow, and interact with hardware using C. Mastering these basics is crucial for any embedded developer, as they form the bedrock for all advanced topics to come.


⚙️ Module 2: Bare-Metal Programming & Interrupt Handling

Here, you’ll dive deeper into the low-level details of embedded systems. This module covers what happens when a microcontroller powers on (startup code) and how it responds to external events using interrupts, a cornerstone of embedded programming.

  • #13 Startup Code Part-1: What is startup code and how the CPU gets from reset to main?
    https://www.youtube.com/watch?v=zFAnW7Tzu4U Summary:
    Explains the critical role of startup code in embedded systems. Startup code is the first code executed after a microcontroller reset. It initializes the stack pointer, copies initialized data from ROM to RAM, zeros the BSS section (uninitialized data), sets up the vector table, and finally jumps to the main() function. This process ensures the C runtime environment is ready before user code runs. Key code concepts:
  // Example: Vector table definition (ARM Cortex-M)
  uint32_t vectors[] __attribute__((section(".isr_vector"))) = {
      STACK_START,           // Initial stack pointer
      (uint32_t)Reset_Handler, // Reset handler
      // ... other exception and interrupt handlers ...
  };

  void Reset_Handler(void) {
      // Copy .data section from flash to SRAM
      // Zero .bss section
      // Call main()
      main();
  }
  • #14 Startup Code Part-2: Replacing the vector-table, embedded software build process
    https://www.youtube.com/watch?v=DfiwWxTRIZE Summary:
    Focuses on the vector table, which holds addresses of all exception and interrupt handlers. Shows how to replace or extend the default vector table to add custom ISRs. Also covers the embedded software build process, including linking and memory layout. Key code concepts:
  // Vector table with weakly defined handlers
  void __attribute__((weak)) Default_Handler(void) { while (1); }
  void __attribute__((weak)) NMI_Handler(void) { Default_Handler(); }
  // ...
  uint32_t vectors[] __attribute__((section(".isr_vector"))) = {
      STACK_START,
      (uint32_t)Reset_Handler,
      (uint32_t)NMI_Handler,
      // ...
  };
  • #15 Startup Code Part-3: Vector table initialization, exception handlers, interrupt handlers
    https://www.youtube.com/watch?v=42HbCf5cz5A Summary:
    Explains how the vector table is initialized and how exception and interrupt handlers are set up. Discusses weak linking, allowing user code to override default handlers. Shows how the reset handler initializes memory and calls constructors for static objects (in C++). Key code concepts:
  // Weak attribute allows user to override handlers
  void __attribute__((weak)) HardFault_Handler(void) { while (1); }
  // ...
  // In C++: call static constructors
  extern void (*__init_array_start[])(void);
  extern void (*__init_array_end[])(void);
  for (auto p = __init_array_start; p != __init_array_end; ++p) (*p)();
  • #16 Interrupts Part-1: What are interrupts, and how they work
    https://www.youtube.com/watch?v=jP1JymlHUtc Summary:
    Introduces interrupts as a mechanism for the CPU to respond to asynchronous events. Explains the difference between polling and interrupt-driven designs. Describes the interrupt vector table, how the CPU saves context, and how ISRs (Interrupt Service Routines) are executed. Key code concepts:
  // Example: Simple ISR in C (AVR)
  ISR(INT0_vect) {
      // Handle external interrupt
  }
  // ARM Cortex-M: Just define the function with the correct name
  void EXTI0_IRQHandler(void) {
      // Handle external interrupt
  }
  • #17 Interrupts Part-2: How most CPUs (e.g. MSP430) handle interrupts?
    https://www.youtube.com/watch?v=o_UaEcbfodo Summary:
    Explains the interrupt handling process in classic microcontrollers like the MSP430. Covers interrupt priorities, vector tables, and how the CPU saves and restores context. Discusses the importance of keeping ISRs short and clearing interrupt flags. Key code concepts:
  // MSP430 ISR example
  #pragma vector=PORT1_VECTOR
  __interrupt void Port_1(void) {
      P1IFG &= ~BIT3; // Clear interrupt flag
  }
  • #18 Interrupts Part-3: How interrupts work on ARM Cortex-M?
    https://www.youtube.com/watch?v=O0Z1D6p7J5A Summary:
    Details the ARM Cortex-M interrupt system, including the Nested Vectored Interrupt Controller (NVIC). Explains automatic context saving, interrupt priorities, and how to enable/disable interrupts. Shows how to write ISRs for Cortex-M and the importance of the volatile keyword for shared variables. Key code concepts:
  // ARM Cortex-M ISR
  void EXTI0_IRQHandler(void) {
      // Clear interrupt flag
      EXTI->PR = 1;
  }
  // Shared variable between main and ISR
  volatile int flag = 0;
  void EXTI0_IRQHandler(void) { flag = 1; }
  • #19 GNU-ARM Toolchain and Eclipse IDE
    https://www.youtube.com/watch?v=BBF3ZMi8WK4 Summary:
    Introduces the GNU-ARM toolchain and Eclipse-based IDEs for embedded development. Covers project setup, compiling, linking, and debugging embedded code. Shows how to configure startup files, linker scripts, and how to flash/debug code on ARM Cortex-M boards. No new code introduced, but demonstrates toolchain and IDE usage.

Module 2 Conclusion:

You now understand the startup process, vector tables, and how interrupts enable responsive, real-time behavior in embedded systems. These skills are essential for writing reliable firmware that interacts directly with hardware.


⏱️ Module 3: Concurrency & Real-Time Operating Systems (RTOS)

This module introduces the critical concepts of concurrency and real-time systems. You will learn about the challenges of managing simultaneous tasks (race conditions) and explore the architecture of Real-Time Operating Systems (RTOS) that manage complexity in sophisticated embedded applications.

  • #20 Race Conditions: What are they and how to avoid them?
    https://www.youtube.com/watch?v=3ha72Y8pyD4 Summary:
    Explains race conditions in embedded systems—when two or more tasks access shared resources simultaneously, leading to unpredictable behavior. Discusses best practices to avoid race conditions: minimize shared resources, use atomic operations, employ synchronization mechanisms (mutexes, semaphores), and use interrupts sparingly. Emphasizes the importance of thorough testing and documentation. Key code concepts:
  // Example: Using a mutex to protect shared resource
  pthread_mutex_t lock;
  pthread_mutex_lock(&lock);
  // Access shared resource
  pthread_mutex_unlock(&lock);
  • #21 Foreground-Background Architecture (“Superloop”)
    https://www.youtube.com/watch?v=AoLLKbvEY8Q Summary:
    Introduces the “superloop” architecture, where the main loop repeatedly calls tasks in sequence. Foreground tasks (interrupts) handle urgent events, while background tasks run in the main loop. This simple cooperative multitasking model is common in embedded systems without an RTOS. Key code concepts:
  int main(void) {
      while (1) {
          Task1();
          Task2();
          // ...
      }
  }
  // Interrupts handle urgent events
  void ISR(void) {
      // Handle interrupt
  }
  • #22 RTOS Part-1: What is a Real-Time Operating System?
    https://www.youtube.com/watch?v=TEq3-p0GWGI Summary:
    Explains the concept of an RTOS and how it differs from a simple superloop. An RTOS manages multiple tasks, provides scheduling, and ensures real-time deadlines are met. Introduces basic RTOS concepts: tasks, context switching, and scheduling. No new code introduced, but discusses RTOS architecture.
  • #23 RTOS Part-2: Automating the context switch
    https://www.youtube.com/watch?v=PKml9ki3178 Summary:
    Shows how an RTOS automates context switching between tasks, allowing multiple tasks to run seemingly in parallel. Explains the role of the scheduler and how the CPU state is saved/restored during a context switch. Key code concepts:
  // Pseudocode for context switch
  void context_switch(Task *next) {
      save_cpu_state(current_task);
      restore_cpu_state(next);
      current_task = next;
  }
  • #24 RTOS Part-3: Automating the scheduling with round-robin policy
    https://www.youtube.com/watch?v=PX_LDyiXs5Y Summary:
    Explains round-robin scheduling, where each task gets a fixed time slice. The scheduler cycles through tasks, giving each a chance to run. Demonstrates how this improves responsiveness compared to a simple superloop. Key code concepts:
  // Pseudocode for round-robin scheduler
  while (1) {
      for (int i = 0; i < NUM_TASKS; i++) {
          run_task(tasks[i]);
      }
  }
  • #25 RTOS Part-4: Efficient blocking of threads
    https://www.youtube.com/watch?v=JurV5BgjQ50 Summary:
    Discusses how an RTOS can block tasks that are waiting for events (e.g., I/O, timers) instead of busy-waiting. This allows the CPU to run other tasks or enter low-power mode, improving efficiency. Key code concepts:
  // Example: Blocking on a semaphore
  xSemaphoreTake(xSemaphore, portMAX_DELAY);
  // Task blocks until semaphore is available
  • #26 RTOS Part-5: What is “real-time”? Preemptive, priority-based scheduling
    https://www.youtube.com/watch?v=kLxxXNCrY60 Summary:
    Defines “real-time” in embedded systems—meeting deadlines predictably. Introduces preemptive, priority-based scheduling, where higher-priority tasks can interrupt lower-priority ones. Explains how this ensures critical tasks meet their deadlines. Key code concepts:
  // Example: Setting task priority (FreeRTOS)
  xTaskCreate(TaskFunc, "Task", STACK_SIZE, NULL, PRIORITY_HIGH, NULL);
  • #27 RTOS Part-6: Synchronization and communication among concurrent threads
    https://www.youtube.com/watch?v=IrDcBZX0AdY Summary:
    Covers synchronization mechanisms (semaphores, mutexes, event flags) and inter-task communication (queues, mailboxes). Shows how these tools prevent race conditions and enable safe data sharing between tasks. Key code concepts:
  // Example: Sending data between tasks using a queue
  xQueueSend(queue, &data, portMAX_DELAY);
  xQueueReceive(queue, &data, portMAX_DELAY);
  • #28 RTOS Part-7: Mutual exclusion mechanisms
    https://www.youtube.com/watch?v=kcpVI3IjUUM Summary:
    Focuses on mutual exclusion (mutexes, critical sections) to protect shared resources. Discusses priority inversion and solutions like priority inheritance. Emphasizes the importance of designing for safe concurrency in real-time systems. Key code concepts:
  // Example: Using a mutex for mutual exclusion
  xSemaphoreTake(mutex, portMAX_DELAY);
  // Access shared resource
  xSemaphoreGive(mutex);

Module 3 Conclusion:

Concurrency and real-time scheduling are at the heart of modern embedded applications. By mastering RTOS concepts and synchronization techniques, you can design systems that are both efficient and robust under real-world conditions.


🧩 Module 4: Advanced Programming Paradigms

Move beyond procedural programming into more advanced, modern software architectures. This section covers Object-Oriented Programming (OOP) principles, event-driven systems, and the powerful concept of State Machines for designing robust and predictable behavior.

  • #29 OOP Part-1: Encapsulation (classes) in C and C++
    https://www.youtube.com/watch?v=dSLodtKuung Summary:
    Introduces encapsulation, the concept of bundling data and functions that operate on that data within a single unit (class/struct). Shows how to use structs in C and classes in C++ to achieve encapsulation, improving code organization and modularity. Key code concepts:
  // C: Encapsulation using struct
  typedef struct {
      int value;
      void (*set)(int v);
  } MyClass;

  // C++: Encapsulation using class
  class MyClass {
      int value;
  public:
      void set(int v) { value = v; }
  };
  • #30 OOP Part-2: Inheritance in C and C++
    https://www.youtube.com/watch?v=oS3a7wn9P_s Summary:
    Explains inheritance, allowing new types to be defined based on existing ones. In C, inheritance is mimicked by embedding structs; in C++, it is supported natively. Inheritance enables code reuse and polymorphic behavior. Key code concepts:
  // C: Inheritance by struct embedding
  typedef struct {
      int base_value;
  } Base;
  typedef struct {
      Base base;
      int derived_value;
  } Derived;

  // C++: Inheritance
  class Base {
  public:
      int base_value;
  };
  class Derived : public Base {
      int derived_value;
  };
  • #31 OOP Part-3: Polymorphism in C++
    https://www.youtube.com/watch?v=xHMje9fL1Bk Summary:
    Covers polymorphism, where a base class pointer can refer to objects of derived classes, and virtual functions allow dynamic method dispatch. This enables flexible and extensible code. Key code concepts:
  class Base {
  public:
      virtual void foo() { /* ... */ }
  };
  class Derived : public Base {
      void foo() override { /* ... */ }
  };
  Base* obj = new Derived();
  obj->foo(); // Calls Derived::foo
  • #32 OOP Part-4: Polymorphism in C
    https://www.youtube.com/watch?v=2v_qM5SJDlY Summary:
    Demonstrates how to achieve polymorphism in C using function pointers within structs. This allows different implementations to be swapped at runtime, mimicking virtual functions in C++. Key code concepts:
  typedef struct {
      void (*foo)(void* self);
  } Base;
  typedef struct {
      Base base;
      // ...
  } Derived;
  void derived_foo(void* self) { /* ... */ }
  Derived d = { .base = { .foo = derived_foo } };
  d.base.foo(&d);
  • #33 Event-Driven Programming Part-1: GUI example, events, event-loop, run-to-completion, no-blocking
    https://www.youtube.com/watch?v=rfb2JI1GGIc Summary:
    Introduces event-driven programming, where the flow is determined by events (e.g., user input, hardware signals). Shows how event loops process events and how run-to-completion semantics avoid blocking. Key code concepts:
  // Simple event loop
  while (1) {
      Event e = get_next_event();
      dispatch_event(e);
  }
  • #34 Event-Driven Programming Part-2: Best practices for concurrency & Active Object pattern
    https://www.youtube.com/watch?v=l69ghMpsp6w Summary:
    Discusses best practices for concurrency in event-driven systems, introducing the Active Object pattern. Each active object has its own event queue and thread of control, improving modularity and responsiveness. Key code concepts:
  // Active Object pattern (simplified)
  typedef struct {
      Queue event_queue;
      void (*dispatch)(Event);
  } ActiveObject;
  void active_object_run(ActiveObject* ao) {
      while (1) {
          Event e = dequeue(&ao->event_queue);
          ao->dispatch(e);
      }
  }
  • #35 State Machines Part-1: What is a state machine?
    https://www.youtube.com/watch?v=EBSxZKjgBXI Summary:
    Introduces finite state machines (FSMs) as a way to model system behavior. Explains states, events, transitions, and actions. Shows how FSMs are used in embedded systems for control logic. Key code concepts:
  typedef enum { STATE_IDLE, STATE_ACTIVE } State;
  State state = STATE_IDLE;
  void dispatch(Event e) {
      switch (state) {
          case STATE_IDLE:
              if (e == EVENT_START) state = STATE_ACTIVE;
              break;
          case STATE_ACTIVE:
              if (e == EVENT_STOP) state = STATE_IDLE;
              break;
      }
  }
  • #36 State Machines Part-2: Guard conditions
    https://www.youtube.com/watch?v=RGQRllvrMyY Summary:
    Explains guard conditions—boolean expressions that must be true for a transition to occur. Shows how to implement guards in state machine code for more precise control. Key code concepts:
  if (state == STATE_ACTIVE && e == EVENT_STOP && can_stop()) {
      state = STATE_IDLE;
  }
  // can_stop() is a guard condition
  • #37 State Machines Part-3: Input-Driven State Machines
    https://www.youtube.com/watch?v=E2Im7jLDDG4 Summary:
    Explains input-driven state machines, where transitions are triggered by external inputs or events. Shows how to structure code so that each event is processed and the state is updated accordingly. Key code concepts:
  void process_input(Event e) {
      dispatch(e); // Event triggers state transition
  }
  // Main loop
  while (1) {
      Event e = get_next_event();
      process_input(e);
  }
  • #38 State Machines Part-4: State Tables and Entry/Exit Actions
    https://www.youtube.com/watch?v=dwRsgSBcawU Summary:
    Introduces state tables as a way to represent state transitions in a tabular format. Discusses entry and exit actions for states, which are executed when entering or leaving a state. Key code concepts:
  typedef struct {
      State current;
      Event event;
      State next;
      void (*action)(void);
  } StateTableEntry;
  // Example entry/exit actions
  void on_entry(void) { /* ... */ }
  void on_exit(void) { /* ... */ }
  • #39 State Machines Part-5: Optimal Implementation in C
    https://www.youtube.com/watch?v=FCymm6PBtOs Summary:
    Shows how to implement state machines efficiently in C using function pointers and state handler functions. This approach improves modularity and performance. Key code concepts:
  typedef void (*StateHandler)(Event);
  void StateIdle(Event e) { /* ... */ }
  void StateActive(Event e) { /* ... */ }
  StateHandler state = StateIdle;
  // Transition
  state = StateActive;
  • #40 State Machines Part-6: What is a Hierarchical State Machine?
    https://www.youtube.com/watch?v=lUvUNuUMQHo Summary:
    Explains hierarchical state machines (HSMs), where states can be nested within other states. HSMs allow for more scalable and maintainable designs by sharing behavior among related states. Key code concepts:
  // Pseudocode for HSM
  void handle_event(Event e) {
      if (!substate_handle(e)) {
          superstate_handle(e);
      }
  }
  • #41 State Machines Part-7: Automatic Code Generation
    https://www.youtube.com/watch?v=FHV5vZyECOA Summary:
    Discusses tools and techniques for automatically generating state machine code from diagrams or tables. This reduces errors and speeds up development. No new code introduced, but demonstrates code generation tools.
  • #42 State Machines Part-8: Semantics of Hierarchical State Machines
    https://www.youtube.com/watch?v=NxV7JlU0-F4 Summary:
    Explores the semantics of hierarchical state machines, including event propagation, entry/exit actions, and state history. Emphasizes the importance of well-defined semantics for predictable behavior. Key code concepts:
  // Example: State history
  State last_active_substate;
  void on_exit_superstate(void) {
      last_active_substate = current_substate;
  }
  void on_entry_superstate(void) {
      current_substate = last_active_substate;
  }

Module 4 Conclusion:

Advanced programming paradigms like OOP, event-driven design, and state machines empower you to build scalable, maintainable, and flexible embedded software. These techniques are invaluable for tackling complex, real-world projects.


🚀 Module 5: Active Objects & Real-Time Design

This advanced module connects the concepts of event-driven programming and real-time systems through the Active Object design pattern. You’ll learn how this powerful pattern helps in building concurrent, safe, and highly efficient real-time applications.

  • #43 Active Objects in Real-Time Part-1: Run-to-Completion and RMS/RMA
    https://www.youtube.com/watch?v=6VhvmJVvaKY Summary:
    Introduces the Active Object (AO) design pattern for embedded systems, which encapsulates data and behavior, runs in its own thread, and processes events from a queue. Explains run-to-completion semantics and how AOs help avoid race conditions and blocking, improving responsiveness and modularity. Discusses Rate Monotonic Scheduling (RMS) and Rate Monotonic Analysis (RMA) for real-time systems. Key code concepts:
  typedef struct {
      Queue event_queue;
      void (*dispatch)(Event);
  } ActiveObject;
  void AO_run(ActiveObject* ao) {
      while (1) {
          Event e = dequeue(&ao->event_queue);
          ao->dispatch(e);
      }
  }
  • #44 Active Objects in Real-Time Part-2: Mutable Events
    https://www.youtube.com/watch?v=e_Mfvat9mAA Summary:
    Explores the use of mutable events in active object systems, where events can carry data and be reused or recycled for efficiency. Discusses event ownership, memory management, and zero-copy event passing to avoid unnecessary data duplication and improve performance. Key code concepts:
  typedef struct {
      int type;
      void* data;
  } Event;
  // Event recycling example
  void recycle_event(Event* e) {
      // Return event to pool for reuse
  }

Module 5 Conclusion:

The Active Object pattern bridges the gap between event-driven and real-time programming, enabling you to design systems that are both responsive and safe from concurrency bugs. Mastery of this pattern is a hallmark of professional embedded developers.


🛠️ Module 6: Software Quality & Professional Practices

Writing code that works is only part of the job. This module focuses on professional practices that ensure your code is robust, maintainable, and reliable. Topics include debugging, testing, and creating traceable documentation.

  • #45 Software Tracing with printf
    https://www.youtube.com/watch?v=i8lYMDcyLAM Summary:
    Demonstrates using printf for software tracing and debugging in embedded systems. Covers redirecting printf output to UART, SWO, or RTT, and discusses the trade-offs (code size, performance, reentrancy). Emphasizes the importance of non-intrusive tracing and alternatives to printf for production code. Key code concepts:
  // Redirect printf to UART
  int _write(int file, char *ptr, int len) {
      for (int i = 0; i < len; i++) {
          uart_send_char(ptr[i]);
      }
      return len;
  }
  // Using SWO for printf
  int __io_putchar(int ch) {
      ITM_SendChar(ch);
      return ch;
  }
  • #46 Software Tracing with Binary Protocols
    https://www.youtube.com/watch?v=rwjV7LBOE8A Summary:
    Introduces binary protocols for efficient software tracing, enabling high-speed, low-overhead logging. Shows how to implement binary trace messages, parse them on the host, and use tools for visualization. Highlights the benefits over text-based tracing, especially for real-time systems. Key code concepts:
  // Example: Binary trace message
  typedef struct {
      uint8_t id;
      uint32_t timestamp;
      uint16_t value;
  } TraceMsg;
  void send_trace(const TraceMsg* msg) {
      uart_send_bytes((uint8_t*)msg, sizeof(TraceMsg));
  }
  • #47 Assertions and Design by Contract, Part-1
    https://www.youtube.com/watch?v=cnEhFwo4u5g Summary:
    Explains the Design by Contract (DbC) philosophy and the use of assertions in embedded C/C++. Shows how to use assertions to catch bugs early, document code contracts, and improve reliability. Discusses the difference between errors and exceptional conditions, and how to implement custom assertion macros for embedded systems. Key code concepts:
  #define ASSERT(test_) ((test_) ? (void)0 : onAssert(__FILE__, __LINE__))
  void onAssert(const char* file, int line) {
      // Handle assertion failure (e.g., log, reset, halt)
  }
  // Usage
  ASSERT(ptr != NULL);
  • #48 Assertions and Design by Contract, Part-2
    https://www.youtube.com/watch?v=yHBmzQQVVas Summary:
    Continues the discussion on assertions and DbC, focusing on best practices for using assertions in embedded systems. Covers preconditions, postconditions, and invariants, and how to structure code for better testability and maintainability. Emphasizes leaving assertions enabled in production for safety-critical systems. Key code concepts:
  #define REQUIRE(test_) ASSERT(test_)
  #define ENSURE(test_)  ASSERT(test_)
  #define INVARIANT(test_) ASSERT(test_)
  // Example usage
  void foo(int x) {
      REQUIRE(x > 0);
      // ...
      ENSURE(result != 0);
  }
  • #49 Embedded Unit Testing
    https://www.youtube.com/watch?v=kcpHuuEt1xs Summary:
    Explains how to write unit tests for embedded systems, focusing on the importance of testing in the development process. Discusses test frameworks, test doubles, and how to integrate testing into the build process. Key code concepts:
  // Example: Simple unit test
  void test_add(void) {
      int a = 10;
      int b = 20;
      ASSERT(add(a, b) == 30);
  }
  • #50 To block or NOT to block, that is the question! Blocking as technical debt…
    https://www.youtube.com/watch?v=qEIL-meKgLs Summary:
    Discusses the trade-offs between blocking and non-blocking approaches in embedded systems. Explains how busy-waiting can be inefficient and how event-driven programming can lead to more efficient designs. Key code concepts:
  // Example: Busy-waiting vs Event-driven
  while (1) {
      // Busy-waiting
      if (condition) {
          // Do something
      }
      // Event-driven
      Event e = get_next_event();
      if (e == EVENT_READY) {
          // Handle event
      }
  }
  • #51 Traceable Documentation with Doxygen & Spexygen
    https://www.youtube.com/watch?v=5naC_z-76Aw Summary:
    Explains how to use Doxygen and Spexygen for generating professional documentation from source code. Covers documenting functions, classes, and modules, and how to integrate documentation into the build process. Key code concepts:
  // Example: Doxygen comment
  /**
   * @brief Adds two numbers.
   * @param a First number.
   * @param b Second number.
   * @return Sum of a and b.
   */
  int add(int a, int b) {
      return a + b;
  }

Module 6 Conclusion:

Professional practices like unit testing, assertions, and documentation are vital for delivering high-quality embedded software. Adopting these habits will set you apart as a reliable and effective engineer.


💡 Module 7: Advanced Architectural Patterns

The final module explores advanced topics that bring together everything you’ve learned. You’ll look at optimizing for low power and implementing more sophisticated scheduling and kernel designs for event-driven systems.

  • #52 Using low-power sleep modes in the “superloop” architecture
    https://www.youtube.com/watch?v=Fe6HoMsrplw Summary:
    Explains how to implement low-power sleep modes in the “superloop” architecture to conserve energy. Covers different sleep modes (idle, stop, standby) and how to manage power consumption during sleep. Key code concepts:
  // Example: Entering sleep mode
  void enter_sleep(void) {
      // Disable interrupts
      __disable_irq();
      // Set sleep mode
      SCB->SCR |= SCB_SCR_SLEEPONEXIT_Msk;
      // Wait for wakeup
      while (1) {
          if (SCB->SCR & SCB_SCR_SLEEPDEEP_Msk) {
              // Wakeup from deep sleep
          }
      }
      // Re-enable interrupts
      __enable_irq();
  }
  • #53 Priority-based scheduler for the “superloop” architecture
    https://www.youtube.com/watch?v=VzgOqOSPS1o Summary:
    Explains how to implement a priority-based scheduler within the “superloop” architecture. Discusses how to manage task priorities, how to handle preemption, and how to ensure critical tasks are always serviced. Key code concepts:
  // Example: Priority-based scheduler
  typedef struct {
      int priority;
      void (*task_func)(void*);
      void* task_arg;
  } Task;
  void scheduler_run(Task* tasks, int num_tasks) {
      while (1) {
          // Find highest priority task
          Task* highest_priority_task = NULL;
          for (int i = 0; i < num_tasks; i++) {
              if (tasks[i].priority > 0) { // Only consider non-idle tasks
                  if (highest_priority_task == NULL || tasks[i].priority > highest_priority_task->priority) {
                      highest_priority_task = &tasks[i];
                  }
              }
          }
          if (highest_priority_task != NULL) {
              // Execute task
              highest_priority_task->task_func(highest_priority_task->task_arg);
          } else {
              // No tasks to run, enter idle mode
              enter_sleep();
          }
      }
  }
  • #54 Non-preemptive QV Kernel for Active Objects
    https://www.youtube.com/watch?v=lXkAQR7TNOg Summary:
    Introduces the concept of a kernel for managing multiple active objects. Discusses how to implement a non-preemptive kernel, where active objects run in a round-robin fashion. Explains how to manage task priorities and how to handle event delivery. Key code concepts:
  // Example: Non-preemptive QV Kernel
  typedef struct {
      Queue event_queue;
      void (*dispatch)(Event);
      int priority;
  } AO;
  void QV_kernel_run(AO* aos, int num_aos) {
      while (1) {
          // Find highest priority AO
          AO* highest_priority_ao = NULL;
          for (int i = 0; i < num_aos; i++) {
              if (aos[i].priority > 0) { // Only consider non-idle AOs
                  if (highest_priority_ao == NULL || aos[i].priority > highest_priority_ao->priority) {
                      highest_priority_ao = &aos[i];
                  }
              }
          }
          if (highest_priority_ao != NULL) {
              // Dispatch events for highest priority AO
              Event e = dequeue(&highest_priority_ao->event_queue);
              highest_priority_ao->dispatch(e);
          } else {
              // No AOs to run, enter idle mode
              enter_sleep();
          }
      }
  }
  • #55 QP/C Real-Time Embedded Framework (RTEF) for ARM Cortex-M
    https://www.youtube.com/watch?v=QFQ5FvQZkq8 Summary:
    Introduces the QP/C Real-Time Embedded Framework (RTEF) for ARM Cortex-M microcontrollers. Explains how QP/C provides a modern, event-driven, active object framework for building robust, maintainable, and scalable embedded applications. Demonstrates the integration of hierarchical state machines, active objects, and real-time scheduling in a professional-grade framework. Shows how to set up a QP/C project, configure the framework, and run a sample application on ARM Cortex-M hardware. Key code concepts:
  // Example: QP/C Active Object definition
  QActive *AO_Blinky;
  void Blinky_ctor(void) {
      AO_Blinky = Q_NEW(QActive);
      QActive_ctor(AO_Blinky, Q_STATE_CAST(&Blinky_initial));
  }
  // Event posting
  QACTIVE_POST(AO_Blinky, Q_NEW(QEvt, BLINKY_TIMEOUT_SIG), me);
  // State handler
  QState Blinky_initial(Blinky * const me, QEvt const * const e) {
      // ...
      return Q_TRAN(&Blinky_blinking);
  }

Module 7 Conclusion:

Advanced architectural patterns, such as low-power design and custom kernels, allow you to push the boundaries of embedded systems. These skills are essential for building innovative, efficient, and future-proof products.


Thank you for reading! If you found this guide helpful, be sure to check out the full video series by Dr. Miro Samek for hands-on demonstrations and deeper dives into each topic.

Leave a Reply

Your email address will not be published. Required fields are marked *