嵌入式的標簽多為:低配,偏硬件,底層,資源緊張,代碼多以C語言,匯編為主,代碼應用邏輯簡單。但隨著AIOT時代的到來,局面組件改變。芯片的性能資源逐漸提升,業(yè)務邏輯也逐漸變得復雜,相對于代碼的效率而言,代碼的復用可移植性要求越來越高,以獲得更短的項目周期 和更高的可維護性。下面是AIOT時代嵌入式設備的常見的軟件框架。
設計模式
設計模式的標簽:高級語言 ,高端,架構等。在AIOT時代,設計模式與嵌入式能擦出怎樣的火花?設計模式可描述為:對于某類相似的問題,經過前人的不斷嘗試,總結出了處理此類問題的公認的有效解決辦法。
嵌入式主要以C語言開發(fā),且面向過程,而設計模式常見于高級語言(面向對象),目前市面上描述設計模式的書籍多數使用JAVA 語言,C語言能實現設計模式嗎?設計模式與語言無關,它是解決問題的方法,JAVA可以實現,C語言同樣可以實現。同樣的,JAVA程序員會遇到需要用模式來處理的問題,C程序員也可能遇見,因此設計模式是很有必要學習的。
模式陷阱:設計模式是針對具體的某些類問題的有效解決辦法,不是所有的問題都能匹配到對應的設計模式。因此,不能一味的追求設計模式,有時候簡單直接的處理反而更有效。有的問題沒有合適的模式,可以盡量滿足一些設計原則,如開閉原則(對擴展開放,對修改關閉)
觀察者模式
情景
在對象之間定義一個一對多的依賴,當一個對象狀態(tài)改變的時候,所有依賴的對象都會自動收到通知。
實現
主題對象提供統(tǒng)一的注冊接口,以及注冊函數 。由觀察者本身實例化observer_intf 接口,然后使用注冊函數,添加到對應的主題列表中,主題狀態(tài)發(fā)生改變,依次通知列表中的所有對象。
?
?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);
?
當主題狀態(tài)發(fā)生改變,將通知到所有觀察者,觀察者本身也可以設置條件,是否選擇接收通知
?
?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; ??????} ?}
?
實例:嵌入式裸機低功耗框架
設備功耗分布
其中線路損耗,電源電路等軟件無法控制,故不討論。板載外設,如傳感器可能通過某條命令配置進入低功耗模式,又或者硬件上支持控制外設電源來控制功耗。片內外設,及芯片內部的外設,通過卸載相關驅動,關閉時鐘配置工作模式來控制功耗。
設備喚醒方式
當系統(tǒng)某個定時事件到來時,系統(tǒng)被主動喚醒處理事件
系統(tǒng)處于睡眠,被外部事件喚醒,如串口接收到一包數據,傳感器檢測到變化,通過引腳通知芯片
被動喚醒
主動喚醒
系統(tǒng)允許睡眠的條件
外設無正在收發(fā)的數據
緩存無需要處理的數據
應用層狀態(tài)處于空閑(無需要處理的事件)
基于觀察者模式的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"); } /*接下來串口驅動檢查緩存?,?發(fā)送?,?接收是否空閑或者忙碌?,?給uart1.pm_condition賦值*/
?
結論
PM 電源管理可以單獨形成模塊,當功耗外設增加時,只需實現接口,注冊即可
通過定義段導出操作,可以更加簡化應用層或外設的注冊邏輯
方便調試,可以很方便打印出系統(tǒng)當前為滿足睡眠條件的模塊
通過條件字段劃分,應該可以實現系統(tǒng)部分睡眠
職責鏈模式
情景
在現實生活中,一個事件(任務)需要經過多個對象處理是很常見的場景。如報銷流程,公司員工報銷, 首先員工整理報銷單,核對報銷金額,有誤則繼續(xù)核對整理,直到無誤,將報銷單遞交到財務,財務部門進行核對,核對無誤后,判斷金額數量,若小于一定金額,則財務部門可直接審批,若金額超過范圍,則報銷單流傳到總經理,得到批準后,整個任務才算結束。類似的情景還有很多,如配置一個WIFI模塊,通過AT指令,要想模塊正確連入WIFI , 需要按一定的順序依次發(fā)送配置指令 , 如設置設置模式 ,設置 需要連接的WIFI名,密碼,每發(fā)送一條配置指令,模塊都將返回配置結果,而發(fā)送者需要判斷結果的正確性,再選擇是否發(fā)送下一條指令或者進行重傳。
總結起來是,一系列任務需要嚴格按照時間線依次處理的順序邏輯,如下圖所示:
在存在系統(tǒng)的情況下,此類邏輯可以很容易的用阻塞延時來實現,實現如下:
?
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; ????????} ????} }
?
在裸機的情況下,為了保證系統(tǒng)的實時性,無法使用阻塞延時,一般使用定時事件配合狀態(tài)機來實現:
?
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(); ????} }
?
和系統(tǒng)實現相比,裸機的實現更加復雜,為了避免阻塞,只能通過狀態(tài)和定時器來實現順序延時的邏輯,可以看到,實現過程相當分散,對于單個任務的處理分散到了3個函數中處理,這樣導致的后果是:修改,移植的不便。而實際的應用中,類似的邏輯相當多,如果按照上面的方法去實現,將會導致應用程序的強耦合。
實現
可以發(fā)現,上面的情景有以下特點:
任務按順序執(zhí)行,只有當前任務執(zhí)行完了(有結論,成功或者失敗)才允許執(zhí)行下一個任務
前一個任務的執(zhí)行結果會影響到下一個任務的執(zhí)行情況
任務有一些特性,如超時時間,延時時間,重試次數
通過以上信息,我們可以抽象出這樣一個模型:任務作為節(jié)點, 每一個任務節(jié)點有其屬性:如超時,延時,重試,參數,處理方法,執(zhí)行結果。當需要按照順序執(zhí)行一系列任務時,依次將任務節(jié)點串成一條鏈,啟動鏈運行,則從任務鏈的第一個節(jié)點開始運行,運行的結果可以是 OK , BUSY ,ERROR 。若是OK, 表示節(jié)點已處理,從任務鏈中刪除,ERROR 表示運行出錯,任務鏈將停止運行,進行錯誤回調,可以有用戶決定是否繼續(xù)運行下去。BUSY表示任務鏈處于等待應答,或者等待延時的情況。當整條任務鏈上的節(jié)點都執(zhí)行完,進行成功回調。
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內存池
使用內存池的必要性:實際情況下,同一時間,責任鏈的條數,以及單條鏈的節(jié)點數比較有限,但種類是相當多的。比如一個支持AT指令的模塊,可能支持幾十條AT指令,但執(zhí)行一個配置操作,可能就只會使用3-5條指令,若全部靜態(tài)定義節(jié)點,將會消耗大量內存資源。因此動態(tài)分配是必要的。
初始化node內存池,內存池內所有節(jié)點都將添加到free_list。當申請節(jié)點時,會取出第一個空閑節(jié)點,加入到used_list , 并且接入到責任鏈。當責任鏈某一個節(jié)點執(zhí)行完,將會被自動回收(從責任鏈中刪除,并從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;????????//節(jié)點鏈 ?void(*resp_done)(void*?result);???????//執(zhí)行結果回調 }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); }
?
職責鏈添加節(jié)點
?
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; ?} ?/*初始化節(jié)點,并加入責任鏈*/ ?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); }
?
定義運行函數,在主循環(huán)中調用
?
void?chain_test_run(void) { ????resp_chain_run(&test_req_chain); }
?
測試節(jié)點添加并啟動觸發(fā)函數
?
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); }
?
分別實現節(jié)點請求函數
?
?/*延時1s?后執(zhí)行下一個節(jié)點*/ ?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); ?}
?
結論
實現了裸機處理 順序延時任務
較大程度的簡化了應用程的實現,用戶只需要實現響應的處理函數 , 調用接口添加,即可按時間要求執(zhí)行
參數為空,表明為請求 ,否則為應答。(在某些場合,請求可能也帶參數,如接下來所說的LAP協議,此時需要通過判斷參數的類型)
評論
查看更多