• 嵌入式软件架构设计-消息交互


    1、前言

    在熟悉任务调度、 程序分层 和模块化编程关于软件架构、分层和模块设计后,除了函数调用设计中出现的情况外,还会遇到同层模块之前如何进行消息交互,通常是应用层之间。

    比如一个设备通过架构设计包含 人机交互应用层模块 (一般会调用按键和显示屏等功能驱动模块)和 通信应用层模块 (一般调用串口、CAN和网络ESP8266等功能驱动模块),两个同层之间的模块如果需要互传数据,一般都是调用各自头文件提供的接口(模块对外提供的接口尽量不要使用全局变量,防止其他模块擅自修改),这样就造成了耦合。

    2、解决思路

    上述情况,也可以采用回调函数的实现方式进行模块解耦,但是需要引入新的内容,即公共模块Commoon层(包含第三方功能库)。

    公共模块主要有各模块都需要使用的类型定义、结构体定义、通用函数或常用宏定义等(通常属于基础类的功能,不会受功能需求和不同平台的影响)。

    基于公共模块,为了解决各模块之前的数据交互,可以通过公共模块实现基础类的功能达到各应用层模块解耦的目的。

    参考消息队列的方式,可以实现一个生产者/消费者的功能模块(这种可以称作 观察者模式 ,即存在 观察者 和 被观察者 ),即某一模块更新数据后,其他模块可以第一时间得到通知更新(采用回调函数的方式实现)

    看图:

    Callback 是一个 指针数组变量 ,每个数组成员都是 函数指针类型 的 变量 ,通过函数 Notify_Attach 拿到了应用层代码函数 OnSaveParam(...) 和OnUpdateParam(...)的函数地址,之后人机交互模块调用了 Notify_EventNotify,从而调用 Callback ,调用方式和直接调用 OnFunction(...) 存在些许差异,因为是数组,所有需要 [ ] 取函数地址,为了保证系统运行安全,调用前要确保  Callback[i] 不为 NULL,否则会引起程序异常。

    从上述看,也许有人感觉这样处理反而复杂了,直接调用不香吗?(上述人机交互模块属于 被观察者 ,参数和其他模块属于 观察者 )

    有以下几个好处:

    1. 避免各模块相互调用,可完成解耦
    2. 即使 观察者 模块其中一个被移除,也不用修改 被观察者 或者 其他观察者 代码,保证系统稳定
    3. 新增一个 观察者 模块,也不需要修改 被观察者 代码,保证系统稳定

    当然这种方式也有缺点:

    1. 如果回调函数过多,或者某一个 观察者 的回调函数执行时间很长,肯定会影响到其他观察者 模块的通知时间,甚至影响 被观察者 模块的正常运行
    2. 如果 观察者 和 被观察者 之间有循环依赖,就会导致他们循环调用,导致系统死机

    避免方式:

    1. 回调函数中一定要保证执行的时间 短 ,不能有执行时间长的功能,甚至延时(一般回调中处理数据更新等执行时间短的即可,数据更新后的需要花时间处理的可以在主循环执行)
    2. 观察者回调函数中尽量避免执行其他观察者的回调函数,防止循环调用

    3、示例代码

    事件通知模块头文件

    1. #ifndef _NOTIFY_H_
    2. #define _NOTIFY_H_
    3. #include <stdint.h>
    4. /**
    5. * @brief 应用模块ID枚举定义
    6. *
    7. */
    8. typedef enum
    9. {
    10. NOTIFY_ID_HMI = 0, // 人机交互模块
    11. NOTIFY_ID_SYS_PARAM, // 参数管理模块
    12. NOTIFY_ID_TOTAL
    13. } NotifyId_e;
    14. /**
    15. * @brief 事件类型枚举定义
    16. *
    17. */
    18. typedef enum
    19. {
    20. NOTIFY_EVENT_PARAM_UPDATE, // 参数更新事件, 对应结构体 PrramUpdateInfo_t
    21. NOTIFY_EVENT_TOTAL
    22. } NotifyEvent_e;
    23. typedef struct
    24. {
    25. uint16_t addr;
    26. uint32_t param;
    27. }PrramUpdateInfo_t;
    28. typedef int (*EventNotifyCB)(NotifyId_e id, NotifyEvent_e eEvent, const void *pData, uint32_t length);
    29. extern void Notify_Init(void);
    30. extern int Notify_Attach(NotifyId_e id, NotifyEvent_e eEvent, EventNotifyCB pfnCallback);
    31. extern int Notify_Detach(NotifyId_e id, NotifyEvent_e eEvent);
    32. extern int Notify_EventNotify(NotifyId_e id, NotifyEvent_e eEvent, const void *pData, uint32_t length);
    33. #endif /* _NOTIFY_H_ */

    事件通知模块源文件:

    1. #include "notify.h"
    2. #include <string.h>
    3. static EventNotifyCB sg_pfnCallback[NOTIFY_ID_TOTAL][NOTIFY_EVENT_TOTAL];
    4. /**
    5. * @brief 事件初始化
    6. *
    7. */
    8. void Notify_Init(void)
    9. {
    10. memset(sg_pfnCallback, 0, sizeof(sg_pfnCallback));
    11. }
    12. /**
    13. * @brief 添加事件监听通知
    14. *
    15. * @param[in] id 应用模块ID
    16. * @param[in] eEvent 事件
    17. * @param[in] pfnCallback 回调函数
    18. * @return 0,成功; -1,失败
    19. */
    20. int Notify_Attach(NotifyId_e id, NotifyEvent_e eEvent, EventNotifyCB pfnCallback)
    21. {
    22. if (id >= 0 && id < NOTIFY_ID_TOTAL && eEvent < NOTIFY_EVENT_TOTAL)
    23. {
    24. sg_pfnCallback[id][eEvent] = pfnCallback;
    25. return 0;
    26. }
    27. return -1;
    28. }
    29. /**
    30. * @brief 删除事件监听通知
    31. *
    32. * @param[in] id 应用模块ID
    33. * @param[in] eEvent 事件
    34. * @return 0,成功; -1,失败
    35. */
    36. int Notify_Detach(NotifyId_e id, NotifyEvent_e eEvent)
    37. {
    38. if (id >= 0 && id < NOTIFY_ID_TOTAL && eEvent < NOTIFY_EVENT_TOTAL)
    39. {
    40. sg_pfnCallback[id][eEvent] = 0;
    41. return 0;
    42. }
    43. return -1;
    44. }
    45. /**
    46. * @brief 事件通知
    47. *
    48. * @param[in] id 应用模块ID
    49. * @param[in] eEvent 事件类型
    50. * @param[in] pData 消息内容
    51. * @param[in] length 消息长度
    52. * @return 0,成功; -1,失败
    53. */
    54. int Notify_EventNotify(NotifyId_e id, NotifyEvent_e eEvent, const void *pData, uint32_t length)
    55. {
    56. int i;
    57. if (eEvent < NOTIFY_EVENT_TOTAL)
    58. {
    59. for (i = 0; i < NOTIFY_ID_TOTAL; i++)
    60. {
    61. if (sg_pfnCallback[i][eEvent] != 0)
    62. {
    63. sg_pfnCallback[i][eEvent](id, eEvent, pData, length);
    64. }
    65. }
    66. return 0;
    67. }
    68. return -1;
    69. }

    参数应用层模块:

    1. #include "notify.h"
    2. static int Param_OnNotifyProc(NotifyId_e id, NotifyEvent_e eEvent, const void *pData, uint32_t length);
    3. void Param_Init(void)
    4. {
    5. Notify_Attach(NOTIFY_ID_SYS_PARAM, NOTIFY_EVENT_PARAM_UPDATE, Param_OnNotifyProc);
    6. }
    7. // 事件回调处理
    8. int Param_OnNotifyProc(NotifyId_e id, NotifyEvent_e eEvent, const void *pData, uint32_t length)
    9. {
    10. switch (eEvent)
    11. {
    12. case NOTIFY_EVENT_PARAM_UPDATE:
    13. {
    14. PrramUpdateInfo_t *pInfo = (PrramUpdateInfo_t *)pData;
    15. SaveParam(pInfo->addr, pInfo->param);// 保存参数
    16. }
    17. break;
    18. default:
    19. break;
    20. }
    21. return 0;
    22. }

    人机交互应用层模块

    1. #include "notify.h"
    2. void Hmi_Init(void)
    3. {
    4. }
    5. // 需要保存参数
    6. int Hmi_SaveProc(void)
    7. {
    8. ParamUpdateInfo_t info;
    9. info.addr = 5;
    10. info.param = 20;
    11. Notify_EventNotify(NOTIFY_ID_HMI, NOTIFY_EVENT_HMI_UPDATE, &info, sizeof(ParamUpdateInfo_t));
    12. }

     

  • 相关阅读:
    Windows原理深入学习系列-访问控制列表
    科普向丨语音芯片烧录工艺的要求
    新年学新语言Go之一
    PMP有什么答题技巧?
    Tomcat 调优之从 Linux 内核源码层面看 Tcp backlog
    Vue3中使用Element-Plus分页组件
    Mysql高级——锁(2)
    Ubuntu系统anaconda安装初始化和env环境切换
    【PyTorch 攻略 (3/7)】线性组件、激活函数
    IPO解读丨“停车场”以外的故事,智慧互通如何书写?
  • 原文地址:https://blog.csdn.net/JavaShark/article/details/125636232