嵌入式
嵌入式的标签多为:低配,偏硬件,底层,资源紧张,代码多以c语言,汇编为主,代码应用逻辑简单。但随着aiot时代的到来,局面组件改变。芯片的性能资源逐渐提升,业务逻辑也逐渐变得复杂,相对于代码的效率而言,代码的复用可移植性要求越来越高,以获得更短的项目周期 和更高的可维护性。下面是aiot时代嵌入式设备的常见的软件框架。
设计模式
设计模式的标签:高级语言 ,高端,架构等。在aiot时代,设计模式与嵌入式能擦出怎样的火花?设计模式可描述为:对于某类相似的问题,经过前人的不断尝试,总结出了处理此类问题的公认的有效解决办法。
嵌入式主要以c语言开发,且面向过程,而设计模式常见于高级语言(面向对象),目前市面上描述设计模式的书籍多数使用java 语言,c语言能实现设计模式吗?设计模式与语言无关,它是解决问题的方法,java可以实现,c语言同样可以实现。同样的,java程序员会遇到需要用模式来处理的问题,c程序员也可能遇见,因此设计模式是很有必要学习的。
模式陷阱:设计模式是针对具体的某些类问题的有效解决办法,不是所有的问题都能匹配到对应的设计模式。因此,不能一味的追求设计模式,有时候简单直接的处理反而更有效。有的问题没有合适的模式,可以尽量满足一些设计原则,如开闭原则(对扩展开放,对修改关闭)
观察者模式
情景
在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
实现
主题对象提供统一的注册接口,以及注册函数 。由观察者本身实例化observer_intf 接口,然后使用注册函数,添加到对应的主题列表中,主题状态发生改变,依次通知列表中的所有对象。
struct observer_ops { void*(handle)(uint8_t evt); }; struct observer_intf { struct observer_intf* next; const char* name; void* condition; const struct observer_ops *ops; } int observer_register(struct topical* top , struct observer_intf* observer);
当主题状态发生改变,将通知到所有观察者,观察者本身也可以设置条件,是否选择接收通知
struct observer_intf observer_list; void xxxx_topical_evt(uint8_t evt) { struct observer_intf* cur_observer = observer_list.next; uint8_t* condition = null; while(cur_observer != null) { condition = (uint8_t*)cur_observer->condition; if(null == condition || (condition && *condition)) { if(cur_observer->ops->handle){ cur_observer->ops->handle(evt); } } cur_observer = cur_observer->next; } }
实例:嵌入式裸机低功耗框架
设备功耗分布
其中线路损耗,电源电路等软件无法控制,故不讨论。板载外设,如传感器可能通过某条命令配置进入低功耗模式,又或者硬件上支持控制外设电源来控制功耗。片内外设,及芯片内部的外设,通过卸载相关驱动,关闭时钟配置工作模式来控制功耗。
设备唤醒方式
当系统某个定时事件到来时,系统被主动唤醒处理事件
系统处于睡眠,被外部事件唤醒,如串口接收到一包数据,传感器检测到变化,通过引脚通知芯片
被动唤醒
主动唤醒
系统允许睡眠的条件
外设无正在收发的数据
缓存无需要处理的数据
应用层状态处于空闲(无需要处理的事件)
基于观察者模式的pm框架实现
pm组件提供的接口
struct pm{ struct pm* next; const char* name; void(*init)(void); void(*deinit(void); void* condition;};static struct pm pm_list;static uint8_t pm_num;static uint8_t pm_status; int pm_register(const struct pm* pm , const char* name){ struct pm* cur_pm = &pm_list; while(cur_pm->next) { cur_pm = cur_pm->next; } cur_pm->next = pm; pm->next = null; pm->name = name; pm_num++;} void pm_loop(void){ uint32_t pm_condition = 0; struct pm* cur_pm = pm_list.next; static uint8_t cnt; /*check all condition*/ while(cur_pm) { if(cur_pm->condition){ pm_condition |= *((uint32_t*)(cur_pm->condition)); } cur_pm = cur_pm->next; } if(pm_condition == 0) { cnt++; if(cnt>=5) { pm_status = ready_sleep; } } else { cnt = 0; } if( pm_status == ready_sleep) { cur_pm = pm_list.next; while(cur_pm) { if(cur_pm->deinit){ cur_pm->deinit(); } cur_pm = cur_pm->next; } pm_status = sleep; enter_sleep_mode(); } /*sleep--->wakeup*/ if(pm_status == sleep) { pm_status = normal; cur_pm = pm_list.next; while(cur_pm) { if(cur_pm->init){ cur_pm->init(); } cur_pm = cur_pm->next; } }}
外设使用pm接口
struct uart_dev{ ... struct pm pm; uint32_t pm_condition; };struct uart_dev uart1; void hal_uart1_init(void);void hal_uart1_deinit(void); void uart_init(void){ uart1.pm.init = hal_uart1_init; uart1.pm.deinit = hal_uart1_deinit; uart1.pm.condition = &uart1.pm_condition; pm_register(&uart1.pm , uart1);}/*接下来串口驱动检查缓存 , 发送 , 接收是否空闲或者忙碌 , 给uart1.pm_condition赋值*/
结论
pm 电源管理可以单独形成模块,当功耗外设增加时,只需实现接口,注册即可
通过定义段导出操作,可以更加简化应用层或外设的注册逻辑
方便调试,可以很方便打印出系统当前为满足睡眠条件的模块
通过条件字段划分,应该可以实现系统部分睡眠
职责链模式
情景
在现实生活中,一个事件(任务)需要经过多个对象处理是很常见的场景。如报销流程,公司员工报销, 首先员工整理报销单,核对报销金额,有误则继续核对整理,直到无误,将报销单递交到财务,财务部门进行核对,核对无误后,判断金额数量,若小于一定金额,则财务部门可直接审批,若金额超过范围,则报销单流传到总经理,得到批准后,整个任务才算结束。类似的情景还有很多,如配置一个wifi模块,通过at指令,要想模块正确连入wifi , 需要按一定的顺序依次发送配置指令 , 如设置设置模式 ,设置 需要连接的wifi名,密码,每发送一条配置指令,模块都将返回配置结果,而发送者需要判断结果的正确性,再选择是否发送下一条指令或者进行重传。
总结起来是,一系列任务需要严格按照时间线依次处理的顺序逻辑,如下图所示:
在存在系统的情况下,此类逻辑可以很容易的用阻塞延时来实现,实现如下:
void process_task(void){ task1_process(); msleep(1000); task2_process(); mq_recv(¶m , 1000); task3_process(); while(mq_recv(¶m , 1000) != ok) { if(retry) { task3_process(); --try; } }}
在裸机的情况下,为了保证系统的实时性,无法使用阻塞延时,一般使用定时事件配合状态机来实现:
void process_task(void){ switch(task_state) { case task1: task1_process(); set_timeout(1000);break; case task2: task1_process(); set_timeout(1000);break; case task3: task1_process(); set_timeout(1000)break; default:break; }}/*定时器超时回调*/void timeout_cb(void){ if(task_state == task1) { task_state = task2; process_task(); } else //task2 and task3 { if(retry) { retry--; process_task(); } }}/*任务的应答回调*/void task_ans_cb(void* param){ if(task==task2) { task_state = task3; process_task(); }}
和系统实现相比,裸机的实现更加复杂,为了避免阻塞,只能通过状态和定时器来实现顺序延时的逻辑,可以看到,实现过程相当分散,对于单个任务的处理分散到了3个函数中处理,这样导致的后果是:修改,移植的不便。而实际的应用中,类似的逻辑相当多,如果按照上面的方法去实现,将会导致应用程序的强耦合。
实现
可以发现,上面的情景有以下特点:
任务按顺序执行,只有当前任务执行完了(有结论,成功或者失败)才允许执行下一个任务
前一个任务的执行结果会影响到下一个任务的执行情况
任务有一些特性,如超时时间,延时时间,重试次数
通过以上信息,我们可以抽象出这样一个模型:任务作为节点, 每一个任务节点有其属性:如超时,延时,重试,参数,处理方法,执行结果。当需要按照顺序执行一系列任务时,依次将任务节点串成一条链,启动链运行,则从任务链的第一个节点开始运行,运行的结果可以是 ok , busy ,error 。若是ok, 表示节点已处理,从任务链中删除,error 表示运行出错,任务链将停止运行,进行错误回调,可以有用户决定是否继续运行下去。busy表示任务链处于等待应答,或者等待延时的情况。当整条任务链上的节点都执行完,进行成功回调。
node数据结构定义
/*shadow node api type for req_chain src*/typedef struct shadow_resp_chain_node{ uint16_t timeout; uint16_t duration; uint8_t init_retry; uint8_t param_type; uint16_t retry; /*used in mpool*/ struct shadow_resp_chain_node* mp_prev; struct shadow_resp_chain_node* mp_next; /*used resp_chain*/ struct shadow_resp_chain_node* next; node_resp_handle_fp handle; void* param;}shadow_resp_chain_node_t;
node内存池
使用内存池的必要性:实际情况下,同一时间,责任链的条数,以及单条链的节点数比较有限,但种类是相当多的。比如一个支持at指令的模块,可能支持几十条at指令,但执行一个配置操作,可能就只会使用3-5条指令,若全部静态定义节点,将会消耗大量内存资源。因此动态分配是必要的。
初始化node内存池,内存池内所有节点都将添加到free_list。当申请节点时,会取出第一个空闲节点,加入到used_list , 并且接入到责任链。当责任链某一个节点执行完,将会被自动回收(从责任链中删除,并从used_list中删除,然后添加到free_list)
职责链数据结构定义
typedef struct resp_chain{ bool enable; //enble == true 责任链启动 bool is_ans; //收到应答,与void* param 共同组成应答信号 uint8_t state; const char* name; void* param; timerevent_t timer; bool timer_is_running; shadow_resp_chain_node_t node; //节点链 void(*resp_done)(void* result); //执行结果回调}resp_chain_t;
职责链初始化
void resp_chain_init(resp_chain_t* chain , const char* name , void(*callback)(void* result)) { resp_assert(chain); /*only init one time*/ resp_chain_mpool_init(); chain->enable = false; chain->is_ans = false; chain->resp_done = callback; chain->name = name; chain->state = resp_status_idle; chain->node.next = null; chain->param = null; timerinit(&chain->timer,null);}
职责链添加节点
int resp_chain_node_add(resp_chain_t* chain , node_resp_handle_fp handle , void* param){ resp_assert(chain); boarddisableirq(); shadow_resp_chain_node_t* node = chain_node_malloc(); if(node == null) { boardenableirq(); resp_log(node malloc error ,no free node); return -2; } /*初始化节点,并加入责任链*/ shadow_resp_chain_node_t* l = &chain->node; while(l->next != null) { l = l->next; } l->next = node; node->next = null; node->handle = handle; node->param = param; node->timeout = resp_chian_node_default_timeout; node->duration = resp_chian_node_default_duration; node->init_retry = resp_chian_node_default_retry; node->retry = (node->init_retry == 0)? 0 :(node->init_retry-1); boardenableirq(); return 0;}
职责链的启动
void resp_chain_start(resp_chain_t* chain){ resp_assert(chain); chain->enable = true;}
职责链的应答
void resp_chain_set_ans(resp_chain_t* chain , void* param){ resp_assert(chain); if(chain->enable) { chain->is_ans = true; if(param != null) chain->param = param; else { chain->param = no param; } }}
职责链的运行
int resp_chain_run(resp_chain_t* chain){ resp_assert(chain); if(chain->enable) { shadow_resp_chain_node_t* cur_node = chain->node.next; /*maybe ans occur in handle,so cannot change state direct when ans comming*/ if(chain->is_ans) { chain->is_ans = false; chain->state = resp_status_ans; } switch(chain->state) { case resp_status_idle: { if(cur_node) { uint16_t retry = cur_node->init_retry; if(cur_node->handle) { cur_node->param_type = resp_param_input; chain->state = cur_node->handle((resp_chain_node_t*)cur_node ,cur_node->param); } else { resp_log(node handle is null ,goto next node); chain->state = resp_status_ok; } if(retry != cur_node->init_retry) { cur_node->retry = cur_node->init_retry>0?(cur_node- >init_retry-1):0; } } else { if(chain->resp_done) { chain->resp_done((void*)resp_result_ok); } chain->enable = 0; chain->state = resp_status_idle; timerstop(&chain->timer); chain->timer_is_running = false; } break; } case resp_status_delay: { if(chain->timer_is_running == false) { chain->timer_is_running = true; timersetvaluestart(&chain->timer , cur_node->duration); } if(timergetflag(&chain->timer) == true) { chain->state = resp_status_ok; chain->timer_is_running = false; } break; } case resp_status_busy: { /*waiting for ans or timeout*/ if(chain->timer_is_running == false) { chain->timer_is_running = true; timersetvaluestart(&chain->timer , cur_node->timeout); } if(timergetflag(&chain->timer) == true) { chain->state = resp_status_timeout; chain->timer_is_running = false; } break; } case resp_status_ans: { /*already got the ans,put the param back to the request handle*/ timerstop(&chain->timer); chain->timer_is_running = false; if(cur_node->handle) { cur_node->param_type = resp_param_ans; chain->state = cur_node->handle((resp_chain_node_t*)cur_node , chain->param); } else { resp_log(node handle is null ,goto next node); chain->state = resp_status_ok; } break; } case resp_status_timeout: { if(cur_node->retry) { cur_node->retry--; /*retry to request until cnt is 0*/ chain->state = resp_status_idle; } else { chain->state = resp_status_error; } break; } case resp_status_error: { if(chain->resp_done) { chain->resp_done((void*)resp_result_error); } chain->enable = 0; chain->state = resp_status_idle; timerstop(&chain->timer); chain->timer_is_running = false; cur_node->retry = cur_node->init_retry>0?(cur_node->init_retry-1):0; chain_node_free_all(chain); break; } case resp_status_ok: { /*get the next node*/ cur_node->retry = cur_node->init_retry>0?(cur_node->init_retry-1):0; chain_node_free(cur_node); chain->node.next = chain->node.next->next; chain->state = resp_status_idle; break; } default: break; } } return chain->enable;}
测试用例
定义并初始化责任链
void chain_test_init(void){ resp_chain_init(&test_req_chain , test request , test_req_callback);}
定义运行函数,在主循环中调用
void chain_test_run(void){ resp_chain_run(&test_req_chain);}
测试节点添加并启动触发函数
void chain_test_tigger(void){ resp_chain_node_add(&test_req_chain , node1_req ,null); resp_chain_node_add(&test_req_chain , node2_req,null); resp_chain_node_add(&test_req_chain , node3_req,null); resp_chain_start(&test_req_chain);}
分别实现节点请求函数
/*延时1s 后执行下一个节点*/ int node1_req(resp_chain_node_t* cfg, void* param) { cfg->duration = 1000; resp_log(node1 send direct request: delay :%d ms , cfg->duration); return resp_status_delay; } /*超时时间1s , 重传次数5次*/ int node2_req(resp_chain_node_t* cfg , void* param) { static uint8_t cnt; if(param == null) { cfg->init_retry = 5; cfg->timeout = 1000; resp_log(node2 send request max retry:%d , waiting for ans...); return resp_status_busy; } resp_log(node2 get ans: %d,(int)param); return resp_status_ok; } /*非异步请求*/ int node3_req(resp_chain_node_t* cfg , void* param) { resp_log(node4 send direct request); return resp_status_ok; } void ans_callback(void* param) { resp_chain_set_ans(&test_req_chain , param); }
结论
实现了裸机处理 顺序延时任务
较大程度的简化了应用程的实现,用户只需要实现响应的处理函数 , 调用接口添加,即可按时间要求执行
参数为空,表明为请求 ,否则为应答。(在某些场合,请求可能也带参数,如接下来所说的lap协议,此时需要通过判断参数的类型)
抗击肺炎疫情 智能电视火速“受宠”
苹果公司正在开发一种用于为iPhone充电的新配件
工业功能安全 值得信赖的合作伙伴
怎样追寻AI+教育这个风口
3伏汽车适配器电路图解说明
如何应用设计模式的思想写出更优质的嵌入式软件应用程序代码
微雪电子QFN8转DIP8测试座简介
鼎桥携手Forsk提供一流TD-SCDMA网规服务
小米6发布会:小米新机三连发?小米6Plus或有惊喜,小米Max2巨屏之外续航更强
VxWorks里怎样load一个文件到内存?
语音提示器MP3解码芯片,支持U盘更新、OTA升级语音ic,WT2003H
智能家居崛起是因为人工智能吗?智能家居的核心是什么?
ios10和ios6你更喜欢哪一个?
锂电池保护板作用_锂电池保护板怎么激活
自动驾驶热度越来高,其背后的核心技术也是大家关注的焦点之一
NVIDIA全新重磅AI产品,运用游戏、创作、生成式AI和机器人
1路非压缩4K DVI光端机的功能特点及应用
图像采集卡的常见问题集合
Intel的Q3财报出炉,192亿美元的营收创造了50年来的新高
基于ADuC7026微处理器和电机实现Quad-rotor飞行器驱动控制设计