当前位置:   article > 正文

嵌入式设计模式

嵌入式设计模式

嵌入式

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

设计模式

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

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

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

观察者模式

情景

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

实现

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

  1.  struct observer_ops
  2.  {
  3.   void*(handle)(uint8_t evt);  
  4.  };
  5.  ​
  6.  struct observer_intf
  7.  {
  8.      struct observer_intf* next;
  9.      const char* name;
  10.      void* condition;
  11.      const struct observer_ops *ops;
  12.  }
  13.  ​
  14.  int observer_register(struct topical* top , struct observer_intf* observer);

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

  1.  struct observer_intf observer_list;
  2.      
  3.  void XXXX_topical_evt(uint8_t evt)
  4.  {
  5.       struct observer_intf* cur_observer = observer_list.next;
  6.       uint8_t* condition = NULL;
  7.       while(cur_observer != NULL)
  8.       {
  9.           condition = (uint8_t*)cur_observer->condition;
  10.           if(NULL == condition || (condition && *condition))
  11.           {
  12.           if(cur_observer->ops->handle){
  13.                   cur_observer->ops->handle(evt);
  14.               }      
  15.           }
  16.           cur_observer = cur_observer->next;
  17.       }
  18.  }

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

  • 设备功耗分布

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

  • 设备唤醒方式

    • 主动唤醒

      当系统某个定时事件到来时,系统被主动唤醒处理事件

    • 被动唤醒

      系统处于睡眠,被外部事件唤醒,如串口接收到一包数据,传感器检测到变化,通过引脚通知芯片

  • 系统允许睡眠的条件

    • 外设无正在收发的数据

    • 缓存无需要处理的数据

    • 应用层状态处于空闲(无需要处理的事件)

  • 基于观察者模式的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接口

  1. struct uart_dev
  2. {
  3. ...
  4. struct pm pm;
  5. uint32_t pm_condition;
  6. };
  7. struct uart_dev uart1;
  8. void hal_uart1_init(void);
  9. void hal_uart1_deinit(void);
  10. void uart_init(void)
  11. {
  12. uart1.pm.init = hal_uart1_init;
  13. uart1.pm.deinit = hal_uart1_deinit;
  14. uart1.pm.condition = &uart1.pm_condition;
  15. pm_register(&uart1.pm , "uart1");
  16. }
  17. /*接下来串口驱动检查缓存 , 发送 , 接收是否空闲或者忙碌 , 给uart1.pm_condition赋值*/

结论

  • PM 电源管理可以单独形成模块,当功耗外设增加时,只需实现接口,注册即可

  • 通过定义段导出操作,可以更加简化应用层或外设的注册逻辑

  • 方便调试,可以很方便打印出系统当前为满足睡眠条件的模块

  • 通过条件字段划分,应该可以实现系统部分睡眠

职责链模式

情景

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

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

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

  1. void process_task(void)
  2. {
  3. task1_process();
  4. msleep(1000);
  5. task2_process();
  6. mq_recv(&param , 1000);
  7. task3_process();
  8. while(mq_recv(&param , 1000) != OK)
  9. {
  10. if(retry)
  11. {
  12. task3_process();
  13. --try;
  14. }
  15. }
  16. }

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

  1. void process_task(void)
  2. {
  3. switch(task_state)
  4. {
  5. case task1:
  6. task1_process();
  7. set_timeout(1000);break;
  8. case task2:
  9. task1_process();
  10. set_timeout(1000);break;
  11. case task3:
  12. task1_process();
  13. set_timeout(1000)break;
  14. default:break;
  15. }
  16. }
  17. /*定时器超时回调*/
  18. void timeout_cb(void)
  19. {
  20. if(task_state == task1)
  21. {
  22. task_state = task2;
  23. process_task();
  24. }
  25. else //task2 and task3
  26. {
  27. if(retry)
  28. {
  29. retry--;
  30. process_task();
  31. }
  32. }
  33. }
  34. /*任务的应答回调*/
  35. void task_ans_cb(void* param)
  36. {
  37. if(task==task2)
  38. {
  39. task_state = task3;
  40. process_task();
  41. }
  42. }

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

实现

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

  • 任务按顺序执行,只有当前任务执行完了(有结论,成功或者失败)才允许执行下一个任务

  • 前一个任务的执行结果会影响到下一个任务的执行情况

  • 任务有一些特性,如超时时间,延时时间,重试次数

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

node数据结构定义

  1. /*shadow node api type for req_chain src*/
  2. typedef struct shadow_resp_chain_node
  3. {
  4. uint16_t timeout;
  5. uint16_t duration;
  6. uint8_t init_retry;
  7. uint8_t param_type;
  8. uint16_t retry;
  9. /*used in mpool*/
  10. struct shadow_resp_chain_node* mp_prev;
  11. struct shadow_resp_chain_node* mp_next;
  12. /*used resp_chain*/
  13. struct shadow_resp_chain_node* next;
  14. node_resp_handle_fp handle;
  15. void* param;
  16. }shadow_resp_chain_node_t;

node内存池

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

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

职责链数据结构定义

  1. typedef struct resp_chain
  2. {
  3. bool enable; //enble == true 责任链启动
  4. bool is_ans; //收到应答,与void* param 共同组成应答信号
  5. uint8_t state;
  6. const char* name;
  7. void* param;
  8. TimerEvent_t timer;
  9. bool timer_is_running;
  10. shadow_resp_chain_node_t node; //节点链
  11. void(*resp_done)(void* result); //执行结果回调
  12. }resp_chain_t;

职责链初始化

  1. void resp_chain_init(resp_chain_t* chain , const char* name ,
  2. void(*callback)(void* result))
  3. {
  4. RESP_ASSERT(chain);
  5. /*only init one time*/
  6. resp_chain_mpool_init();
  7. chain->enable = false;
  8. chain->is_ans = false;
  9. chain->resp_done = callback;
  10. chain->name = name;
  11. chain->state = RESP_STATUS_IDLE;
  12. chain->node.next = NULL;
  13. chain->param = NULL;
  14. TimerInit(&chain->timer,NULL);
  15. }

职责链添加节点

  1. int resp_chain_node_add(resp_chain_t* chain ,
  2. node_resp_handle_fp handle , void* param)
  3. {
  4. RESP_ASSERT(chain);
  5. BoardDisableIrq();
  6. shadow_resp_chain_node_t* node = chain_node_malloc();
  7. if(node == NULL)
  8. {
  9. BoardEnableIrq();
  10. RESP_LOG("node malloc error ,no free node");
  11. return -2;
  12. }
  13. /*初始化节点,并加入责任链*/
  14. shadow_resp_chain_node_t* l = &chain->node;
  15. while(l->next != NULL)
  16. {
  17. l = l->next;
  18. }
  19. l->next = node;
  20. node->next = NULL;
  21. node->handle = handle;
  22. node->param = param;
  23. node->timeout = RESP_CHIAN_NODE_DEFAULT_TIMEOUT;
  24. node->duration = RESP_CHIAN_NODE_DEFAULT_DURATION;
  25. node->init_retry = RESP_CHIAN_NODE_DEFAULT_RETRY;
  26. node->retry = (node->init_retry == 0)? 0 :(node->init_retry-1);
  27. BoardEnableIrq();
  28. return 0;
  29. }

职责链的启动

  1. void resp_chain_start(resp_chain_t* chain)
  2. {
  3. RESP_ASSERT(chain);
  4. chain->enable = true;
  5. }

职责链的应答

  1. void resp_chain_set_ans(resp_chain_t* chain , void* param)
  2. {
  3. RESP_ASSERT(chain);
  4. if(chain->enable)
  5. {
  6. chain->is_ans = true;
  7. if(param != NULL)
  8. chain->param = param;
  9. else
  10. {
  11. chain->param = "NO PARAM";
  12. }
  13. }
  14. }

职责链的运行

  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. }

测试用例

  • 定义并初始化责任链

    1.  void chain_test_init(void)
    2.  {
    3.   resp_chain_init(&test_req_chain , "test request" , test_req_callback);
    4.  }
  • 定义运行函数,在主循环中调用

    1.  void chain_test_run(void)
    2.  {
    3.   resp_chain_run(&test_req_chain);
    4.  }
  • 测试节点添加并启动触发函数

    1.  void chain_test_tigger(void)
    2.  {
    3.   resp_chain_node_add(&test_req_chain ,  node1_req ,NULL);
    4.   resp_chain_node_add(&test_req_chain ,  node2_req,NULL);
    5.   resp_chain_node_add(&test_req_chain ,  node3_req,NULL);
    6.   resp_chain_start(&test_req_chain);
    7.  }
  • 分别实现节点请求函数

    1.  /*延时1s 后执行下一个节点*/
    2.  int node1_req(resp_chain_node_t* cfg, void* param)
    3.  {
    4.   cfg->duration = 1000;
    5.   RESP_LOG("node1 send direct request: delay :%d ms" , cfg->duration);
    6.   return RESP_STATUS_DELAY;
    7.  }
    8.  /*超时时间1S , 重传次数5次*/
    9.  int node2_req(resp_chain_node_t* cfg , void* param)
    10.  {
    11.   static uint8_t cnt;
    12.   if(param == NULL)
    13.   {
    14.   cfg->init_retry = 5;
    15.      cfg->timeout  = 1000;
    16.  ​
    17.      RESP_LOG("node2 send request max retry:%d , waiting for ans...");
    18.   return RESP_STATUS_BUSY;
    19.   }
    20.   RESP_LOG("node2 get ans: %d",(int)param);
    21.   return RESP_STATUS_OK;
    22.  }
    23.  /*非异步请求*/  
    24.  int node3_req(resp_chain_node_t* cfg , void* param)
    25.  {
    26.   RESP_LOG("node4 send direct request");
    27.   return RESP_STATUS_OK;
    28.  }
    29.  ​
    30.  void ans_callback(void* param)
    31.  {
    32.      resp_chain_set_ans(&test_req_chain , param);
    33.  }

结论

  • 实现了裸机处理 顺序延时任务

  • 较大程度的简化了应用程的实现,用户只需要实现响应的处理函数 , 调用接口添加,即可按时间要求执行

  • 参数为空,表明为请求 ,否则为应答。(在某些场合,请求可能也带参数,如接下来所说的LAP协议,此时需要通过判断参数的类型)

设计模式参考

Head First 设计模式(中文版)

人人都懂设计模式:从生活中领悟设计模式:Python实现

设计模式之禅

设计模式的C语言应用

适配器模式

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/143879
推荐阅读
相关标签
  

闽ICP备14008679号