在嵌入式开发领域,硬件模块间的数据通信一直是个令人头疼的问题。想象一下这样的场景:你的温度传感器每隔100ms采集一次数据,需要同时更新到LCD显示屏、触发报警判断、记录到存储模块。传统的实现方式往往陷入两个极端:
第一种是"意大利面条式"代码,所有功能揉在一起:
c复制while(1) {
temp = read_sensor();
display_temp(temp); // 显示
check_alarm(temp); // 报警判断
save_to_flash(temp); // 存储
delay(100);
}
这种写法看似简单,但各功能模块高度耦合。当需要修改显示逻辑时,可能意外影响报警功能;想优化存储策略,又得小心翼翼避开其他模块的代码。
第二种是"轮询式"查询,各模块主动获取数据:
c复制void display_task() {
while(1) {
temp = get_current_temp();
update_display(temp);
delay(100);
}
}
void alarm_task() {
while(1) {
temp = get_current_temp();
check_threshold(temp);
delay(100);
}
}
这种方式虽然解耦了模块,但造成了严重的资源浪费。两个任务可能在不同时间点读取传感器,导致数据不一致,而且大量无意义的查询操作挤占了宝贵的CPU时间。
观察者模式(Observer Pattern)恰如其分地解决了这些问题。它建立了一种"发布-订阅"机制:
这种机制带来了三个核心优势:
标准的观察者模式包含四个关键组件:
在嵌入式C环境中,由于缺乏面向对象特性,我们需要用结构体和函数指针来模拟这个模式:
c复制typedef struct {
int data;
void (*notify_func[MAX_OBSERVERS])(int);
} Subject;
void subscribe(Subject *sub, void (*callback)(int)) {
// 将回调函数添加到通知列表
}
void notify(Subject *sub) {
// 遍历调用所有注册的回调函数
}
嵌入式系统通常面临三大资源限制:
针对这些限制,我们可以采取以下优化措施:
固定大小数组替代动态列表
c复制#define MAX_OBSERVERS 5
typedef void (*ObserverCallback)(int);
typedef struct {
ObserverCallback callbacks[MAX_OBSERVERS];
uint8_t count;
} ObserverList;
这种方式避免了内存分配,同时通过count字段跟踪实际观察者数量。
批量通知优化
当同一数据需要通知多个观察者时:
c复制void notify_all(const ObserverList *list, int data) {
for(uint8_t i=0; i<list->count; i++) {
list->callbacks[i](data);
}
}
相比逐个通知,减少了函数调用开销。
事件标志位触发
在RTOS环境中,可以结合事件标志:
c复制void sensor_task(void *pv) {
while(1) {
int temp = read_sensor();
if(temp != last_temp) {
xEventGroupSetBits(event_group, TEMP_UPDATE_BIT);
last_temp = temp;
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
在STM32项目中,良好的硬件抽象是关键。我们设计三层结构:
以温度传感器为例:
c复制// 驱动层
float DS18B20_ReadTemp(void) {
// 具体传感器读取实现
}
// 抽象层
typedef struct {
float (*read)(void);
float last_value;
} SensorDevice;
// 应用层
void Sensor_Update(SensorDevice *sensor) {
float current = sensor->read();
if(current != sensor->last_value) {
sensor->last_value = current;
notify_all(&temp_observers, current);
}
}
在中断服务程序(ISR)中使用观察者模式需要特别注意:
安全实现方案:
c复制// 定义线程安全的观察者列表
typedef struct {
ObserverCallback callbacks[MAX_OBSERVERS];
uint8_t count;
osMutexId_t mutex;
} SafeObserverList;
// ISR中的通知触发
void ADC_IRQHandler(void) {
if(ADC1->ISR & ADC_ISR_EOC) {
int value = ADC1->DR;
xQueueSendFromISR(adc_queue, &value, NULL);
}
}
// 任务中处理实际通知
void adc_task(void *pv) {
int value;
while(1) {
if(xQueueReceive(adc_queue, &value, portMAX_DELAY)) {
osMutexAcquire(obs_list.mutex, osWaitForever);
notify_all(&obs_list, value);
osMutexRelease(obs_list.mutex);
}
}
}
在资源受限的STM32F103C8T6(20K RAM)上实测:
优化技巧:
通过void指针和枚举实现多类型支持:
c复制typedef enum {
DATA_INT,
DATA_FLOAT,
DATA_STRUCT
} DataType;
typedef struct {
DataType type;
union {
int i_val;
float f_val;
void *p_val;
} data;
} GenericData;
typedef void (*GenericObserver)(GenericData);
typedef struct {
const char *topic;
GenericObserver callbacks[MAX_OBSERVERS];
uint8_t count;
} TopicObserver;
集中管理多个观察主题:
c复制typedef struct {
TopicObserver topics[MAX_TOPICS];
uint8_t count;
} TopicManager;
int TopicManager_Subscribe(TopicManager *tm,
const char *topic,
GenericObserver callback) {
// 查找或创建主题
// 添加观察者
}
void TopicManager_Publish(TopicManager *tm,
const char *topic,
GenericData data) {
// 查找主题
// 通知所有观察者
}
创建专门的主题服务任务:
c复制void topic_service_task(void *pv) {
TopicManager *tm = pv;
TopicMessage msg;
while(1) {
if(xQueueReceive(tm->queue, &msg, portMAX_DELAY)) {
TopicManager_Publish(tm, msg.topic, msg.data);
}
}
}
// 其他任务通过队列发布消息
void sensor_task(void *pv) {
GenericData data;
while(1) {
data.f_val = read_sensor();
TopicMessage msg = {
.topic = "temperature",
.data = data
};
xQueueSend(tm->queue, &msg, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
我们在STM32F407(168MHz)上测试了三种实现:
| 实现方式 | CPU占用率 | 内存占用 | 延迟(μs) |
|---|---|---|---|
| 传统轮询 | 15-20% | 1.2KB | 50-100 |
| 简单观察者 | 3-5% | 2.8KB | 10-20 |
| 优化版观察者 | 2-3% | 1.5KB | 5-10 |
测试条件:
我们模拟了以下极端场景:
测试结果:
问题现象:某个观察者执行复杂运算,阻塞了整个通知链。
解决方案:
c复制void heavy_observer(int data) {
xTaskNotify(heavy_task, data, eSetValueWithOverwrite);
}
void heavy_task(void *pv) {
while(1) {
uint32_t value;
xTaskNotifyWait(0, ULONG_MAX, &value, portMAX_DELAY);
// 分段处理数据
}
}
问题现象:系统无法容纳更多观察者。
解决方案:
问题现象:观察者A通知B,B又通知A,形成无限循环。
解决方案:
c复制#define MAX_NOTIFY_DEPTH 3
void notify_all(ObserverList *list, int data, int depth) {
if(depth >= MAX_NOTIFY_DEPTH) return;
// ...
observer->callback(data, depth+1);
}
推荐的项目目录结构:
code复制/observers
├── include/
│ ├── observer.h // 核心接口
│ ├── topic.h // 主题管理
│ └── observer_cfg.h // 配置选项
├── src/
│ ├── observer.c
│ └── topic.c
└── examples/ // 使用示例
c复制void debug_notify_all(ObserverList *list, int data) {
printf("Notifying %d observers...\n", list->count);
for(int i=0; i<list->count; i++) {
printf("-> Observer %d\n", i);
list->callbacks[i](data);
}
}
将硬件中断转换为观察者事件:
c复制void EXTI0_IRQHandler(void) {
if(EXTI->PR & EXTI_PR_PR0) {
EXTI->PR = EXTI_PR_PR0; // 清除标志
notify_all(&button_observers, 0);
}
}
在双核STM32H7上,通过HSEM实现核间观察者:
c复制void HSEM_IRQHandler(void) {
if(HSEM->C1ISR & HSEM_C1ISR_ISF0) {
HSEM->C1ICR = HSEM_C1ICR_CISC0;
notify_all(&ipcc_observers, HSEM_READ_DATA);
}
}
在STOP模式下通过观察者唤醒:
c复制void enter_stop_mode(void) {
// 配置唤醒源
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
// 设置最后观察者作为唤醒回调
subscribe(&wakeup_observer, stop_mode_wakeup);
// 进入低功耗
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
}
在嵌入式开发中,观察者模式的价值随着系统复杂度的提升而愈发明显。我在多个STM32项目中的实践表明,合理使用观察者模式可以使模块间通信代码量减少40%-60%,同时提高系统响应速度30%以上。特别是在传感器数据分发、用户界面更新、系统状态通知等场景下,这种模式展现出了惊人的效率。