您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
嵌入式中设计模式的艺术
 
作者: 初光
   次浏览      
 2021-11-17
 
编辑推荐:
本文主要介绍嵌入式设备的常见的软件框架,观察者模式以及职责链模式的情景实现以及示例,希望对您的学习有所帮助。
本文来自于 糖果Autosar ,由火龙果软件Alice编辑、推荐。

嵌入式的标签多为: 低配,偏硬件,底层,资源紧张,代码多以C语言,汇编为主,代码应用逻辑简单。 但随着AIOT时代的到来,局面组件改变。 芯片的性能资源逐渐提升,业务逻辑也逐渐变得复杂,相对于代码的效率而言,代码的复用可移植性要求越来越高,以获得更短的项目周期 和更高的可维护性。 下面是AIOT时代嵌入式设备的常见的软件框架。

设计模式

设计模式的标签:高级语言 ,高端,架构等。在AIOT时代,设计模式与嵌入式能擦出怎样的火花?设计模式可描述为:对于某类相似的问题,经过前人的不断尝试,总结出了处理此类问题的 公认 的 有效 解决办法。

嵌入式主要以C语言开发,且面向过程,而设计模式常见于高级语言(面向对象),目前市面上描述设计模式的书籍多数使用JAVA 语言,C语言能实现设计模式吗?设计模式与语言无关,它是解决问题的方法,JAVA可以实现,C语言同样可以实现。同样的,JAVA程序员会遇到需要用模式来处理的问题,C程序员也可能遇见,因此设计模式是很有必要学习的。

模式陷阱:设计模式是针对具体的某些类问题的有效解决办法,不是所有的问题都能匹配到对应的设计模式。因此,不能一味的追求设计模式,有时候简单直接的处理反而更有效。有的问题没有合适的模式,可以尽量满足一些设计原则,如开闭原则(对扩展开放,对修改关闭)

观察者模式

情景

在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

实现

主题对象提供统一的注册接口,以及注册函数 。由观察者本身实例化observer_intf 接口,然后使用注册函数,添加到对应的主题列表中,主题状态发生改变,依次通知列表中的所有对象。

当主题状态发生改变,将通知到所有观察者,观察者本身也可以设置条件,是否选择接收通知

实例:嵌入式裸机低功耗框架

  • 设备功耗分布

其中线路损耗,电源电路等软件无法控制,故不讨论。板载外设,如传感器可能通过某条命令配置进入低功耗模式,又或者硬件上支持控制外设电源来控制功耗。片内外设,及芯片内部的外设,通过卸载相关驱动,关闭时钟配置工作模式来控制功耗。

  • 设备唤醒方式
    • 主动唤醒系统某个定时事件到来时,系统被主动唤醒处理事件
    • 被动唤醒系统处于睡眠,被外部事件唤醒,如串口接收到一包数据,传感器检测到变化,通过引脚通知芯片
  • 系统允许睡眠的条件
    • 外设无正在收发的数据
    • 缓存无需要处理的数据
    • 应用层状态处于空闲(无需要处理的事件)
  • 基于观察者模式的PM框架实现

PM组件提供的接口

  1. struct pm

  2. {

  3. struct pm * next;

  4. const char * name;

  5. void (*init)( void );

  6. void (* deinit ( void );

  7. void * condition;

  8. };

  9. static struct pm pm_list;

  10. static uint8_t pm_num;

  11. static uint8_t pm_status;


  12. int pm_register ( const struct pm* pm , const char * name)

  13. {

  14. struct pm* cur_pm = &pm_list;

  15. while (cur_pm->next)

  16. {

  17. cur_pm = cur_pm->next;

  18. }

  19. cur_pm->next = pm;

  20. pm->next = NULL ;

  21. pm->name = name;

  22. pm_num++;

  23. }


  24. void pm_loop ( void )

  25. {

  26. uint32_t pm_condition = 0 ;

  27. struct pm* cur_pm = pm_list.next;

  28. static uint8_t cnt;


  29. /*check all condition*/

  30. while (cur_pm)

  31. {

  32. if (cur_pm->condition){

  33. pm_condition |= *(( uint32_t *)(cur_pm->condition));

  34. }

  35. cur_pm = cur_pm->next;

  36. }

  37. if (pm_condition == 0 )

  38. {

  39. cnt++;

  40. if (cnt>= 5 )

  41. {

  42. pm_status = READY_SLEEP;

  43. }

  44. }

  45. else

  46. {

  47. cnt = 0 ;

  48. }

  49. if ( pm_status == READY_SLEEP)

  50. {

  51. cur_pm = pm_list.next;

  52. while (cur_pm)

  53. {

  54. if (cur_pm->deinit){

  55. cur_pm-> deinit ();

  56. }

  57. cur_pm = cur_pm->next;

  58. }

  59. pm_status = SLEEP;

  60. ENTER_SLEEP_MODE ();

  61. }

  62. /*sleep--->wakeup*/

  63. if (pm_status == SLEEP)

  64. {

  65. pm_status = NORMAL;

  66. cur_pm = pm_list.next;

  67. while (cur_pm)

  68. {

  69. if (cur_pm->init){

  70. cur_pm-> init ();

  71. }

  72. cur_pm = cur_pm->next;

  73. }

  74. }

  75. }

外设使用PM接口

结论

  • PM 电源管理可以单独形成模块,当功耗外设增加时,只需实现接口,注册即可
  • 通过定义段导出操作,可以更加简化应用层或外设的注册逻辑
  • 方便调试,可以很方便打印出系统当前为满足睡眠条件的模块
  • 通过条件字段划分,应该可以实现系统部分睡眠

职责链模式

情景

在现实生活中,一个事件(任务)需要经过多个对象处理是很常见的场景。如报销流程,公司员工报销, 首先员工整理报销单,核对报销金额,有误则继续核对整理,直到无误,将报销单递交到财务,财务部门进行核对,核对无误后,判断金额数量,若小于一定金额,则财务部门可直接审批,若金额超过范围,则报销单流传到总经理,得到批准后,整个任务才算结束。类似的情景还有很多,如配置一个WIFI模块,通过AT指令,要想模块正确连入WIFI , 需要按一定的顺序依次发送配置指令 , 如设置设置模式 ,设置 需要连接的WIFI名,密码,每发送一条配置指令,模块都将返回配置结果,而发送者需要判断结果的正确性,再选择是否发送下一条指令或者进行重传。

总结起来是,一系列任务需要严格按照时间线依次处理的顺序逻辑,如下图所示 。

在存在系统的情况下,此类逻辑可以很容易的用阻塞延时来实现,实现如下:

在裸机的情况下,为了保证系统的实时性,无法使用阻塞延时,一般使用定时事件配合状态机来实现:

和系统实现相比,裸机的实现更加复杂,为了避免阻塞,只能通过状态和定时器来实现顺序延时的逻辑,可以看到,实现过程相当分散,对于单个任务的处理分散到了3个函数中处理,这样导致的后果是:修改,移植的不便。而实际的应用中,类似的逻辑相当多,如果按照上面的方法去实现,将会导致应用程序的强耦合。

实现

可以发现,上面的情景有以下特点:

  • 任务按顺序执行,只有当前任务执行完了(有结论,成功或者失败)才允许执行下一个任务
  • 前一个任务的执行结果会影响到下一个任务的执行情况
  • 任务有一些特性,如超时时间,延时时间,重试次数

通过以上信息,我们可以抽象出这样一个模型:任务作为节点, 每一个任务节点有其属性:如超时,延时,重试,参数,处理方法,执行结果。当需要按照顺序执行一系列任务时,依次将任务节点串成一条链,启动链运行,则从任务链的第一个节点开始运行,运行的结果可以是 OK , BUSY ,ERROR 。若是OK, 表示节点已处理,从任务链中删除,ERROR 表示运行出错,任务链将停止运行,进行错误回调,可以有用户决定是否继续运行下去。BUSY表示任务链处于等待应答,或者等待延时的情况。当整条任务链上的节点都执行完,进行成功回调。

node数据结构定义

node内存池

使用内存池的必要性:实际情况下,同一时间,责任链的条数,以及单条链的节点数比较有限,但种类是相当多的。比如一个支持AT指令的模块,可能支持几十条AT指令,但执行一个配置操作,可能就只会使用3-5条指令,若全部静态定义节点,将会消耗大量内存资源。因此动态分配是必要的。

初始化node内存池,内存池内所有节点都将添加到free_list。当申请节点时,会取出第一个空闲节点,加入到used_list , 并且接入到责任链。当责任链某一个节点执行完,将会被自动回收(从责任链中删除,并从used_list中删除,然后添加到free_list)

职责链数据结构定义

职责链初始化

职责链添加节点

职责链的启动

职责链的应答

职责链的运行

  1. int resp_chain_run ( resp_chain_t * chain)

  2. {

  3. RESP_ASSERT (chain);

  4. if (chain->enable)

  5. {

  6. shadow_resp_chain_node_t * cur_node = chain->node.next;

  7. /*maybe ans occur in handle,so cannot change state direct when ans comming*/

  8. if (chain->is_ans)

  9. {

  10. chain->is_ans = false ;

  11. chain->state = RESP_STATUS_ANS;

  12. }

  13. switch (chain->state)

  14. {

  15. case RESP_STATUS_IDLE:

  16. {

  17. if (cur_node)

  18. {

  19. uint16_t retry = cur_node->init_retry;

  20. if (cur_node->handle)

  21. {

  22. cur_node->param_type = RESP_PARAM_INPUT;

  23. chain->state = cur_node-> handle (( resp_chain_node_t *)cur_node ,cur_node->param);

  24. }

  25. else

  26. {

  27. RESP_LOG ( "node handle is null ,goto next node" );

  28. chain->state = RESP_STATUS_OK;

  29. }

  30. if (retry != cur_node->init_retry)

  31. {

  32. cur_node->retry = cur_node->init_retry> 0 ?(cur_node- >init_retry -1 ): 0 ;

  33. }

  34. }

  35. else

  36. {

  37. if (chain->resp_done)

  38. {

  39. chain-> resp_done (( void *)RESP_RESULT_OK);

  40. }

  41. chain->enable = 0 ;

  42. chain->state = RESP_STATUS_IDLE;

  43. TimerStop (&chain->timer);

  44. chain->timer_is_running = false ;

  45. }

  46. break ;

  47. }

  48. case RESP_STATUS_DELAY:

  49. {

  50. if (chain->timer_is_running == false )

  51. {

  52. chain->timer_is_running = true ;

  53. TimerSetValueStart (&chain->timer , cur_node->duration);

  54. }

  55. if ( TimerGetFlag (&chain->timer) == true )

  56. {

  57. chain->state = RESP_STATUS_OK;

  58. chain->timer_is_running = false ;

  59. }

  60. break ;

  61. }

  62. case RESP_STATUS_BUSY:

  63. {

  64. /*waiting for ans or timeout*/

  65. if (chain->timer_is_running == false )

  66. {

  67. chain->timer_is_running = true ;

  68. TimerSetValueStart (&chain->timer , cur_node->timeout);

  69. }

  70. if ( TimerGetFlag (&chain->timer) == true )

  71. {

  72. chain->state = RESP_STATUS_TIMEOUT;

  73. chain->timer_is_running = false ;

  74. }

  75. break ;

  76. }

  77. case RESP_STATUS_ANS:

  78. {

  79. /*already got the ans,put the param back to the request handle*/

  80. TimerStop (&chain->timer);

  81. chain->timer_is_running = false ;

  82. if (cur_node->handle)

  83. {

  84. cur_node->param_type = RESP_PARAM_ANS;

  85. chain->state = cur_node-> handle (( resp_chain_node_t *)cur_node , chain->param);

  86. }

  87. else

  88. {

  89. RESP_LOG ( "node handle is null ,goto next node" );

  90. chain->state = RESP_STATUS_OK;

  91. }

  92. break ;

  93. }

  94. case RESP_STATUS_TIMEOUT:

  95. {

  96. if (cur_node->retry)

  97. {

  98. cur_node->retry--;

  99. /*retry to request until cnt is 0*/

  100. chain->state = RESP_STATUS_IDLE;

  101. }

  102. else

  103. {

  104. chain->state = RESP_STATUS_ERROR;

  105. }

  106. break ;

  107. }

  108. case RESP_STATUS_ERROR:

  109. {

  110. if (chain->resp_done)

  111. {

  112. chain-> resp_done (( void *)RESP_RESULT_ERROR);

  113. }

  114. chain->enable = 0 ;

  115. chain->state = RESP_STATUS_IDLE;

  116. TimerStop (&chain->timer);

  117. chain->timer_is_running = false ;

  118. cur_node->retry = cur_node->init_retry> 0 ?(cur_node->init_retry -1 ): 0 ;

  119. chain_node_free_all (chain);

  120. break ;

  121. }

  122. case RESP_STATUS_OK:

  123. {

  124. /*get the next node*/

  125. cur_node->retry = cur_node->init_retry> 0 ?(cur_node->init_retry -1 ): 0 ;

  126. chain_node_free (cur_node);

  127. chain->node.next = chain->node.next->next;

  128. chain->state = RESP_STATUS_IDLE;

  129. break ;

  130. }

  131. default :

  132. break ;

  133. }

  134. }

  135. return chain->enable;

  136. }

测试用例

  • 定义并初始化责任链
  • 定义运行函数,在主循环中调用
  • 测试节点添加并启动触发函数
  • 分别实现节点请求函数

结论

  • 实现了裸机处理 顺序延时任务
  • 较大程度的简化了应用程的实现,用户只需要实现响应的处理函数 , 调用接口添加,即可按时间要求执行
  • 参数为空,表明为请求 ,否则为应答。(在某些场合,请求可能也带参数,如接下来所说的LAP协议,此时需要通过判断参数的类型)

 

 

 

 
   
次浏览       
 
相关文章

云计算的架构
对云计算服务模型
云计算核心技术剖析
了解云计算的漏洞
 
相关文档

云计算简介
云计算简介与云安全
下一代网络计算--云计算
软浅析云计算
 
相关课程

云计算原理与应用
云计算应用与开发
CMMI体系与实践
基于CMMI标准的软件质量保证
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]
 
最新文章
基于FPGA的异构计算在多媒体中的应用
深入Linux内核架构——简介与概述
Linux内核系统架构介绍
浅析嵌入式C优化技巧
进程间通信(IPC)介绍
最新课程
嵌入式Linux驱动开发
代码整洁之道-态度、技艺与习惯
嵌入式软件测试
嵌入式C高质量编程
嵌入式软件可靠性设计
更多...   
成功案例
某军工所 嵌入式软件架构
中航工业某研究所 嵌入式软件开发指南
某轨道交通 嵌入式软件高级设计实践
深圳 嵌入式软件架构设计—高级实践
某企业 基于IPD的嵌入式软件开发
更多...