<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          當(dāng)單片機遇到狀態(tài)機——入門QP

          共 6723字,需瀏覽 14分鐘

           ·

          2021-11-09 15:32

          關(guān)注、星標(biāo)公眾號,直達精彩內(nèi)容

          來源:技術(shù)讓夢想更偉大

          作者:ming_mei

          前言

          前些日子在微信上看到李肖遙的公眾號,里面系統(tǒng)講述了QP框架,我很有感觸。我用QP框架很多年了,一開始是使用QM和QPC++,到后來拋棄了QM,直接使用QPC裸寫程序,到后來自己寫狀態(tài)機框架。

          可以這么說,QP框架引導(dǎo)了我的技術(shù)成長。我共享的博文,雖然都以QP為起點進行展開,但很多東西,都是QP官網(wǎng)的資料所沒有的。我希望接受大家的意見、建議和批評,相信對我來說,會有更大的提升。

          這一系列的博文,稱為《當(dāng)單片機遇上狀態(tài)機》系列,暫時先規(guī)劃以下幾篇:

          • 入門QP

          讓大家開始使用QP,消除對QP的畏難心理,建立起初步的信心。這一步非常重要。

          • 從switch-case到框架的進化

          大家很難理解,自己用switch-case實現(xiàn)狀態(tài)機,用的好好的,干嘛要用狀態(tài)機框架。這篇博文,就是為了說明,switch-case狀態(tài)機,是如何一步一步進化到一個狀態(tài)機框架的。我們所寫的這個狀態(tài)機框架,和QP之間,到底有著什么關(guān)系,有著多少差距。

          • QP的高階使用和QM的使用

          QM作為一個輔助工具?它的作用是什么?它是怎么生成代碼的?它和QP之間是什么關(guān)系?在這一篇里,將會做詳細介紹。

          • QP的哲學(xué)

          精通QP,理解其哲學(xué)思想非常重要。它的哲學(xué)思想是什么樣的?是如何體現(xiàn)的?

          • 其他

          后續(xù)的規(guī)劃,我希望根據(jù)大家的反饋意見而定。我用狀態(tài)機框架多年,難免做不到換位思考,不能照顧到初學(xué)者的感受。希望大家踴躍反饋意見。無論是贊揚還是批評,我都虛心接受。

          入門QP

          我們學(xué)習(xí)一個語言,或者一項技術(shù),第一件要做的事情,就是實現(xiàn)一個類似于Hello world的最小程序。在單片機上,當(dāng)然就是LED燈的閃爍。不說廢話了,先上代碼。

          代碼結(jié)構(gòu)

          代碼結(jié)構(gòu),可以在Keil工程中看到,是一個QP的運行最小系統(tǒng)。QP版本使用的是最新的V6.9.3版本。

          為了便于大家的學(xué)習(xí),我拋棄了官方例程。官方例程有些繁瑣,里面還有大量的doxygen格式的注釋,對初學(xué)者不友好。與官方例程相比,能刪掉的部分,全部都刪掉了,只留下代碼和必要中文注釋,目的就是為了最大限度降低大家學(xué)習(xí)QP的入門門檻,也算是中國特色吧。這四個源碼,代碼未來我們程序架構(gòu)的不同層次,以后所有的例程,就是以這個代碼結(jié)構(gòu)為基礎(chǔ),進行擴充。

          還有一個需要說明的,第一個例程,我并沒有使用QM建模工具進行LED狀態(tài)機的建模和代碼生成。QM工具,本質(zhì)上基于模型的開發(fā)方法,是形式化開發(fā)方法之一。在軟件開發(fā)中,這種方法一直飽受爭議。這個世界現(xiàn)存的大部分軟件框架,是不存在所謂代碼生成工具的。目前我對QM等建模工具持保守態(tài)度,軟件開發(fā)還是要回歸代碼本身,能利用工具,但不要依賴工具。QM工具,我認為是QP框架在營銷和商業(yè)上的需求推動的。因此,在未來的教程中,我將QM的使用,放在次要位置,主要還直接編程為主,我認為這樣才會給大家?guī)碚嬲奶嵘?/p>

          這四個源碼分別是:

          • main.c 包含了硬件的初始化、QP框架的初始化、各狀態(tài)機模塊(暫定稱呼,嚴謹應(yīng)叫AO模塊)的構(gòu)建,框架的啟動等一系列流程。

          • bsp.c 硬件初始化,此處僅包含SysTick的初始化和SysTick中斷函數(shù)。

          • ao_led.c LED狀態(tài)機的源碼。

          • hook.c QP框架的回調(diào)函數(shù)的實現(xiàn),此處都為空函數(shù),暫時不予實現(xiàn)。

          • evt_def.h 事件的定義。QP框架的事件定義,使用枚舉實現(xiàn)。個人覺得,事件的定義,如果用字符串實現(xiàn),更加有利于模塊的解耦和對分布式的支持(這個問題可參考后續(xù)的博客《將軟總線進行到底》)。QP使用枚舉來定義事件,個人認為是為了降低RAM和CPU的開銷。

          • 其他

            • QP源碼
            • QP接口代碼
            • QP框架對硬件平臺或者RTOS的接口源碼。
            • MCU相關(guān)代碼,包含Startup文件、CMSIS相關(guān)、固件庫相關(guān)代碼

          QP的啟動流程

          以下代碼就是QP框架的啟動過程。

          #include?"qpc.h"????????????????????????????????????????//?qpc框架頭文件
          #include?"evt_def.h"????????????????????????????????????//?事件定義頭文件
          #include?"bsp.h"????????????????????????????????????????//?硬件初始化
          #include?"ao_led.h"?????????????????????????????????????//?LED狀態(tài)機

          Q_DEFINE_THIS_MODULE("Main")????????//?定義當(dāng)前的模塊名稱,此名稱在QS和斷言中會使用。

          ao_led_t?led;???????????????????????????????????????????//?狀態(tài)機LED對象

          int?main(void)
          {
          ????static?QSubscrList?sub_sto[MAX_PUB_SIG];????????????//?定義訂閱緩沖區(qū)
          ????static?QF_MPOOL_EL(m_evt_t)?sml_pool_sto[128];??????//?定義事件池
          ????
          ????QF_init();??????????????????????????????????????????//?狀態(tài)機框架初始化
          ????QF_psInit(sub_sto,?Q_DIM(sub_sto));?????????????????//?發(fā)布-訂閱緩沖區(qū)的初始化
          ????QF_poolInit(sml_pool_sto,???????????????????????????//?事件池的初始化
          ????????????????sizeof(sml_pool_sto),
          ????????????????sizeof(sml_pool_sto[0]));
          ????????????????
          ????ao_led_ctor(&led);??????????????????????????????????//?狀態(tài)機的構(gòu)建
          ????
          ????return?QF_run();????????????????????????????????????//?框架啟動
          }

          QP的回調(diào)函數(shù)

          通常的調(diào)用,都是上層函數(shù)調(diào)用底層函數(shù)。如果使用了某個函數(shù),需要上層實現(xiàn),這樣就產(chǎn)生了底層對上層函數(shù)的調(diào)用,稱為回調(diào)函數(shù)(Call back),也叫鉤子函數(shù)(Hook)。

          一般而言,回調(diào)函數(shù),主要用于頂層功能在底層模塊里的插入,或者實現(xiàn)底層模塊的定制功能。QP框架定義四個回調(diào)函數(shù),需要QP的使用者來實現(xiàn)。

          void?QF_onStartup(void)?{
          ????bsp_init();?????????????????????????????????????????//?硬件初始化
          }
          void?QF_onCleanup(void)?{}
          void?QV_onIdle(void)?{}
          void?Q_onAssert(char_t?const?*?const?module,?int_t?const?loc)
          {
          ????(void)module;
          ????(void)loc;
          ????while?(1);
          }

          QF_onStartup是用于QP框架啟動時,所調(diào)用的回調(diào)函數(shù)。一般可以執(zhí)行一些初始化工作,比如硬件初始化,內(nèi)存初始化。這也就是為什么在main函數(shù)中沒有看到硬件初始化的原因。

          QF_onCleanup與RTOS相關(guān),暫時用不到。

          QV_onIdle是QP框架空閑時,也就是沒有任何事件產(chǎn)生時,所執(zhí)行的函數(shù)。

          Q_onAssert是QP的斷言的實現(xiàn)。斷言,是程序一種檢查機制,當(dāng)程序的執(zhí)行發(fā)生異常時,用于檢查不可能發(fā)生情況。比如下面的函數(shù),當(dāng)函數(shù)func_add的兩個參數(shù),都不可能大于或者等于100時,就可以對使用斷言進行檢查,以防御可能出現(xiàn)的參數(shù)輸入錯誤。這種編程方式,也叫做防御式編程。防御式編程的思想就是,若崩潰,就崩潰的更猛烈些,以便在編程的早期,就發(fā)現(xiàn)程序錯誤,并強迫開發(fā)者解決掉。

          int?func_add(int?x,?int?y)
          {
          ????Q_ASSERT(x?????Q_ASSERT(y?
          ????return?(x?+?y);
          }

          系統(tǒng)嘀嗒

          在當(dāng)前的歷程中,使用一個QP中自帶的協(xié)作式內(nèi)核QV。在使用了QV內(nèi)核的前提下,SysTick只有一個作用,那就是為時間事件提供時間基準。

          #include?"bsp.h"
          #include?"stm32f10x.h"
          #include?"qpc.h"

          void?bsp_init(void)
          {
          ????SysTick_Config(SystemCoreClock?/?1000);?????????//?時間基準為1ms
          ????NVIC_SetPriority(SysTick_IRQn,?0);??????????????//?設(shè)置中斷優(yōu)先級
          }

          void?SysTick_Handler(void)
          {
          ????QF_TICK_X(0U,?&l_SysTick_Handler);??????????????//?時間基準
          }

          如果大家需要換一個芯片跑這個例程,那么僅僅需要更換Keil RTE中的Deivce和這里的代碼即可。只有這里的代碼是硬件相關(guān)的。以后大家寫程序,也是一樣,要執(zhí)行硬件相關(guān)最小原則,也就是說,要把硬件相關(guān)的代碼壓縮到最低。

          LED狀態(tài)機

          LED狀態(tài)機是核心功能,學(xué)會了這個,就入門了QP。在QP中,AO(Active Object)是核心,QP的所有功能都是圍繞AO展開的,就好比在RTOS中任務(wù)是核心一樣。AO之間,純粹靠事件進行通信,原則上是不允許AO間共享全局變量的。

          LED狀態(tài)機的類定義

          下面是頭文件的定義。頭文件中,主要定義了LED狀態(tài)機類,并聲明了類方法。這里所說的類,是在邏輯上的類。在C語言中,沒有類的概念,只能使用結(jié)構(gòu)體替代類的實現(xiàn)。

          #include?"qpc.h"

          #define?AO_LED_QUEUE_LENGTH?????????????????32

          //?LED類的定義
          typedef?struct?ao_led_tag{
          ????QActive?super;??????????????????????????????????????//?對QActive類的繼承
          ????
          ????QEvt?const?*evt_queue[AO_LED_QUEUE_LENGTH];?????????//?事件隊列
          ????QTimeEvt?timeEvt;???????????????????????????????????//?延時事件
          ????
          ????bool?status;????????????????????????????????????????//?LED狀態(tài)
          }?ao_led_t;

          //?LED的類方法?構(gòu)造函數(shù)
          void?ao_led_ctor(ao_led_t?*?const?me);

          LED狀態(tài)機是完全按照C語言面向?qū)ο蟮姆椒▽崿F(xiàn)的。在C語言中,由于在語言層面并沒有對面向?qū)ο筮M行支持,因此面向?qū)ο蟮腃開發(fā),是運用了一些特殊技巧的。

          QActive類,簡單說就是狀態(tài)機類。在定義一個狀態(tài)機對象時,需要從QActive類進行繼承。

          LED狀態(tài)機類的實現(xiàn)

          LED狀態(tài)機類的實現(xiàn),共分為兩個部分,一是類方法的實現(xiàn),二是類狀態(tài)的實現(xiàn)。

          這里只有一個類方法,那就是LED類的構(gòu)造函數(shù)。構(gòu)造函數(shù),是C++中的概念,C語言中并沒有這個概念,這里與類相似,仍然是構(gòu)造功能的模擬。從代碼可以看出,構(gòu)造函數(shù)有幾個內(nèi)容,一個必須的步驟,就是活動對象的構(gòu)造和啟動。構(gòu)造函數(shù)中的另一個內(nèi)容,就是初始化一個時間事件的對象,因為每500ms要發(fā)送一個Evt_Time_500ms事件。

          //?活動對象(AO,Active?Object)LED的構(gòu)建
          void?ao_led_ctor(ao_led_t?*?const?me)
          {
          ????//?LED對象的變量初始化
          ????me->status?=?false;

          ????//?活動對象的構(gòu)建
          ????QActive_ctor(&me->super,?Q_STATE_CAST(&state_init));
          ????//?時間對象的構(gòu)建
          ????QTimeEvt_ctorX(&me->timeEvt,?&me->super,?Evt_Time_500ms,?0U);
          ????//?活動對象的啟動
          ????QACTIVE_START(??&me->super,
          ????????????????????1,??????????????????????????????//?優(yōu)先級
          ????????????????????me->evt_queue,??????????????????//?事件隊列
          ????????????????????AO_LED_QUEUE_LENGTH,????????????//?事件隊列深度
          ????????????????????(void?*)0,??????????????????????//?任務(wù)棧,RTOS相關(guān),可忽略
          ????????????????????0U,?????????????????????????????//?任務(wù)棧深度,RTOS相關(guān),可忽略
          ????????????????????(QEvt?*)0);
          }

          LED狀態(tài)類有三個狀態(tài),初始狀態(tài),ON狀態(tài)和OFF狀態(tài)。

          初始狀態(tài)

          所有的初始狀態(tài)都是一樣的,就是先訂閱狀態(tài)機運行所需要的事件。然后直接跳轉(zhuǎn)到某個特定的狀態(tài)。實際上,事件的訂閱,不一定要在初始狀態(tài)里執(zhí)行。在狀態(tài)機運行時,隨時都能訂閱事件,或者解除對事件的訂閱。

          這個事件的訂閱機制,就是在軟件設(shè)計模式中,大名鼎鼎的發(fā)布-訂閱模式。發(fā)布-訂閱模式的最大好處,就是模塊間的徹底解耦。這里插入一個程序設(shè)計原則,好的程序,一定是解耦良好的程序。所謂耦合,就是模塊A變了,模塊B也得跟著變,否則,B模塊會運行不正常,模塊之間有依賴;所謂解耦,就是去除模塊之間的依賴,模塊A變了,模塊B無須改變。

          //?初始狀態(tài)
          static?QState?state_init(ao_led_t?*?const?me,?void?const?*?const?par)
          {
          ????//?事件Evt_Time_500ms的訂閱
          ????QActive_subscribe(&me->super,?Evt_Time_500ms);

          ????return?Q_TRAN(&state_on);
          }

          ON狀態(tài)

          • 參數(shù)的傳輸

          從代碼中,可以看到,當(dāng)產(chǎn)生事件時,框架會自動調(diào)用state_on函數(shù),led對象,是通過參數(shù)me傳進來的,這個me指針,相當(dāng)于C++里的this指針,而所產(chǎn)生的事件,是通過參數(shù)e傳輸進來的。

          • 事件的處理

          大家注意到代碼里有三個事件Q_ENTRY_SIG、Q_EXIT_SIG和Evt_Time_500ms。其中前兩個是系統(tǒng)事件,也就是QP框架默認支持的事件。Q_ENTRY_SIG是狀態(tài)進入事件,當(dāng)進入一個狀態(tài)時,QP框架會默認執(zhí)行這個事件。Q_EXIT_SIG是狀態(tài)退出事件,當(dāng)退出一個狀態(tài)時,QP框架也會默認執(zhí)行這個事件。Evt_Time_500ms是用戶事件,也就是我們自己定義的事件。Q_ENTRY_SIG和Q_EXIT_SIG并不強制定義,而我們要根據(jù)自己的需要,看在進入或者退出一個狀態(tài)時,是否有動作執(zhí)行,來決定是否對這兩個系統(tǒng)事件進行實現(xiàn)。QP還有一個系統(tǒng)事件,Q_INIT_SIG,這個和層次化狀態(tài)機相關(guān),以后再討論。

          • 事件后的返回值

          大家注意到每個狀態(tài)機在不同的case分支下,都有不同的返回值,比如Q_HANDLED(),Q_TRAN(&state_off)或者Q_SUPER(&QHsm_top)。

          之所以有這些返回值的不同,是為了在處理完畢一個事件后,告訴框架,下一步要干什么。Q_SUPER(&QHsm_top)告訴框架此事件被忽略,什么也不處理;Q_HANDLED()告訴框架,此事件已經(jīng)處理;而Q_TRAN(&state_off)告訴框架,需要跳轉(zhuǎn)到state_off狀態(tài),框架這時會執(zhí)行當(dāng)前狀態(tài)的退出事件和下一個狀態(tài)的進入事件。

          • QP框架的技術(shù)約束

          無論是事件處理的機制,還是返回值的格式,都是QP框架的技術(shù)約束。任何一個軟件框架,在帶來編程便利的同時,也會帶來性能上的開銷和技術(shù)的約束。我們要使用一個框架,也就要遵守它制定的技術(shù)約束,否則框架就沒有辦法有效的運行。

          //?LED的on狀態(tài)
          static?QState?state_on(ao_led_t?*?const?me,?QEvt?const?*?const?e)
          {
          ????switch?(e->sig)?{
          ????????case?Q_ENTRY_SIG:???????????????????????????//?狀態(tài)的進入事件
          ????????????me->status?=?true;??????????????????????//?打開LED燈
          ????????????QTimeEvt_armX(&me->timeEvt,?500,?0U);???//?500ms后發(fā)送時間事件
          ????????????return?Q_HANDLED();?????????????????????//?通知框架,事件已處理

          ????????case?Q_EXIT_SIG:????????????????????????????//?狀態(tài)的退出事件
          ????????????QTimeEvt_disarm(&me->timeEvt);
          ????????????return?Q_HANDLED();

          ????????case?Evt_Time_500ms:
          ????????????return?Q_TRAN(&state_off);??????????????//?通知框架,狀態(tài)轉(zhuǎn)移至state_off

          ????????default:
          ????????????return?Q_SUPER(&QHsm_top);??????????????//?其他事件,在此時不處理
          ????}
          }

          //?LED的Off狀態(tài)
          static?QState?state_off(ao_led_t?*?const?me,?QEvt?const?*?const?e)
          {
          ????switch?(e->sig)?{
          ????????case?Q_ENTRY_SIG:
          ????????????me->status?=?false;?????????????????????//?關(guān)閉LED燈
          ????????????QTimeEvt_armX(&me->timeEvt,?500,?0U);
          ????????????return?Q_HANDLED();

          ????????case?Q_EXIT_SIG:
          ????????????QTimeEvt_disarm(&me->timeEvt);
          ????????????return?Q_HANDLED();

          ????????case?Evt_Time_500ms:
          ????????????return?Q_TRAN(&state_on);

          ????????default:
          ????????????return?Q_SUPER(&QHsm_top);??????????????//?其他事件,在此時不處理
          ????}
          }
          • OFF狀態(tài)

          與ON狀態(tài)一樣,不再贅述。有人可以會提出疑問,在收到Evt_Time_500ms事件的時候,讓LED的狀態(tài)翻轉(zhuǎn),不必跳轉(zhuǎn)到OFF狀態(tài),不就節(jié)約了一個狀態(tài)嗎?的確,這樣寫的確更簡練,但我們的目的是為了展示狀態(tài)機的使用,因此可以增加了一個OFF狀態(tài)。

          https://blog.csdn.net/ming_mei??請勿二次轉(zhuǎn)載,否則將舉報,謝謝

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

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

          歡迎關(guān)注我的視頻號:

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

          瀏覽 150
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  老司机AV | 日本视频在线三区 | 激情偷拍网 | 男人天堂网www | 欧美三级片播放 |