嵌入式
嵌入式的標(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ū)ο螅?,目前市面上描述設(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)收到通知。
主題對(duì)象提供統(tǒng)一的注冊(cè)接口,以及注冊(cè)函數(shù) 。由觀察者本身實(shí)例化observer_intf 接口,然后使用注冊(cè)函數(shù),添加到對(duì)應(yīng)的主題列表中,主題狀態(tài)發(fā)生改變,依次通知列表中的所有對(duì)象。
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;
}
intobserver_register(structtopical*top,structobserver_intf*observer);
當(dāng)主題狀態(tài)發(fā)生改變,將通知到所有觀察者,觀察者本身也可以設(shè)置條件,是否選擇接收通知。
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;
}
}
實(shí)例:嵌入式裸機(jī)低功耗框架
- 設(shè)備功耗分布

-
設(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組件提供的接口:
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;
}
}
}
外設(shè)使用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");
}
/*接下來(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)銷流程,公司員工報(bào)銷, 首先員工整理報(bào)銷單,核對(duì)報(bào)銷金額,有誤則繼續(xù)核對(duì)整理,直到無(wú)誤,將報(bào)銷單遞交到財(cái)務(wù),財(cái)務(wù)部門(mén)進(jìn)行核對(duì),核對(duì)無(wú)誤后,判斷金額數(shù)量,若小于一定金額,則財(cái)務(wù)部門(mén)可直接審批,若金額超過(guò)范圍,則報(bà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)如下:
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;
}
}
}
在裸機(jī)的情況下,為了保證系統(tǒng)的實(shí)時(shí)性,無(wú)法使用阻塞延時(shí),一般使用定時(shí)事件配合狀態(tài)機(jī)來(lái)實(shí)現(xiàn):
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;
}
}
/*定時(shí)器超時(shí)回調(diào)*/
void timeout_cb(void)
{
if(task_state == task1)
{
task_state = task2;
process_task();
}
else //task2 and task3
{
if(retry)
{
retry--;
process_task();
}
}
}
/*任務(wù)的應(yīng)答回調(diào)*/
void task_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ù)
node數(shù)據(jù)結(jié)構(gòu)定義:
/*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內(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)分配是必要的。
職責(zé)鏈數(shù)據(jù)結(jié)構(gòu)定義:
typedef struct resp_chain
{
bool enable; //enble == true 責(zé)任鏈啟動(dòng)
bool is_ans; //收到應(yīng)答,與void* param 共同組成應(yīng)答信號(hào)
uint8_t state;
const char* name;
void* param;
TimerEvent_t timer;
bool timer_is_running;
shadow_resp_chain_node_t node; //節(jié)點(diǎn)鏈
void(*resp_done)(void* result); //執(zhí)行結(jié)果回調(diào)
}resp_chain_t;
職責(zé)鏈初始化:
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);
}
職責(zé)鏈添加節(jié)點(diǎn):
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é)點(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();
return 0;
}
職責(zé)鏈的啟動(dòng):
void resp_chain_start(resp_chain_t* chain)
{
RESP_ASSERT(chain);
chain->enable = true;
}
職責(zé)鏈的應(yīng)答:
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";
}
}
}
職責(zé)鏈的運(yùn)行:
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;
}
測(cè)試用例:
- 定義并初始化責(zé)任鏈
void chain_test_init(void)
{
resp_chain_init(&test_req_chain , "test request" , test_req_callback);
}
- 定義運(yùn)行函數(shù),在主循環(huán)中調(diào)用
void chain_test_run(void)
{
resp_chain_run(&test_req_chain);
}
- 測(cè)試節(jié)點(diǎn)添加并啟動(dòng)觸發(fā)函數(shù)
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);
}
- 分別實(shí)現(xiàn)節(jié)點(diǎn)請(qǐng)求函數(shù)
/*延時(shí)1s 后執(zhí)行下一個(gè)節(jié)點(diǎn)*/
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;
}
/*超時(shí)時(shí)間1S , 重傳次數(shù)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;
}
/*非異步請(qǐng)求*/
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);
}
結(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ù)的類型)
-
芯片
+關(guān)注
關(guān)注
460文章
52520瀏覽量
441134 -
嵌入式
+關(guān)注
關(guān)注
5152文章
19675瀏覽量
317691 -
C語(yǔ)言
+關(guān)注
關(guān)注
180文章
7632瀏覽量
141812
原文標(biāo)題:如何在嵌入式中使用設(shè)計(jì)模式的思想?
文章出處:【微信號(hào):c-stm32,微信公眾號(hào):STM32嵌入式開(kāi)發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
深入了解嵌入式編程
如何在嵌入式系統(tǒng)設(shè)計(jì)中使用UML技術(shù)

怎么利用分層思想進(jìn)行嵌入式項(xiàng)目設(shè)計(jì)
如何在嵌入式系統(tǒng)設(shè)計(jì)中使用UML技術(shù)?
如何在嵌入式系統(tǒng)設(shè)計(jì)中使用UML技術(shù)?
嵌入式系統(tǒng)硬件抽象層的設(shè)計(jì)思想簡(jiǎn)析
基于AVR單片機(jī)的嵌入式“瘦服務(wù)器”系統(tǒng)設(shè)計(jì)思想

評(píng)論