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