嵌入式常用设计模式

44 阅读4分钟

一、架构与调度模式

这是嵌入式系统的骨架,决定了程序执行流程。

1.前后台模式

这个是最基础、最常用的裸机模式。

  • 原理:

    • 后台:一个无限循环(while(1)),负责处理对实时性要求不高的任务(如LCD显示、数据处理)。
    • 前台:中断服务程序,负责处理实时性要求高的事件(如按钮检测,ADC采集,串口接收)。
  • 适用场景: 逻辑简单、低成本、无需RTOS的中小型MCU项目。

  • 代码结构:

    void main() {
        Init();
        while(1) {
            if (flag_10ms) {
                Task_10ms();
                flag_10ms = 0;
            }   // 后台
            if (flag_process_data) {
                ProcessData();
                flag_process_data = 0;
            }
        }
    }
    void ISR_Timer() {
        flag_10ms = 1;
    }
    

2.时间触发模式

  • 原理: 基于定时器构建一个简单的调度器。将任务划分为不同周期(如10ms,100ms,1s),在定时中断中计数,在主循环中按时间片轮询执行。
  • 优点: 执行时序确定性强,会比单纯的Super Loop更有条理。
  • 缺点: 如果某个任务执行时间过长,会阻塞后续任务,导致“抖动”。

3.分层架构模式

为了解决移植性差的问题,必须将硬件与业务逻辑剥离。

  • 结构:

    1. 硬件层:MCU、外设。
    2. 驱动层:直接寄存器操作,提供API(如Create,Read,Write)。
    3. 中间件/功能模块层:协议层、文件系统、算法库(与硬件无关)。
    4. 应用层:具体的业务逻辑。
  • 核心思想:上层调用下层,下层不依赖上层(通过回调函数除外)。

二、状态管理模式

嵌入式设备本质时状态机(如:待机、运行、故障、设置)。

1.有限状态机

  • 实现方式A:Switch-Case法

    • 最简单,适用于状态比较少的情况。
    • 缺点:随着状态增加,代码变成“面条代码”,难以维护。
  • 实现方式B:函数指针/查表法

    • 原理:定义状态枚举和事件枚举,建立一个二位数组(或结构体数组),存储当前状态、事件-> 下一个状态 & 执行动作。

    • 优点:逻辑清晰,增加状态只需要修改表格,O(1)查找效率。

    • 代码示例:

      typedef struct {
          State_t nextState;
          void (*action)(void);
      } StateTrans_t;
      ​
      StateTrans_t stateTable[MAX_STATES][MAX_EVENTS];
      ​
      void HandleEvent(Event_t evt) {
          StateTrans_t *trans = &stateTable[currentState][evt];
          if (trans->action) trans->action();
          currentState = trans->nexState;
      }
      

三、数据处理与通信模式

1.生产者-消费者模式

  • 核心组件:环形缓冲区。
  • 场景:UART接收中断(生产者)高速写入数据,主循环解析任务(消费者)低速读取数据。
  • 作用:解决速率不匹配问题,解耦中断和主线程,防止数据丢失。
  • 注意:需要处理读写指针的原子性(尤其是在裸机中断中)。

2.观察者模式

  • 场景:一个传感器数据(如温度)更新后,需要通知:屏幕显示模块、日志存储模块、网络上传模块。

  • 传统写法:在温度采集函数里直接调用Display_Update(),Log_Write(),Net_Send()。耦合度高。

  • 模式写法:

    • 定义一个“主题”。
    • 各模块注册回调函数到主题列表。
    • 数据更新时,便利列表调用回调。
  • 优点:增加新的接收模块时,不需要修改采集模块的代码。

四、C语言面向对象模式

C语言虽然不是面向对象语言,但在嵌入式驱动开发中,模拟OOP时非常高级且常用的技巧。

1.结构体封装与堕胎

  • 场景:系统中有3个不同的传感器(I2C接口、SPI接口、ADC接口),但上层应用只想调用Sensor_Read()

  • 实现:

    // 定义接口(基类)
    typedef struct {
        uint8_t (*init)(void);
        uint16_t (*read)(void);
    } sensor_driver_t;
    ​
    // 具体实现(子类)
    uint16_t temp_sensor_read() {/* I2C mode */}
    uint16_t light_sensor_read() {/* ADC mode */}
    ​
    // 实例化
    sensor_driver_t temp_driver = {.read = temp_sensor_read};
    sensor_driver_t light_driver = {.read = light_sensor_read};
    ​
    // 多态调用
    void app_read(sensor_driver_t *driver) {
        uint16_t val = driver->read();
    }
    

2.单例模式

  • 场景:硬件资源是唯一的(例如MCU只有一个LCD控制器或一个配置管理器)。
  • 实现:在C语言中,通常通过结构体实例设为static并仅提供get_instance()接口,或者直接隐藏数据结构,只暴露操作API来实现。

五、可靠性与安全模式

1.哨兵模式/看门狗

  • 策略:不要在中断中喂狗,也不要在主循环的一个地方喂狗。

  • 任务流监控模式:

    • 每个关键任务执行完成后设置一个标志位。
    • 仅当所有标志位都置位是,才真正喂硬件看门狗。
    • 这能检测“程序没死机,但某个传感器任务卡死”的情况。

2.双缓冲模式

  • 场景:DMA数据传输(如音频播放、高速ADC采样)

  • 原理:适用两个buffer(A和B)。

    • DMA正在向buffer A写入数据。
    • CPU同时处理buffer B的数据。
    • A写满后,DMA自动切换写B,CPU开始处理A。
  • 作用:提高吞吐量,实现无缝连续的数据流处理。