Embedded Programming: Bare Metal vs Real-Time Systems

Embedded system development can be divided into Bare Metal and RTOS (Real-Time Operating System).

Bare metal development means no RTOS, with programs running directly on the MCU, where all task scheduling is completely managed by the developer. Bare metal development is suitable for simple tasks, low-power systems, and small embedded devices, but when tasks become complex, an RTOS is needed for management.

Embedded Programming: Bare Metal vs Real-Time Systems

Bare metal programming is a development approach that does not rely on RTOS and directly manipulates MCU hardware, typically employing: ✅ Super LoopInterrupt DrivenState Machine

int main() {    hardware_init();  // Hardware initialization (GPIO, UART, I2C, SPI)    while (1) {  // Main loop        task1();  // Handle sensor data        task2();  // Handle button input        task3();  // Handle display refresh    }}

This programming is very simple, as shown in the program.

Let’s talk about the issues with bare metal ->

  1. Task management is difficult: multiple tasks need to be manually arranged in execution order. Since it runs from start to finish, if some subtasks are complex, they can consume a large amount of CPU time, preventing subsequent tasks from executing.

  2. Poor real-time performance: task execution times are unpredictable and can easily cause deadlocks. This is the latter part of point 1, where subsequent tasks may starve.

  3. Synchronization and communication are challenging: when multiple tasks share resources, data races can easily occur. Information transfer between bare metal tasks requires global variables, leading to many unexpected issues, such as data races.

  4. Code is difficult to maintain: as task logic becomes complex, bare metal programs become hard to maintain. I think this is not a problem with bare metal itself; it’s purely due to writing too much, which makes it complex.

Embedded Programming: Bare Metal vs Real-Time Systems

Here are some summarized scenarios and uses

Simple single-task/few-task embedded systems → Suitable for bare metalComplex multi-task systems with high real-time requirements → Require RTOS

Super Loop, simple systems like LED control and button scanning

void main() {    hardware_init();  // Hardware initialization    while (1) {        led_task();        button_task();        uart_task();    }}

This kind of code is my favorite🌶

Interrupt Driven, event-driven systems like UART and ADC acquisition

void USART_IRQHandler() {      char data = USART_ReceiveData();      process_data(data);  }

Reduces CPU burden, processes data only when it arrivesTask communication is difficult, if multiple interrupts are preempted, data can easily be lost because current data is stored on the stack during interrupts.

Timer + Task Scheduling, periodic tasks like collecting sensor data every 100ms

void TIM2_IRQHandler() {    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {        sensor_read_task();        display_update_task();        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);    }}

Periodic task scheduling improves real-time performanceAs tasks increase, timer scheduling becomes difficult to manage. In other words, the timer’s timestamp is used for each sub-task.

State Machine, button detection, and device mode switching

typedef enum { IDLE, PROCESSING, ERROR } SystemState;SystemState current_state = IDLE;void main() {    while (1) {        switch (current_state) {            case IDLE:                  if (button_pressed()) current_state = PROCESSING;                break;            case PROCESSING:                  process_task();                current_state = IDLE;                break;            case ERROR:                error_handle();                break;        }    }}

Suitable for complex logic (such as industrial control, state machine management)As states increase, code readability decreases

void Task1(void *argument) {    while (1) {        printf("Task 1 running\n");        vTaskDelay(1000);    }}void Task2(void *argument) {    while (1) {        printf("Task 2 running\n");        vTaskDelay(2000);    }}void main() {    xTaskCreate(Task1, "Task1", 512, NULL, 1, NULL);    xTaskCreate(Task2, "Task2", 512, NULL, 2, NULL);    vTaskStartScheduler();}

Then we can write such tasks

What is the use of RTC peripherals? Didn’t I write an RTC yesterday? I have always had a fondness for timers, so let’s analyze how to design a clock.

Alarm applications often require precise time management, low-power operation, and timed wake-ups, making the best choice the internal RTC (Real-Time Clock) peripheral of the MCU.

Embedded Programming: Bare Metal vs Real-Time Systems

If designing a detonator, it would be bare metal; I feel that the detonators in Hong Kong dramas are too low-tech, cutting wires? Laughing out loud, I would add all sensors, complex judgment conditions, and precise timing; no one would survive.

  1. Initialize RTC

  2. Set RTC alarm

  3. Enter low-power mode

  4. RTC triggers interrupt when the time is up, waking up the MCU

  5. Play buzzer/LED alert

  6. Wait for user to stop the alarm

The process is roughly like this, right?

#include "stm32f4xx.h"void RTC_Alarm_IRQHandler(void) {    if (RTC->ISR & RTC_ISR_ALRAF) {        RTC->ISR &= ~RTC_ISR_ALRAF; // Clear alarm flag        printf("The alarm is ringing!\n");        buzzer_on();  // Turn on the buzzer    }}void RTC_SetAlarm(uint8_t hour, uint8_t min, uint8_t sec) {    RTC->ALRMAR = (hour << 16) | (min << 8) | sec;  // Set alarm time    RTC->CR |= RTC_CR_ALRAIE;  // Enable alarm interrupt}void main() {    RTC_Init();  // Initialize RTC    RTC_SetAlarm(7, 30, 0);  // Set alarm for 7:30:00    enter_low_power_mode();  // Enter low power mode}

After the alarm is triggered, the MCU wakes up and executes <span>buzzer_on()</span> to ringCan only manage one alarm; multiple alarms require manual code management

Now let’s look at RTOS,

  1. Create RTC task

  2. Task loop listens for RTC time

  3. Manage a list of multiple alarms

  4. RTC triggers alarm, sends task notification

  5. Task handles ringing logic

  6. Supports low-power mode

#include "FreeRTOS.h"#include "task.h"#include "queue.h"TaskHandle_t AlarmTaskHandle;typedef struct {    uint8_t hour;    uint8_t min;    uint8_t sec;} AlarmTime_t;QueueHandle_t xAlarmQueue;  // Alarm queuevoid RTC_Alarm_IRQHandler(void) {    BaseType_t xHigherPriorityTaskWoken = pdFALSE;    xTaskNotifyFromISR(AlarmTaskHandle, 0, eNoAction, &amp;xHigherPriorityTaskWoken);    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);}void AlarmTask(void *pvParameters) {    AlarmTime_t alarm;    while (1) {        if (xQueueReceive(xAlarmQueue, &amp;alarm, portMAX_DELAY)) {            printf("The alarm is ringing! Time: %02d:%02d:%02d\n", alarm.hour, alarm.min, alarm.sec);            buzzer_on();        }    }}void main() {    xAlarmQueue = xQueueCreate(5, sizeof(AlarmTime_t));  // Create alarm queue    xTaskCreate(AlarmTask, "AlarmTask", 512, NULL, 1, &amp;AlarmTaskHandle);    vTaskStartScheduler();}

Supports multiple alarms; alarm times can be stored in a queueTask management, controllable priority, high real-time performanceCan automatically turn off the buzzer after a delay following <span>buzzer_on()</span>

Embedded Programming: Bare Metal vs Real-Time Systems

To summarize

Leave a Comment