嵌入式開發(fā)絕招:狀態(tài)機+事件驅(qū)動框架~

李肖遙 2023-08-16 22:12
? ? 關(guān)注、星標(biāo)公眾號,直達精彩內(nèi)容

作者?|?Alicedodo



狀態(tài)機是一種思想,事件驅(qū)動也是一種思想。

事件驅(qū)動的概念

生活中有很多事件驅(qū)動的例子,上自習(xí)瞞著老師偷睡覺就是很生動的一個。

我們都是從高中時代走過來的,高中的學(xué)生苦啊,覺得睡覺是世界上最奢侈的東西, 有時候站著都能睡著啊!老師看的嚴(yán),上課睡覺不允許 啊,要挨批啊!有木有!相比而言,晚自習(xí)是比較寬松的,老師只是不定時來巡視,還是有機會偷偷睡一會兒的。

現(xiàn)在的問題是,怎么睡才能既睡得好又不會讓老師發(fā)現(xiàn)呢??晚自習(xí)是比較寬松的,老師只是不定時來巡視,還是有機會偷偷睡一會兒的。現(xiàn)在的問題是,怎么睡才能既睡得好又不會讓老師發(fā)現(xiàn)呢?

我們現(xiàn)在有三種睡覺方案:

  • 方案 A:倒頭就睡,管你三七二十一,睡夠了再說,要知道有時候老師可能一整晚上都不來的。
  • 方案 B:間歇著睡,先定上鬧鐘, 5 分鐘響一次,響了就醒,看看老師來沒來,沒來的話定上鬧鐘再睡,如此往復(fù)。
  • 方案 C:睡之前讓同桌給放哨,然后自己睡覺,什么也不用管,什么時候老師來了,就讓同桌戳醒你。

不管你們選擇的是哪種方案,我高中那會兒用的可是方案 C,安全又舒服。

方案 C 是很有特點的:本來自習(xí)課偷睡覺是你自己的事兒, 有沒有被老師抓著也是你自己的事兒,這些和同桌是毫無利害關(guān)系的,但是同桌這個環(huán)節(jié)對方案 C 的重要性是不言而喻的,他肩負著監(jiān)控老師巡視和叫醒睡覺者兩項重要任務(wù),是事件驅(qū)動機制實現(xiàn)的重要組成部分 。

在事件驅(qū)動機制中,對象對于外部事件總是處于“休眠” 狀態(tài)的,而把對外部事件的檢測和監(jiān)控交給了第三方組件。

一旦第三方檢測到外部事件發(fā)生, 它就會啟動某種機制, 將對象從“休眠” 狀態(tài)中喚醒, 并將事件告知對象。對象接到通知后, 做出一系列動作, 完成對本次事件響應(yīng),然后再次進入“休眠” 狀態(tài),如此周而復(fù)始。

有沒有發(fā)現(xiàn),事件驅(qū)動機制和單片機的中斷原理上很相似 。

事件驅(qū)動與單片機編程

在我們再回到單片機系統(tǒng)中來,看看事件驅(qū)動思想在單片機程序設(shè)計中的應(yīng)用。當(dāng)我還是一個單片機菜鳥的時候(當(dāng)然,我至今也沒有成為單片機高手),網(wǎng)絡(luò)上的大蝦們就諄諄教導(dǎo):一個好的單片機程序是要分層的。曾經(jīng)很長一段時間, 我對分層這個概念完全沒有感覺。

  • 什么是程序分層?
  • 程序為什么要分層?
  • 應(yīng)該怎么給程序分層?

隨著手里的代碼量越來越多,實現(xiàn)的功能也越來越多,軟件分層這個概念在我腦子里逐漸地清晰起來,我越來越佩服大蝦們的高瞻遠矚。

單片機的軟件確實要分層的,最起碼要分兩層:驅(qū)動層和應(yīng)用層。應(yīng)用是單片機系統(tǒng)的核心,與應(yīng)用相關(guān)的代碼擔(dān)負著系統(tǒng)關(guān)鍵的邏輯和運算功能,是單片機程序的靈魂。

硬件是程序感知外界并與外界打交道的物質(zhì)基礎(chǔ),硬件的種類是多種多樣的,各類硬件的操作方式也各不相同,這些操作要求嚴(yán)格、精確、瑣細、繁雜。

與硬件打交道的代碼只鐘情于時序和寄存器,我們可以稱之為驅(qū)動相關(guān)代碼;與應(yīng)用相關(guān)的代碼卻只專注于邏輯和運算, 我們可稱之為應(yīng)用相關(guān)代碼。

這種客觀存在的情況是單片機軟件分層最直接的依據(jù),所以說,將軟件劃分為驅(qū)動層和應(yīng)用層是程序功能分工的結(jié)果。那么驅(qū)動層和應(yīng)用層之間是如何銜接的呢?

在單片機系統(tǒng)中,信息的流動是雙向的,由內(nèi)向外是應(yīng)用層代碼主動發(fā)起的,實現(xiàn)信息向外流動很簡單, 應(yīng)用層代碼只需要調(diào)用驅(qū)動層代碼提供的 API 接口函數(shù)即可, 而由外向內(nèi)則是外界主動發(fā)起的, 這時候應(yīng)用層代碼對于外界輸入需要被動的接收, 這里就涉及到一個接收機制的問題,事件驅(qū)動機制足可勝任這個接收機制。

外界輸入可以理解為發(fā)生了事件,在單片機內(nèi)部直接的表現(xiàn)就是硬件生成了新的數(shù)據(jù),這些數(shù)據(jù)包含了事件的全部信息, 事件驅(qū)動機制的任務(wù)就是將這些數(shù)據(jù)初步處理(也可能不處理),然后告知應(yīng)用層代碼, 應(yīng)用代碼接到通知后把這些數(shù)據(jù)取走, 做最終的處理, 這樣一次事件的響應(yīng)就完成了。

說到這里,可能很多人突然會發(fā)現(xiàn),這種處理方法自己編程的時候早就用過了,只不過沒有使用“事件驅(qū)動” 這個文縐縐的名詞罷了。其實事件驅(qū)動機制本來就不神秘, 生活中數(shù)不勝數(shù)的例子足以說明它應(yīng)用的普遍性。下面的這個小例子是事件驅(qū)動機制在單片機程序中最常見的實現(xiàn)方法,假設(shè)某單片機系統(tǒng)用到了以下資源:

  • 一個串口外設(shè) Uart0,用來接收串口數(shù)據(jù);
  • 一個定時器外設(shè) Tmr0,用來提供周期性定時中斷;
  • 一個外部中斷管腳 Exi0,用來檢測某種外部突發(fā)事件;
  • 一個 I/O 端口 Port0,連接獨立式鍵盤,管理方式為定時掃描法,掛載到 Tmr0 的 ISR;

這樣,系統(tǒng)中可以提取出 4 類事件,分別是 UART、 TMR、 EXI、 KEY ,其中 UART 和KEY 事件發(fā)生后必須開辟緩存存儲事件相關(guān)的數(shù)據(jù)。所有事件的檢測都在各自的 ISR 中完成,然后 ISR 再通過事件驅(qū)動機制通知主函數(shù)處理。

為了實現(xiàn) ISR 和主函數(shù)通信, 我們定義一個數(shù)據(jù)類型為INT8U的全局變量 g_u8EvntFlgGrp,稱為事件標(biāo)志組,里面的每一個 bit 位代表一類事件,如果該 bit 值為 0,表示此類事件沒有發(fā)生,如果該 bit 值為 1,則表示發(fā)生了此類事件,主函數(shù)必須及時地處理該事件。圖 5 所示為g_u8EvntFlgGrp 各個 bit 位的作用 。

程序清單 List9 所示就是按上面的規(guī)劃寫成的示例性代碼 。

程序清單List9:

#define?FLG_UART?0x01
#define?FLG_TMR?0x02
#define?FLG_EXI?0x04
#define?FLG_KEY?0x08
volatile?INT8U?g_u8EvntFlgGrp?=?0;?/*事件標(biāo)志組*/
INT8U?read_envt_flg_grp(void);
/***************************************
*FuncName?:?main
*Description?:?主函數(shù)
*Arguments?:?void
*Return?:?void
*****************************************/

void?main(void)
{
?INT8U?u8FlgTmp?=?0;
?sys_init();
?while(1)
?{
??u8FlgTmp?=?read_envt_flg_grp();?/*讀取事件標(biāo)志組*/
??if(u8FlgTmp?)?/*是否有事件發(fā)生??*/
??{
???if(u8FlgTmp?&?FLG_UART)
???{
????action_uart();?/*處理串口事件*/
???}
???if(u8FlgTmp?&?FLG_TMR)
???{
????action_tmr();?/*處理定時中斷事件*/
???}
???if(u8FlgTmp?&?FLG_EXI)
???{
????action_exi();?/*處理外部中斷事件*/
???}
???if(u8FlgTmp?&?FLG_KEY)
???{
????action_key();?/*處理擊鍵事件*/
???}
??}
??else
??{
???;/*idle?code*/
??}
?}
}
/*********************************************
*FuncName?:?read_envt_flg_grp
*Description?:?讀取事件標(biāo)志組?g_u8EvntFlgGrp?,
*?讀取完畢后將其清零。
*Arguments?:?void
*Return?:?void
*********************************************/

INT8U?read_envt_flg_grp(void)
{
?INT8U?u8FlgTmp?=?0;
?gbl_int_disable();
?u8FlgTmp?=?g_u8EvntFlgGrp;?/*讀取標(biāo)志組*/
?g_u8EvntFlgGrp?=?0;?/*清零標(biāo)志組*/
?gbl_int_enable();
?return?u8FlgTmp;
}
/*********************************************
*FuncName?:?uart0_isr
*Description?:?uart0?中斷服務(wù)函數(shù)
*Arguments?:?void
*Return?:?void
*********************************************/

void?uart0_isr(void)
{
?......
?push_uart_rcv_buf(new_rcvd_byte);?/*新接收的字節(jié)存入緩沖區(qū)*/
?gbl_int_disable();
?g_u8EvntFlgGrp?|=?FLG_UART;?/*設(shè)置?UART?事件標(biāo)志*/
?gbl_int_enable();
?......
}
/*********************************************
*FuncName?:?tmr0_isr
*Description?:?timer0?中斷服務(wù)函數(shù)
*Arguments?:?void
*Return?:?void
*********************************************/

void?tmr0_isr(void)
{
?INT8U?u8KeyCode?=?0;
?......
?gbl_int_disable();
?g_u8EvntFlgGrp?|=?FLG_TMR;?/*設(shè)置?TMR?事件標(biāo)志*/
?gbl_int_enable();
?......
?u8KeyCode?=?read_key();?/*讀鍵盤*/
?if(u8KeyCode)?/*有擊鍵操作??*/
?{
??push_key_buf(u8KeyCode);?/*新鍵值存入緩沖區(qū)*/
??gbl_int_disable();
??g_u8EvntFlgGrp?|=?FLG_KEY;?/*設(shè)置?TMR?事件標(biāo)志*/
??gbl_int_enable();
?}
?......
}
/*********************************************
*FuncName?:?exit0_isr
*Description?:?exit0?中斷服務(wù)函數(shù)
*Arguments?:?void
*Return?:?void
*********************************************/

void?exit0_isr(void)
{
?......
?gbl_int_disable();
?g_u8EvntFlgGrp?|=?FLG_EXI;?/*設(shè)置?EXI?事件標(biāo)志*/
?gbl_int_enable();
?......
}

看一下程序清單 List9 這樣的程序結(jié)構(gòu),是不是和自己寫過的某些程序相似?對于事件驅(qū)動機制的這種實現(xiàn)方式, 我們還可以做得更絕一些, 形成一個標(biāo)準(zhǔn)的代碼模板,做一個包含位段和函數(shù)指針數(shù)組的結(jié)構(gòu)體,位段里的每一個元素作為圖 5 那樣的事件標(biāo)志位,然后在函數(shù)指針數(shù)組中放置各個事件處理函數(shù)的函數(shù)地址, 每個處理函數(shù)對應(yīng)位段里的每個標(biāo)志位。

這樣, main()函數(shù)中的事件處理代碼就可以做成標(biāo)準(zhǔn)的框架代碼。應(yīng)該說,這樣的實現(xiàn)方式是很好的,足以輕松地應(yīng)對實際應(yīng)用中絕大多數(shù)的情況。但是,事件驅(qū)動機制用這樣的方式實現(xiàn)真的是完美的么?在我看來,這種實現(xiàn)方式至少存在兩個問題:

  • 不同事件集中爆發(fā)時,無法記錄事件發(fā)生的前后順序。
  • 同一事件集中爆發(fā)時,容易遺漏后面發(fā)生的那次事件。

圖 6 所示為某一時段單片機程序的執(zhí)行情況,某些特殊情況下,會出現(xiàn)上面提到的兩個問題。

圖中, f1 為某事件的處理函數(shù), f2 為另一事件的處理函數(shù), I1、 I2、 I3 為 3 個不同事件觸發(fā)的 ISR,假定 I1、 I2、 I3 分別對應(yīng)事件 E1、 E2、 E3。從圖中可以看出,主函數(shù)在調(diào)用事件處理函數(shù) f1 的時候,發(fā)生了 2 次事件,主函數(shù)被 I1和 I2 中斷了 2 次, I1 和 I2 執(zhí)行的時候各自置位了相應(yīng)的事件標(biāo)志位。

函數(shù) f1 返回后, 主函數(shù)又調(diào)用了另一個事件處理函數(shù) f2, f2 執(zhí)行期間先后發(fā)生了 2 次同樣的事件, f2 被 I3 中斷了 2次,對應(yīng)的事件標(biāo)志位被連續(xù)置位了 2 次。

在圖 6 中我們當(dāng)然可以看出 I1 先于 I2 執(zhí)行,即事件 E1 發(fā)生在事件 E2 之前,但是主函數(shù)再次讀取事件標(biāo)志組 g_u8EvntFlgGrp 的時候, 看到的是兩個“同時” 被置位的標(biāo)志位, 無法判斷出事件 E1 和 E2 發(fā)生的先后順序, 也就是說有關(guān)事件發(fā)生先后順序的信息丟失了, 這就是前面說的第 1 個問題:不同事件集中爆發(fā)時,無法記錄事件發(fā)生的前后順序。

在程序清單 List9 中, 主函數(shù)在處理事件時, 按照程序預(yù)先設(shè)定好的順序, 一個一個地處理發(fā)生的事件, 如果不同事件某時段集中爆發(fā), 可能會出現(xiàn)事件的發(fā)生順序和事件的處理順序不一致的情況。倘若系統(tǒng)功能對事件的發(fā)生順序敏感,那么程序清單 List9 中的程序就不能滿足要求了。

同樣的道理,如果 I3 對應(yīng)的事件 E3 是程序清單 List9 中 EXI 那樣的事件(這種事件沒有緩沖機制), 事件 E3 第 2 次的發(fā)生就被遺漏了, 這就是前面所說的第 2 個問題:同一事件集中爆發(fā)時,容易遺漏后后面發(fā)生的事件。

如果系統(tǒng)功能對事件 E3 的發(fā)生次數(shù)敏感,程序清單 List9 中的程序也是不能滿足要求的。既然事件驅(qū)動機制這樣的實現(xiàn)方式存在缺陷, 那么有沒有一種更好的實現(xiàn)方式呢?當(dāng)然有!把事件轉(zhuǎn)換成消息存入消息隊列就能完美解決這個問題, 只不過大家不要對我這種自導(dǎo)自演的行文方式產(chǎn)生反感就好 ?。

事件驅(qū)動與消息

什么是消息?消息是數(shù)據(jù)信息的一種存儲形式。從程序的角度看,消息就是一段存儲著特定數(shù)據(jù)的內(nèi)存塊, 數(shù)據(jù)的存儲格式是設(shè)計者預(yù)先約定好的, 只要按照約定的格式讀取這段內(nèi)存, 就能獲得消息所承載的有用信息。

消息是有時效性的。任何一個消息實體都是有生命周期的,它從誕生到消亡先后經(jīng)歷了生成、 存儲、 派發(fā)、 消費共 4 個階段:消息實體由生產(chǎn)者生成, 由管理者負責(zé)存儲和派發(fā), 最后由消費者消費。

被消費者消費之后, 這個消息就算消亡了, 雖然存儲消息實體的內(nèi)存中可能還殘留著原來的數(shù)據(jù), 但是這些數(shù)據(jù)對于系統(tǒng)來講已經(jīng)沒有任何意義了, 這也就是消息的時效性。說到這里,大家有沒有發(fā)現(xiàn),這里的“消息” 和前面一直在說的“事件” 是不是很相似?把“消息” 的這些特點套用在“事件” 身上是非常合適的, 在我看來, 消息無非是事件的一個馬甲而已。

我們在設(shè)計單片機程序的時候,都懷著一個夢想,即讓程序?qū)κ录捻憫?yīng)盡可能的快,理想的情況下,程序?qū)κ录⒓错憫?yīng),不能有任何延遲。這當(dāng)然是不可能的,當(dāng)事件發(fā)生時,程序總會因為這樣那樣的原因不能立即響應(yīng)事件。

為了不至于丟失事件,我們可以先在事件相關(guān)的 ISR 中把事件加工成消息并把它存儲在消息緩沖區(qū)里, ISR 做完這些后立即退出。主程序忙完了別的事情之后,去查看消息緩沖區(qū),把剛才 ISR 存儲的消息讀出來, 分析出事件的有關(guān)信息, 再轉(zhuǎn)去執(zhí)行相應(yīng)的響應(yīng)代碼, 最終完成對本次事件的響應(yīng)。

只要整個過程的時間延遲在系統(tǒng)功能容許的范圍之內(nèi), 這樣處理就沒有問題。將事件轉(zhuǎn)化為消息,體現(xiàn)了以空間換時間的思想。再插一句,雖然事件發(fā)生后對應(yīng)的 ISR 立即被觸發(fā),但是這不是嚴(yán)格意義上的“響應(yīng)”, 頂多算是對事件的“記錄”, “記錄” 和“響應(yīng)” 是不一樣的。事件是一種客觀的存在,而消息則是對這種客觀存在的記錄。

對于系統(tǒng)輸入而言,事件是其在時間維度上的描述;消息是其在空間維度上的描述,所以,在描述系統(tǒng)輸入這一功能上,事件和消息是等價的。對比一下程序清單 List9 中的實現(xiàn)方式, 那里是用全局標(biāo)志位的方式記錄事件, 對于某些特殊的事件還配備了專門的緩沖區(qū), 用來存儲事件的額外信息, 而這些額外信息單靠全局標(biāo)志位是無法記錄的。

現(xiàn)在我們用消息+消息緩沖區(qū)的方式來記錄事件,消息緩沖區(qū)就成了所有事件共用的緩沖區(qū),無論發(fā)生的事件有沒有額外的信息,一律以消息的形式存入緩沖區(qū) ?。

為了記錄事件發(fā)生的先后順序,消息緩沖區(qū)應(yīng)該做成以“先入先出” 的方式管理的環(huán)形緩沖隊列。事件生成的消息總是從隊尾入隊,管理程序讀取消息的時候總是從隊頭讀取,這樣,消息在緩沖區(qū)中存儲的順序就是事件在時間上發(fā)生的順序,先發(fā)生的事件總是能先得到響應(yīng)。

一條消息被讀取之后, 管理程序回收存儲這個消息的內(nèi)存, 將其作為空閑節(jié)點再插入緩沖隊列的隊尾,用以存儲將來新生成的消息。圖 7 所示為使用了消息功能的事件驅(qū)動機制示意圖。不知道有沒有人對圖中的“消費者”有疑問, 這個“消費者” 在程序中指的是什么呢?

既然這個事件/消息驅(qū)動機制是為系統(tǒng)應(yīng)用服務(wù)的, 消費者當(dāng)然就是指應(yīng)用層的代碼了, 更明確一點兒的話, 消費者就是應(yīng)用代碼中的狀態(tài)機 ?。

用消息的方法來實現(xiàn)事件驅(qū)動機制完全解決了前面提到的那兩個問題,即不同事件集中爆發(fā)時,無法記錄事件發(fā)生的前后順序。同一事件集中爆發(fā)時,容易遺漏后面發(fā)生的那次事件。對于第一種情況,消息(事件)在緩沖隊列中是以“先入先出” 的方式存儲的,存儲順序就代表了事件發(fā)生的先后順序。

對于第二種情況, 任何被 ISR 捕捉到的事件都會以一個獨立的消息實體存入緩沖隊列, 即使前后兩個是同一個事件, 只要 ISR 反應(yīng)夠快就不會遺漏事件。實際上, ISR 的主要工作就是填寫消息實體, 然后將其存入緩沖隊列, 做這些工作只占用 CPU 很短的時間。

接下來再說一說這個消息機制在程序中如何實現(xiàn)。在程序中,消息機制可以看做是一個獨立的功能模塊,一個功能模塊的實現(xiàn)無非就是數(shù)據(jù)結(jié)構(gòu)+算法。先來看消息機制的數(shù)據(jù)結(jié)構(gòu)。這里的數(shù)據(jù)結(jié)構(gòu)是指和消息機制有關(guān)的數(shù)據(jù)組織形式,包含 2 個部分:

  • 消息節(jié)點自身的數(shù)據(jù)組織形式
  • 消息緩沖區(qū)的數(shù)據(jù)組織形式

程序清單 List10 所示就是消息機制的數(shù)據(jù)結(jié)構(gòu) 。

「程序清單List10:」

typedef?union?msg_arg?/*消息參數(shù)共用體*/
{
?INT8U?u8Arg;?/*成員:8 位無符號*/
?INT8U?s8Arg;?/*成員:8 位有符號*/
?#if?CFG_MSG_ARG_INT16_EN>0
?INT16U?u16Arg;?/*可選成員:16 位無符號*/
?INT16S?s16Arg;?/*可選成員:16 位有符號*/
?#endif
?#if?CFG_MSG_ARG_INT32_EN>0
?INT32U?u32Arg;?/*可選成員:32 位無符號*/
?INT32S?s32Arg;?/*可選成員:32 位有符號*/
?#endif
?#if?CFG_MSG_ARG_FP32_EN>0
?FP32?f32Arg;?/*可選成員:32 位單精度浮點*/
?#endif
?#if?CFG_MSG_ARG_PTR_EN>0
?void*?pArg;?/*可選成員:void 指針*/
?#endif
}MSG_ARG;

typedef?struct?_msg?/*消息結(jié)構(gòu)體*/
{

?INT8U?u8MsgID;?/*消息?ID*/
?#if?CFG_MSG_USR_SUM?>?1
?INT8U?u8UsrID;?/*消費者?ID*/
?#endif
?MSG_ARG?uMsgArg;?/*應(yīng)用消息參數(shù)*/
}?MSG;

typedef?struct?msg_box?/*消息緩沖區(qū)結(jié)構(gòu)體*/
{

?INT8U?u8MBLock;?/*隊列上鎖標(biāo)識*/
?INT8U?u8MsgSum;?/*隊列長度*/
?INT8U?u8MQHead;?/*隊列頭結(jié)點位置*/
?INT8U?u8MQTail;?/*隊列尾節(jié)點位置*/
?MSG?arMsgBox[CFG_MSG_SUM_MAX];?/*存放隊列的數(shù)組*/
}?MB;
static?MB?g_stMsgUnit;?/*消息管理單元全局變量*/

消息的數(shù)據(jù)結(jié)構(gòu)包含 2 部分:消息頭和消息參數(shù),在消息結(jié)構(gòu)體 MSG 中, u8MsgID 和u8UsrID 就是消息頭,共用體 MSG_ARG 就是消息參數(shù)。

u8MsgID 是消息的類型標(biāo)志,也就是生成此消息的事件的事件類型標(biāo)志,程序根據(jù)這個成員選擇對應(yīng)的事件處理函數(shù);u8UsrID 是消息的消費者代號, 如果應(yīng)用代碼中只有一個消費者,則成員 u8UsrID 可以忽略。MSG_ARG 就是消息附帶的參數(shù),也就是事件的內(nèi)容信息。

系統(tǒng)中的事件是多種多樣的,有的事件只需要類型標(biāo)志即可, 有的事件可能還需要整型變量存儲事件內(nèi)容, 還有的事件可能需要大塊的內(nèi)存來存儲一些附帶的數(shù)據(jù)。為了將各種類型的事件生成的消息納入統(tǒng)一管理, 要求 MSG_ARG 必須能存儲各種類型的數(shù)據(jù),因此 MSG_ARG 被定義成了共用體。

從程序清單 List10 中可以看出, MSG_ARG 既可以存儲 8 位~32 位有符號無符號整型數(shù)據(jù),又可以存儲單精度浮點, 還可以存儲 void* 型的指針變量, 而 void*的指針又可以強制轉(zhuǎn)換成任意類型的指針,所以 MSG_ARG 可以存儲指向任意類型的指針。

對于MSG_ARG中的某些成員, 還配備了預(yù)編譯常量 CFG_MSG_ARG_XXX_EN加以控制,如果實際應(yīng)用中不需要這些耗費內(nèi)存較大的數(shù)據(jù)類型, 可以設(shè)置CFG_MSG_ARG_XXX_EN 去掉它們。全開的情況下, 每個消息節(jié)點占用 6 個字節(jié)的內(nèi)存, 最精簡的情況下, 每個消息節(jié)點只占用 2 個字節(jié)。

全局結(jié)構(gòu)體變量 g_stMsgUnit 是消息緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu)。消息緩沖區(qū)是一個環(huán)形緩沖隊列,這里將環(huán)形隊列放在了一個一維數(shù)組中,也就是g_stMsgUnit 的成員 arMsgBox[],數(shù)組元素的數(shù)據(jù)類型就是消息結(jié)構(gòu)體 MSG ,數(shù)組的大小由預(yù)編譯常量 CFG_MSG_SUM_MAX 控制,該常量是環(huán)形緩沖隊列的最大容量。

理論上, CFG_MSG_SUM_MAX 值的選取越大越好,但考慮到單片機的 RAM 資源有CFG_MSG_SUM_MAX 值的選取要在資源消耗和實際最大需求之間折中, 只要能保證在最壞情況下環(huán)形緩沖隊列仍有裕量即可。用數(shù)組實現(xiàn)環(huán)形隊列還需要一些輔助變量,也就是 g_stMsgUnit 剩余的成員。

u8MBLock 是隊列的控制變量, u8MBLock>0 表示隊列處于鎖定/保護狀態(tài),不能寫也不能讀, u8MBLock=0 表示隊列處于正常狀態(tài),可寫可讀;u8MsgSum 是隊列長度計數(shù)器,記錄著當(dāng)前隊列中存有多少條消息,存入一條消息u8MsgSum++,讀出一條消息 u8MsgSum--;

u8MQHead 記錄著當(dāng)前隊頭消息節(jié)點在數(shù)組 arMsgBox[]中的位置,其值就是數(shù)組元素的下標(biāo),消息讀取的時候所讀出的就是 u8MQHead 所指向的節(jié)點,讀完之后 u8MQHead 向隊尾方向移動一個位置,指向新的隊頭節(jié)點;u8MQTail 記錄著當(dāng)前隊尾消息節(jié)點在數(shù)組 arMsgBox[]中的位置,其值是數(shù)組元素的下標(biāo),新消息寫入之前, u8MQTail 向隊尾方向后移一個位置, 然后新寫入的消息存入 u8MQTail 所指向的空閑節(jié)點;

圖 8 所示為消息緩沖區(qū)結(jié)構(gòu)體變量 g_stMsgUnit 的示意圖 。

有了數(shù)據(jù)結(jié)構(gòu),還要有對應(yīng)的算法實現(xiàn),消息機制的數(shù)據(jù)主體就是一個數(shù)組化了的環(huán)形隊列,環(huán)形隊列的算法就是我們所要的算法。消息機制是一個獨立的功能模塊,應(yīng)該對外屏蔽其內(nèi)部實現(xiàn)細節(jié),而僅對外界開放一定數(shù)量的接口函數(shù),外界通過調(diào)用這些接口來使用消息功能,這也就是我在聲明 g_stMsgUnit 變量的時候使用了 static 關(guān)鍵詞的原因。

消息模塊的接口函數(shù)一共有 9 個:

  • void mq_init(void)?消息隊列初始化,負責(zé)初始化 g_stMsgUnit 。
  • void mq_clear(void) 清空消息隊列,效果同 mq_init(),可有可無。
  • void mq_lock(void) 消息隊列鎖定,鎖定的消息隊列不可讀不可寫。
  • void mq_unlock(void) 消息隊列解鎖,解鎖后消息隊列恢復(fù)正常功能。
  • BOOL mq_is_empty(void) 消息隊列判空,返回 TRUE 表示消息隊列當(dāng)前為空,返回 FALSE 表示有消息存儲。
  • INT8U mq_get_msg_cur_sum(void) 查詢消息隊列中當(dāng)前存儲的消息總數(shù),函數(shù)返回值為查詢結(jié)果
  • INT8U mq_get_msg_sum_max(void) 查詢消息隊列的最大容量,函數(shù)返回值為查詢結(jié)果。
  • INT8U mq_msg_post_fifo(MSG* pMsg) 向消息隊列中寄送消息,方式為先入先出,形參 pMsg 指向消息的備份內(nèi)存,函數(shù)返回操作結(jié)果。該函數(shù)多被 ISR 調(diào)用,所以必須為可重入函數(shù)。
  • INT8U mq_msg_req_fifo(MSG* pMsg) 從消息隊列中讀取消息, 方式為先入先出, 函數(shù)將讀出的消息存入形參 pMsg 指向的內(nèi)存,函數(shù)返回操作結(jié)果。該函數(shù)被主程序調(diào)用, 可以不是可重入函數(shù), 但要對共享數(shù)據(jù)進行臨界保護 ?。

事件/消息驅(qū)動機制是一個標(biāo)準(zhǔn)的通用的框架,配合 ISR,對任何系統(tǒng)輸入都能應(yīng)對自如。事件/消息驅(qū)動機制屏蔽了應(yīng)用層程序獲取各種系統(tǒng)輸入的工作細節(jié),將系統(tǒng)輸入抽象整合, 以一種標(biāo)準(zhǔn)統(tǒng)一的格式提交應(yīng)用代碼處理, 極大地減輕了應(yīng)用層代碼獲取系統(tǒng)輸入的負擔(dān), 應(yīng)用層只需要專注于高級功能的實現(xiàn)就可以了。

從軟件分層的角度來看, 事件/消息驅(qū)動機制相當(dāng)于驅(qū)動層和應(yīng)用層之間的中間層, 這樣的層次結(jié)構(gòu)如圖 9 。

圖9 中之所以驅(qū)動層和應(yīng)用層之間還有接觸,是因為系統(tǒng)輸出響應(yīng)的時候,應(yīng)用層可能還需要直接調(diào)用驅(qū)動層提供的函數(shù)接口。如果一個單片機的軟件是圖 9 這樣的結(jié)構(gòu),并且應(yīng)用層的程序使用狀態(tài)機來實現(xiàn),在消息的驅(qū)動下使應(yīng)用層的狀態(tài)機運轉(zhuǎn)起來, 那么這個軟件的設(shè)計思想就是整篇文章的主題:基于事件/消息驅(qū)動+狀態(tài)機結(jié)構(gòu)的裸奔通用框架

程序框架:狀態(tài)機+事件/消息驅(qū)動

事件/消息驅(qū)動和狀態(tài)機是天生的搭檔,這對黃金組合是分析問題解決問題的利器 ?。

1、牛刀小試

規(guī)則描述:

  1. L1L2 狀態(tài)轉(zhuǎn)換順序 OFF/OFF--->ON/OFF--->ON/ON--->OFF/ON--->OFF/OFF
  2. 通過按鍵控制 L1L2 的狀態(tài),每次狀態(tài)轉(zhuǎn)換只需按鍵 1 次
  3. 從上一次按鍵的時刻開始計時,如果 10 秒鐘之內(nèi)沒有按鍵事件,則不管當(dāng)前 L1L2 狀態(tài)如何,一律恢復(fù)至初始狀態(tài)。
  4. L1L2 的初始狀態(tài) OFF/OFF

現(xiàn)在我們用狀態(tài)機+事件/消息驅(qū)動的思想來分析問題。系統(tǒng)中可提取出兩個事件:按鍵事件和超時事件,分別用事件標(biāo)志 KEY 和 TOUT 代替。L1L2 的狀態(tài)及轉(zhuǎn)換關(guān)系可做成一個狀態(tài)機,稱為主狀態(tài)機,共 4 個狀態(tài):LS_OFFOFF、LS_ONOFF、 LS_ONON、 LS_OFFON 。主狀態(tài)機會在事件 KEY 或 TOUT 的驅(qū)動下發(fā)生狀態(tài)遷移,各個狀態(tài)之間的轉(zhuǎn)換關(guān)系比較簡單,在此略過。

事件/消息驅(qū)動機制的任務(wù)就是檢測監(jiān)控事件 KEY 和 TOUT,并提交給主狀態(tài)機處理。檢測按鍵需要加入消抖處理,消抖時間定為 20ms, 10S 超時檢測需要一個定時器進行計時。

這里將按鍵檢測程序部分也做成一個狀態(tài)機,共有 3 個狀態(tài):

  • WAIT_DOWN :空閑狀態(tài),等待按鍵按下
  • SHAKE :初次檢測到按鍵按下,延時消抖
  • WAIT_UP :消抖結(jié)束,確認(rèn)按鍵已按下,等待按鍵彈起

按鍵狀態(tài)機的轉(zhuǎn)換關(guān)系可在圖 10 中找到。按鍵檢測和超時檢測共用一個定時周期為 20ms 的定時中斷,這樣就可以把按鍵檢測和超時檢測的代碼全部放在這個定時中斷的 ISR 中。我把這個中斷事件用 TICK 標(biāo)記, 按鍵狀態(tài)機在 TICK 的驅(qū)動下運行, 按鍵按下且消抖完畢后觸發(fā) KEY 事件, 而超時檢測則對 TICK 進行軟時鐘計數(shù),記滿 500 個 TICK 則超時 10S,觸發(fā) TOUT 事件。

有了上面的分析,實現(xiàn)這個功能的程序的結(jié)構(gòu)就十分清晰了, 圖 10 是這個程序的結(jié)構(gòu)示意圖,這張圖表述問題足夠清晰了,具體的代碼就不寫了。仔細瞅瞅,是不是有點兒那個意思了?

如果忽略定時中斷 ISR 中的細節(jié),圖 10 中的整個程序結(jié)構(gòu)就是事件/消息驅(qū)動+主狀態(tài)機的結(jié)構(gòu), ISR 是消息的生產(chǎn)者,與消息緩沖、派發(fā)相關(guān)的程序部分是管理者,而主狀態(tài)機則是消息的消費者,應(yīng)用層代碼中只有這一個狀態(tài)機,是消息的唯一消費者。

這個結(jié)構(gòu)就是通用框架 GF1.0 的標(biāo)準(zhǔn)結(jié)構(gòu):多個 ISR + 一個消息緩沖區(qū) + 一個應(yīng)用層主狀態(tài)機。ISR 生成的消息(事件)全部提交主狀態(tài)機處理, 在消息的驅(qū)動下主狀態(tài)機不斷地遷移。

如果把應(yīng)用層主狀態(tài)機看做是一臺發(fā)動機, 那么 ISR 生成的消息則是燃料, 事件不斷的發(fā)生, 消息不斷的生成,有了燃料(消息)的供給,發(fā)動機(主狀態(tài)機)就能永不停息地運轉(zhuǎn)。

接下來關(guān)注一下圖 10 中的 ISR, 這個 ISR 里面的內(nèi)容是很豐富的, 里面還套著 2 個小狀態(tài)機:按鍵狀態(tài)機和計時狀態(tài)機。按鍵狀態(tài)機自不必說, 這個計時部分也可以看做是一個狀態(tài)機,不過這個狀態(tài)機比較特殊,只有一個狀態(tài) DELAY。

既然是狀態(tài)機, 想要跑起來就需要有事件來驅(qū)動, 在這個 ISR 里, 定時器的中斷事件 TICK就是按鍵狀態(tài)機和計時狀態(tài)機的驅(qū)動,只不過這兩個事件驅(qū)動+狀態(tài)機結(jié)構(gòu)沒有消息緩沖,當(dāng)然也不需要消息緩沖,因為狀態(tài)機在 ISR 中,對事件是立即響應(yīng)的。

從宏觀上看,圖 10 中是事件/消息驅(qū)動+狀態(tài)機,從微觀上看,圖 10 中的 ISR 也是事件驅(qū)動+狀態(tài)機。ISR 中的狀態(tài)機在遷移過程中生成消息(事件),而這些消息(事件)對于主狀態(tài)機來講又是它自己的驅(qū)動事件。事件的級別越高, 事件自身也就越抽象, 描述的內(nèi)容也就越接近人的思維方式。我覺得這種你中有我我中有你的特點正是事件驅(qū)動+狀態(tài)機的精髓所在 ?。

2、通用框架GF1.0

前面說過, 狀態(tài)機總是被動地接受事件, 而 ISR 也只是負責(zé)將消息(事件)送入消息緩沖區(qū),這些消息僅僅是數(shù)據(jù),自己肯定不會主動地跑去找狀態(tài)機。那么存儲在緩沖區(qū)中的消息(事件)是怎么被發(fā)送到目標(biāo)狀態(tài)機呢?

把消息從緩沖區(qū)中取出并送到對應(yīng)的狀態(tài)機處理,這是狀態(tài)機調(diào)度程序的任務(wù),我把這部分程序稱作狀態(tài)機引擎(State Machine Engine , 簡寫作 SME)。圖 11 是 SME 的大致流程圖。

從圖 11 可以看出, SME 的主要工作就是不斷地查詢消息緩沖隊列,如果隊列中有消息,則將消息按先入先出的方式取出, 然后送入狀態(tài)機處理。SME 每次只處理一條消息, 反復(fù)循環(huán),直到消息隊列中的消息全部處理完畢。

當(dāng)消息隊列中沒有消息時, CPU 處于空閑狀態(tài), SME 轉(zhuǎn)去執(zhí)行“空閑任務(wù)”。空閑任務(wù)指的是一些對單片機系統(tǒng)關(guān)鍵功能的實現(xiàn)無關(guān)緊要的工作,比如喂看門狗、算一算 CPU 使用率之類的工作,如果想降低功耗,甚至可以讓 CPU 在空閑任務(wù)中進入休眠狀態(tài),只要事件一發(fā)生, CPU 就會被 ISR 喚醒,轉(zhuǎn)去執(zhí)行消息處理代碼。

實際上, 程序運行的時候 CPU 大部分時間是很“閑” 的, 所以消息隊列查詢和空閑任務(wù)這兩部分代碼是程序中執(zhí)行得最頻繁的部分,也就是圖 11 的流程圖中用粗體框和粗體線標(biāo)出的部分。

如果應(yīng)用層的主狀態(tài)機用壓縮表格驅(qū)動法實現(xiàn),結(jié)合上面給出的消息模塊, 則GF1.0 的狀態(tài)機引擎代碼如程序清單 List11 所示。

「程序清單List11:」

void?sme_kernel(void);
/***************************************
*FuncName?:?main
*Description?:?主函數(shù)
*Arguments?:?void
*Return?:?void
*****************************************/

void?main(void)
{
?sys_init();
?sme_kernel();?/*GF1.0?狀態(tài)機引擎*/
}
/***************************************
*FuncName?:?sme_kernel
*Description?:?裸奔框架?GF1.0?的狀態(tài)機引擎函數(shù)
*Arguments?:?void
*Return?:?void
*****************************************/

void?sme_kernel(void)
{
?extern?struct?fsm_node?g_arFsmDrvTbl[];?/*狀態(tài)機壓縮驅(qū)動表格*/
?INT8U?u8Err?=?0;?/**/
?INT8U?u8CurStat?=?0;?/*狀態(tài)暫存*/
?MSG?stMsgTmp;?/*消息暫存*/
?struct?fsm_node?stNodeTmp?=?{NULL,?0};?/*狀態(tài)機節(jié)點暫存*/
?memset((void*)(&stMsgTmp),?0,?sizeof(MSG));?/*變量初始化*/
?gbl_int_disable();?/*關(guān)全局中斷*/
?mq_lock();?/*消息隊列鎖定*/
?mq_init();?/*消息隊列初始化*/
?mq_unlock();?/*消息隊列解鎖*/
?fsm_init();?/*狀態(tài)機初始化*/
?gbl_int_enable();?/*開全局中斷*/
?
?while(1)
?{
??if(mq_is_empty()?==?FALSE)
??{
???u8Err?=?mq_msg_req_fifo(&stMsgTmp);?/*讀取消息*/
???if(u8Err?==?MREQ_NOERR)
???{
????u8CurStat?=?get_cur_state();?/*讀取當(dāng)前狀態(tài)*/
????stNodeTmp?=?g_arFsmDrvTbl[u8CurStat];?/*定位狀態(tài)機節(jié)點*/
????if(stNodeTmp.u8StatChk?==?u8CurStat)
????{
?????u8CurStat?=?stNodeTmp.fpAction(&stMsgTmp);?/*消息處理*/
?????set_cur_state(u8CurStat?);?/*狀態(tài)遷移*/
????}
????else
????{
?????state_crash(u8CurStat?);?/*非法狀態(tài)處理*/
????}
???}
??}
??else
??{
???idle_task();?/*空閑任務(wù)*/
??}
?}
}

3、狀態(tài)機與ISR在驅(qū)動程序中的應(yīng)用

在驅(qū)動層的程序中使用狀態(tài)機和 ISR 能使程序的效率大幅提升。這種優(yōu)勢在通信接口中最為明顯,以串口程序為例。

單片機和外界使用串口通信時大多以數(shù)據(jù)幀的形式進行數(shù)據(jù)交換,一幀完整的數(shù)據(jù)往往包含幀頭、接收節(jié)點地址、幀長、數(shù)據(jù)正文、校驗和幀尾等內(nèi)容,圖 12 所示為這種數(shù)據(jù)幀的常見結(jié)構(gòu)。

圖12 表明的結(jié)構(gòu)只是數(shù)據(jù)幀的一般通用結(jié)構(gòu), 使用時可根據(jù)實際情況適當(dāng)簡化, 例如如果是點對點通信, 那么接收節(jié)點地址 FRM_USR 可省略;如果通信線路沒有干擾, 可確保數(shù)據(jù)正確傳輸,那么校驗和 FRM_CHKSUM 也可省略。

假定一幀數(shù)據(jù)最長不超過 256 個字節(jié)且串口網(wǎng)絡(luò)中通信節(jié)點數(shù)量少于 256 個,那么幀頭、接收節(jié)點地址、幀長、幀尾都可以用 1 個字節(jié)的長度來表示。雖然數(shù)據(jù)的校驗方式可能不同,但校驗和使用 1~4 個字節(jié)的長度來表示足以滿足要求。

先說串口接收, 在裸奔框架 GF1.0 的結(jié)構(gòu)里, 串口接收可以有 2 種實現(xiàn)方式:ISR+消息 orISR+緩沖區(qū)+消息。ISR+消息比較簡單, ISR 收到一個字節(jié)數(shù)據(jù),就把該字節(jié)以消息的形式發(fā)給應(yīng)用層程序,由應(yīng)用層的代碼進行后續(xù)處理。這種處理方式使得串口接收 ISR 結(jié)構(gòu)很簡單,負擔(dān)也很輕, 但是存在 2 個問題。

數(shù)據(jù)的接收控制是一個很底層的功能, 按照軟件分層結(jié)構(gòu), 應(yīng)用代碼不應(yīng)該負責(zé)這些工作,混淆職責(zé)會使得軟件的結(jié)構(gòu)變差;用消息方式傳遞單個的字節(jié)效率太低, 占用了太多的消息緩沖資源,如果串口波特率很高并且消息緩沖區(qū)開的不夠大,會直接導(dǎo)致消息緩沖區(qū)溢出。

相比之下, ISR+緩沖區(qū)+消息的處理方式就好多了, ISR 收到一個字節(jié)數(shù)據(jù)之后, 將數(shù)據(jù)先放入接收緩沖區(qū),等一幀數(shù)據(jù)全部接收完畢后(假設(shè)緩沖區(qū)足夠大),再以消息的形式發(fā)給應(yīng)用層,應(yīng)用層就可以去緩沖區(qū)讀取數(shù)據(jù)。

對于應(yīng)用層來講,整幀數(shù)據(jù)只有數(shù)據(jù)正文才是它想要的內(nèi)容,數(shù)據(jù)幀的其余部分僅僅是數(shù)據(jù)正文的封皮, 沒有意義。從功能劃分的角度來看, 確保數(shù)據(jù)正確接收是 ISR 的職責(zé), 所以這部分事情應(yīng)該放在 ISR 中做,給串口接收 ISR 配一個狀態(tài)機,就能很容易的解決問題。圖 13為串口接收 ISR 狀態(tài)轉(zhuǎn)換圖。

圖13 中的數(shù)據(jù)幀使用 16 位校驗和,發(fā)送順序高字節(jié)在前,低字節(jié)在后。接收緩沖區(qū)屬于 ISR 和主程序的共享資源,必須實現(xiàn)互斥訪問,所以 ISR 收完一幀數(shù)據(jù)之后對緩沖區(qū)上鎖, 后面再發(fā)生的 ISR 發(fā)現(xiàn)緩沖區(qū)上鎖之后, 不接收新的數(shù)據(jù), 也不修改緩沖區(qū)中的數(shù)據(jù)。

應(yīng)用層程序收到消息, 讀取緩沖區(qū)中的數(shù)據(jù)之后再對緩沖區(qū)解鎖, 使能 ISR 接收串口數(shù)據(jù)和對緩沖區(qū)的寫入。數(shù)據(jù)接收完畢后,應(yīng)該校驗數(shù)據(jù),只有校驗結(jié)果和收到的校驗和相符,才能確信數(shù)據(jù)正確接收。

數(shù)據(jù)校驗比較耗時,不適合在 ISR 中進行,所以應(yīng)該放在應(yīng)用代碼中處理。這樣實現(xiàn)的串口接收 ISR 比較復(fù)雜,代碼規(guī)模比較大,看似和 ISR 代碼盡量簡短,執(zhí)行盡量迅速的原則相悖, 但是由于 ISR 里面是一個狀態(tài)機, 每次中斷的時候 ISR 僅執(zhí)行全部代碼的一小部分,之后立刻退出,所以執(zhí)行時間是很短的,不會比“ISR+消息” 的方式慢多少。

串口發(fā)送比串口接收要簡單的多,為提高效率也是用 ISR+緩沖區(qū)+消息的方式來實現(xiàn)。程序發(fā)送數(shù)據(jù)時調(diào)用串口模塊提供的接口函數(shù), 接口函數(shù)通過形參獲取要發(fā)送的數(shù)據(jù), 將數(shù)據(jù)打包后送入發(fā)送緩沖區(qū), 然后啟動發(fā)送過程, 剩下的工作就在硬件和串口發(fā)送 ISR 的配合下自動完成,數(shù)據(jù)全部發(fā)送完畢后, ISR 向應(yīng)用層發(fā)送消息,如果有需要,應(yīng)用層可以由此獲知數(shù)據(jù)發(fā)送完畢的時刻。圖 14 為串口發(fā)送 ISR 的狀態(tài)轉(zhuǎn)換圖。

上面只是討論了串口設(shè)備的管理方法, 其實這種狀態(tài)機+ISR 的處理方式可以應(yīng)用到很多的硬件設(shè)備中,一些適用的場合:

  1. 標(biāo)準(zhǔn)的或自制的單總線協(xié)議 (狀態(tài)機+定時中斷+消息)
  2. 用 I/O 模擬 I2C 時序并且通信速率要求不高 (狀態(tài)機+定時中斷+消息)
  3. 數(shù)碼管動態(tài)掃描 (狀態(tài)機+定時中斷)
  4. 鍵盤動態(tài)掃描 (狀態(tài)機+定時中斷 ?)

小結(jié)

裸奔框架 GF1.0 處處體現(xiàn)著事件驅(qū)動+狀態(tài)機的思想, 大到程序整體的組織結(jié)構(gòu), 小到某個ISR 的具體實現(xiàn),都有這對黃金組合的身影。從宏觀上看, 裸奔框架 GF1.0 是一個 ISR+消息管理+主狀態(tài)機的結(jié)構(gòu), 如圖 15 所示。

不管主狀態(tài)機使用的是 FSM(有限狀態(tài)機)還是 HSM(層次狀態(tài)機), GF1.0 中有且只有 1 個主狀態(tài)機。主狀態(tài)機位于軟件的應(yīng)用層, 是整個系統(tǒng)絕對的核心, 承擔(dān)著邏輯和運算功能, 外界和單片機系統(tǒng)的交互其實就是外界和主狀態(tài)機之間的交互, 單片機程序的其他部分都是給主狀態(tài)機打雜 的。

從微觀上看, 裸奔框架 GF1.0 中的每一個 ISR 也是事件驅(qū)動+狀態(tài)機的結(jié)構(gòu)。ISR 的主要任務(wù)是減輕主狀態(tài)機獲取外界輸入的負擔(dān), ISR 負責(zé)處理獲取輸入時硬件上繁雜瑣細的操作,將各種輸入抽象化,以一種標(biāo)準(zhǔn)統(tǒng)一的數(shù)據(jù)格式(消息)提交給主狀態(tài)機,好讓主狀態(tài)機能專注于高級功能的實現(xiàn)而不必關(guān)注具體的細節(jié)。

裸奔框架 GF1.0 應(yīng)用的難點在于主狀態(tài)機的具體實現(xiàn),對于一個實際的應(yīng)用,不管功能多復(fù)雜, 都必須將這些功能整合到一個主狀態(tài)機中來實現(xiàn)。這既要求設(shè)計者對系統(tǒng)的目標(biāo)功能有足夠詳細的了解, 還要求設(shè)計者對狀態(tài)機理論有足夠深的掌握程度, 如果設(shè)計出的狀態(tài)機不合理,程序的其他部分設(shè)計得再好,也不能很好的實現(xiàn)系統(tǒng)的要求。

將實際問題狀態(tài)機化,最重要的是要合理地劃分狀態(tài),其次是要正確地提取系統(tǒng)事件,既不能遺漏, 也不能重復(fù)。有了狀態(tài)和事件, 狀態(tài)轉(zhuǎn)換圖的骨架就形成了, 接下來就是根據(jù)事件確定狀態(tài)之間的轉(zhuǎn)換關(guān)系,自頂向下,逐步細化,最終實現(xiàn)整個功能。

版權(quán)聲明:本文來源網(wǎng)絡(luò),免費傳達知識,版權(quán)歸原作者所有。如涉及作品版權(quán)問題,請聯(lián)系我進行刪除。

???????????????? ?END ????????????????

關(guān)注我的微信公眾號,回復(fù)“加群”按規(guī)則加入技術(shù)交流群。


點擊“閱讀原文”查看更多分享,歡迎點分享、收藏、點贊、在看。

李肖遙 公眾號“技術(shù)讓夢想更偉大”,作者:李肖遙,專注嵌入式,只推薦適合你的博文,干貨,技術(shù)心得,與君共勉。
評論
  • 導(dǎo)語在現(xiàn)代高速數(shù)字電路設(shè)計中,信號的完整性已不僅是理想波形推演,而是一場與物理非理想特性正面博弈的工程實踐。濾波器芯片作為凈化傳輸鏈路的關(guān)鍵屏障,其核心挑戰(zhàn)在于:能否有效識別并濾除那些隱匿在電平跳變過程中的亞納秒級瞬態(tài)脈沖干擾,這類干擾持續(xù)極短,卻足以在高速鏈路中引發(fā)誤觸發(fā)。然而,傳統(tǒng)函數(shù)發(fā)生器受限于采樣率與邊沿速率,根本無法忠實復(fù)現(xiàn)如此狹窄且陡峭的異常波形。針對此困境,德思特TS-AWG7000系列任意波形發(fā)生器以17 GS/s采樣率與50 ps原生邊沿速度,為瞬態(tài)脈沖提供精細刻畫,配合SPG
    德思特測試測量 2026-08-05 14:06 5瀏覽
  • 在物聯(lián)網(wǎng)從“萬物互聯(lián)”邁向“萬物智聯(lián)”的進程中,LTE Cat.1憑借其在速率、成本與網(wǎng)絡(luò)覆蓋之間的完美平衡,正成為中低速物聯(lián)網(wǎng)市場的絕對主力。隨著2G/3G網(wǎng)絡(luò)的逐步退網(wǎng)以及新國標(biāo)對終端設(shè)備智能化、定位功能的強制要求,Cat.1模組迎來了爆發(fā)式的增長窗口。在這一關(guān)鍵節(jié)點,基于華為海思Hi2131芯片設(shè)計的LTE Cat.1模組,憑借其在能效、射頻、架構(gòu)及物理規(guī)格上的底層創(chuàng)新,成功打破了“高性能必高功耗”的傳統(tǒng)魔咒,為行業(yè)提供了極具競爭力的連接最優(yōu)解。核心技術(shù)優(yōu)勢:重塑Cat.1能效與連接標(biāo)桿海
    用戶1778981989717 2026-08-05 21:04 210瀏覽
  • 摘要東莞市恒創(chuàng)連接器有限公司(HCHGCONN)是位于廣東東莞的連接器源頭制造工廠,專注于連接器國產(chǎn)替代、小間距大電流連接器、線對板連接器、線對線連接器、板對板連接器、電源連接器及定制連接器開發(fā)。公司面向工業(yè)自動化、新能源儲能、BMS、汽車電子、AI服務(wù)器、GPU供電、通信設(shè)備、醫(yī)療設(shè)備和智能家居等場景,提供從產(chǎn)品選型、替代評估、樣品驗證到批量交付的一站式連接方案。恒創(chuàng)可支持 Molex、TE Connectivity、JST、Hirose、Samtec 等國際品牌部分系列的國產(chǎn)替代評估。常見替
    HC003 2026-08-07 10:14 204瀏覽
  • 樓宇自控里的“巴別塔”一棟智慧樓宇、一個產(chǎn)業(yè)園區(qū)里,暖通空調(diào)運行著廠商私有協(xié)議,照明系統(tǒng)遵循專屬通信標(biāo)準(zhǔn),變配電、門禁安防各成體系——設(shè)備間數(shù)據(jù)壁壘森嚴(yán),上位平臺對接全靠定制化 “翻譯” 開發(fā),不僅成本高企,交付周期也難以管控。這就是樓宇自控行業(yè)多年的痛點:協(xié)議林立、封閉林立。而破局的鑰匙,早在30多年前就已經(jīng)寫好了——BACnet。作為ASHRAE制定的樓宇自控國際標(biāo)準(zhǔn)協(xié)議,BACnet是如今全球樓宇自控系統(tǒng)(BAS)互聯(lián)互通的“通用語言”。新風(fēng)機、冷源群控、變配電、照明、門禁,凡是需要被樓宇
    飛凌嵌入式 2026-08-10 09:26 256瀏覽
  • 做儲能項目,你是怎么選控制器的?隨著工商業(yè)儲能、電網(wǎng)側(cè)儲能的規(guī)模化落地,作為儲能EMS“大腦”的嵌入式控制器,直接決定了整套系統(tǒng)的運行穩(wěn)定性、數(shù)據(jù)可靠性與項目落地效率。不少從業(yè)者都踩過類似的坑:接口數(shù)量不足要額外加裝擴展模塊、戶外高溫導(dǎo)致設(shè)備宕機、電磁干擾引發(fā)數(shù)據(jù)丟包、軟件生態(tài)薄弱拖慢開發(fā)周期……選對儲能控制器,核心要盯準(zhǔn)四個關(guān)鍵維度。首先看接口能力:全鏈路覆蓋,才是降本的核心。一套完整的儲能系統(tǒng),需要同時對接PCS、BMS、智能電表、溫濕度傳感器、除濕機、煙感消防主機,還要連接急停開關(guān)、斷路器
    飛凌嵌入式 2026-08-11 08:46 96瀏覽
  • 產(chǎn)品使用18V供電,配套4串鋰離子電池,需要設(shè)計一個充電器。因為輸入18V,電池最高 16.8V,壓差僅 1.2V。因為壓差太小。使用開關(guān)型的充電控制芯片不穩(wěn)定。嘗試自行設(shè)計一個。整體采用分立三極管 + TL431架構(gòu),分4個功能單元:使能控制回路、限流環(huán)路、TL431 精密恒壓反饋環(huán)路、PNP 功率管主功率輸出回路;實現(xiàn)完整鋰電池充電邏輯。使能控制回路按鍵按下:5V 電壓經(jīng) R12 (2kΩ) 送入 NPN 管 Q3 基極,Q3 飽和導(dǎo)通,對地拉低 R1、R2 組成的分壓控制節(jié)點電位;該節(jié)點直
    southcreek 2026-08-07 13:45 506瀏覽
  •        很多人只見過封裝在SMD外殼里的小尺寸晶振,卻不知道一顆2016、1612甚至1210封裝的微型石英晶振,從一塊巴掌大的石英原礦,到最終能穩(wěn)定起振的成品,要經(jīng)過十幾道精密工序。它內(nèi)部的石英晶片厚度最薄不到20微米,比普通A4紙的1/5還薄,整個生產(chǎn)過程里哪怕0.1微米的誤差,都會直接廢掉整批產(chǎn)品,這也是小尺寸晶振過去長期依賴進口,國內(nèi)廠商花了十幾年才啃下量產(chǎn)硬骨頭的核心原因。1. 原石定向切割:0.01度的角度誤差都不能有小尺寸晶振的第一步,
    TKD泰晶科技 2026-08-07 08:43 552瀏覽
  • 做電池供電攝像頭、可視門鈴、戶外安防的同學(xué)應(yīng)該都踩過這個坑:攝像頭要拍,ISP、sensor、無線模塊全得開著,整機電流動輒幾百 mA。一節(jié) 18650 撐不了幾天,用戶投訴“怎么又沒電了”。有人會說:加個 PIR 人體紅外不就行了?問題是 PIR 怕熱、怕寵物、怕隔著亞克力/玻璃殼,而且只能感知“有溫差的人體移動”,靈敏度飄忽。真正省電的思路應(yīng)該是:讓攝像頭平時徹底睡死,只有“前面有人/有動靜”時才被一鍵喚醒——而負責(zé)這個“看門”角色的,最合適的是一顆低功耗雷達。一、為什么是雷達,而不是 PI
    唯創(chuàng)知音語音芯片 2026-08-07 14:44 231瀏覽
  • 做藍牙音箱的工程師大概都碰過這個局面。產(chǎn)品要連手機放歌,要本地播開機提示音和按鍵音,還要接 App 做配網(wǎng)和狀態(tài)上報。三件事拆開,早先的常規(guī)做法是音頻藍牙一顆料、數(shù)傳藍牙一顆料、本地播放再掛一顆小 MCU 加一片 Flash。板子越畫越大,BOM 越算越貴,待機電流和固件工作量也跟著漲。WT2605C 出來以后,這套需求可以集成到一顆芯片,就是 WT2605C-32N,該芯片采用QFN32 封裝,4×4 毫米,把音頻藍牙、BLE 數(shù)傳和 MP3 本地播放三樣全包了。對藍牙音箱來說,最實在的好處就
    唯創(chuàng)知音語音芯片 2026-08-06 15:14 391瀏覽
  • 讓微弱脈沖信號更清晰——鎖相放大器集成Boxcar平均器在脈沖激光、泵浦-探測、時間分辨光譜、飛行時間質(zhì)譜等實驗中,真正需要測量的信號往往只出現(xiàn)在一個很短的時間窗口內(nèi),卻被淹沒在遠大于它的噪聲和背景之中。 針對這類穩(wěn)定重復(fù)的微弱脈沖與瞬態(tài)信號,賽恩科儀為OE2052雙通道數(shù)字鎖相放大器帶來全新的Boxcar Averager功能升級:在鎖相檢測之外,進一步提供從周期波形觀察、門控測量到多周期平均的完整數(shù)字Boxcar測量鏈路。 Boxcar平均器,如何從噪聲中“截取”目標(biāo)信號?Boxcar平均器
    SSI_賽恩科儀 2026-08-07 15:49 21401瀏覽
  •                                                                        
    廣州鐵金剛 2026-08-06 17:44 218888瀏覽
  • 文:杜杰編輯:侯煜當(dāng)?shù)貢r間7月28日,美國特朗普政府落地最新科技封鎖政策,美國聯(lián)邦通信委員會(FCC)正式更新《受管制清單》,新增兩大類設(shè)備——境外生產(chǎn)的先進機器人設(shè)備、境外生產(chǎn)的聯(lián)網(wǎng)電力逆變器。FCC在官方情況說明書中明確,該類境外制造產(chǎn)品,“會對美國國家安全或美國民眾的人身安全構(gòu)成無法承受的風(fēng)險”。本次新規(guī)設(shè)置明確豁免機制:經(jīng)美國國防部(DoW)、電力逆變器品類經(jīng)美國國土安全部(DHS)審核并授予“有條件批準(zhǔn)”,認(rèn)定對應(yīng)設(shè)備品類不存在重大安全風(fēng)險,即可獲得準(zhǔn)入豁免。同時政策嚴(yán)格執(zhí)行新老劃斷原
    華爾街科技眼 2026-08-03 21:28 370瀏覽
  • 一、方案簡介在Buildroot構(gòu)建的嵌入式Linux系統(tǒng)中部署Flutter應(yīng)用,主流有兩套成熟的開源方案,分別適配不同的顯示場景與開發(fā)需求:方案一:flutter-elinux(Sony開源方案)由索尼官方維護的嵌入式Linux Flutter工具鏈,既支持DRM/GBM直接渲染,也兼容Wayland、X11等多種顯示后端,場景適配性更廣。方案二:flutter-pi(輕量級嵌入器)一款輕量級Flutter引擎宿主程序,僅基于底層DRM/GBM接口實現(xiàn)渲染,無需依賴桌面環(huán)境,資源占用更低,更
    飛凌嵌入式 2026-08-07 09:34 211瀏覽
我要評論
0
點擊右上角,分享到朋友圈 我知道啦
請使用瀏覽器分享功能 我知道啦
主站蜘蛛池模板: 富贵门国语全集| 小向美奈子 花与蛇| 人猿泰山1995版迅雷| 李小环与苗翠花| 黄色仓库在线电影| 没女大翻身| 保罗雪莉禁忌1-4| 《美丽屋景1| 觉醒年代一共多少集| 高清年少轻狂 第五季| 猪坚强已瘦骨嶙峋| 苑冉后援会| 爱丫爱丫在线观看免费高清电视剧有 | 宁城县| 日出免费| 动漫《落魄贵族琉璃川》| 潜罪犯粤语| 我的美女上司遥控器| 水浒笑传下载| 电视剧婆家娘家| 最终列车醉汉免费观看第二集| 乔家大院全集| 剃刀边缘全集| 母亲完整版| 山河令免费全集电视剧西瓜| 一起愁愁愁电视剧30分钟在线观看 | 主君的太阳人人影视| 原来我很爱你电视剧免费观看西瓜| 摆渡人电影下载| 高清《给我钱 第12季》| 电视剧莲花楼完整版免费观看| 海贼王1074| 神探狄仁杰电视剧| 无心法师第4季免费播放| 画江湖之不良人**版| 百万新娘第二部| 卖保险的女销售电影完整版| 抗日奇侠全集播放| 克莱尔《X迷宫》全集免费看| 最后的希望| 斯巴达女版三部曲免费观看| 叶问 粤语| 动感之星第199集妖精| 仁心解码电视剧| 凌云志志法国满天星| 二次元胸像气球越来越大的视频| 国风按摩院免费观看| 电视剧驻站免费观看| 电影《需要爸爸播种美国》女演员还 | 双j恋吧| 《狙击手-斯莫希军官》| 一站到底徐琦| 歌曲父亲原唱播放| 3d金瓶梅在线观看| 电影《女性治疗营》| 觉醒年代免费观看| 古堡女仆的守护| 咒术回战第三季| 97免费观看在线观看电视剧| 大鸿米店电影| 水手服饲养未减版主演是谁| 我与梦露的一周| 建国门枪击事件| 免费视频电影免费完整版观看| 完美女人电影| 斗罗大陆观看| 艾曼纽3| 俄罗斯妈妈电影2024年上映吗 | 一夜情约会| 《不正经的魔术讲师与禁忌教典》| 西安董雷案| 独家记忆百度云| 亲密百度影音| 我们手机在线观看免费| 朋友的学生hd| 甲铁城的卡巴内瑞第一季在线观看| 《新人类之毒医肥尸》| 善良的岳母在线| 坎贝奇最经典的4部电影| 爱人欲望| 渔夫荒淫史在线| 黑暗视频大全免费观看| 扫黑风暴 720P 下载| 明星大侦探第五季| 金鱼妻电视剧| 一脸嫌弃的表情第三季| 年轻的阿姨5在观整有限中字| 大长今国语版| 春心荡漾在线观看完整版| 三年大片大全免费观看国语版全集 | 一次特殊的游泳课| 超级老师3免费全集| 潮与虎39| 遍地狼烟电视剧全集| 《印度空姐》完整版在线观看| 《真理与背叛》| 墨水心下载| 嘻哈四重奏第5季| 中文字Dl幕岳在线观看| 铁石心肠泰语版电视剧| 伊波拉病毒免费观看完整国语版| 说不出的爱电视剧| 公公之孚手1| 战狼6高清完整免费观看| 灯塔市| 极乐宝鉴电影免费观看在线播放中文 | 新版笑傲江湖| 疯狂动物城1免费观看中文版| xl司令第二季无马赛第八集| 霹雳战元史之天竞鏖锋| 会员制餐厅完整版免费高清| 你和我的倾城时光歌曲| 终末的女武神 樱花| 夜班经理 第二季电视剧| 伦理《法国空乘13》手机观看| 哥谭高中| 束缚游戏全集| 一世清欢短剧在线观看| 圣特罗佩姑娘们1982满天星| 半熟恋人综艺第三期下在线观看| 三年在线免费观看| 谋杀第二季| 《急诊护士》法国版电影免费观看 | 单身妈妈| 三级片一个好妈妈| 简言的夏冬在线观看| 人猿泰山意大利在线观看| 万能钥匙电影| 电视剧骄阳伴我免费观看全集| 我们在线观看免费观看完整版 | 亡羊补牢的下一句| 陪你逐风飞翔电视剧免费观看| 《角斗士3满天星版》在线观看| 纸箱包子| 插曲的痛120集在线观看免费下载| 李梦:张隆每个月都有按时付钱| 狐妖小红娘金晨曦篇动漫免费观看| 阜宁县| 林家铺子| 麦迪乐女超人在线观看完整免费高清原声满天星百度云 | 美国荷尔蒙在线观看| 核弹危机| 埃丽诺娃满天星| 与外婆同行| 日本混血女大学生精油按摩三位混血女大学生BDHD | 明星新娘发型图片| 1一68集电视剧心如铁免费播放| 两男一女免费观看电视剧高清全集| 飞天小女警z中文版全集| KKKBO| 下一个奇迹百度影音| 和讨厌的前男友一起去出差旅播放| 我的世界大电影免费观看| 《潘金莲》翁虹完整版| 《学生》完整版观看| 拥抱MV| 天地传说之宝莲灯2001林志颖| 电车魔女2| 冲田杏梨 qvod| 偷听女人心| 电视剧da师完整版免费播放| 美食总动员在线观看| 新抱喜相逢粤语| 更衣服人偶坠入爱河 在线观看| 酒店1-75集免费观看电视剧中文字| 《darkblue》动漫樱花动漫| 部长出差的日子完整版| 部长一起去出差在线观看| 年轻的母亲4在线看完整版| 国模小黎吧| 偷窥无罪2诱人犯罪| 不要见怪| 曾静二泉吟| 阿修罗 剧场版| 黑帮大佬和我365天完整版| 妹妹在线观看| 庆余年电视剧免费播放| 上错花轿嫁对郎电视剧完整版| 变形记20140714| 朴妮唛迅雷种子| 种马猎人3电影免费| 樱桃小丸子| 电影《热辣披萨女孩》免费观看 | 电影,四少妇推油按摩| 多大点事免费观看全集| 插曲30分钟高清全集网站| 和嫂子同居日子在线| 抉择电视剧| 掩不住的阳光| 电影偷窥狂人| 抬花轿豫剧| 理想之城连续剧40集免费播放 | 将门嫡女沙场:不输男儿郎短剧全集| 美国伦理片想要爸爸的种子在线看| 玫瑰的故事电视剧在线观看| 白衣绳索牙医生| 庆余年免费手机全集在线观看| 杏林春暖在线免费观看| 和部长日本出差电影免费| 梦中情人泰剧在线观看免费| 维修师傅的的艳遇电影| 猪哥亮与朱慧珍| 由菱开始电影免费观看| 大风杀在线观看完整版免费 | 瓜达卢佩的玫瑰HD电影| 太极2:英雄崛起| 新垣结衣电视剧| 《女演员:性试镜》免费观看| 熊出没之回家过年| 暴躁100集全部免费观看下载| 爱的奴隶| 1985G点原版完整版| 上课时胸罩不小心掉下来的事| 韩式翘睫| 灵媒女主| 黑寡妇第2| 警察故事3 超级警察| 明朗少女成功记| 漂亮的妈妈7| 电影远山的呼唤| 金银梅1至5普通话| 第一拽妃txt下载| 班主任辞职参军全班沸腾| 女扮男装闯江湖短剧全集| 爱情电影网 佐山爱| 淫荡女警| 苏州河在线观看| 360天大佬爱上二免费全集| 超清国语黄色仓库网vlp| 战旗如画电视剧| 美丽妻子替丈夫还债第一季| 七月与安生迅雷下载| 消失的她免费完整观看| 乡村爱情3| 流浪的青春| 黑白配版普通话| 乒乓球混双决赛直播在线观看| 红嫁衣鬼片国语版 | 今夜无人入眠免费观看| 电影亡灵| 泾阳县| 朱塞佩 托纳多雷| 绝对权势电影| 抽曲的痛免费观看完整版| 当我飞奔向你电视剧| 《第二十条》原型| 《你好 李焕英》| 太古神王在线观看| 美容院5电影| 大闹广昌隆电影完整版| 唐人街探案3电影完整版免费| 房奴试爱激情戏| 尖叫之夜免费观看2023年电影 | 禁忌女孩在线观看泰剧| 再见艳阳天粤语| 女超人麦乐迪电影免费观看| 简言的夏冬在线观看| 《急诊护士》法国版电影在线观看 | 银行大劫案完整版| 中国媳妇主题曲| 法国版《古墓丽影》免费观看| 逃英乱港分子染上奥密克戎| 《离婚后她带崽嫁入豪门》| 我和房东在做爰电影| 那山那人那狗电影| 太太度假时电影| 平凡英雄电影免费观看| 喜爱夜蒲国语版高清| 日本动漫《花嫁高柳家》完整版电影 | 高清《你的婚礼》免费观看| 苍井空电影在线直播| 我老板的女儿| 真命小和尚| 宁安如梦剧免费观看策驰| 飞虎出征字幕| 进口媳妇在线观看免费西瓜| 新金银悔1-5普| 中国警花| 幸福最晴天花絮| 在线播放:SSPD-128 怪盜 女蝶 天海翼-凌辱快感 - 国产高清传媒 | 正在播放: JUL-945 在没有丈夫的平日里,我被打工归来的无与伦比的岳父强奸, | 《血恋3之血河重生》 | 男生女生一起相差差差差免费观看电视剧| 电视剧小白菜奇案| 金银梅10普通话版| 愤怒的黄牛电影在线观看完整版| 危情三日高清下载| 空调维修工的艳遇在线观看| 鬼灭之刃第二季11集免费| xl司令第一季全集在线观看漫画| 离婚前规则主题曲叫什么| 蒋玲玲版《渔夫的荒野史》电影| 为何新增病例越来越多?上海回应| 《驯服小峓子3》完整版| 乡村爱情小夜曲第6部| 罗丽星克莱尔迷宫HD版在线观看| 特种兵火凤凰63全集| 波野多结衣种子下载| 伊丽莎白坎贝奇的三部曲在线完整版| 电影独臂刀王| 美乃雀替夫还债的电影免费观看| 排球少年第四季免费| 泰国版还珠格格| 成都大熊猫玩菜刀| 劳拉的痴迷(1996)星克莱尔| 美式保罗1-4经典结局| 长安二十四计全集免费看| 大凶女前辈在线观看| 聊斋艳谭全集在线观看| 电视剧民国大侦探在线观看完整版| 牙医姐妹郝板栗完整版| 少年张三丰电视剧| 人民的名义23集| 《图书馆的女朋友》第一季免费| 善恶的彼岸| 建德市| 奇妙萌可第一季| 山丹丹开花红艳艳简谱| 6帕克禁忌| 电视剧繁花在线观看全集| 非凡医者电视剧免费观看| 暴躁老妈高清免费观看电视| 八兄弟电视剧全集在线观看免费| 中原镖局| 四个少妇去按摩在线观看| 斗罗大陆121| 徐若萱电影| 夜空中最亮的星电视剧| 蒂法3d在线观看| 别墅轮换2的剧情简介| 时尚王国电视剧| 恶魔的艺术2:邪降恐怖电影| 我的不良义姐| 禁室培欲2:爱的四十日| 喜剧之王周星驰国语| 俄罗斯美女大逼| 神枪电视剧| 朋友的妈妈完整版| 369你懂的电影在线观看| 中原镖局之天地英豪| 自动取精| 韩国电影嘘禁止想象| 与敌同行国语下载| 外卖奇遇| 她的紧致让他闷哼出声电影| 人生七年| 坎贝奇《无憾》在线观看| 出师未捷身先死指的人物是谁| 在线观看璀璨人生| 热带暴风雨行动满天星版| 漠风吟电视剧在线观看| 方子传电影免费观看全集在线播放 | 左手视频免费观看| 梅花影库在线观看最新一期预告| 北极电影| 丈姆娘来了全集| 修理工的艳遇韩国| 坂口美穗乃| 揉情港都| XXXX69美国| 芭比公主之时尚童话| 聚会的目的1在线| 牙医姊妹电影在线观看| 盲井无删减完整版| 小巷人家电视剧全集免费观看| 台湾版黛比浪漫家教第一季在线看| 非你莫属2010| 《啄木鸟:军事不当行为》看 | 沧元图60集在线观看| 《部长出差的日子》免费电影| 呆佬贺寿粤语| 大敌当前电影| 机器侠mp4下载| 漂亮女人的日记| 灯草花和灯免费完整版| 活死人之夜| 尤里西斯的生命之旅| 战狼6| 善良的妈妈| 奇迹笨小孩电影免费高清在线观看| 巨乳保险推销员| 搞笑一家人国语版1| 二人世界直播在哪看| 同在屋檐下电视剧全集| 警察锅哥40集电视剧免费完整版| 妙笔生花电影| 庆余年免费观看| 一个好妈妈的D5申字中头| 巡回检察官| 指甲刀人魔| 嗨放派第一季完整版全集| 就爱小姐姐| 爱上电视剧| 生死决断在线观看秋瓷炫完整版| 那年冬天风在吹全集| 杜拉拉升职记 电影| 台湾《小凤新婚下》在线观看 | 电影《呼吸》| 风流女管家满天星电视剧在线观看 | 天天偷窥网| 麦乐迪马斯克女超人在线高清免费观看| 大江大河在线播放免费观看| 电影奋斗| 被义子糟蹋电影| 《黑寡妇》丹尼尔斯电影免费观看| 情侣交换日记| 坎贝奇完整版| 杰顿魔物1~2集全集免费观看| 陈玉莲 连体完整版| 激战丛林在线观看免费完整电影| 成均馆绯闻10| 国宝疑云| 菲律宾电影《私人教练》的创作背景| 为了爱离开你| 一仆二主全集在线观看| 不良执念清除师| 字圣传奇| 美国伦理电影需要爸爸播种| 步步登顶:宫斗后位全集免费| 警方证实33岁女子在出租屋离世 | 鹰眼侠 电影| 食物链无删减| 《特别的酒店》免费观看中文版全集| 愁愁愁30分钟电视剧在线观看我| 巜公妇之诱感肉欲HD在线播放| 《特殊面试2》| 《特殊牙科诊所》完整版| 美国男人大鸟| 御狐之绊| 《插曲30分钟在线观看》| 金刚狼2 字幕| 芈月传电视剧全集在线观看| 韩国年轻子1| 午夜狂情| 哪里可以看晚娘| 常盘贵子《恶魔之吻》 | 男人和女人愁愁愁电视剧全集免费| 东京女孩时尚秀| 爱天地无用| 硬汉1在线观看免费| 古惑仔在线观看免费完整版高清| 罚罪全集在线完整免费| 姐姐来了在线免费观看| 阴阳魔界| 啄木鸟壮志凌云法版免费观看完整版| 同门粤语| 黑衣人高清下载| 白镖联盟全集免费播放| 致命怪谈2电影完整版| 妹妹有点怪真人演员全集| 危险关系未删减版电影完整版| 《血恋3之血河重生》 | 需要爸爸的种子国语版免费观看| 太平公主秘史 电视剧| 嗯好深啊用力哦嗯啊秘书视频| 电影《恨锁金瓶》电影完整版 | 校草是女生短剧全集| 坎贝尔电影大全免费观看| 剿匪英雄大结局| 喜欢就好dj| 3d柔铺团之极宝乐鉴宝电影在线观看| 监控人1到100集免费看| 某某电视剧在线观看全集免费播放| 法国漂亮售楼小组三级片| 夏目彩春57部在线电影| 我愿意在线观看免费完整版电影| 连续7天失去理智的日剧是第几集| 画皮之真爱无悔大结局| 北越暴行女民兵1-3集全 | 飞屋环游记电影国语版免费版观看| 《女员工的付出》中文| 婶婶免费观看| 漂亮小瘦子3| 相反的你和我未删减| 绝品邪医的桃花劫短剧免费观看| 台湾土著版酋长的女儿| 胜者即是正义2| 电影大毒枭| 爱我几何免费版| 隋唐英雄82| 奥特曼赛罗全集| 99re视频| 《妻子的选择》| 女子上班被老板灌醉侵犯网站| 劳拉的痴迷1996电影免费播放| 忘年的恋曲电影免费观看| 猪猪侠之竞速小英雄8免费观看| 男女愁愁免费看| 哑舍 2024| 艳母动画| 刚结婚陪部长出差的日剧叫什么| 棋士在线观看| 所有女人都是骗子| 杨幂的421事件到底是什么意思| 以家人之名免费观看| 日本巜与上司的商务旅行3| 丈夫的秘密结局| 代号麻雀| 站着再来一次第26集剧情| 电视剧胜算全集免费观看| 坏蛋联盟电影完整版免费观看| 《零度暧昧》免费全集| 史前巨鳄2| 男女一起愁愁愁在线| 美食天王| 假如爱有天意电影免费观看| 王李丹妮演的电影《维修工》| 斗罗大陆第134集免费| 鼹鼠的故事国语版| 机器生活完整版| 陈冠希道歉| 麦乐迪女超人HD版全集| 需要爸爸播种伦理电影| 薛平贵与王宝钗| 疯狂萝卜| 密挑| 担保韩国电影| 无敌少侠 第四季未删减| 丈夫上司装饰品的妻子演员表| 满好人生电视连续剧免费观看| 高清《致命的艳遇》电影| 年会不能停免费观看完整版电影| 鬼影泰国版电影| 女大学生的特殊按摩电影| 高清《为你遗憾》电影| 决战风铃渡电视剧全集高清| 法国空乘15在线看免费版| 心灵法医免费观看全集| 四虎院影亚洲永久| 战狼6高清国语免费| 船长哈洛克| 红蝎子3之妖惑人心| 锵锵锵锵锵锵锵锵免费观看电视| 圣诞之吻第二季| 和讨厌的前任一起去差旅| 柳州记电视剧在线观看| 老师好在线观看| 黑白配免费观看全集高清| 玫瑰的故事电视剧免费看| 勾魂绮梦完整版| 《社长夫人》天使萌剧免费观看 | 普法栏目剧女囚的自白| 消失的妻子全集观看| 两个好媳妇电影完整版 | 在线观看电视剧| 满天星《乌托邦》2024| 非诚勿扰加长版| 白色城堡电视剧在线观看| 法国《少女农场》1976| 性感海滩3丝袜补丁| 文化经营许可证| 一起去吃拉面吗| 斗罗大陆217在线观看| 《女狱警》法国版正版| 被欺凌的女人全集| 金汉斯烤肉自助| 八戒八戒10在线看免费观看完整版| 蜡笔小新3| 人民的名义 电视剧| 京香监狱电影免费观看| 奔跑吧兄弟第三季第三期| 玉女心经2阴阳和合人物介绍| 白峰电影在线播放免费版| 扮猪吃虎:隐藏大佬不好惹短剧全集| 溏心风暴3| 女教师地狱| 商务旅行戴帽子的老板同行电影完整版| 特朗普与哈里斯首辩倒计时| 特殊交易| 仙逆最新一集| 借种无删减| 人约离婚后国语高清| 超人麦乐迪。| 军火女王 第二季| 路南柯歌词什么意思| 《满天星》莫妮卡满天星中多少时间 | 禁忌1980满天星凯·帕克| 死亡游行| 免费观看90分钟高清电视| 周弘版全集高清在线观看| 火蓝刀锋30| 凶城计中计粤语| 插曲的痛第60集全集能看见的全部私密处 | 《驯服小峓子》丽卡| 电视剧禁区| 热辣滚烫电影完整免费观看高清| 女律师的堕落HD高清播放| 规范交谊舞| 电影郝蕾版《颐明园》有没有播出| 酒店1—80集免费50集剧情介绍| 《万能遥控器2》女主角是谁| 玉蒲团完整版| 我的漂亮妻子4星空| 杰西.简壮志凌云| 好姐妹日本完整观看| 红高粱全集免费| 金牌销售秘密1HD中字| 志愿军雄兵出击完整版在线观看| 电视剧《薄冰》免费观看| 误杀电影| 挡不住的疯情 电影| 《雪豹》免费观看| 1-75集酒店| 电视剧洪湖赤卫队| 猫愿三角恋31| 电影桃色凶器| 我的爷爷和奶奶的故事| 牵牛的夏天第二部| 银河奥特曼7| 神探夏洛克可恶的新娘字幕| 《被拒绝的爱》结局| 欧式少女第16集完整版| 洛丽塔1997| 云南虫谷电影| 铿铿锵锵锵免费观看高清| 蛋仔派对钩爪教程| 这个杀手好怕丑| 《私人航空》免费观看在线播放| 咱俩结婚吧电视剧| 她在丈夫身边被耍了| 电影《天空》完整版| 妓院里的中国姑娘| 判处勇者刑未删减| 乔迪-加森| 家和万事兴全集| 免费观看罗丽星克莱尔的电影 | 强伦轩办公室女教师HD| 潜入调查官绝对不会输第2集| 电视剧红颜的岁月| 荒野渔夫| 浪迹天涯高清| 碟中谍1未删减版资源| HD版《激战丛林》类似的电影 | 魔唇劫国语| 茶馆妈妈 电影| 巨乳继拇做爰2| 上门龙婿全集免费| 父贵荣华短剧全集| 儿子爱上日本妈妈是哪部电影| 原罪免费在线观看| 祈今朝电视剧免费观看全集完整版| 致命一击| 1dldss00361当我打电话叫应召女郎时,我发现了一个高傲的大胸秘书......在办公 | 987dy| 韩剧《鬼宫》全集免费| 第二个农厂的姑娘们| 赌圣之上海滩粤语| 别墅轮换3攻略免费| 水怪在线观看| 三少爷的剑百度云资源| 性奴触手道具玉柱调教-《丰满的继牳3》无删减-成人AV | 聊斋艳谭之孽欲孤鬼| 高压监狱伦理片| 紫钗奇缘粤语版| 夏娃的丑闻| 买乐迪女超人| 三A级做爰片免费观看玉蒲团| 乐队的夏天第三季在线观看| 高清我独自蜜月未删减| 午夜寂寞影院安卓香港版| 乡村爱情13部全集免费刘能| 刑侦十二国语免费观看| 皇家奶娘在线观看,美丽的姑娘观看在| 怪想售动漫| 紫禁惊雷下载| 新版金银梅5普通话主题曲叫什么 《罗丽星克迷宫》免费观看 | 《超级保镖》全集观看| 白峰电影免费版高清在线播放| 农场主儿女1986免费观看| 两天一夜20110529| 《斗罗大陆剧场版 剑道尘心》| 维修工人的绝遇2中文版| 非诚勿扰33期| 香坂美优| 韩剧电影在线观看高清免费完整版| 日历女孩在线版完整免费观看中文 | 秘密日记电影在线完整观看| 教父2免费| 长歌行迪丽热巴免费全集西瓜 | 麦克斯的魔法世界| 女超人麦乐迪全集免费下载| 美神禁猎区| 韩剧霸总爱上单亲妈妈| 变形计山呼海唤| 黑白配免费视频在线观看| 连诗雅三级| 彩翼儿童美术| 隔窗恋爱未剪删超清| 1新金银悔1一5普通话| 火蓝刀锋 电视剧| 男爵夫人的调教| 《炸天小姐》在线观看美国| 男按摩师做乳房按摩伦理片在线播放 | 黄金大劫案 下载| 由良千岁电影大全| 独家童话全集免费观看高清完整版| 长月无烬电视剧全集免费观看| 雷洛手下猪油仔原型| 变形金刚7免费完整版在线观看 | 丧尸片《活着》| 邻居也疯狂32集完整版免费看| 综合笔记视频图片《韩剧办公室蓝色隐身帽子》 - 全集免费观看 - 永时影院印度 | 环太平洋 迅雷| 爱av电影| 坎贝奇电影高清免费看 | 《女超人Melody满天星》在线观看 | 中国直销论坛| 画中迷电视剧全集免费| 猫眼看天下| 加藤绫佳| 兴化市| 还珠格格第1部全集| 山野女人香| 刘智贤《水润人生》免费看| 意大利电影此时此刻| 怪侠欧阳德剧情分集介绍| 牙医姐妹免费观看完整版电影| 神医小娇妻全集免费| 《淮水竹亭》免费观看| 三农资本| 农民伯伯2国语版免费播放| 一念之差| 宜川县| 法国高压监狱3完整版| 别对我说谎第一季| 成吉思汗的电视剧| 时间都知道电视剧免费观看完整版| 《我的私人游戏教练》| 玉米男孩| 佐佐木与宫野| 可不可以韩国电影完整版| 火热的部长妻子| 舍我其谁| 李洛夫奇案| 中缅边境禁区已经划下| XXXX 美国| 马苏比拉米| 斗罗大陆高清版免费观看| 《李宗瑞全集》云播| 澈情从林短剧免费观看全集| 上流复仇| 我和我的家乡免费版播放| 娜塔莉的情人在线| 需要爸爸播种英文免费看| 电影斗牛剧情| 天柱剑毫| 人生马戏团粤语全集| 电影《香醇的绣感》男主吴绮珊| 高清《夏日情人》| 寄宿电影| 小辣椒3完整版| 普通文艺2b| 《图书女朋友》动漫免费观看全集 | 出轨的后裔| 南妹儿动感光波| 维修师傅的艳遇BD高清在线观看| 出轨同学会 电影| 张响神枪电视剧全集| 特殊按摩急诊室| 韩版甄嬛传| 电视剧欢乐颂1全集完整版在线观看| 豫剧吹牛| 荣玉守则| 青柠免费观看在线高清电视剧| 年代秀20120622| 饥饿的阿姆6高清版下载| 铃木里美在线观看| 粘豆包电视剧| 郭芯如和郭德纲什么关系| 破冰行动免费完整版| abo动画成结模拟免费观看| 韩国女主播艾琳| 战狼6免费观看在线| 铁拳第一季| 第一夫人在线观看| 败家仔国语版| 跛豪叶子楣| 爱我几何完整版免费观看原声在线| 视频在线看| 杀死我的温柔| 火凤凰33集| 白峰美羽无码电影观看| 绝命幽灵船免费观看完整版| 电视剧如果奔跑是我的人生免费观看| 天天想你在线观看完整版电影视频 | 《女销售的销售秘密2》| 美式禁忌-3| 高清《花琉璃轶闻》电视剧| 柳熙烈的写生簿权志龙| 土豆美容护肤小窍门| 站着再来一次最新一集免费| 自w到抖还要继续吗| 免疫屏蔽| 公与媳2电视剧免费观看电影院| 禁止的爱 善良的小姨子| 闪耀的她1-36集免费观看| 《示范销售避孕套》2| 严础熙三级未删减版观看| 樱桃电视剧免费观看最新章节更新 | 《美丽的秘密》完整版| 我的老公是卧底| 征服人妻第1-52集资源请求| 兄弟战争4| 丈夫和初恋重逢| 彭丹 蛇王波后| 50部韩剧普通话国语版| 僵尸新娘| 韩国电影疯狂婚姻| 需要爸爸播种子电影在线观看| 強暴強姦敵國女士兵电影在线观看 | 王楚钦退出WTT仁川冠军赛| 免费韩国电影在线电影| 《激战酒店》1-5集在线播放| 只对他乖| 发电俏娇娃在线观看| 《参加夫妻派对聚会》播放 | 成化十四年电视剧免费观看 | 陈蓓琪全部电影大全| 赶尸谭艳| 《忠者无敌》电视剧| 《家庭矛盾4》麦乐迪观看视频| 健身教练81话你现在样子很美| 沉香如屑电视剧免费观看55集| 高清《百万人推理》电视剧| 失恋33天mp4下载| 郝板栗《千金小姐》在线播放| 千金驾到短剧免费观看| 动画情侣剧烈做运动的表情包| 全职猎人34| 徐少强新版金银瓶免费| 理发馆| 飞屋环游记字幕| 《聊斋艳谭8陆判性经》| 黑狐之风影三大美女| 台北县| 圣特罗佩的姑娘们| 种其因者 须食其果短剧免费观看| 团圆饭电视剧全集| 神之晚餐全集在线观看| 罗生门免费| 尖峰时刻1国语| 异物志在线观看全集免费| 高清钢铁森林| 清凌凌的水| 武神赵子龙电视剧| 性空山在线观看| 《军事不当行为》免费观看法国| 帕那索斯博士奇幻秀| 马斯克笑称不用去劳改营了| 密爱:我朋友的妈妈2 电影| 野兽电影在线观看|