前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >嵌入式编程之战:裸机VS实时系统

嵌入式编程之战:裸机VS实时系统

作者头像
云深无际
发布2025-02-20 00:08:02
发布2025-02-20 00:08:02
10600
代码可运行
举报
文章被收录于专栏:云深之无迹
运行总次数:0
代码可运行

嵌入式系统开发可以分为 裸机(Bare Metal)RTOS(实时操作系统) 两种方式。

裸机开发意味着 没有 RTOS,程序直接运行在 MCU 上,所有任务调度 完全由开发者手动管理。裸机开发适用于 简单任务,低功耗系统,小型嵌入式设备,但当任务复杂到一定程度,就需要 RTOS 来管理。

裸机编程是 不依赖 RTOS,直接操作 MCU 硬件 的开发方式,通常采用:✅ 主循环(Super Loop)中断驱动(Interrupt Driven)状态机(State Machine)

代码语言:javascript
代码运行次数:0
复制
int main() {
    hardware_init();  // 硬件初始化(GPIO、UART、I2C、SPI)
    while (1) {  // 主循环
        task1();  // 处理传感器数据
        task2();  // 处理按键输入
        task3();  // 处理显示屏刷新
    }
}

这个编程起来就非常的简单,如程序所示。

说说裸机的问题->

  1. 任务管理困难:多个任务需要手动安排执行顺序 .因为是从头运行到尾部,一些子任务复杂,就要占用大量的CPU时间,下面的任务就执行不了。
  2. 实时性差:任务执行时间不可预测,容易卡死。这条就是1的后半段,后面的一些任务就饿死了。
  3. 同步与通信难:多个任务共享资源时,容易发生数据竞争。裸机之间的信息传输是需要global变量,这就有很多意想不到的问题,比如数据竞争什么的。
  4. 代码难以维护:任务逻辑复杂后,裸机程序难以维护。这个我觉得不是裸机的问题,纯粹就是写多了,都复杂。

这里总结了一些场景和用途

✅ 简单的单任务/少任务嵌入式系统 → 适合裸机 ✅ 复杂的多任务、实时性要求高的系统 → 需要 RTOS

主循环(Super Loop),简单系统,如 LED 控制、按键扫描

代码语言:javascript
代码运行次数:0
复制
void main() {
    hardware_init();  // 硬件初始化
    while (1) {
        led_task();
        button_task();
        uart_task();
    }
}

这种代码最喜欢🌶

中断驱动(Interrupt Driven),事件驱动系统,如 UART、ADC 采集

代码语言:javascript
代码运行次数:0
复制
void USART_IRQHandler() {  
    char data = USART_ReceiveData();  
    process_data(data);  
}

✅ 减少 CPU 负担,数据来了才处理 ❌ 任务间通信难,如果多个中断抢占,容易丢失数据,因为中断来的 时候,当前的数据是放在栈里面。

定时器 + 任务调度,定期任务,如 100ms 采集传感器数据

代码语言:javascript
代码运行次数:0
复制
void TIM2_IRQHandler() {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        sensor_read_task();
        display_update_task();
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

✅ 定期任务调度,提高实时性 ❌ 任务增多时,定时器调度难以管理。也就是说定时器的时间戳给每个子任务使用。

状态机(State Machine),按键检测、设备模式切换

代码语言:javascript
代码运行次数:0
复制
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;
        }
    }
}

✅ 适用于复杂逻辑(如工控、状态机管理) ❌ 状态增多时,代码可读性下降

代码语言:javascript
代码运行次数:0
复制
void Task1(void *argument) {
    while (1) {
        printf("任务 1 运行\n");
        vTaskDelay(1000);
    }
}

void Task2(void *argument) {
    while (1) {
        printf("任务 2 运行\n");
        vTaskDelay(2000);
    }
}

void main() {
    xTaskCreate(Task1, "Task1", 512, NULL, 1, NULL);
    xTaskCreate(Task2, "Task2", 512, NULL, 2, NULL);
    vTaskStartScheduler();
}

然后我们就可以写这种任务

RTC外设有什么用? 昨天不是写了一个RTC吗?我一直对定时器情有独钟,那我们就分析一下设计一个时钟怎么做?

闹钟应用通常需要 精准的时间管理、低功耗运行、定时唤醒,最佳选择是 MCU 内部的 RTC(Real-Time Clock)外设。

如果是设计一个引爆器,就是裸机了,感觉港剧里面的起爆器太low了,剪线?笑死,我把所有的传感器都加上,复杂的判断条件,精通的定时,谁都别想活。

  1. 初始化 RTC
  2. 设置 RTC 闹钟
  3. 进入低功耗模式
  4. RTC 到时间触发中断,唤醒 MCU
  5. 播放蜂鸣器 / LED 提示
  6. 等待用户停止闹钟

流程大概就是这样吧?

代码语言:javascript
代码运行次数:0
复制
#include "stm32f4xx.h"

void RTC_Alarm_IRQHandler(void) {
    if (RTC->ISR & RTC_ISR_ALRAF) {
        RTC->ISR &= ~RTC_ISR_ALRAF; // 清除闹钟标志位
        printf("闹钟响了!\n");
        buzzer_on();  // 打开蜂鸣器
    }
}

void RTC_SetAlarm(uint8_t hour, uint8_t min, uint8_t sec) {
    RTC->ALRMAR = (hour << 16) | (min << 8) | sec;  // 设置闹钟时间
    RTC->CR |= RTC_CR_ALRAIE;  // 使能闹钟中断
}

void main() {
    RTC_Init();  // 初始化 RTC
    RTC_SetAlarm(7, 30, 0);  // 设置闹钟 7:30:00
    enter_low_power_mode();  // 进入低功耗模式
}

闹钟触发后 MCU 唤醒,执行 buzzer_on() 响铃只能管理一个闹钟,多个闹钟需要手动写代码管理

我们来看看RTOS,

  1. 创建 RTC 任务
  2. 任务循环监听 RTC 时间
  3. 管理多个闹钟列表
  4. RTC 触发闹钟,发送任务通知
  5. 任务处理响铃逻辑
  6. 支持低功耗模式
代码语言:javascript
代码运行次数:0
复制
#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;  // 闹钟队列

void RTC_Alarm_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xTaskNotifyFromISR(AlarmTaskHandle, 0, eNoAction, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

void AlarmTask(void *pvParameters) {
    AlarmTime_t alarm;
    while (1) {
        if (xQueueReceive(xAlarmQueue, &alarm, portMAX_DELAY)) {
            printf("闹钟响了!时间:%02d:%02d:%02d\n", alarm.hour, alarm.min, alarm.sec);
            buzzer_on();
        }
    }
}

void main() {
    xAlarmQueue = xQueueCreate(5, sizeof(AlarmTime_t));  // 创建闹钟队列
    xTaskCreate(AlarmTask, "AlarmTask", 512, NULL, 1, &AlarmTaskHandle);
    vTaskStartScheduler();
}

✅ 支持多个闹钟,闹钟时间可以存入队列 ✅ 任务管理,优先级可控,实时性高 ✅ 可以在 buzzer_on() 之后,延迟一定时间自动关闭蜂鸣器

总结一下

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-02-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云深之无迹 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 主循环(Super Loop),简单系统,如 LED 控制、按键扫描
  • 中断驱动(Interrupt Driven),事件驱动系统,如 UART、ADC 采集
  • 定时器 + 任务调度,定期任务,如 100ms 采集传感器数据
  • 状态机(State Machine),按键检测、设备模式切换
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档