
嵌入式
(資料圖片僅供參考)
嵌入式的標(biāo)簽多為:低配,偏硬件,底層,資源緊張,代碼多以C語(yǔ)言,匯編為主,代碼應(yīng)用邏輯簡(jiǎn)單。但隨著AIOT時(shí)代的到來(lái),局面組件改變。芯片的性能資源逐漸提升,業(yè)務(wù)邏輯也逐漸變得復(fù)雜,相對(duì)于代碼的效率而言,代碼的復(fù)用可移植性要求越來(lái)越高,以獲得更短的項(xiàng)目周期 和更高的可維護(hù)性。下面是AIOT時(shí)代嵌入式設(shè)備的常見(jiàn)的軟件框架。
設(shè)計(jì)模式
設(shè)計(jì)模式的標(biāo)簽:高級(jí)語(yǔ)言 ,高端,架構(gòu)等。在AIOT時(shí)代,設(shè)計(jì)模式與嵌入式能擦出怎樣的火花?設(shè)計(jì)模式可描述為:對(duì)于某類相似的問(wèn)題,經(jīng)過(guò)前人的不斷嘗試,總結(jié)出了處理此類問(wèn)題的公認(rèn)的有效解決辦法。
嵌入式主要以C語(yǔ)言開(kāi)發(fā),且面向過(guò)程,而設(shè)計(jì)模式常見(jiàn)于高級(jí)語(yǔ)言(面向?qū)ο螅壳笆忻嫔厦枋鲈O(shè)計(jì)模式的書(shū)籍多數(shù)使用JAVA語(yǔ)言,C語(yǔ)言能實(shí)現(xiàn)設(shè)計(jì)模式嗎?設(shè)計(jì)模式與語(yǔ)言無(wú)關(guān),它是解決問(wèn)題的方法,JAVA可以實(shí)現(xiàn),C語(yǔ)言同樣可以實(shí)現(xiàn)。同樣的,JAVA程序員會(huì)遇到需要用模式來(lái)處理的問(wèn)題,C程序員也可能遇見(jiàn),因此設(shè)計(jì)模式是很有必要學(xué)習(xí)的。
模式陷阱:設(shè)計(jì)模式是針對(duì)具體的某些類問(wèn)題的有效解決辦法,不是所有的問(wèn)題都能匹配到對(duì)應(yīng)的設(shè)計(jì)模式。因此,不能一味的追求設(shè)計(jì)模式,有時(shí)候簡(jiǎn)單直接的處理反而更有效。有的問(wèn)題沒(méi)有合適的模式,可以盡量滿足一些設(shè)計(jì)原則,如開(kāi)閉原則(對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉)
觀察者模式
情景
在對(duì)象之間定義一個(gè)一對(duì)多的依賴,當(dāng)一個(gè)對(duì)象狀態(tài)改變的時(shí)候,所有依賴的對(duì)象都會(huì)自動(dòng)收到通知。
實(shí)現(xiàn)
主題對(duì)象提供統(tǒng)一的注冊(cè)接口,以及注冊(cè)函數(shù) 。由觀察者本身實(shí)例化observer_intf 接口,然后使用注冊(cè)函數(shù),添加到對(duì)應(yīng)的主題列表中,主題狀態(tài)發(fā)生改變,依次通知列表中的所有對(duì)象。
structobserver_ops{void*(handle)(uint8_tevt);};structobserver_intf{structobserver_intf*next;constchar*name;void*condition;conststructobserver_ops*ops;}intobserver_register(structtopical*top,structobserver_intf*observer);
當(dāng)主題狀態(tài)發(fā)生改變,將通知到所有觀察者,觀察者本身也可以設(shè)置條件,是否選擇接收通知
structobserver_intfobserver_list;voidXXXX_topical_evt(uint8_tevt){structobserver_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;}}
實(shí)例:嵌入式裸機(jī)低功耗框架
設(shè)備功耗分布
其中線路損耗,電源電路等軟件無(wú)法控制,故不討論。板載外設(shè),如傳感器可能通過(guò)某條命令配置進(jìn)入低功耗模式,又或者硬件上支持控制外設(shè)電源來(lái)控制功耗。片內(nèi)外設(shè),及芯片內(nèi)部的外設(shè),通過(guò)卸載相關(guān)驅(qū)動(dòng),關(guān)閉時(shí)鐘配置工作模式來(lái)控制功耗。
設(shè)備喚醒方式
當(dāng)系統(tǒng)某個(gè)定時(shí)事件到來(lái)時(shí),系統(tǒng)被主動(dòng)喚醒處理事件
系統(tǒng)處于睡眠,被外部事件喚醒,如串口接收到一包數(shù)據(jù),傳感器檢測(cè)到變化,通過(guò)引腳通知芯片
被動(dòng)喚醒
主動(dòng)喚醒
系統(tǒng)允許睡眠的條件
外設(shè)無(wú)正在收發(fā)的數(shù)據(jù)
緩存無(wú)需要處理的數(shù)據(jù)
應(yīng)用層狀態(tài)處于空閑(無(wú)需要處理的事件)
基于觀察者模式的PM框架實(shí)現(xiàn)
PM組件提供的接口
structpm{structpm*next;constchar*name;void(*init)(void);void(*deinit(void);void*condition;};staticstructpmpm_list;staticuint8_tpm_num;staticuint8_tpm_status;intpm_register(conststructpm*pm,constchar*name){structpm*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++;}voidpm_loop(void){uint32_tpm_condition=0;structpm*cur_pm=pm_list.next;staticuint8_tcnt;/*checkallcondition*/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;}}}
外設(shè)使用PM接口
structuart_dev{...structpmpm;uint32_tpm_condition;};structuart_devuart1;voidhal_uart1_init(void);voidhal_uart1_deinit(void);voiduart_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");}/*接下來(lái)串口驅(qū)動(dòng)檢查緩存,發(fā)送,接收是否空閑或者忙碌,給uart1.pm_condition賦值*/
結(jié)論
PM 電源管理可以單獨(dú)形成模塊,當(dāng)功耗外設(shè)增加時(shí),只需實(shí)現(xiàn)接口,注冊(cè)即可
通過(guò)定義段導(dǎo)出操作,可以更加簡(jiǎn)化應(yīng)用層或外設(shè)的注冊(cè)邏輯
方便調(diào)試,可以很方便打印出系統(tǒng)當(dāng)前為滿足睡眠條件的模塊
通過(guò)條件字段劃分,應(yīng)該可以實(shí)現(xiàn)系統(tǒng)部分睡眠
職責(zé)鏈模式
情景
在現(xiàn)實(shí)生活中,一個(gè)事件(任務(wù))需要經(jīng)過(guò)多個(gè)對(duì)象處理是很常見(jiàn)的場(chǎng)景。如報(bào)銷(xiāo)流程,公司員工報(bào)銷(xiāo), 首先員工整理報(bào)銷(xiāo)單,核對(duì)報(bào)銷(xiāo)金額,有誤則繼續(xù)核對(duì)整理,直到無(wú)誤,將報(bào)銷(xiāo)單遞交到財(cái)務(wù),財(cái)務(wù)部門(mén)進(jìn)行核對(duì),核對(duì)無(wú)誤后,判斷金額數(shù)量,若小于一定金額,則財(cái)務(wù)部門(mén)可直接審批,若金額超過(guò)范圍,則報(bào)銷(xiāo)單流傳到總經(jīng)理,得到批準(zhǔn)后,整個(gè)任務(wù)才算結(jié)束。類似的情景還有很多,如配置一個(gè)WIFI模塊,通過(guò)AT指令,要想模塊正確連入WIFI , 需要按一定的順序依次發(fā)送配置指令 , 如設(shè)置設(shè)置模式 ,設(shè)置 需要連接的WIFI名,密碼,每發(fā)送一條配置指令,模塊都將返回配置結(jié)果,而發(fā)送者需要判斷結(jié)果的正確性,再選擇是否發(fā)送下一條指令或者進(jìn)行重傳。
總結(jié)起來(lái)是,一系列任務(wù)需要嚴(yán)格按照時(shí)間線依次處理的順序邏輯,如下圖所示:
在存在系統(tǒng)的情況下,此類邏輯可以很容易的用阻塞延時(shí)來(lái)實(shí)現(xiàn),實(shí)現(xiàn)如下:
voidprocess_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;}}}
在裸機(jī)的情況下,為了保證系統(tǒng)的實(shí)時(shí)性,無(wú)法使用阻塞延時(shí),一般使用定時(shí)事件配合狀態(tài)機(jī)來(lái)實(shí)現(xiàn):
voidprocess_task(void){switch(task_state){casetask1:task1_process();set_timeout(1000);break;casetask2:task1_process();set_timeout(1000);break;casetask3:task1_process();set_timeout(1000)break;default:break;}}/*定時(shí)器超時(shí)回調(diào)*/voidtimeout_cb(void){if(task_state==task1){task_state=task2;process_task();}else//task2andtask3{if(retry){retry--;process_task();}}}/*任務(wù)的應(yīng)答回調(diào)*/voidtask_ans_cb(void*param){if(task==task2){task_state=task3;process_task();}}
和系統(tǒng)實(shí)現(xiàn)相比,裸機(jī)的實(shí)現(xiàn)更加復(fù)雜,為了避免阻塞,只能通過(guò)狀態(tài)和定時(shí)器來(lái)實(shí)現(xiàn)順序延時(shí)的邏輯,可以看到,實(shí)現(xiàn)過(guò)程相當(dāng)分散,對(duì)于單個(gè)任務(wù)的處理分散到了3個(gè)函數(shù)中處理,這樣導(dǎo)致的后果是:修改,移植的不便。而實(shí)際的應(yīng)用中,類似的邏輯相當(dāng)多,如果按照上面的方法去實(shí)現(xiàn),將會(huì)導(dǎo)致應(yīng)用程序的強(qiáng)耦合。
實(shí)現(xiàn)
可以發(fā)現(xiàn),上面的情景有以下特點(diǎn):
任務(wù)按順序執(zhí)行,只有當(dāng)前任務(wù)執(zhí)行完了(有結(jié)論,成功或者失敗)才允許執(zhí)行下一個(gè)任務(wù)
前一個(gè)任務(wù)的執(zhí)行結(jié)果會(huì)影響到下一個(gè)任務(wù)的執(zhí)行情況
任務(wù)有一些特性,如超時(shí)時(shí)間,延時(shí)時(shí)間,重試次數(shù)
通過(guò)以上信息,我們可以抽象出這樣一個(gè)模型:任務(wù)作為節(jié)點(diǎn), 每一個(gè)任務(wù)節(jié)點(diǎn)有其屬性:如超時(shí),延時(shí),重試,參數(shù),處理方法,執(zhí)行結(jié)果。當(dāng)需要按照順序執(zhí)行一系列任務(wù)時(shí),依次將任務(wù)節(jié)點(diǎn)串成一條鏈,啟動(dòng)鏈運(yùn)行,則從任務(wù)鏈的第一個(gè)節(jié)點(diǎn)開(kāi)始運(yùn)行,運(yùn)行的結(jié)果可以是 OK , BUSY ,ERROR 。若是OK, 表示節(jié)點(diǎn)已處理,從任務(wù)鏈中刪除,ERROR 表示運(yùn)行出錯(cuò),任務(wù)鏈將停止運(yùn)行,進(jìn)行錯(cuò)誤回調(diào),可以有用戶決定是否繼續(xù)運(yùn)行下去。BUSY表示任務(wù)鏈處于等待應(yīng)答,或者等待延時(shí)的情況。當(dāng)整條任務(wù)鏈上的節(jié)點(diǎn)都執(zhí)行完,進(jìn)行成功回調(diào)。
node數(shù)據(jù)結(jié)構(gòu)定義
/*shadownodeapitypeforreq_chainsrc*/typedefstructshadow_resp_chain_node{uint16_ttimeout;uint16_tduration;uint8_tinit_retry;uint8_tparam_type;uint16_tretry;/*usedinmpool*/structshadow_resp_chain_node*mp_prev;structshadow_resp_chain_node*mp_next;/*usedresp_chain*/structshadow_resp_chain_node*next;node_resp_handle_fphandle;void*param;}shadow_resp_chain_node_t;
node內(nèi)存池
使用內(nèi)存池的必要性:實(shí)際情況下,同一時(shí)間,責(zé)任鏈的條數(shù),以及單條鏈的節(jié)點(diǎn)數(shù)比較有限,但種類是相當(dāng)多的。比如一個(gè)支持AT指令的模塊,可能支持幾十條AT指令,但執(zhí)行一個(gè)配置操作,可能就只會(huì)使用3-5條指令,若全部靜態(tài)定義節(jié)點(diǎn),將會(huì)消耗大量?jī)?nèi)存資源。因此動(dòng)態(tài)分配是必要的。
初始化node內(nèi)存池,內(nèi)存池內(nèi)所有節(jié)點(diǎn)都將添加到free_list。當(dāng)申請(qǐng)節(jié)點(diǎn)時(shí),會(huì)取出第一個(gè)空閑節(jié)點(diǎn),加入到used_list , 并且接入到責(zé)任鏈。當(dāng)責(zé)任鏈某一個(gè)節(jié)點(diǎn)執(zhí)行完,將會(huì)被自動(dòng)回收(從責(zé)任鏈中刪除,并從used_list中刪除,然后添加到free_list)
職責(zé)鏈數(shù)據(jù)結(jié)構(gòu)定義
typedefstructresp_chain{boolenable;//enble==true責(zé)任鏈啟動(dòng)boolis_ans;//收到應(yīng)答,與void*param共同組成應(yīng)答信號(hào)uint8_tstate;constchar*name;void*param;TimerEvent_ttimer;booltimer_is_running;shadow_resp_chain_node_tnode;//節(jié)點(diǎn)鏈void(*resp_done)(void*result);//執(zhí)行結(jié)果回調(diào)}resp_chain_t;
職責(zé)鏈初始化
voidresp_chain_init(resp_chain_t*chain,constchar*name,void(*callback)(void*result)){RESP_ASSERT(chain);/*onlyinitonetime*/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);}
職責(zé)鏈添加節(jié)點(diǎn)
intresp_chain_node_add(resp_chain_t*chain,node_resp_handle_fphandle,void*param){RESP_ASSERT(chain);BoardDisableIrq();shadow_resp_chain_node_t*node=chain_node_malloc();if(node==NULL){BoardEnableIrq();RESP_LOG("nodemallocerror,nofreenode");return-2;}/*初始化節(jié)點(diǎn),并加入責(zé)任鏈*/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();return0;}
職責(zé)鏈的啟動(dòng)
voidresp_chain_start(resp_chain_t*chain){RESP_ASSERT(chain);chain->enable=true;}
職責(zé)鏈的應(yīng)答
voidresp_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="NOPARAM";}}}
職責(zé)鏈的運(yùn)行
intresp_chain_run(resp_chain_t*chain){RESP_ASSERT(chain);if(chain->enable){shadow_resp_chain_node_t*cur_node=chain->node.next;/*maybeansoccurinhandle,socannotchangestatedirectwhenanscomming*/if(chain->is_ans){chain->is_ans=false;chain->state=RESP_STATUS_ANS;}switch(chain->state){caseRESP_STATUS_IDLE:{if(cur_node){uint16_tretry=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("nodehandleisnull,gotonextnode");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;}caseRESP_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;}caseRESP_STATUS_BUSY:{/*waitingforansortimeout*/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;}caseRESP_STATUS_ANS:{/*alreadygottheans,puttheparambacktotherequesthandle*/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("nodehandleisnull,gotonextnode");chain->state=RESP_STATUS_OK;}break;}caseRESP_STATUS_TIMEOUT:{if(cur_node->retry){cur_node->retry--;/*retrytorequestuntilcntis0*/chain->state=RESP_STATUS_IDLE;}else{chain->state=RESP_STATUS_ERROR;}break;}caseRESP_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;}caseRESP_STATUS_OK:{/*getthenextnode*/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;}}returnchain->enable;}
測(cè)試用例
定義并初始化責(zé)任鏈
voidchain_test_init(void){resp_chain_init(&test_req_chain,"testrequest",test_req_callback);}
定義運(yùn)行函數(shù),在主循環(huán)中調(diào)用
voidchain_test_run(void){resp_chain_run(&test_req_chain);}
測(cè)試節(jié)點(diǎn)添加并啟動(dòng)觸發(fā)函數(shù)
voidchain_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);}
分別實(shí)現(xiàn)節(jié)點(diǎn)請(qǐng)求函數(shù)
/*延時(shí)1s后執(zhí)行下一個(gè)節(jié)點(diǎn)*/intnode1_req(resp_chain_node_t*cfg,void*param){cfg->duration=1000;RESP_LOG("node1senddirectrequest:delay:%dms",cfg->duration);returnRESP_STATUS_DELAY;}/*超時(shí)時(shí)間1S,重傳次數(shù)5次*/intnode2_req(resp_chain_node_t*cfg,void*param){staticuint8_tcnt;if(param==NULL){cfg->init_retry=5;cfg->timeout=1000;RESP_LOG("node2sendrequestmaxretry:%d,waitingforans...");returnRESP_STATUS_BUSY;}RESP_LOG("node2getans:%d",(int)param);returnRESP_STATUS_OK;}/*非異步請(qǐng)求*/intnode3_req(resp_chain_node_t*cfg,void*param){RESP_LOG("node4senddirectrequest");returnRESP_STATUS_OK;}voidans_callback(void*param){resp_chain_set_ans(&test_req_chain,param);}
結(jié)論
實(shí)現(xiàn)了裸機(jī)處理 順序延時(shí)任務(wù)
較大程度的簡(jiǎn)化了應(yīng)用程的實(shí)現(xiàn),用戶只需要實(shí)現(xiàn)響應(yīng)的處理函數(shù) , 調(diào)用接口添加,即可按時(shí)間要求執(zhí)行
參數(shù)為空,表明為請(qǐng)求 ,否則為應(yīng)答。(在某些場(chǎng)合,請(qǐng)求可能也帶參數(shù),如接下來(lái)所說(shuō)的LAP協(xié)議,此時(shí)需要通過(guò)判斷參數(shù)的類型)
審核編輯:湯梓紅標(biāo)簽: