嵌入式軟件開發(fā)之狀態(tài)機(jī)+事件驅(qū)動(dòng)

strongerHuang 2024-08-25 20:20

關(guān)注+星標(biāo)公眾,不錯(cuò)過精彩內(nèi)容

素材來源 | 網(wǎng)絡(luò)


狀態(tài)機(jī)是一種程序架構(gòu),事件驅(qū)動(dòng)也是一種程序架構(gòu),在某些開發(fā)場景里面,兩者是可以一同合并使用的。

事件驅(qū)動(dòng)的概念

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

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

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

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

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

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

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

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

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

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

事件驅(qū)動(dòng)與單片機(jī)編程

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

曾經(jīng)很長一段時(shí)間, 我對分層這個(gè)概念完全沒有感覺。

  • 什么是程序分層?

  • 程序?yàn)槭裁匆謱樱?/span>

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

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

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

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

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

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

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

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

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

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

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

為了實(shí)現(xiàn) ISR 和主函數(shù)通信, 我們定義一個(gè)數(shù)據(jù)類型為INT8U的全局變量 g_u8EvntFlgGrp,稱為事件標(biāo)志組,里面的每一個(gè) bit 位代表一類事件,如果該 bit 值為 0,表示此類事件沒有發(fā)生,如果該 bit 值為 1,則表示發(fā)生了此類事件,主函數(shù)必須及時(shí)地處理該事件。圖 5 所示為g_u8EvntFlgGrp 各個(gè) 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();?/*處理定時(shí)中斷事件*/
???}
???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ū)動(dòng)機(jī)制的這種實(shí)現(xiàn)方式, 我們還可以做得更絕一些, 形成一個(gè)標(biāo)準(zhǔn)的代碼模板,做一個(gè)包含位段和函數(shù)指針數(shù)組的結(jié)構(gòu)體,位段里的每一個(gè)元素作為圖 5 那樣的事件標(biāo)志位,然后在函數(shù)指針數(shù)組中放置各個(gè)事件處理函數(shù)的函數(shù)地址, 每個(gè)處理函數(shù)對應(yīng)位段里的每個(gè)標(biāo)志位。

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

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

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

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

函數(shù) f1 返回后, 主函數(shù)又調(diào)用了另一個(gè)事件處理函數(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 的時(shí)候, 看到的是兩個(gè)“同時(shí)” 被置位的標(biāo)志位, 無法判斷出事件 E1 和 E2 發(fā)生的先后順序, 也就是說有關(guān)事件發(fā)生先后順序的信息丟失了, 這就是前面說的第 1 個(gè)問題:不同事件集中爆發(fā)時(shí),無法記錄事件發(fā)生的前后順序。

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

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

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

事件驅(qū)動(dòng)與消息

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

程序清單 List10 所示就是消息機(jī)制的數(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 位單精度浮點(diǎn)*/
?#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;?/*消費(fèi)者?ID*/
?#endif
?MSG_ARG?uMsgArg;?/*應(yīng)用消息參數(shù)*/
}?MSG;

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

?INT8U?u8MBLock;?/*隊(duì)列上鎖標(biāo)識*/
?INT8U?u8MsgSum;?/*隊(duì)列長度*/
?INT8U?u8MQHead;?/*隊(duì)列頭結(jié)點(diǎn)位置*/
?INT8U?u8MQTail;?/*隊(duì)列尾節(jié)點(diǎn)位置*/
?MSG?arMsgBox[CFG_MSG_SUM_MAX];?/*存放隊(duì)列的數(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ù)這個(gè)成員選擇對應(yīng)的事件處理函數(shù);u8UsrID 是消息的消費(fèi)者代號, 如果應(yīng)用代碼中只有一個(gè)消費(fèi)者,則成員 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ù),又可以存儲單精度浮點(diǎn), 還可以存儲 void* 型的指針變量, 而 void*的指針又可以強(qiáng)制轉(zhuǎn)換成任意類型的指針,所以 MSG_ARG 可以存儲指向任意類型的指針。

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

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

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

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

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

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

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

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

  • void mq_init(void)?消息隊(duì)列初始化,負(fù)責(zé)初始化 g_stMsgUnit 。

  • void mq_clear(void) 清空消息隊(duì)列,效果同 mq_init(),可有可無。

  • void mq_lock(void) 消息隊(duì)列鎖定,鎖定的消息隊(duì)列不可讀不可寫。

  • void mq_unlock(void) 消息隊(duì)列解鎖,解鎖后消息隊(duì)列恢復(fù)正常功能。

  • BOOL mq_is_empty(void) 消息隊(duì)列判空,返回 TRUE 表示消息隊(duì)列當(dāng)前為空,返回 FALSE 表示有消息存儲。

  • INT8U mq_get_msg_cur_sum(void) 查詢消息隊(duì)列中當(dāng)前存儲的消息總數(shù),函數(shù)返回值為查詢結(jié)果

  • INT8U mq_get_msg_sum_max(void) 查詢消息隊(duì)列的最大容量,函數(shù)返回值為查詢結(jié)果。

  • INT8U mq_msg_post_fifo(MSG* pMsg) 向消息隊(duì)列中寄送消息,方式為先入先出,形參 pMsg 指向消息的備份內(nèi)存,函數(shù)返回操作結(jié)果。該函數(shù)多被 ISR 調(diào)用,所以必須為可重入函數(shù)。

  • INT8U mq_msg_req_fifo(MSG* pMsg) 從消息隊(duì)列中讀取消息, 方式為先入先出, 函數(shù)將讀出的消息存入形參 pMsg 指向的內(nèi)存,函數(shù)返回操作結(jié)果。該函數(shù)被主程序調(diào)用, 可以不是可重入函數(shù), 但要對共享數(shù)據(jù)進(jìn)行臨界保護(hù) ?。

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

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

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

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

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

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. 從上一次按鍵的時(shí)刻開始計(jì)時(shí),如果 10 秒鐘之內(nèi)沒有按鍵事件,則不管當(dāng)前 L1L2 狀態(tài)如何,一律恢復(fù)至初始狀態(tài)。
  4. L1L2 的初始狀態(tài) OFF/OFF

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

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

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

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

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

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

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

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

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

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

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

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

2、通用框架GF1.0

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

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

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

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

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

如果應(yīng)用層的主狀態(tài)機(jī)用壓縮表格驅(qū)動(dòng)法實(shí)現(xiàn),結(jié)合上面給出的消息模塊, 則GF1.0 的狀態(tài)機(jī)引擎代碼如程序清單 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)機(jī)引擎*/
}
/***************************************
*FuncName?:?sme_kernel
*Description?:?裸奔框架?GF1.0?的狀態(tài)機(jī)引擎函數(shù)
*Arguments?:?void
*Return?:?void
*****************************************/

void?sme_kernel(void)
{
?extern?struct?fsm_node?g_arFsmDrvTbl[];?/*狀態(tài)機(jī)壓縮驅(qū)動(dòng)表格*/
?INT8U?u8Err?=?0;?/**/
?INT8U?u8CurStat?=?0;?/*狀態(tài)暫存*/
?MSG?stMsgTmp;?/*消息暫存*/
?struct?fsm_node?stNodeTmp?=?{NULL,?0};?/*狀態(tài)機(jī)節(jié)點(diǎn)暫存*/
?memset((void*)(&stMsgTmp),?0,?sizeof(MSG));?/*變量初始化*/
?gbl_int_disable();?/*關(guān)全局中斷*/
?mq_lock();?/*消息隊(duì)列鎖定*/
?mq_init();?/*消息隊(duì)列初始化*/
?mq_unlock();?/*消息隊(duì)列解鎖*/
?fsm_init();?/*狀態(tài)機(jī)初始化*/
?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)機(jī)節(jié)點(diǎn)*/
????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)機(jī)與ISR在驅(qū)動(dòng)程序中的應(yīng)用

在驅(qū)動(dòng)層的程序中使用狀態(tài)機(jī)和 ISR 能使程序的效率大幅提升。這種優(yōu)勢在通信接口中最為明顯,以串口程序?yàn)槔?/span>

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

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

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

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

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

相比之下, ISR+緩沖區(qū)+消息的處理方式就好多了, ISR 收到一個(gè)字節(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 配一個(gè)狀態(tài)機(jī),就能很容易的解決問題。圖 13為串口接收 ISR 狀態(tài)轉(zhuǎn)換圖。

圖13 中的數(shù)據(jù)幀使用 16 位校驗(yàn)和,發(fā)送順序高字節(jié)在前,低字節(jié)在后。接收緩沖區(qū)屬于 ISR 和主程序的共享資源,必須實(shí)現(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)該校驗(yàn)數(shù)據(jù),只有校驗(yàn)結(jié)果和收到的校驗(yàn)和相符,才能確信數(shù)據(jù)正確接收。

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

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

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

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

小結(jié)

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

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

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

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

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

聲明:本文素材來源網(wǎng)絡(luò),版權(quán)歸原作者所有。如涉及作品版權(quán)問題,請與我聯(lián)系刪除。


------------?END?------------



●專欄《嵌入式工具

●專欄《嵌入式開發(fā)》

●專欄《Keil教程》

●嵌入式專欄精選教程


關(guān)注公眾號回復(fù)“加群”按規(guī)則加入技術(shù)交流群,回復(fù)“1024”查看更多內(nèi)容。


點(diǎn)擊“閱讀原文”查看更多分享。

strongerHuang 作者黃工,高級嵌入式軟件工程師,分享嵌入式軟硬件、物聯(lián)網(wǎng)、單片機(jī)、開發(fā)工具、電子等內(nèi)容。
評論
  • 做電池供電攝像頭、可視門鈴、戶外安防的同學(xué)應(yīng)該都踩過這個(gè)坑:攝像頭要拍,ISP、sensor、無線模塊全得開著,整機(jī)電流動(dòng)輒幾百 mA。一節(jié) 18650 撐不了幾天,用戶投訴“怎么又沒電了”。有人會(huì)說:加個(gè) PIR 人體紅外不就行了?問題是 PIR 怕熱、怕寵物、怕隔著亞克力/玻璃殼,而且只能感知“有溫差的人體移動(dòng)”,靈敏度飄忽。真正省電的思路應(yīng)該是:讓攝像頭平時(shí)徹底睡死,只有“前面有人/有動(dòng)靜”時(shí)才被一鍵喚醒——而負(fù)責(zé)這個(gè)“看門”角色的,最合適的是一顆低功耗雷達(dá)。一、為什么是雷達(dá),而不是 PI
    唯創(chuàng)知音語音芯片 2026-08-07 14:44 228瀏覽
  • 產(chǎn)品使用18V供電,配套4串鋰離子電池,需要設(shè)計(jì)一個(gè)充電器。因?yàn)檩斎?8V,電池最高 16.8V,壓差僅 1.2V。因?yàn)閴翰钐 J褂瞄_關(guān)型的充電控制芯片不穩(wěn)定。嘗試自行設(shè)計(jì)一個(gè)。整體采用分立三極管 + TL431架構(gòu),分4個(gè)功能單元:使能控制回路、限流環(huán)路、TL431 精密恒壓反饋環(huán)路、PNP 功率管主功率輸出回路;實(shí)現(xiàn)完整鋰電池充電邏輯。使能控制回路按鍵按下:5V 電壓經(jīng) R12 (2kΩ) 送入 NPN 管 Q3 基極,Q3 飽和導(dǎo)通,對地拉低 R1、R2 組成的分壓控制節(jié)點(diǎn)電位;該節(jié)點(diǎn)直
    southcreek 2026-08-07 13:45 503瀏覽
  • 導(dǎo)語在現(xiàn)代高速數(shù)字電路設(shè)計(jì)中,信號的完整性已不僅是理想波形推演,而是一場與物理非理想特性正面博弈的工程實(shí)踐。濾波器芯片作為凈化傳輸鏈路的關(guān)鍵屏障,其核心挑戰(zhàn)在于:能否有效識別并濾除那些隱匿在電平跳變過程中的亞納秒級瞬態(tài)脈沖干擾,這類干擾持續(xù)極短,卻足以在高速鏈路中引發(fā)誤觸發(fā)。然而,傳統(tǒng)函數(shù)發(fā)生器受限于采樣率與邊沿速率,根本無法忠實(shí)復(fù)現(xiàn)如此狹窄且陡峭的異常波形。針對此困境,德思特TS-AWG7000系列任意波形發(fā)生器以17 GS/s采樣率與50 ps原生邊沿速度,為瞬態(tài)脈沖提供精細(xì)刻畫,配合SPG
    德思特測試測量 2026-08-05 14:06 5瀏覽
  • 做藍(lán)牙音箱的工程師大概都碰過這個(gè)局面。產(chǎn)品要連手機(jī)放歌,要本地播開機(jī)提示音和按鍵音,還要接 App 做配網(wǎng)和狀態(tài)上報(bào)。三件事拆開,早先的常規(guī)做法是音頻藍(lán)牙一顆料、數(shù)傳藍(lán)牙一顆料、本地播放再掛一顆小 MCU 加一片 Flash。板子越畫越大,BOM 越算越貴,待機(jī)電流和固件工作量也跟著漲。WT2605C 出來以后,這套需求可以集成到一顆芯片,就是 WT2605C-32N,該芯片采用QFN32 封裝,4×4 毫米,把音頻藍(lán)牙、BLE 數(shù)傳和 MP3 本地播放三樣全包了。對藍(lán)牙音箱來說,最實(shí)在的好處就
    唯創(chuàng)知音語音芯片 2026-08-06 15:14 391瀏覽
  • 在物聯(lián)網(wǎng)從“萬物互聯(lián)”邁向“萬物智聯(lián)”的進(jìn)程中,LTE Cat.1憑借其在速率、成本與網(wǎng)絡(luò)覆蓋之間的完美平衡,正成為中低速物聯(lián)網(wǎng)市場的絕對主力。隨著2G/3G網(wǎng)絡(luò)的逐步退網(wǎng)以及新國標(biāo)對終端設(shè)備智能化、定位功能的強(qiáng)制要求,Cat.1模組迎來了爆發(fā)式的增長窗口。在這一關(guān)鍵節(jié)點(diǎn),基于華為海思Hi2131芯片設(shè)計(jì)的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瀏覽
  • 讓微弱脈沖信號更清晰——鎖相放大器集成Boxcar平均器在脈沖激光、泵浦-探測、時(shí)間分辨光譜、飛行時(shí)間質(zhì)譜等實(shí)驗(yàn)中,真正需要測量的信號往往只出現(xiàn)在一個(gè)很短的時(shí)間窗口內(nèi),卻被淹沒在遠(yuǎn)大于它的噪聲和背景之中。 針對這類穩(wěn)定重復(fù)的微弱脈沖與瞬態(tài)信號,賽恩科儀為OE2052雙通道數(shù)字鎖相放大器帶來全新的Boxcar Averager功能升級:在鎖相檢測之外,進(jìn)一步提供從周期波形觀察、門控測量到多周期平均的完整數(shù)字Boxcar測量鏈路。 Boxcar平均器,如何從噪聲中“截取”目標(biāo)信號?Boxcar平均器
    SSI_賽恩科儀 2026-08-07 15:49 20883瀏覽
  • 一、方案簡介在Buildroot構(gòu)建的嵌入式Linux系統(tǒng)中部署Flutter應(yīng)用,主流有兩套成熟的開源方案,分別適配不同的顯示場景與開發(fā)需求:方案一:flutter-elinux(Sony開源方案)由索尼官方維護(hù)的嵌入式Linux Flutter工具鏈,既支持DRM/GBM直接渲染,也兼容Wayland、X11等多種顯示后端,場景適配性更廣。方案二:flutter-pi(輕量級嵌入器)一款輕量級Flutter引擎宿主程序,僅基于底層DRM/GBM接口實(shí)現(xiàn)渲染,無需依賴桌面環(huán)境,資源占用更低,更
    飛凌嵌入式 2026-08-07 09:34 208瀏覽
  • 樓宇自控里的“巴別塔”一棟智慧樓宇、一個(gè)產(chǎn)業(yè)園區(qū)里,暖通空調(diào)運(yùn)行著廠商私有協(xié)議,照明系統(tǒng)遵循專屬通信標(biāo)準(zhǔn),變配電、門禁安防各成體系——設(shè)備間數(shù)據(jù)壁壘森嚴(yán),上位平臺對接全靠定制化 “翻譯” 開發(fā),不僅成本高企,交付周期也難以管控。這就是樓宇自控行業(yè)多年的痛點(diǎn):協(xié)議林立、封閉林立。而破局的鑰匙,早在30多年前就已經(jīng)寫好了——BACnet。作為ASHRAE制定的樓宇自控國際標(biāo)準(zhǔn)協(xié)議,BACnet是如今全球樓宇自控系統(tǒng)(BAS)互聯(lián)互通的“通用語言”。新風(fēng)機(jī)、冷源群控、變配電、照明、門禁,凡是需要被樓宇
    飛凌嵌入式 2026-08-10 09:26 251瀏覽
  •                                                                        
    廣州鐵金剛 2026-08-06 17:44 218696瀏覽
  • 做儲能項(xiàng)目,你是怎么選控制器的?隨著工商業(yè)儲能、電網(wǎng)側(cè)儲能的規(guī)模化落地,作為儲能EMS“大腦”的嵌入式控制器,直接決定了整套系統(tǒng)的運(yùn)行穩(wěn)定性、數(shù)據(jù)可靠性與項(xiàng)目落地效率。不少從業(yè)者都踩過類似的坑:接口數(shù)量不足要額外加裝擴(kuò)展模塊、戶外高溫導(dǎo)致設(shè)備宕機(jī)、電磁干擾引發(fā)數(shù)據(jù)丟包、軟件生態(tài)薄弱拖慢開發(fā)周期……選對儲能控制器,核心要盯準(zhǔn)四個(gè)關(guān)鍵維度。首先看接口能力:全鏈路覆蓋,才是降本的核心。一套完整的儲能系統(tǒng),需要同時(shí)對接PCS、BMS、智能電表、溫濕度傳感器、除濕機(jī)、煙感消防主機(jī),還要連接急停開關(guān)、斷路器
    飛凌嵌入式 2026-08-11 08:46 82瀏覽
  • 摘要東莞市恒創(chuàng)連接器有限公司(HCHGCONN)是位于廣東東莞的連接器源頭制造工廠,專注于連接器國產(chǎn)替代、小間距大電流連接器、線對板連接器、線對線連接器、板對板連接器、電源連接器及定制連接器開發(fā)。公司面向工業(yè)自動(dòng)化、新能源儲能、BMS、汽車電子、AI服務(wù)器、GPU供電、通信設(shè)備、醫(yī)療設(shè)備和智能家居等場景,提供從產(chǎn)品選型、替代評估、樣品驗(yàn)證到批量交付的一站式連接方案。恒創(chuàng)可支持 Molex、TE Connectivity、JST、Hirose、Samtec 等國際品牌部分系列的國產(chǎn)替代評估。常見替
    HC003 2026-08-07 10:14 202瀏覽
  •        很多人只見過封裝在SMD外殼里的小尺寸晶振,卻不知道一顆2016、1612甚至1210封裝的微型石英晶振,從一塊巴掌大的石英原礦,到最終能穩(wěn)定起振的成品,要經(jīng)過十幾道精密工序。它內(nèi)部的石英晶片厚度最薄不到20微米,比普通A4紙的1/5還薄,整個(gè)生產(chǎn)過程里哪怕0.1微米的誤差,都會(huì)直接廢掉整批產(chǎn)品,這也是小尺寸晶振過去長期依賴進(jìn)口,國內(nèi)廠商花了十幾年才啃下量產(chǎn)硬骨頭的核心原因。1. 原石定向切割:0.01度的角度誤差都不能有小尺寸晶振的第一步,
    TKD泰晶科技 2026-08-07 08:43 552瀏覽
  • 文:杜杰編輯:侯煜當(dāng)?shù)貢r(shí)間7月28日,美國特朗普政府落地最新科技封鎖政策,美國聯(lián)邦通信委員會(huì)(FCC)正式更新《受管制清單》,新增兩大類設(shè)備——境外生產(chǎn)的先進(jìn)機(jī)器人設(shè)備、境外生產(chǎn)的聯(lián)網(wǎng)電力逆變器。FCC在官方情況說明書中明確,該類境外制造產(chǎn)品,“會(huì)對美國國家安全或美國民眾的人身安全構(gòu)成無法承受的風(fēng)險(xiǎn)”。本次新規(guī)設(shè)置明確豁免機(jī)制:經(jīng)美國國防部(DoW)、電力逆變器品類經(jīng)美國國土安全部(DHS)審核并授予“有條件批準(zhǔn)”,認(rèn)定對應(yīng)設(shè)備品類不存在重大安全風(fēng)險(xiǎn),即可獲得準(zhǔn)入豁免。同時(shí)政策嚴(yán)格執(zhí)行新老劃斷原
    華爾街科技眼 2026-08-03 21:28 370瀏覽
我要評論
0
點(diǎn)擊右上角,分享到朋友圈 我知道啦
請使用瀏覽器分享功能 我知道啦
主站蜘蛛池模板: 江口县| 日本美女免费电影视频| 人间中毒在线观看整板| 泷泽萝拉第二部字幕| 《俘虏之锁》第一集免费| 《猎杀史卡佩塔 第一季》| 《杉杉来了》免费观看| 女超人麦乐迪满天星版 | 坎贝奇影片在线观看| 丰满女教师BD高清| 赛车手张岩| 温暖的僵尸百度影音| 人猿泰山未删减意大利版| 动感之星视频全集观看| 电视剧猎场| 女篮决赛| 全是肉的糙汉文1v1| 日轮早夜| xl司令| 煎饼侠电影完整版| 入室暴行魔电影免费观看高清 | 万万没想到之小兵过年| 高清《怪谈》免费观看| 美国睫毛膏4| 金梅瓶下载| 韩剧热情的邻居| 我的女邻居秘密在线观看| 华丽的外出电影韩国版在线观看| 高压监狱2法国无删减版| 大江大河第二部全集免费观看西瓜| 《新来的瑜伽教练》| 法在身边| 庆余年电视剧第二部| 小岳岳相声| 看不见的朋友| 为美好的世界献上祝福| 《健身房的特殊待遇》| 还珠格格第1部| 亲爱的热爱的全集观看| 凯登农场保卫战满天星免费观看| 超级教师电视剧免费观看高清| 秀身材在线播放免费观看电视剧| 鹊刀门传奇2免费观看| 东北告别团队2| 新金银悔1-5普通话| 黑帮大佬和我说365天第一季| 《爱我几何》高清完整版在哪看| 良医第一季在线观看免费完整版| 青年医生电视剧全集| 《热带雨》完整版| 西环浮尸电影免费观看完整版| 老板办公室狂躁秘书在线观看| 单身地狱第四季免费观看| 青梅竹马是消防员第二季完整版未打 | 麻美ゆま| 森林深处的秘密花园| 名侦探柯南 影音| 三年国语免费完整版| 河北电视台农民频道绝对有戏| 和部长一起出差电影名字| 伤痛的不止是一个| 一路向西 torrent| 《雷霆特攻队*》| 高清《香蜜沉沉烬如霜》电视剧| 略阳县| 张卫健西游记国语| 总统夫人罗丽星克莱尔版翻译| 中国油价处在世界什么水平| 脱狱者侵犯排卵期人妻| 朱之文星光大道初赛| 97在线看| 执法者们 电视剧| 青春环游记5季免费播放| 总统夫人电影在线观看| 你比星光更美丽免费观看全集 | 《满天星女狱警》| 《爱我几何》| 电视剧有你才幸福| 初体验3在线观看| 断刀纪录片全集| 分宜县| 宝贝计划剧照| 酒店激战第1-5集李丽珍| 雪中悍刀行2第二部在线播放| 高清死亡账号未删减| 非狐外传 周秀娜| 播求训练| 邻家间谍| 恶灵骑士1免费观看完整版在线观看| 《少妇》6| 隋唐英雄117| 韩剧逆转女王国语版| 算命少女全集免费| 《主播女孩重度依赖》| 分手大师完整版| 三秒后野兽住在联谊会角落| 神探狄仁杰 第四部| 权力的游戏第1季| 美丽的他| 孤岛惊魂2修改器| 勇敢者游戏1决战丛林在线观看| 韩版泡沫之夏| 爰你电视剧免费观看| 青面修罗电影| 渔夫荒淫史电影在线观看| 初恋这件小事完整版| 国产免费高清观看电视剧| 翁虹《玉尺经》徐锦江| 孟羽童疑似成立个人工作室| 亲吻拥抱动漫| 1980禁忌1980禁忌1芭芭拉全集 | 泰国屠宰娃娃观看| 病毒姐妹在线观看| 特殊家政服务电视剧在线观看 | 卢旺达的玫瑰在线观看 | 叶罗丽第11季正片免费观看| 山村致富短剧全集| 悬崖37| 官人我要电影| 电视剧嘎达梅林| 我心蔚蓝| 幸福爱人电视剧全集在线观看| 卿卿日常电视剧免费观看| 迪迦奥特曼大全集| 特殊飞行| 《手机》免费观看全集| 《爱我几何》免费完整版| 西瓜和牛油果能一起吃吗| 凯莉的《满天星》| 电视剧亲情保卫战全集| 台剧黛比浪漫女家教1最新集数和剧情 | 军事不当| 大地5资源在线观看高清国语版| 做aj的电影免费观看大陆| 福音战士新剧场版| 爸爸播种| 刺刀电视剧| 我喜欢你全集在线观看免费 | 3d肉蒲团图片| 安斋拉拉在线观看视频| 北京地铁电视剧| 丰胸沙龙治疗3| 电影在线观看免费完整版| 电视剧特殊争夺全集| 天降艳福不是福| 磁力链最佳的搜索引擎| 饭冈かなこ在线播放| 埃博拉前线电视剧免费观看 | 四娘物语游戏| 凤凰台上电视剧全集免费观看| 一个妈妈的女儿第二集| 不要对我说谎电视剧| 黑白配电影免费高清在线观看国语| 父贵荣华短剧全集| 房间里的二人世界原声| 江南平寇记| 《水润女人》刘志贤演谁| 落水狗qvod| 石首市| 素食主义者百度影音| 年轻保姆3免费观看| 方谬神探| 地震发生前有哪些预兆| 人民的名义电视剧| 扫毒粤语| 《新婚的滋味》HD| 唐古拉风暴| 锵锵锵免费完整观看| 戏说乾隆第2部电视剧| 上思县| 舞乐传奇 电视剧| 新乌龙山剿匪记电视剧| 郭达蔡明小品打针| 米高杰克逊演唱会| 哇嘎全集在线观看| 韩国禁令啥意思| 浪漫的女家教第1集免费观看| 蓝男色蓝摄GAY裸男CHINA| cekc1| 检察官公主大结局| 爱情公寓3第21集| 《熟妇的荡欲》无删减迅雷| 美国好人家| 高压监狱在线观看完整免费高清原声高烧动漫| 《我的老师中字ID》完整版| 浮之手公公| 路亚发烧友| 《需要爸爸播种子》HD免费在线播放 - 全集电影 - 独角影视网 | 安娜贝尔2:诞生| 江河日上完整版免费观看| 《无能丈夫》日剧| 波多野结衣 torrent| 活着韩国丧尸片完整版| 电视剧大明风华62全集免费观看| 两个好媳妇中文在线观看| 人民的名义dvd版| 出差被前男友欺负中文| 旋风管家国语版| 食物链2大尺寸片段| 第一滴血2| 星辰变在线| 时光恋人电影| 挡不住的风情 下载| 痞老板大电影免费观看国语版| 以你的心诠释我的爱泰剧免费观看| 韩剧妈妈的三级片| 喜羊羊与灰太狼之决战四时代| 《被下药强辱的妻子》在线观看| 死神来了5 qvod| 鲁邦三世特别公映版| 流浪地球二免费观看高清完整版| 哈利波特1国语版免费| 韩国电影我的游泳女教练免费观看 | 追凶者也未删减| 《花与蛇·1》| 凡人修仙传第160集更新| 裸婚以后| kris 李宗瑞| 春雨电影大全免费动漫版| 丰满的继拇| 猎物电影| 《图书馆的女朋友》1-6结局| 老八吃粑粑| 金装四大才子粤语版| 我们这一辈| 盛唐风流电影免费观看完整版国语| 随时随地都能干的小镇I| 大江大河第一部免费完整版在线观看| 名侦探学院第六季免费观看完整版| 蓝色帽子隐身完整版| 鉴证实录| 《维修工人的艳遇》| 山河令36集全集| 免费vip电影电视剧| 突围37| 邵氏电影| 功夫吉他| 吉它兄弟| 柳州女子喝醉被| 保罗波茨| 麦乐迪女超人在线观看完整免费高清原声满天星百度网盘 | 高清世外| 宝贝把它掏出来憋不住了| 朱之文星光大道月赛| 回复术士的重来人生 樱花动漫| 商务旅行戴帽子的老板同行电影完整版 | 始作俑者的意思| 管家后宫学园10| 玛丽玛丽满天星| 东边日出西边雨电视剧| 国风按摩院在线看| 花月电视剧免费观看| 《玉女心经3》高清电影在线观看| 华为Mate70系列进入最后阶段| 倔媳妇的农村生活的百家号| 人心俱乐部电视剧在线观看| 无名天使3d粤语| 无憾坎贝奇电影完整版在线播放免费版| 澳门风云在线观看| 美容店的待遇5HD韩国三级片| 《荷尔蒙2》在线观看免费| 老师破女学生处特级毛OOO片 | 性解密| 车水马龙电影免费版| 保罗一家1-4免费看| 《坏修女》免费观看高清电影 | 鬼灭之刃游郭篇| 我与婶婶| 姚明入选FIBA篮球名人堂| 赤裸迷情| 韩国朋友的妈妈2| 在你灿烂的季节电视剧| 妻子妈妈双字ID电影| 公与3个媳HD播放| yellow电影免费观看| 铁马少年国语版| overflwer全集免费播放| 卡珊德拉满天星在线观看免费版中文| 金牌冰人| 二胡独奏曲江河水| 东北抗联电视剧| 义姐是不是良在哪里看| 迷人的保姆| 保险推销员电影| 太子发疯地撞着公主的小说| 女儿国3满天星版| 三大队电影免费| 战狼6免费观看高清下载电影 | 险恶的绣感完整版| 直到黎明全集观看| 盛唐风流电影免费观看国语版| 经典影视| 法国满天星《行政助理》| 罗马的房间图解| 暴躁46集全免费剧情| 非礼勿视2电影完整版在线观看| 张若昀人之初全集观看| 游戏风云神曲| 女教授隐藏的魅力| 《销售的秘密2》| 龙门客栈在线观看免费完整版| 挣扎吧亚当君第二集在线观看| 深入阴宅| 千年迷局| 暗夜与黎明免费播放| 米易县| 启航:当风起时第二季| 借枪30| 兔子先生第一季第6期24k高清无打码| 现在我们相爱吗| melody女超人在线观看| 新闺蜜时代剧情介绍| 翻唱日本歌曲| 男子坐飞机和妈祖邻座| 错把儿子当丈夫的电影名字| 火山口两个人没有打马赛电影| 电影《塔蒂亚娜2》在线| 变形金刚 1| 摩天轮dj| 高清胡乱的深见君未删减| 木下檀檩子jlU698零售价| 侠僧探案传奇大夜叉| 《法国空乘5》在线观看| 爱的保镖 电视剧| 美国往事| 未知| 我是歌手官方网站| 古墓丽影满天星在线观看完整免费高清 | 裕树先生被树弄得是那几集| 娱乐百分百20120924| 孚力影院| 三国演义老版全集| 新版水浒传主题曲| 贫女复仇记满天星版| 黄金瞳在线观看免费全集高清| 《总统》(星克莱尔)在线观看| 与我同眠百度影音| 《催眠软件》动漫第一季| 日剧和讨厌的部长出差| 非常人贩| 哇嘎嘎免费观看在线播放| 点爱成金| 只手遮天九千岁1到100集| 金银梅5普通话版| 小沈阳和他媳妇唱的歌| 《女销售的销售秘密》2| 腹黑总裁:只对我温柔短剧全集| 回复术士的重启人生11集| 穿越古代:风生水起全集免费| 三度诱惑| 21世纪爱情指南完整版免费观看| 疯狂一夜| 需要爸爸的播放| 非诚勿扰20120714| 天降之物final| 你比星光美丽1-40集免费观看| 笔仙咒怨| 1995年意大利版《人猿泰山》中字电影网 | 扫黑风暴全集观看| 《军事行为》布兰迪洛夫观看| 水浒传25| 新妈妈2韩国电影| 樱桃电影在线免费观看| 父贵荣华短剧全集| 哇嘎国语电影| 《神话》| 马大帅第一部| 万能阿曼| 谢娜唱哭了| 阿宾房东太太视频| 《民国奇探》免费观看| 韩剧她很漂亮| 《蜜桃成熟时2》| 吴尊友:新十条出台时机恰到好处| 男女一起免费观看全集高清完整版| 《步步惊心》免费播放| 灯草和尚聊斋| 成全完整版动漫高清免费| 约会大作战动漫| 电视剧粉红女郎| 电影伊波拉病毒完整版在线观看 | 坚果之王第一名是什么| 四个少妇推油| 少女爱上姐姐明亮的三颗星| tobu4美国免费| 胸前的大兔子蹦了出来作文| 乌丫传说| Overflower第一季无码| 韩国《女员工的滋味》在线观看| 坎贝奇品味人生在线观看完整版| 东莞第一高楼| 战狼6免费观看下载高清| 变节之潜罪犯下载| 《谍影重重》大结局| 与丈夫上司出轨的妻子三级片| 娜塔莎 劳佳姆| 金银梅日本| 超级大坏蛋国语版免费观看| 浮之公9| 心花路放电影| 好日子高清在线观看免费播放| 迷人的保姆 中文在线| 半熟恋人| 动画片喜羊羊与灰太狼免费观看| 电影《落凡尘》免费观看| 锵锵锵锵锵锵锵免费看完整版| 谍海计中计结局| 克罗斯凯登电影作品| 关东棋王| 女人的故事| 天师钟馗之红梅阁| 没什么大不了电影| 至上主义教室第3季在线观看| 《葡萄成熟时》短剧全集| 年轻的瘦子29| 我的妻子妹妹3在线播放中文| 男女在一起愁愁愁免费观看全集| 错嫁甜婚短剧全集| 厦门夜生活网| 成熟玫瑰| 青青草在9线观看| 公浮滔滔之手| 局外人韩国| 八戒八戒在线观看免费观看高清8| 回心转意伴奏| 51vv视频社区福| 赤板栗牙医电影观看| 泰罗 奥特曼| 滚烫公媳全集免费观看电视剧| 荒野求生 贝尔| 全职高手电视剧免费播放| 需要爸爸播种籽在线| 恨锁金瓶电影| 1996年杨敏思版电视剧第三回合 | 我的游泳女教练3季中文版| 宄字怎么读| 中国阅兵式2009高清| 《年轻的女保险销售员》电影全集在线播放 - 泡剧网 | Alovinghome麦乐迪和父亲| 恋童电影全部免费播放| 岳池县| 电影《德州情谜》免费看| 陈情令电视剧完整版免费播放| 女子护卫队满天星| 咋们结婚了吧| 哇嘎嘎免费观看在线播放| 今天是什么日子为什么放鞭炮| 成瀬心美| 《我的身体》1985年帕尼怀尔 | 战地花开| 人生遥控器影评| 和部长一起去出差旅的日子日剧| 铠甲勇士之帝皇侠电影| 新金银悔1—5普| 未来妈妈电视剧免费观看全集高清| 安徽卫视海豚tv| 禁忌年转一代| 《真实》崔雪莉多少秒出场| xl司令在线观看全集免费| 神咲诗织快播| 《青囊传》电视剧| 私人女子监狱视频中文字幕版| 晚春楼在线电影| 美容院特珠待遇5| oh透明人间| 浴血十四年电视剧全集免费观看| 朴麦娜28集在线观看视频 | 女官女相:权倾朝野短剧全集 | 电影小兵张嘎免费观看| 白月梵星电视剧在线观看星辰影院 | 有菜电影在线播放| 国家宝藏4| 血誓电视剧全30集| 农民伯伯下乡2第二部叫什么名字| 亲爱的完整版免费观看| 战狼6免费观看在线播放视频播放完整版| 隔壁的邻居| 被光抓走的人电影完整版观看| 双人调情按摩术电影免费观看 | 锹里奏鸣曲| 打人女司机为何不构成寻衅滋事| 高清他和她的谎| 《需要爸爸播种美国》| 寒战2粤语| 特战荣耀免费观看全集| 公之孚之中字50| 慢摇现场| 落跑甜心全集电视剧大结局| 《女超人》2013麦乐迪视频 | 《快乐到死》电影| 年轻的小瘦子| 露点电影| 林妙可歌唱祖国| 小红帽满天星| 草莓100| 铁甲钢拳高清| 战狼9女版免费全集 | 二十一世性格爱情指南日韩| 伪恋ova3| 第八种爱情| 出差未婚妻被最讨厌的人欺负日剧| 电影《最亲密的兄妹》在线看| 情欲色香味| 火星没事电影| 阿ben 长发妹| 美国《炸天小姐》| 赤壁完整版| 问情 黄思婷| 和部长一起出差的新婚妻子电影| 缘分 电影| 电影警报战在线观看| 凯登·克罗斯全集| 步步倾心| 美国式保罗1-4| 丫丫落地时现场接机群众欢呼| 国际泳联晒全红婵完美207C| 女员工的付出中字1230| 特殊美容院5在线观看| 《热带雨》删除的部分| 好想为你哭| 超级奶爸:带娃赚钱两不误全集免费 | 空即是色2015| 诱人的女房东| 电影英雄下载| 百家讲坛光绪| 妻子8免费观看全集| 落花时节又逢君40集免费观看| 电视剧我和老妈一起嫁| 杨敏思版1-5集电视剧免费看| 强我 电影| 《西部通缉令》1986在线观看| 二虎逆袭人生短剧免费观看| 以你的心诠释我的爱第二季电视剧| 井向蓝| 你们一个一个来我好痛| 锁定美军特使下载| 四个少妇全身稞体精油按摩| 电影小蓝第二部| 212喷奶视频| 电影《青楼李师师》免费观看| 步步高电影网| 公之浮之6| 法国极右翼领导人勒庞被查| 奶油柠檬| 美女在线观看完整免费高清原声满天星奔跑吧 | 斯巴达(成大版)1-2电影 | 韩国绝伦推理片高分剧推荐大全| 《特殊游泳教练》免费观看下拉| 朋友的女儿2| 白蛇2:青蛇劫起2021唐小喜| 恶老板2| 陈芊芊传闻中的电视剧免费观看| 维修工艳遇| 电影《香港奇案》| 机甲兽神第一部| 《爱你几何》高清全集免费看| 华晨宇无字歌| 全职高手第二季在线看| 四叠半牺牲的母亲中文翻译| 美景之屋1在线未删减| 快乐的大人| 妈妈的职业5完整版结局| xl司令免费全集在线观看| 电影哪吒1完整版| 《美容院的特殊待遇》所有演员是谁| 禁室培欲终结篇| 宫河家的空腹| 巜健身房激情HD中字三个教练| 二手4500| 婆婆妈妈综艺在线观看| 天与地 快播| 限制别人网速| 私人航空法国电影免费观看| 无人区高清在线观看完整版| 大宋少年志2结局| 《需要爸爸播种美国》演员表| 黑帮大佬我和我第365天男主| 杀手保镖| 盛唐风流完整免费观看| 电视剧守望| 老虎出更| 卖百科全书的人未删减版叫什么名字| 神雕之风流浪子杨过| 电影朋友的妈妈在线观看| 三年成全主演名单| 妻子的秘密49| 穿越时空的思念| 美屋之屋6| 花与蛇在线看| 2024电视剧《漂白》在线播放 | 秘密的爱电影免费观看剧情解析| 陆贞传奇52| 女教师的诱惑在线观看| 想你爱你更恨你美国版| 没想到儿子的竟然那么大 | 19岁大学生真人电视| 无间免费观看40集全集| 风火战纪| 丛林肉搏| 爱丫爱丫影院电视剧在线观看| 千年迷局| X L司令| 活佛济公下载| 珍娜詹森| 坏性:被强奸的妻子2| 一仆二主电视剧 全集| 电影 蝙蝠| 妻子6免费完整高清中文版电视剧欢迎光临| 陆小凤与花满楼结局| 今时之欲365天无删减在线观看| 粉色大白菜台湾电影| 万家灯火养生2013| 可疑的美容院中文字幕| 《爱我有几何》免费观看| 腓尼基计划未删减| 街角凶宅| 《千金小姐赤子板栗》| 情欲九歌无删在线观看| 《人妻上司》完整版| 塔日酒店完整版在线观看| 高清《课外辅导》完整版| 农村丫头高清免费播放电视剧| 潘金梅三级做爰| 开始懂了mv| 2对2交换| 3v1三人一次性体检在线观看| 镖行天下前传| 龙帅归来全集免费| 初体验5正版| 黑白道粤语| 团鬼六美容师招聘| 拳击手2满天星在线观看完整版| 厦门brt公交车爆炸| 新倾国倾城在线| 《生活中的玛丽》经典片段| 甄嬛传全集在线观看百度影音| 玄奘之路纪录片| 浮之公手6| 聚会约会目的| 狂野式到轮式的转换技巧| 夺冠电影| 俄罗斯空姐4| 善良的嫂子韩剧| 美国派百度影音| 卖房子的女销售在线看| 美国电影《杀手》| 特种兵之火凤凰演员| 荷尔蒙6欧美版哪里看| 我记得 电影| 欢迎来到游荡的精灵森林| 渔家三个姑娘1| 带上婆婆嫁| 不一样的男子| 麦乐迪在线| 变成黑皮肤和朋友| 1980法国空乘在线播放| 君九龄免费观看| 细细的红线电影| 《玉女心经3:阴阳和合》未删减| 妈祖电视剧免费观看| 高清《死人开关》电影| 丰满的继牳3理伦片| 王力宏什么歌好听| 命中注定我爱你剧情介绍| 需要爸爸播种 美国版在哪里可以看| 快播美女| 青春环游记5季免费播放| 《女警坠落2》完整版| 乘风破浪5免费观看| 龙门镖局 影音| 贡觉县| 火口的两人 原版| 苍狼传世| 旋转的爱全集| 巨茎挺进李淑芬的体| 电视剧向东是大海| 棋魂剧场版| 美国忌讳1~7| 寒战2粤语| 再喊一声爹娘| 新晃| 色即是空2015字幕| 烬相思 电视剧| 前妻归来全集免费| x迷宫罗丽星克莱尔| 藏海花之暗潮汹涌在线观看| 马德里1987| 《修理工的艳遇》接丈夫电话还在做爱 | 天才与白痴国语| 女狙击手满天星在线播放| 刀锋 1937| 康熙王朝一共多少集| 我的巨乳继拇| 少年神探狄仁杰20| 昨夜长风二十集免费观看| 《安塔芮丝》| 公的浮之手无字| 电影《黑三角》| 小爸爸电视剧插曲| 私人家教1983年版演员表 | 民国奇探 电视剧| 美式保罗4中文版| 袁隆平写给母亲的一封信| 一生一世在线观看| 新有菜电影| 瑞克和莫蒂第一季免费观看| 甜蜜惩罚2我是看守专用宠物| 火口子的两人| 高压监狱2免费播放完整版| 生化危机5电影| 浦安铁筋家族| 村姑2| 李绍萍聊天记录| 胡歌版的射雕英雄传| 激情交叉点| 兄弟换麦子6国语版免费| 郝蕾郭晓冬电影《颐和》是真的吗| 年轻的母亲在线3中文版| 战狼6免费观看高清下载电影 | 公浮之手中2完整版免费播放| 密爱高清完整版| 电影韩国太太的告白| 韩剧借种| 维修工的艳遇韩国完整版| 善良的男人电视剧大结局免费观看| 家有大姐| 克莱尔的迷宫| 午夜狂情| 金瓶梅电影完整版在线观看| 许晴背叛| 巨茎挺进李淑芬的体| 武神主宰电视剧在线观看免费全集 | 义姐不是| 爱情公寓免费观看完整版在线观看 | 局内人2| 天下第一大结局| 《隐形帽子》完整版| 手术直播间免费观看完整版| 无名世界的终结电影免费观看| 复仇者联盟 快播| 壮志凌云2女飞行员版在线观看| 纯情罗曼史动漫| 《我的初恋不可能是姐姐》| 法国空乘2019在线观看完整免费高清原声满天星奔跑吧ep | 凭本事单身免费观看电视剧全集| 鬼眼刑警| 黄色伦理片善良的同学欧美片 - 百度 | 光能使者国语版全集| 日军屠杀慰安妇影像| 想爱就爱2| 女子私人监狱完整版免费观看| 三年在线观看免费完整版中文| 卫生队的故事电视剧免费观看| 《义姐不是贤妻良母》动漫全集樱花| 向井蓝在线观看| 芈月传1至81全集免费观看| 闪婚五十岁短剧全集播放免费观看| 麦乐迪和继父演的电影是什么| 沉香如屑全集在线观看| 仙逆动漫免费在线| 十二谭电视剧免费观看追剧吧| 法国《古墓丽影》| 《美丽毒素》| 母亲5免费完整版全集电视剧| 1996版杨敏1-5电视剧高清| 星辰变2| 电视剧焦裕禄| 恶夜活死鸡| 三年大片大全在线观看完整版 | xl司令全集在线观看免费播放| 不安于室短剧免费观看| 烈火青春19| 蜡笔没了小新| 白峰美羽被操的电影| 沧元图动漫第60集| 水鬼电影| 双重幻想| 法国女超人电影完整版免费观看| 天舞纪电视剧免费观看| 爱上他的漂亮妈妈| 侯门深宅短剧全集| 日本少女恃殊按摩3| 春节起源于哪个朝代| 与鬼同眠| 女子护卫队1973满天星| 水润女人刘志贤全集剧情介绍| 贵定县| 卢旺达的玫瑰在线观看 | 第二农场姑娘| 甜蜜惩罚免费观看全集| 因为太怕痛所以就全点防御力了| 1992意大利电影地爆天星| 《和部长一起去出差旅》在线观看| 战狼10国语免费观看影视| 白洁全集| 生死决断完整版秋瓷炫| 高清楠木邸的神明庭院| 浴火之路免费观看策驰影院| 全城热恋2011| 做aj电视剧在线观看| 波多野结衣加嘞比在线观看| 启示录在线观看免费完整版高清| 绅士们电影| 公的浮之手电影完整版| 高清《分享镇》电影| 女忍者忍法帖2| 扒开双腿疯狂进出爽爽爽动态照片| 台湾版黛比浪漫家教电影| 跨界歌王第三季| 印度歌曲mp3| 恋之罪在线观看| 《乳头按摩》免费观看| 为爱所困 泰剧| 星辰变超前点播| 高度潜力美剧| 女超人法国电影完整在线观看 | 画心师免费观看全集20| 凯·帕克《囚禁》| 韩国片两个同学的妈妈| 巜出轨上司的人妻2中字| 黑塔利亚第一季免费观看| 酒店1-80集全集免费,高清 | 咒怨录像带| 叶罗丽时间与免费观看完整版| 凯登克罗斯合集免费观看| 赶尸艳谭恐怖电影免费观看高清版| 圣斗士传说| 东北黑社会乔四| 隐形的帽子在线观看完整版免费高清| 奶茶影视| 韩剧《美人》完整版| 电影潘金莲完整版在线观看| 《新上任的秘书》全集| 善良的嫂子韩国电影4| 黑天鹅在线观看| 喷出巨量精子系列在线观看| 高压监狱2无删减1080P| 哇嘎全集免费播放在线观看| 空姐电视剧韩国| 姐姐疼我电视剧| 高清《潜渊》免费观看全集| 美容院特殊待遇在线| 高清《法利赛人》| 《赶尸艳谈》无删减版| 好好的时光| 金银悔1一5普通话| 宇将军被磊真实| 伸冤人3在线| 电影赤壁| 天美影院电视剧电影在线观看| 我的巴比伦恋人在线观看 |