嵌入式C设计模式完全指南(基于《C嵌入式编程设计模式》)

手把手教你用C语言实现硬件抽象层、并发调度、状态机、高可靠系统------所有模式附完整代码

目录

  1. 基础:用C模拟面向对象与状态机
  2. Harmony/ERT开发流程与模式选用方法
  3. 硬件访问设计模式(7种)
  4. 并发与资源管理设计模式(8种)
  5. 状态机实现设计模式(5种)
  6. 安全性与可靠性设计模式(6种)

第1章 基础:用C模拟面向对象与状态机

嵌入式C开发者常抱怨C不是面向对象语言。但书中给出了用C模拟类、继承、多态、状态机的成熟技术。这是理解后面所有模式的前提。

1.1 类(Class)的实现

核心思想 :用struct存放数据成员,用一组普通函数作为方法,每个方法的第一个参数是me指针(指向当前对象的指针)。使用头文件公开接口,C文件隐藏实现。

示例:Sensor类

c 复制代码
// Sensor.h
#ifndef SENSOR_H
#define SENSOR_H

typedef struct Sensor Sensor;   // 不透明指针

struct Sensor {
    int value;          // 属性
    int updateFreq;
    int filterFreq;
};

// 构造函数与析构函数
void Sensor_Init(Sensor* const me);
void Sensor_Cleanup(Sensor* const me);
Sensor* Sensor_Create(void);
void Sensor_Destroy(Sensor* const me);

// 成员函数(getter/setter)
int Sensor_getValue(const Sensor* const me);
void Sensor_setValue(Sensor* const me, int v);
int Sensor_getUpdateFreq(const Sensor* const me);
void Sensor_setUpdateFreq(Sensor* const me, int freq);
// ...

#endif
c 复制代码
// Sensor.c
#include "Sensor.h"
#include <stdlib.h>

void Sensor_Init(Sensor* const me) {
    me->value = 0;
    me->updateFreq = 100;
    me->filterFreq = 50;
}

void Sensor_Cleanup(Sensor* const me) {
    // 若有动态内存释放,在此处理
}

int Sensor_getValue(const Sensor* const me) {
    return me->value;
}

void Sensor_setValue(Sensor* const me, int v) {
    me->value = v;
}

int Sensor_getUpdateFreq(const Sensor* const me) {
    return me->updateFreq;
}

void Sensor_setUpdateFreq(Sensor* const me, int freq) {
    me->updateFreq = freq;
}

// 其它getter/setter...

Sensor* Sensor_Create(void) {
    Sensor* me = (Sensor*)malloc(sizeof(Sensor));
    if (me != NULL) {
        Sensor_Init(me);
    }
    return me;
}

void Sensor_Destroy(Sensor* const me) {
    if (me != NULL) {
        Sensor_Cleanup(me);
        free(me);
    }
}

关键点

  • 使用不透明指针(前向声明struct Sensor)隐藏实现。
  • _Init用于初始化已分配的内存(栈或静态对象),_Create动态分配并初始化。
  • 所有函数第一个参数都是me,指向当前对象。这模拟了面向对象中的this

1.2 多态与虚函数(Virtual Function)

核心思想 :在struct中存放函数指针,构造函数中将指针赋值为具体实现。客户通过函数指针调用,实现动态绑定。

示例:队列(Queue)

c 复制代码
// Queue.h
#ifndef QUEUE_H
#define QUEUE_H

#define QUEUE_SIZE 10

typedef struct Queue Queue;
struct Queue {
    int buffer[QUEUE_SIZE];
    int head, tail, size;
    
    // 虚函数指针
    int (*isFull)(Queue* const me);
    int (*isEmpty)(Queue* const me);
    void (*insert)(Queue* const me, int k);
    int (*remove)(Queue* const me);
};

void Queue_Init(Queue* const me);
void Queue_Cleanup(Queue* const me);

// 默认实现
int Queue_isFull(Queue* const me);
int Queue_isEmpty(Queue* const me);
void Queue_insert(Queue* const me, int k);
int Queue_remove(Queue* const me);

Queue* Queue_Create(void);
void Queue_Destroy(Queue* const me);

#endif
c 复制代码
// Queue.c
#include "Queue.h"
#include <stdlib.h>

void Queue_Init(Queue* const me) {
    me->head = 0;
    me->tail = 0;
    me->size = 0;
    // 设置虚函数指针
    me->isFull = Queue_isFull;
    me->isEmpty = Queue_isEmpty;
    me->insert = Queue_insert;
    me->remove = Queue_remove;
}

int Queue_isFull(Queue* const me) {
    return (me->size == QUEUE_SIZE);
}

int Queue_isEmpty(Queue* const me) {
    return (me->size == 0);
}

void Queue_insert(Queue* const me, int k) {
    if (!me->isFull(me)) {
        me->buffer[me->head] = k;
        me->head = (me->head + 1) % QUEUE_SIZE;
        me->size++;
    }
}

int Queue_remove(Queue* const me) {
    int val = -1;
    if (!me->isEmpty(me)) {
        val = me->buffer[me->tail];
        me->tail = (me->tail + 1) % QUEUE_SIZE;
        me->size--;
    }
    return val;
}

Queue* Queue_Create(void) {
    Queue* me = (Queue*)malloc(sizeof(Queue));
    if (me) Queue_Init(me);
    return me;
}

void Queue_Destroy(Queue* const me) {
    free(me);
}

多态的体现:子类可以覆盖这些函数指针,实现自己的行为(见下面的继承示例)。

1.3 继承(Inheritance)的C模拟

核心思想 :将基类对象作为子类结构体的第一个成员。这样基类指针可以指向子类,子类可以调用基类的方法。子类可以重写虚函数指针。

示例:带磁盘缓存的队列(CachedQueue)继承自Queue

c 复制代码
// CachedQueue.h
#ifndef CACHEDQUEUE_H
#define CACHEDQUEUE_H

#include "Queue.h"

typedef struct CachedQueue CachedQueue;
struct CachedQueue {
    Queue base;             // 基类必须放在第一个成员
    char filename[80];
    int numberOnDisk;
    Queue* outputQueue;     // 额外的聚合
    
    // 新的虚函数
    void (*flush)(CachedQueue* const me);
    void (*load)(CachedQueue* const me);
};

void CachedQueue_Init(CachedQueue* const me, char* fname);
void CachedQueue_Cleanup(CachedQueue* const me);

// 重写的虚函数实现
int CachedQueue_isFull(CachedQueue* const me);
int CachedQueue_isEmpty(CachedQueue* const me);
void CachedQueue_insert(CachedQueue* const me, int k);
int CachedQueue_remove(CachedQueue* const me);

void CachedQueue_flush(CachedQueue* const me);
void CachedQueue_load(CachedQueue* const me);

CachedQueue* CachedQueue_Create(char* fname);
void CachedQueue_Destroy(CachedQueue* const me);

#endif
c 复制代码
// CachedQueue.c 关键部分
#include "CachedQueue.h"
#include <string.h>

void CachedQueue_Init(CachedQueue* const me, char* fname) {
    // 初始化基类
    Queue_Init(&me->base);
    // 覆盖虚函数指针
    me->base.isFull = (int (*)(Queue*))CachedQueue_isFull;
    me->base.isEmpty = (int (*)(Queue*))CachedQueue_isEmpty;
    me->base.insert = (void (*)(Queue*, int))CachedQueue_insert;
    me->base.remove = (int (*)(Queue*))CachedQueue_remove;
    
    // 初始化自己的属性
    strcpy(me->filename, fname);
    me->numberOnDisk = 0;
    me->outputQueue = Queue_Create();
    
    // 设置新虚函数
    me->flush = CachedQueue_flush;
    me->load = CachedQueue_load;
}

int CachedQueue_isFull(CachedQueue* const me) {
    return Queue_isFull(&me->base) && Queue_isFull(me->outputQueue);
}

void CachedQueue_insert(CachedQueue* const me, int k) {
    if (Queue_isFull(&me->base)) {
        me->flush(me);   // 将基类队列刷新到磁盘
    }
    Queue_insert(&me->base, k);
}

解释

  • 子类CachedQueue的第一个成员是Queue base,这保证了内存布局上基类在前,因此(Queue*)cachedQueue是合法的。
  • 子类在_Init中把基类的虚函数指针替换成自己的函数,实现了重写。
  • 客户代码如果持有Queue*指针,调用insert时会调用到子类版本,实现了多态。

1.4 有限状态机(FSM)基础

状态机包含:状态(圆角矩形)、转换(箭头)、事件(触发转换)、动作(入口/出口/转换动作)。

最简单的C实现 :状态变量 + 事件分发函数中的switch-case

c 复制代码
// 状态枚举
typedef enum {
    STATE_IDLE,
    STATE_ACCEPTING,
    STATE_CHECKING_LEN,
    STATE_VALIDATING_PIN
} State;

// 事件类型枚举
typedef enum {
    EV_KEYPRESS,
    EV_CANCEL,
    EV_ENTER
} EventType;

// 事件结构(可携带数据)
typedef struct {
    EventType type;
    int key;       // 按键值
} Event;

State currentState;

void dispatchEvent(Event e) {
    switch (currentState) {
        case STATE_IDLE:
            if (e.type == EV_KEYPRESS && e.key == CANCEL_KEY) {
                // 出口动作
                // 转换动作
                currentState = STATE_IDLE;
                // 入口动作
            }
            break;
        case STATE_ACCEPTING:
            if (e.type == EV_KEYPRESS) {
                if (isDigit(e.key)) {
                    // 停留在ACCEPTING状态
                    addKey(e.key);
                } else if (e.key == ENTER_KEY) {
                    // 转换到CHECKING_LEN
                    currentState = STATE_CHECKING_LEN;
                }
            }
            break;
        // 其它状态...
    }
}

这个实现的问题:当状态和事件很多时,单个dispatchEvent函数会非常庞大且难以维护。后面的第5章会介绍更好的实现模式。


第2章 Harmony/ERT过程与模式选用方法

这一章不是设计模式,而是使用模式的方法论。核心是三个步骤:

  1. 先让功能正确:不考虑优化,实现一个可以工作的模型和代码,并通过单元测试。
  2. 识别并加权设计标准:列出你要优化的目标(如最坏情况执行时间、内存占用、可维护性、功耗等),给每个目标一个权重(0-10)。
  3. 选择并应用模式:对候选模式方案进行权衡分析(用电子表格),然后实例化模式。

书中案例:ECG心电数据分发系统。设计标准及权重:

  • 执行效率(权重7)
  • 可维护性(权重5)
  • 运行时灵活性(权重4)
  • 内存效率(权重7)

三个候选方案:客户-服务器(轮询)、推送、观察者模式。打分后观察者总分最高,因此选用。

模式实例化 :将模式中的抽象角色(如AbstractSubjectAbstractObserver)替换为具体类(TMDQueueWaveformDisplay等)。书中给出了完整的观察者模式C实现(已在3.4节详述)。


第3章 硬件访问设计模式

这一章包含7个模式,用于封装和优化硬件交互。

3.1 硬件代理模式(Hardware Proxy)

问题:直接操作硬件寄存器(内存映射、端口、位域)会导致硬件变化时大量代码需要修改。

解决方案 :创建一个代理类,封装所有硬件访问细节(地址、位操作、时序),对外提供高层API(如readValue()writeCmd())。

UML结构ProxyClientHardwareProxyHardwareDevice

HardwareProxy包含deviceAddr(void*或int),公有方法包括access()mutate()initialize()configure()disable(),私有方法marshal()/unmarshal()处理位编码。

C实现示例:马达控制器

c 复制代码
// MotorProxy.h
#ifndef MOTOR_PROXY_H
#define MOTOR_PROXY_H

#include <stdint.h>

typedef struct MotorProxy MotorProxy;
struct MotorProxy {
    volatile uint16_t* regAddr;   // 内存映射寄存器地址
    uint16_t rotaryArmLen;        // 旋臂长度,用于速度调整
};

void MotorProxy_Init(MotorProxy* me);
void MotorProxy_configure(MotorProxy* me, uint16_t* addr, uint16_t len);
void MotorProxy_enable(MotorProxy* me);
void MotorProxy_disable(MotorProxy* me);
void MotorProxy_setSpeed(MotorProxy* me, uint16_t speed);
uint16_t MotorProxy_getSpeed(MotorProxy* me);
uint8_t MotorProxy_getErrorStatus(MotorProxy* me);

#endif
c 复制代码
// MotorProxy.c
#include "MotorProxy.h"

// 私有函数:将用户数据打包成硬件寄存器格式
static uint16_t marshal(uint8_t enable, uint8_t direction, uint8_t speed) {
    uint16_t reg = 0;
    if (enable) reg |= (1 << 0);
    if (direction == 1) reg |= (1 << 1);
    reg |= (speed & 0x1F) << 3;
    return reg;
}

// 私有函数:解包硬件寄存器为用户数据
static void unmarshal(uint16_t reg, uint8_t* enable, uint8_t* direction, uint8_t* speed) {
    *enable = (reg >> 0) & 1;
    *direction = (reg >> 1) & 1;
    *speed = (reg >> 3) & 0x1F;
}

void MotorProxy_configure(MotorProxy* me, uint16_t* addr, uint16_t len) {
    me->regAddr = addr;
    me->rotaryArmLen = len;
}

void MotorProxy_setSpeed(MotorProxy* me, uint16_t speed) {
    // 根据旋臂长度调整实际电机速度(保持末端线速度恒定)
    uint16_t adjustedSpeed = speed;
    if (me->rotaryArmLen > 0) {
        adjustedSpeed = (uint16_t)((speed * 10) / (2 * 3.14159 * me->rotaryArmLen));
    }
    // 读取当前寄存器,只修改速度位
    uint16_t current = *me->regAddr;
    current &= ~(0x1F << 3);   // 清除速度位
    current |= (adjustedSpeed & 0x1F) << 3;
    *me->regAddr = current;
}

uint16_t MotorProxy_getSpeed(MotorProxy* me) {
    uint16_t reg = *me->regAddr;
    uint8_t enable, direction, speedRaw;
    unmarshal(reg, &enable, &direction, &speedRaw);
    // 反向调整,返回用户期望的末端速度
    if (me->rotaryArmLen > 0) {
        return (uint16_t)((speedRaw * 2 * 3.14159 * me->rotaryArmLen) / 10);
    }
    return speedRaw;
}

// 其他函数略...

效果 :硬件寄存器布局变化只需修改marshal/unmarshal和寄存器地址。客户代码完全不变。

3.2 硬件适配器模式(Hardware Adapter)

问题:已经写好的硬件代理提供的接口与系统期望的接口不匹配(例如参数类型、单位、函数名不同)。

解决方案:增加适配器层,将期望的接口调用转换为实际代理的调用。经典的对象适配器形式。

UML结构ClientHardwareAdapter(实现ClientInterface) → HardwareProxyDevice

C实现示例:两个不同的氧气传感器适配器

c 复制代码
// 期望的接口:返回0-100的浓度,返回cc/min的流量
int expected_getO2Conc(void);
int expected_getO2Flow(void);

// 实际传感器A:getO2Conc直接返回0-100,getO2Flow返回cc/sec
// 适配器A
int AcmeAdapter_getO2Conc(void* proxy) {
    return AcmeProxy_readConc(proxy);
}
int AcmeAdapter_getO2Flow(void* proxy) {
    return AcmeProxy_readFlow(proxy) * 60;   // cc/sec -> cc/min
}

// 实际传感器B:accessO2Conc返回0.000-1.000 double,accessGasFlow返回升/小时
// 适配器B
int UltimateAdapter_getO2Conc(void* proxy) {
    double conc = UltimateProxy_getConc(proxy);
    return (int)(conc * 100);
}
int UltimateAdapter_getO2Flow(void* proxy) {
    double flow_l_per_hr = UltimateProxy_getGasFlow(proxy);
    double total_cc_per_min = flow_l_per_hr * 1000.0 / 60.0;
    double o2_cc_per_min = total_cc_per_min * UltimateProxy_getConc(proxy);
    return (int)o2_cc_per_min;
}

效果:不改动客户代码和硬件代理,只需选择相应的适配器即可更换传感器。

3.3 中介者模式(Mediator)

问题:多个硬件设备(如机器人多关节伺服电机)需要复杂协调,如果它们直接相互通信,会导致网状依赖,难以维护。

解决方案:引入一个中介者对象,集中管理所有设备的协同逻辑。每个设备只和中介者通信。

UML结构Mediator持有所有Colaborator的引用,Colaborator在感兴趣的事件发生时通知Mediator

C实现示例:机器人手臂管理器

c 复制代码
// 关节类型
typedef struct RotatingJoint { int angle; } RotatingJoint;
typedef struct SlidingJoint { int position; } SlidingJoint;

// 中介者
typedef struct RobotArmManager {
    RotatingJoint* joints[4];
    SlidingJoint* slides[2];
    GraspingManipulator* gripper;
    
    // 预计算的动作序列
    struct Action {
        int joint1, joint2, joint3, joint4;
        int slide1, slide2;
        int gripperForce;
        int gripperOpen;
    } actions[100];
    int numSteps;
    int currentStep;
} RobotArmManager;

void RobotArmManager_computeTrajectory(RobotArmManager* me, int x, int y, int z, int t) {
    // 复杂运动规划算法,生成actions数组
    me->numSteps = ...;
}

int RobotArmManager_executeStep(RobotArmManager* me) {
    if (me->currentStep >= me->numSteps) return 0;
    struct Action* a = &me->actions[me->currentStep];
    // 向各个关节发送命令
    int err = 0;
    err |= RotatingJoint_moveTo(me->joints[0], a->joint1);
    err |= RotatingJoint_moveTo(me->joints[1], a->joint2);
    // ...
    if (a->gripperOpen)
        err |= GraspingManipulator_open(me->gripper);
    else
        err |= GraspingManipulator_close(me->gripper);
    me->currentStep++;
    return err;  // 0表示成功
}

效果:各关节之间无直接耦合,运动策略集中在中介者中,易于修改和调试。

3.4 观察者模式(Observer / Publish-Subscribe)

问题:传感器数据需要分发给多个未知的消费者。如果消费者轮询,效率低;如果服务器主动推,需要服务器知道所有消费者,破坏解耦。

解决方案 :服务器维护一个订阅者列表。消费者通过subscribe将自己的回调函数添加到列表。新数据到达时,服务器遍历列表调用回调。

UML结构AbstractSubject(有subscribeunsubscribenotify)和AbstractObserver(有update)。具体类实现它们。

C实现(书中GasSensor例子):

c 复制代码
// 回调函数类型
typedef void (*UpdateFunc)(void* instance, struct GasData* data);

// 通知句柄(链表节点)
typedef struct NotificationHandle {
    void* instance;
    UpdateFunc updateFunc;
    struct NotificationHandle* next;
} NotificationHandle;

// 主题(气体传感器)
typedef struct GasSensor {
    GasData* data;
    NotificationHandle* subscribers;
} GasSensor;

void GasSensor_subscribe(GasSensor* me, void* instance, UpdateFunc func) {
    NotificationHandle* nh = (NotificationHandle*)malloc(sizeof(NotificationHandle));
    nh->instance = instance;
    nh->updateFunc = func;
    nh->next = me->subscribers;
    me->subscribers = nh;
}

void GasSensor_notify(GasSensor* me) {
    NotificationHandle* nh = me->subscribers;
    while (nh) {
        nh->updateFunc(nh->instance, me->data);
        nh = nh->next;
    }
}

void GasSensor_newData(GasSensor* me, int flow, int o2, int n2) {
    // 更新data
    me->data->flow = flow;
    me->data->o2 = o2;
    me->data->n2 = n2;
    GasSensor_notify(me);
}

观察者示例

c 复制代码
typedef struct DisplayClient {
    // ...
} DisplayClient;

void DisplayClient_update(void* instance, GasData* data) {
    DisplayClient* me = (DisplayClient*)instance;
    printf("Flow: %d, O2: %d%%\n", data->flow, data->o2);
}

void main() {
    GasSensor sensor;
    DisplayClient display;
    GasSensor_subscribe(&sensor, &display, DisplayClient_update);
    GasSensor_newData(&sensor, 10, 21, 78);   // 触发回调
}

效果:完全解耦,支持动态增删订阅者。

3.5 去抖动模式(Debouncing)

问题:机械按钮、继电器等开关在闭合时会产生多次弹跳(几个毫秒内多次通断),导致多个中断或事件。

解决方案:收到第一个中断后,等待一段时间(通常20-50ms),再读取引脚状态。如果状态稳定且与之前不同,才认为是真实事件。

UML结构BouncingDevice(硬件)→ Debouncer(使用DebouncingTimer)→ ApplicationClient

C实现

c 复制代码
#define DEBOUNCE_MS 40

typedef struct ButtonDriver {
    int oldState;              // 上一次确认的状态
    int toggleOn;              // 应用状态
    Button* button;
    Timer* timer;
    MicrowaveEmitter* emitter;
} ButtonDriver;

void ButtonDriver_eventReceive(ButtonDriver* me) {
    // 等待40ms(忙等待或使用定时器)
    Timer_delay(me->timer, DEBOUNCE_MS);
    
    int newState = Button_getState(me->button);
    if (newState != me->oldState) {
        // 真实状态变化
        me->oldState = newState;
        if (newState == 0) {   // 按钮释放
            me->toggleOn = !me->toggleOn;
            if (me->toggleOn) {
                MicrowaveEmitter_start(me->emitter);
                Button_setBacklight(me->button, 1);
            } else {
                MicrowaveEmitter_stop(me->emitter);
                Button_setBacklight(me->button, 0);
            }
        }
    }
}

注意:实际使用中应用定时器中断或RTOS的延时函数,不要用忙等待浪费CPU。

3.6 中断模式(Interrupt)

问题:高紧急事件需要立即响应。轮询不能满足实时性。

解决方案:编写中断服务程序(ISR),将它的地址安装到中断向量表。ISR做最少的工作(保存数据到队列、清除中断标志),然后返回。复杂处理在后台任务中执行。

关键点

  • ISR必须保存和恢复CPU寄存器(编译器interrupt关键字可自动处理)。
  • ISR不能调用可能阻塞的函数。
  • ISR与任务共享数据时必须用原子操作或临界区。

C实现(以8051为例,使用Keil扩展)

c 复制代码
// 安装中断向量
void install_button_isr(void) {
    // 保存原向量
    old_isr = (void (*)())ISR_TABLE[INT0_VECTOR];
    // 设置新向量
    ISR_TABLE[INT0_VECTOR] = button_isr;
}

// ISR
void button_isr(void) interrupt 0 {
    // 读取按键值
    uint8_t key = P1 & 0x0F;
    // 放入队列(无锁队列,需确保原子性)
    key_queue_push(key);
    // 清除中断标志
    IE0 = 0;
}

后台任务

c 复制代码
void key_processing_task(void) {
    while (1) {
        if (key_queue_not_empty()) {
            uint8_t key = key_queue_pop();
            // 处理按键
        }
        // 让出CPU
        taskYIELD();
    }
}

竞态条件处理:队列操作必须是原子的。可以临时禁用中断或使用无锁环形缓冲区。

3.7 轮询模式(Polling)

问题:硬件不支持中断,或者数据更新频率低、实时性要求不高。

解决方案:定期或不定期检查硬件状态。有两种变体:

  • 机会轮询 :在主循环的空闲时机调用poll()
  • 定期轮询 :用定时器中断触发poll()

C实现(定期轮询)

c 复制代码
// 设备列表
Device* devices[MAX_DEVICES];
int numDevices;

void poll_all(void) {
    for (int i=0; i<numDevices; i++) {
        if (Device_hasNewData(devices[i])) {
            int data = Device_read(devices[i]);
            DataClient_update(devices[i]->client, data);
        }
    }
}

// 定时器ISR
void timer_isr(void) {
    // 清除标志
    // 调用轮询
    poll_all();
}

优缺点:简单可靠,但事件响应延迟取决于轮询周期。周期应小于最短事件间隔。


第4章 并发与资源管理设计模式

这一章解决多任务环境下的调度、同步、资源共享和死锁问题。

4.1 循环执行模式(Cyclic Executive)

适用场景:非常小的系统(无RTOS),或高安全系统中需要确定性行为。

做法:一个无限循环,依次调用每个任务函数。任务函数必须运行到结束(不能有无限循环)。

C实现

c 复制代码
void task1(void) { /* 短时操作 */ }
void task2(void) { /* 短时操作 */ }
void task3(void) { /* 短时操作 */ }

int main(void) {
    // 初始化
    while (1) {
        task1();
        task2();
        task3();
    }
}

可调度性条件:设任务i的最坏执行时间为C_i,则所有任务的C_i之和加上循环开销必须 ≤ 最小周期任务的周期。

变体:时间触发循环执行(使用定时器开始每个循环,如果提前完成则等待,防止循环漂移)。

4.2 静态优先级模式(Static Priority)

适用场景:大多数RTOS应用。每个任务有固定优先级,调度器总是运行最高优先级的就绪任务。

优先级分配:最常用的是速率单调调度(RMS):周期越短,优先级越高。RMS在理论上是最优的固定优先级调度算法。

C实现(伪代码,使用RTOS API)

c 复制代码
void high_priority_task(void* p) {
    while (1) {
        OS_WaitSemaphore(timer_sem);   // 等待50ms周期
        // 工作
    }
}

void low_priority_task(void* p) {
    while (1) {
        OS_WaitSemaphore(timer_sem2);  // 等待200ms周期
        // 工作
    }
}

int main() {
    OS_Init();
    OS_CreateTask(high_priority_task, 1, 1024);
    OS_CreateTask(low_priority_task, 2, 1024);
    OS_Start();
}

优先级倒置问题:低优先级任务持有互斥锁,高优先级任务等待锁,中优先级任务抢占低优先级,导致高优先级无限等待。解决:使用优先级继承协议(RTOS互斥锁通常支持)。

4.3 临界区模式(Critical Region)

适用场景:极短时间(几个指令)内需要独占资源,且不希望有任务切换开销。

做法:在临界区入口禁用任务调度(或禁用中断),出口处恢复。

C实现

c 复制代码
void critical_function(void) {
    OS_DISABLE_SCHEDULER();   // 或 asm("DI")
    // 操作共享变量
    shared_var++;
    OS_ENABLE_SCHEDULER();    // 或 asm("EI")
}

缺点:长时间禁用调度会破坏实时性。只能用于微秒级的临界区。

4.4 守卫调用模式(Guarded Call)

适用场景:保护共享资源(如全局变量、硬件),允许多个任务安全访问。

做法 :每个资源配一个互斥信号量。访问资源前lock,访问后unlock。若锁已被占用,调用者阻塞。

C实现

c 复制代码
typedef struct {
    int data;
    Mutex* mutex;
} SharedResource;

void SharedResource_set(SharedResource* res, int val) {
    Mutex_lock(res->mutex);
    res->data = val;
    Mutex_unlock(res->mutex);
}

int SharedResource_get(SharedResource* res) {
    Mutex_lock(res->mutex);
    int val = res->data;
    Mutex_unlock(res->mutex);
    return val;
}

优先级继承 :使用支持优先级继承的互斥锁(如VxWorks的semMCreate(SEM_INVERSION_SAFE))。

4.5 队列模式(Queuing)

适用场景:任务间异步传递数据。生产者不关心消费者何时处理,消费者不关心数据何时到达。

做法 :共享一个环形缓冲区(队列),用互斥锁保护。生产者调用insert,消费者调用remove。若队列满,生产者可阻塞或丢弃;若队列空,消费者阻塞。

C实现(简化版):

c 复制代码
#define QUEUE_SIZE 10

typedef struct {
    int buffer[QUEUE_SIZE];
    int head, tail, count;
    Mutex* mutex;
    Semaphore* notFull;
    Semaphore* notEmpty;
} Queue;

void Queue_init(Queue* q) {
    q->head = q->tail = q->count = 0;
    q->mutex = Mutex_create();
    q->notFull = Semaphore_create(QUEUE_SIZE);  // 计数信号量,初始为容量
    q->notEmpty = Semaphore_create(0);
}

void Queue_insert(Queue* q, int item) {
    Semaphore_wait(q->notFull);     // 等待有空位
    Mutex_lock(q->mutex);
    q->buffer[q->head] = item;
    q->head = (q->head+1) % QUEUE_SIZE;
    q->count++;
    Mutex_unlock(q->mutex);
    Semaphore_signal(q->notEmpty);  // 通知有数据
}

int Queue_remove(Queue* q) {
    Semaphore_wait(q->notEmpty);    // 等待有数据
    Mutex_lock(q->mutex);
    int item = q->buffer[q->tail];
    q->tail = (q->tail+1) % QUEUE_SIZE;
    q->count--;
    Mutex_unlock(q->mutex);
    Semaphore_signal(q->notFull);   // 通知有空位
    return item;
}

变体:优先级队列(根据消息优先级排序)、溢出到磁盘的缓冲队列。

4.6 汇合模式(Rendezvous)

适用场景:多个任务必须在某点上同步,例如所有任务都完成第一阶段后才能开始第二阶段。

经典实现:线程屏障(Barrier)。

C实现

c 复制代码
typedef struct {
    int expected;        // 需要等待的任务数
    int current;         // 当前到达的任务数
    Mutex* mutex;
    Semaphore* barrier;  // 初始为0
} Barrier;

void Barrier_init(Barrier* b, int n) {
    b->expected = n;
    b->current = 0;
    b->mutex = Mutex_create();
    b->barrier = Semaphore_create(0);
}

void Barrier_wait(Barrier* b) {
    Mutex_lock(b->mutex);
    b->current++;
    if (b->current == b->expected) {
        // 最后一个到达,释放所有等待的任务
        for (int i=0; i<b->expected; i++) {
            Semaphore_signal(b->barrier);
        }
        b->current = 0;   // 重置
    }
    Mutex_unlock(b->mutex);
    Semaphore_wait(b->barrier);
}

4.7 同时锁定模式(Simultaneous Locking)

问题:死锁产生的四个必要条件之一是"持有并等待"。要避免死锁,可以要求任务一次锁住所有需要的资源,要么全锁,要么一个都不锁。

做法 :使用trylock尝试获取所有锁,如果任何一个失败,则释放已经获取的锁,稍后重试。

C实现

c 复制代码
int lock_all(Mutex* locks[], int n) {
    for (int i=0; i<n; i++) {
        if (!Mutex_trylock(locks[i])) {
            // 失败,释放已经锁住的
            for (int j=0; j<i; j++) {
                Mutex_unlock(locks[j]);
            }
            return 0;   // 失败
        }
    }
    return 1;   // 成功
}

void do_work(Mutex* r1, Mutex* r2) {
    Mutex* locks[] = {r1, r2};
    while (!lock_all(locks, 2)) {
        // 可选:短暂延迟后重试
        delay(10);
    }
    // 操作资源
    // ...
    Mutex_unlock(r1);
    Mutex_unlock(r2);
}

4.8 排序锁定模式(Ordered Locking)

原理:破坏"循环等待"条件。为每个资源分配唯一的ID,强制所有任务按ID升序锁定资源。

C实现

c 复制代码
typedef struct {
    int id;
    Mutex* mutex;
} OrderedResource;

// 全局资源列表管理器
static int current_max_locked = -1;
static Mutex* global_lock = NULL;

int OrderedResource_lock(OrderedResource* res) {
    Mutex_lock(global_lock);
    if (res->id <= current_max_locked) {
        // 违反顺序
        Mutex_unlock(global_lock);
        return -1;
    }
    Mutex_lock(res->mutex);
    current_max_locked = res->id;
    Mutex_unlock(global_lock);
    return 0;
}

void OrderedResource_unlock(OrderedResource* res) {
    Mutex_unlock(res->mutex);
    // 注意:释放后需要重新计算当前最大锁定ID,比较复杂
    // 实际实现中通常用一个栈记录锁定顺序
}

更常见的实现是:设计时规定所有资源固定的锁定顺序,开发人员遵守该顺序。


第5章 状态机实现设计模式

这一章给出5种实现有限状态机的模式,从简单到复杂。

5.1 单事件接收器模式(Single Event Receptor)

特点 :一个事件分发函数,接收包含事件类型和数据的结构体,内部根据当前状态做switch-case

适用:小型状态机,事件种类少,状态少。

C实现(解析数字的状态机,如识别"12.34"):

c 复制代码
// 事件类型枚举
typedef enum { EV_DIGIT, EV_DOT, EV_WS, EV_EOS } EventType;

typedef struct {
    EventType type;
    char digit;  // 仅当type == EV_DIGIT有效
} Event;

// 状态枚举
typedef enum { ST_NONE, ST_NO_NUMBER, ST_GOT_NUMBER, ST_PROC_WHOLE, ST_PROC_FRAC } State;

static State state = ST_NO_NUMBER;
static double result = 0.0;
static double tenths = 10.0;

void dispatch(Event e) {
    switch (state) {
        case ST_NO_NUMBER:
            if (e.type == EV_DIGIT) {
                // 转换到 GOT_NUMBER 并进入 PROC_WHOLE
                state = ST_PROC_WHOLE;
                result = e.digit - '0';
                tenths = 10.0;
            } else if (e.type == EV_DOT) {
                // 转换到 GOT_NUMBER 并进入 PROC_FRAC
                state = ST_PROC_FRAC;
                result = 0.0;
                tenths = 10.0;
            }
            break;
        case ST_PROC_WHOLE:
            if (e.type == EV_DIGIT) {
                result = result * 10 + (e.digit - '0');
            } else if (e.type == EV_DOT) {
                state = ST_PROC_FRAC;
            } else if (e.type == EV_WS || e.type == EV_EOS) {
                printf("Result: %g\n", result);
                state = ST_NO_NUMBER;
            }
            break;
        case ST_PROC_FRAC:
            if (e.type == EV_DIGIT) {
                result += (e.digit - '0') / tenths;
                tenths *= 10.0;
            } else if (e.type == EV_WS || e.type == EV_EOS) {
                printf("Result: %g\n", result);
                state = ST_NO_NUMBER;
            }
            break;
    }
}

缺点dispatch函数随着状态和事件增多变得庞大,且所有状态逻辑混在一起。

5.2 多事件接收器模式(Multiple Event Receptor)

特点 :为每个事件写一个独立的处理函数(如onDigitonDot等)。函数内部根据当前状态执行动作。

适用:同步状态机,事件种类固定,每个事件的处理逻辑相对独立。

C实现

c 复制代码
static State state = ST_NO_NUMBER;
static double result, tenths;

void onDigit(char c) {
    switch (state) {
        case ST_NO_NUMBER:
            // 转换
            state = ST_PROC_WHOLE;
            result = c - '0';
            tenths = 10.0;
            break;
        case ST_PROC_WHOLE:
            result = result * 10 + (c - '0');
            break;
        case ST_PROC_FRAC:
            result += (c - '0') / tenths;
            tenths *= 10.0;
            break;
    }
}

void onDot(void) {
    switch (state) {
        case ST_PROC_WHOLE:
            state = ST_PROC_FRAC;
            break;
        case ST_NO_NUMBER:
            state = ST_PROC_FRAC;
            result = 0.0;
            tenths = 10.0;
            break;
        default:
            break;
    }
}

void onTerminator(void) {
    if (state == ST_PROC_WHOLE || state == ST_PROC_FRAC) {
        printf("Result: %g\n", result);
        state = ST_NO_NUMBER;
    }
}

优点 :每个事件函数短小,易于理解。

缺点 :仍然有switch(state),状态增多时每个函数都会变大。

5.3 状态表模式(State Table)

特点:用二维数组(状态×事件)存储转换信息:监护条件、动作函数指针、新状态。调度时直接查表执行。

适用:大的、扁平的状态机(没有嵌套),追求运行时性能和可扩展性。

C实现

c 复制代码
// 动作函数类型
typedef void (*Action)(void* ctx);
// 监护条件类型,返回1表示允许转换
typedef int (*Guard)(void* ctx);

typedef struct {
    Guard guard;
    Action exitAct;
    Action transAct;
    Action entryAct;
    int nextState;
} Transition;

// 假设有3个状态,4个事件
#define STATE_COUNT 3
#define EVENT_COUNT 4

Transition table[STATE_COUNT][EVENT_COUNT];

// 初始化表(在程序启动时填充)
void init_table(void) {
    // 例如:状态0收到事件0时,监护条件为NULL(总是真),无exit动作,transAct为func1,entryAct为func2,下一状态为1
    table[0][0].guard = NULL;
    table[0][0].exitAct = NULL;
    table[0][0].transAct = func1;
    table[0][0].entryAct = func2;
    table[0][0].nextState = 1;
    // ... 填充所有单元格
}

// 调度函数
int currentState = 0;

void dispatch(int event, void* ctx) {
    Transition* t = &table[currentState][event];
    if (t->guard && !t->guard(ctx)) return;  // 监护条件不满足
    if (t->exitAct) t->exitAct(ctx);
    if (t->transAct) t->transAct(ctx);
    currentState = t->nextState;
    if (t->entryAct) t->entryAct(ctx);
}

优点 :调度时间为O(1),扩展状态或事件只需扩展表格,不改动调度逻辑。

缺点:初始化表格代码冗长;表可能稀疏浪费内存;不支持嵌套状态。

5.4 状态模式(State Pattern,GoF)

特点:将每个状态封装成一个对象(结构体),含有该状态下各事件的处理函数指针。Context持有当前状态对象,所有事件委托给当前状态处理。

适用 :状态逻辑复杂,需要动态改变行为,且希望避免大的switch-case

C实现

c 复制代码
// 前向声明
struct Context;

// 状态虚表
typedef struct State {
    void (*onDigit)(struct Context* ctx, char c);
    void (*onDot)(struct Context* ctx);
    void (*onTerm)(struct Context* ctx);
} State;

// 具体状态对象(单例)
extern State stateNoNumber;
extern State stateProcWhole;
extern State stateProcFrac;

// Context
typedef struct Context {
    const State* current;
    double result;
    double tenths;
} Context;

void Context_init(Context* ctx) {
    ctx->current = &stateNoNumber;
    ctx->result = 0.0;
    ctx->tenths = 10.0;
}

void Context_onDigit(Context* ctx, char c) {
    ctx->current->onDigit(ctx, c);
}

// 实现具体状态的行为
static void noNumber_onDigit(Context* ctx, char c) {
    ctx->current = &stateProcWhole;
    ctx->result = c - '0';
    ctx->tenths = 10.0;
}
static void noNumber_onDot(Context* ctx, char c) {
    ctx->current = &stateProcFrac;
    ctx->result = 0.0;
    ctx->tenths = 10.0;
}
// 其它状态...

State stateNoNumber = {
    .onDigit = noNumber_onDigit,
    .onDot = noNumber_onDot,
    .onTerm = noNumber_onTerm
};
// 定义其他状态...

优点 :状态行为完全隔离,新增状态无需修改已有状态;状态可共享(如多个context共用同一状态对象)。

缺点:C实现较繁琐,需要很多函数指针赋值。

5.5 分解与状态模式(Decomposed And-State)

特点:处理UML中的"与状态"(正交区域)。一个复合状态包含多个并行的子状态机,每个子状态机独立运行。事件广播到所有正交区域。

适用:系统有多个独立变化维度(如交通灯的颜色和闪烁频率)。

C实现

c 复制代码
// 正交区域1:颜色状态机
typedef enum { COLOR_RED, COLOR_YELLOW, COLOR_GREEN } ColorState;
ColorState colorState = COLOR_RED;

void color_handleEvent(Event e) {
    switch (colorState) {
        case COLOR_RED: if (e.type == EV_TIMER) colorState = COLOR_GREEN; break;
        case COLOR_GREEN: if (e.type == EV_TIMER) colorState = COLOR_YELLOW; break;
        case COLOR_YELLOW: if (e.type == EV_TIMER) colorState = COLOR_RED; break;
    }
}

// 正交区域2:闪烁速率状态机
typedef enum { RATE_STEADY, RATE_SLOW, RATE_FAST } RateState;
RateState rateState = RATE_STEADY;

void rate_handleEvent(Event e) {
    if (e.type == EV_BUTTON_PRESS) {
        if (rateState == RATE_STEADY) rateState = RATE_SLOW;
        else if (rateState == RATE_SLOW) rateState = RATE_FAST;
        else rateState = RATE_STEADY;
    }
}

// 顶层Context
void dispatchToAll(Event e) {
    color_handleEvent(e);
    rate_handleEvent(e);
}

注意:事件可能同时在两个区域触发转换,执行顺序不确定,可能导致竞争条件。书中建议用"事件传播"来强制顺序:一个区域处理事件后生成新事件给另一个区域。


第6章 安全性与可靠性设计模式

6.1 二进制反码模式(Ones' Complement)

原理:存储数据时,同时存储其按位取反的副本。读取时比较,可检测单位翻转或多位翻转。

C实现

c 复制代码
typedef struct {
    int16_t value;
    int16_t inverted;   // ~value
} ProtectedInt16;

void ProtectedInt16_set(ProtectedInt16* p, int16_t v) {
    p->value = v;
    p->inverted = ~v;
}

int16_t ProtectedInt16_get(ProtectedInt16* p) {
    if (p->value == (int16_t)~p->inverted) {
        return p->value;
    } else {
        errorHandler(MEMORY_CORRUPTION);
        return 0;   // 默认安全值
    }
}

6.2 CRC模式

原理:为数据块计算CRC校验码(16位或32位),存储或发送时附加。接收或读取时重新计算,比较是否一致。

C实现(查表法CRC-16):

c 复制代码
static const uint16_t crc16_table[256] = { ... }; // 预计算表

uint16_t crc16(uint8_t* data, size_t len, uint16_t seed) {
    uint16_t crc = seed;
    for (size_t i=0; i<len; i++) {
        crc = (crc << 8) ^ crc16_table[((crc >> 8) ^ data[i]) & 0xFF];
    }
    return crc;
}

typedef struct {
    uint8_t payload[256];
    uint16_t crc;
} ProtectedBlock;

int ProtectedBlock_verify(ProtectedBlock* blk) {
    uint16_t calc = crc16(blk->payload, sizeof(blk->payload), 0xFFFF);
    return (calc == blk->crc);
}

6.3 智能数据模式(Smart Data)

原理:将数据和其合法范围、约束封装成一个类,设置值时自动检查,无效则触发错误。

C实现

c 复制代码
typedef struct {
    int value;
    int min;
    int max;
    ErrorHandler* err;
} SmartInt;

int SmartInt_set(SmartInt* si, int v) {
    if (v < si->min || v > si->max) {
        ErrorHandler_raise(si->err, OUT_OF_RANGE);
        return -1;
    }
    si->value = v;
    return 0;
}

6.4 通道模式(Channel)

原理:将传感器数据处理流程建模为一系列变换步骤:传感器驱动 → 变换1 → 变换2 → ... → 执行器驱动。每个步骤是一个独立模块,可单独测试和替换。

C实现

c 复制代码
// 变换函数类型
typedef int (*Transform)(int input, void* ctx);

typedef struct Channel {
    SensorDriver* sensor;
    Transform steps[5];
    void* stepCtx[5];
    int numSteps;
    ActuatorDriver* actuator;
} Channel;

void Channel_run(Channel* ch) {
    int data = SensorDriver_read(ch->sensor);
    for (int i=0; i<ch->numSteps; i++) {
        data = ch->steps[i](data, ch->stepCtx[i]);
        if (data == ERROR) break;
    }
    if (data != ERROR) {
        ActuatorDriver_write(ch->actuator, data);
    }
}

6.5 保护单通道模式(Protected Single Channel)

原理:在通道的某些变换点增加检查(范围、合理性、反向计算等)。发现故障时系统进入故障安全状态。

C实现(在变换中插入检查):

c 复制代码
int transform_with_check(int input, void* ctx) {
    int output = do_transform(input);
    if (output < MIN_ACCEPTABLE || output > MAX_ACCEPTABLE) {
        enter_failsafe();
        return ERROR;
    }
    return output;
}

6.6 双通道模式(Dual Channel)

原理:使用两个独立通道(可以是同构或异构),通过比较或表决提高可靠性。

几种变体

  • 同构冗余:两个相同通道,输出比较,不一致则关闭。
  • 异构冗余:不同设计(避免共因故障),输出比较。
  • 三模冗余(TMR):三个通道,投票决定。
  • 完整性检查:一个主通道,一个轻量级检查通道,不一致则切换。
  • 监视器-执行器:执行通道工作,监视通道用独立传感器检测物理结果。

C实现(双通道比较)

c 复制代码
typedef struct DualChannel {
    Channel chA;
    Channel chB;
    int tolerance;
} DualChannel;

int DualChannel_run(DualChannel* dc) {
    int outA = Channel_run_get(&dc->chA);
    int outB = Channel_run_get(&dc->chB);
    if (abs(outA - outB) <= dc->tolerance) {
        return (outA + outB) / 2;   // 平均
    } else {
        enter_failsafe();
        return 0;
    }
}

结束语

以上是《C嵌入式编程设计模式》全书的详尽内容。从C模拟面向对象的基础,到7种硬件访问模式、8种并发与资源管理模式、5种状态机实现模式、6种安全可靠性模式,每个模式都给出了可直接使用的C代码。掌握这些模式后,你将能够编写出高内聚、低耦合、实时可靠、易于维护的嵌入式系统。

最后强调:设计模式是优化工具,不是第一原则。请先写出功能正确的代码,再根据设计标准(性能、内存、可维护性等)权衡选择适当的模式。

本文完全基于Bruce Powel Douglass《Design Patterns for Embedded Systems in C》编写,所有代码示例遵循原书风格。

相关推荐
灰鲸广告联盟1 小时前
新老用户广告价值不同?差异化策略如何实现收益最大化
android·开发语言·flutter·ios
周杰伦fans1 小时前
C# CAD 二次开发:无需启动 AutoCAD 实现 DWG 转 DXF 的完整技术指南
开发语言·c#
都在酒里1 小时前
FreeRTOS 手动移植教程(二):任务管理——多任务创建、优先级抢占与删除
stm32·单片机·嵌入式硬件·rtos
qq_283720051 小时前
2026 最新 Python+AI 零基础入门全教程 :从零搭建人工智能完整项目
开发语言·人工智能·python
时尚IT男2 小时前
Python发票识别实战:从PDF中精准提取发票号与(小写)¥金额
开发语言·python·pdf
basketball6162 小时前
Go 语言从入门到进阶:6. 一文彻底吃透结构体(Struct)
开发语言·unity·golang
ch.ju2 小时前
Java Programming Chapter 4——Private attribute
java·开发语言
提伯斯6462 小时前
Jetson_Pixhawk局域网UDP连接QGC
linux·网络·嵌入式硬件·网络协议·udp·jetson