☰
一、关于发布者和订阅者模型的抽象和使用
2026/9/26 3:46:06 网站建设 项目流程

1、对发布者和订阅者进行抽象:

// 1. 定义事件 ID(你可以用枚举来管理)typedefuint32_tEventID;// 2. 定义回调函数类型(订阅者需要实现这个函数)// 参数: 事件ID, 数据指针, 数据长度typedefvoid(*EventCallback)(EventID event,void*data,uint32_tlen);// 配置参数#defineMAX_EVENTS32// 最大支持的事件种类#defineMAX_SUBSCRIBERS8// 每个事件最大支持的订阅者数量// 内部数据结构typedefstruct{EventCallback callbacks[MAX_SUBSCRIBERS];uint8_tcount;}EventNode;staticEventNode event_table[MAX_EVENTS];staticbool is_initialized=false;// 初始化voidpubsub_init(void){memset(event_table,0,sizeof(event_table));is_initialized=true;}// 订阅boolpubsub_subscribe(EventID event,EventCallback cb){if(!is_initialized||event>=MAX_EVENTS||cb==NULL)returnfalse;EventNode*node=&event_table[event];// 检查是否已经订阅过for(uint8_ti=0;i<node->count;i++){if(node->callbacks[i]==cb)returntrue;}// 添加新订阅if(node->count<MAX_SUBSCRIBERS){node->callbacks[node->count++]=cb;returntrue;}returnfalse;// 订阅者满了}// 取消订阅boolpubsub_unsubscribe(EventID event,EventCallback cb){if(!is_initialized||event>=MAX_EVENTS||cb==NULL)returnfalse;EventNode*node=&event_table[event];for(uint8_ti=0;i<node->count;i++){if(node->callbacks[i]==cb){// 用最后一个元素覆盖当前元素,然后减少计数node->callbacks[i]=node->callbacks[--node->count];returntrue;}}returnfalse;}// 发布voidpubsub_publish(EventID event,void*data,uint32_tlen){if(!is_initialized||event>=MAX_EVENTS)return;EventNode*node=&event_table[event];// 遍历并执行所有回调for(uint8_ti=0;i<node->count;i++){if(node->callbacks[i]!=NULL){node->callbacks[i](event,data,len);}}}// 定义事件枚举enum{EVT_SENSOR_DATA=0,EVT_BUTTON_PRESSED,EVT_MAX};// --- 订阅者 A:打印传感器数据 ---voidsensor_logger(EventID event,void*data,uint32_tlen){int*val=(int*)data;printf("[Logger] 收到传感器数据: %d\n",*val);}// --- 订阅者 B:数据超限报警 ---voidthreshold_checker(EventID event,void*data,uint32_tlen){int*val=(int*)data;if(*val>100){printf("[Checker] 警告!数据超限: %d\n",*val);}}// --- 发布者:模拟硬件读取 ---voidhardware_task(){intsensor_value=120;// 假设读到了 120printf("[Publisher] 硬件读取完毕,发布事件...\n");// 发布者只管发,不管谁在听pubsub_publish(EVT_SENSOR_DATA,&sensor_value,sizeof(sensor_value));}intmain(){pubsub_init();// 订阅者注册自己的处理函数pubsub_subscribe(EVT_SENSOR_DATA,sensor_logger);pubsub_subscribe(EVT_SENSOR_DATA,threshold_checker);// 触发一次发布hardware_task();// 取消订阅pubsub_unsubscribe(EVT_SENSOR_DATA,sensor_logger);printf("\n--- 取消 Logger 后再次发布 ---\n");hardware_task();return0;}

从结构流程上看:

发布者——》给出清单(event)——》并发表event对应的动作,如gpio反转。
订阅者———》获取到event——》注册(对应事件的回调处理)如:发布者发布了gpio反转的这个事件,那对应订阅者的回调就会出现gpio反转这个动作,如对这个动作进行传输显示到屏幕上。

2、具体如何使用:

这里给出实例:
串口1,作为交互和输出信息。
串口5,
gpiog7,
fdcan1,
time3,
这些作为event触发者,给发布者提供事件,
订阅者则根据订阅的内容进行输出,当前订阅后对应什么事件。
这里展示部分主要代码:

typedefenum{EVT_NONE=0,EVT_TIMER_TICK,EVT_UART5_RX,EVT_FDCAN_RX,EVT_FDCAN_TX_DONE,EVT_GPIO7_TOGGLE,EVT_MAX}EventType;// 订阅者回调函数类型:每个订阅者收到事件时执行此函数。// evt:事件类型;data:附加参数(如消息指针、状态指针等)。typedefvoid(*SubscriberCallback)(EventType evt,void*data);// 每个订阅者节点都是一个链表节点:事件类型 + 回调函数 + 下一个节点。typedefstructSubscriberNode{EventType evt;SubscriberCallback callback;structSubscriberNode*next;}SubscriberNode;// 事件队列:中断函数将事件写入队列,主循环统一消费。// 这样可以避免在中断里做复杂逻辑,保证中断服务时间短。typedefstruct{EventType evt;void*data;}EventMsg;staticEventMsg s_eventQueue[EVENT_QUEUE_SIZE];staticvolatileuint16_ts_head=0;staticvolatileuint16_ts_tail=0;//=================== 初始化 ====================// 作用:清空订阅关系、事件队列和节点池,确保系统从干净状态启动。voidPubSub_Init(void){for(inti=0;i<EVT_MAX;i++){s_subscribers[i]=NULL;}memset(s_nodeAllocFlag,0,sizeof(s_nodeAllocFlag));s_head=0;s_tail=0;}// ==================== 订阅/取消订阅 ====================// 作用:让某个模块关注某类事件。// 例如:系统状态模块订阅 EVT_TIMER_TICK、EVT_UART5_RX 等。boolPubSub_Subscribe(EventType evt,SubscriberCallback cb){if(evt<=EVT_NONE||evt>=EVT_MAX||cb==NULL)returnfalse;SubscriberNode*node=s_subscribers[evt];while(node){if(node->callback==cb)returnfalse;node=node->next;}SubscriberNode*newNode=NodeAlloc();if(!newNode)returnfalse;newNode->evt=evt;newNode->callback=cb;newNode->next=s_subscribers[evt];s_subscribers[evt]=newNode;returntrue;}boolPubSub_Unsubscribe(EventType evt,SubscriberCallback cb){if(evt<=EVT_NONE||evt>=EVT_MAX||cb==NULL)returnfalse;SubscriberNode**pp=&s_subscribers[evt];while(*pp){if((*pp)->callback==cb){SubscriberNode*toDelete=*pp;*pp=(*pp)->next;NodeFree(toDelete);returntrue;}pp=&(*pp)->next;}returnfalse;}// 作用:真正把事件送给所有订阅该事件的回调函数。staticvoidDispatch(EventType evt,void*data){if(evt<=EVT_NONE||evt>=EVT_MAX)return;printf("[PUBSUB] dispatch evt=%d to subscribers\r\n",(int)evt);SubscriberNode*node=s_subscribers[evt];while(node){if(node->callback){printf("[PUBSUB] callback invoked for evt=%d\r\n",(int)evt);node->callback(evt,data);}node=node->next;}}// 作用:中断函数调用,直接把事件压入队列,不在中断中做耗时工作。voidPubSub_PublishFromISR(EventType evt,void*data){uint16_tnext=(s_head+1U)%EVENT_QUEUE_SIZE;if(next!=s_tail){s_eventQueue[s_head].evt=evt;s_eventQueue[s_head].data=data;s_head=next;printf("[PUBSUB] published evt=%d to queue\r\n",(int)evt);}else{printf("[PUBSUB] queue full, evt=%d dropped\r\n",(int)evt);}}// 作用:普通线程/函数发布事件,先关中断再入队,保证线程安全。voidPubSub_Publish(EventType evt,void*data){printf("[PUBSUB] publish request evt=%d\r\n",(int)evt);__disable_irq();PubSub_PublishFromISR(evt,data);__enable_irq();}// 作用:主循环中消费事件,依次取出队列并分发给订阅者。voidPubSub_ProcessQueue(void){__disable_irq();while(s_head!=s_tail){EventMsg msg=s_eventQueue[s_tail];s_tail=(s_tail+1U)%EVENT_QUEUE_SIZE;__enable_irq();Dispatch(msg.evt,msg.data);__disable_irq();}__enable_irq();}// ============================================// 2. 业务订阅者:系统状态处理模块// 作用:作为“唯一的业务处理者”,统一处理定时器、串口、FDCAN 和 GPIO 事件。// 这样主循环和硬件回调不直接写具体业务代码,而是做事件分发。此处就是//对应订阅者的处理。// ============================================staticvoidSystemStatusConsumer(EventType evt,void*data){switch(evt){caseEVT_TIMER_TICK:// printf("[SYS] Timer tick -> heartbeat\r\n");HAL_GPIO_TogglePin(GPIOG,GPIO_PIN_7);//随定时器的周期闪烁。但需要将打印信息注释break;caseEVT_UART5_RX:printf("[SYS] UART5 receive event\r\n");break;caseEVT_FDCAN_RX:printf("[SYS] FDCAN receive event\r\n");break;caseEVT_FDCAN_TX_DONE:printf("[SYS] FDCAN tx complete\r\n");break;caseEVT_GPIO7_TOGGLE:HAL_GPIO_TogglePin(GPIOG,GPIO_PIN_7);break;default:break;}}// 作用:注册所有真存在的事件订阅者。voidSystemSubscribers_Init(void){PubSub_Subscribe(EVT_TIMER_TICK,SystemStatusConsumer);PubSub_Subscribe(EVT_UART5_RX,SystemStatusConsumer);PubSub_Subscribe(EVT_FDCAN_RX,SystemStatusConsumer);PubSub_Subscribe(EVT_FDCAN_TX_DONE,SystemStatusConsumer);PubSub_Subscribe(EVT_GPIO7_TOGGLE,SystemStatusConsumer);}// ============================================// 3. GPIOG7 输出控制// 作用:GPIOG7 是当前板子上真实存在的输出引脚,// 用来做状态指示或心跳输出,配合 TIM 定时器周期翻转。// ============================================voidGpio7Publisher_Init(void){HAL_GPIO_WritePin(GPIOG,GPIO_PIN_7,GPIO_PIN_RESET);}voidGpio7Publisher_Toggle(void){PubSub_Publish(EVT_GPIO7_TOGGLE,NULL);}main(){// 1) 初始化事件总线:清空订阅关系和事件队列,准备接收后续硬件事件PubSub_Init();// 2) 初始化真实存在的功能模块Gpio7Publisher_Init();// 3) 注册事件订阅关系:哪些模块关心哪些事件SystemSubscribers_Init();// 4) 触发测试事件,验证事件总线工作正常Gpio7Publisher_Toggle();while(1){PubSub_ProcessQueue();}}

源码:

https://gitee.com/c_y_l_N_H/tmp_pit/tree/master
对应输出结果:

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询