電子產(chǎn)業(yè)一站式賦能平臺

PCB聯(lián)盟網(wǎng)

搜索
查看: 52|回復(fù): 0
收起左側(cè)

表驅(qū)動法在STM32中的應(yīng)用

[復(fù)制鏈接]

485

主題

485

帖子

1623

積分

三級會員

Rank: 3Rank: 3

積分
1623
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 2022-10-10 08:30:00 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式
概念6 q! J4 e; X0 l& w* L2 F  [" m
所謂表驅(qū)動法(Table-Driven Approach)簡而言之就是用查表的方法獲取數(shù)據(jù)。此處的“表”通常為數(shù)組,但可視為數(shù)據(jù)庫的一種體現(xiàn)。根據(jù)字典中的部首檢字表查找讀音未知的漢字就是典型的表驅(qū)動法,即以每個字的字形為依據(jù),計算出一個索引值,并映射到對應(yīng)的頁數(shù)。相比一頁一頁地順序翻字典查字,部首檢字法效率極高。具體到編程方面,在數(shù)據(jù)不多時可用邏輯判斷語句(if…else或switch…case)來獲取值;但隨著數(shù)據(jù)的增多,邏輯語句會越來越長,此時表驅(qū)動法的優(yōu)勢就開始顯現(xiàn)。: j5 e; |7 C6 {5 Z  G. x
簡單示例
7 x( |: }' N% S# f2 ]% \1 Q5 `2 k" \上面講概念總是枯燥的,我們簡單寫一個C語言的例子。下面例子功能:傳入不同的數(shù)字打印不同字符串。使用if…else逐級判斷的寫法如下/ |  Y+ c9 x% L
  • void fun(int day){    if (day == 1)    {        printf("Monday7 F$ t* n4 _" j/ z! ?9 {5 u' {' @* k
    ");    }    else if (day == 2)    {        printf("Tuesday
    . V; x% y0 q( y' A/ D1 z+ }");    }    else if (day == 3)    {        printf("Wednesday1 ^& ^) J! g  s9 d2 t: r$ ?' w# ]
    ");    }    else if (day == 4)    {        printf("Thursday* l: I% n. x! d1 `: S
    ");    }    else if (day == 5)    {        printf("Friday1 J0 i5 l' \1 N
    ");    }    else if (day == 6)    {        printf("Saturday
    . I$ P, }/ p' T" I2 N6 D2 @; P) B2 a# a");    }    else if (day == 7)    {        printf("Sunday; J) b1 [1 G! q
    ");    }}使用switch…case的方法寫
    3 k8 D# A) p, o* z  G( r+ a
  • void fun(int day){    switch (day)    {    case 1:        printf("Monday1 q3 p* k: G1 P4 Q" |- D  t
    ");        break;    case 2:        printf("Tuesday
    ; g( C- H/ p0 j- c: f/ x; l; \  }");        break;    case 3:        printf("Wednesday
    3 X* q4 r0 t4 y2 c");        break;    case 4;        printf("Thursday* f& k) Z7 V' R. v; J; t# ^
    ");        break;        case 5:        printf("Friday
    0 k: X8 p2 r1 s");        break;    case 6:        printf("Saturday
    ' I% ^& a/ ?. H4 i! A; E, c" ?" n");        break;    case 7:printf("Sunday
    5 a% i  G, h, W2 b2 K) V");        break;    default:        break;    }}使用表驅(qū)動法實現(xiàn)
    - o$ }( m3 r3 m  J3 |9 n$ o
  • char weekDay[] = {Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};void fun(int day){  printf("%s
    4 O5 A. ^. g% ]$ S$ y5 O4 @& t* ^",weekDay[day]);}看完示例,可能“恍然大悟”,一拍大腿,原來表驅(qū)動法就是這么簡單啊。是的,它的核心原理就是這個簡單,如上面例子一樣。
    9 i" _2 M2 f' v& I9 ]# E如果上面的例子還沒get這種用法的好處,那么再舉一個栗子。! a  ^# [4 Q4 h; i% }
    統(tǒng)計用戶輸入的一串?dāng)?shù)字中每個數(shù)字出現(xiàn)的次數(shù)。
    % k) L: n, j$ v9 _常規(guī)寫法
  • int32_t aDigitCharNum[10] = {0}; /* 輸入字符串中各數(shù)字字符出現(xiàn)的次數(shù) */int32_t dwStrLen = strlen(szDigits);7 M" a6 F+ R6 e. }8 f
    int32_t dwStrIdx = 0;for (; dwStrIdx {    switch (szDigits[dwStrIdx])    {    case '1':        aDigitCharNum[0]++;        break;    case '2':        aDigitCharNum[1]++;        break;    //... ...    case '9':        aDigitCharNum[8]++;        break;    }}表驅(qū)動法
    6 r- S1 |7 n" _5 v" _
  • for(; dwStrIdx {    aDigitCharNum[szDigits[dwStrIdx] - '0']++;}偶爾在一些開源項目中看到類似的操作,驚呼“騷操作”,其實他們有規(guī)范的叫法:表驅(qū)動法。
    ' r  T- N' y" D- m* D0 X在MCU中應(yīng)用. m; p6 D9 g; L2 F
    在MCU中的應(yīng)用示例,怎么少的了點燈大師操作呢?首先來點一下流水LED燈吧。
    8 g- v, g8 T; R& V/ T/ X0 i8 H: h常規(guī)寫法4 V4 L! [% v. I9 o, n9 q9 Y; B
  • void LED_Ctrl(void){    static uint32_t sta = 0;, g5 j6 S: t8 B/ K
        if (0 == sta)    {        LED1_On();    }    else    {        LED1_Off();    }
    3 _2 R* f9 b. @+ n' Z6 o. j0 X  G! M    if (1 == sta)    {        LED2_On();    }    else    {        LED2_Off();    }
    2 f% V! N) F0 {4 A+ W; Y" Z    /* 兩個燈,最大不超過2 */    sta = (sta + 1) % 2;}8 D* k9 [# e9 A9 r
    /* 主函數(shù)運行 */int main(void){    while (1)    {        LED_Ctrl();        os_delay(200);    }}表驅(qū)動法+ i0 S2 _) o* S/ s1 W
  • extern void LED1_On(void);extern void LED1_Off(void);extern void LED2_On(void);extern void LED2_Off(void);, e4 p! e" _( C) ]4 y: e/ p
    /* 把同一個燈的操作封裝起來 */struct tagLEDFuncCB{    void (*LedOn)(void);    void (*LedOff)(void);};1 K( k$ o! C" i- u1 i' F- Y" Y+ a
    /* 定義需要操作到的燈的表 */const static struct tagLEDFuncCB LedOpTable[] ={        {LED1_On, LED1_Off},        {LED2_On, LED2_Off},};+ H: }9 d4 q2 _: u
    void LED_Ctrl(void){    static uint32_t sta = 0;    uint8_t i;
    ; s& \$ J; A/ g    for (i = 0; i sizeof(LedOpTable) / sizeof(LedOpTable[0]); i++)    {        (sta == i) ? (LedOpTable.LED_On()) : (LedOpTable.LED_Off());    }
    % r( Y& B- Q3 q    /* 跑下個燈 */    sta = (sta + 1) % (sizeof(LedOpTable) / sizeof(LedOpTable[0]));}' h7 l/ o" A6 }7 m3 h% m# D
    int main(void){    while (1)    {        LED_Ctrl();        os_delay(200);    }}這樣的代碼結(jié)構(gòu)緊湊,因為和結(jié)構(gòu)體結(jié)合起來了,方便添加下一個LED燈到流水燈序列中,這其中涉及到函數(shù)指針,詳細(xì)請看《回調(diào)函數(shù)》,只需要修改LedOpTable如下
    ' z% Y4 m  C8 L! [
  • const static struct tagLEDFuncCB LedOpTable[] ={    {LED1_On, LED1_Off},    {LED2_On, LED2_Off},    {LED3_On, LED3_Off},};這年頭誰還把流水燈搞的這么花里胡哨的啊,那么就舉例在串口解析中的應(yīng)用,之前的文章推送過《回調(diào)函數(shù)在命令解析中的應(yīng)用》,下面只貼一下代碼
  • typedef struct{    rt_uint8_t CMD;    rt_uint8_t (*callback_func)(rt_uint8_t cmd, rt_uint8_t *msg, uint8_t len);} _FUNCCALLBACK;, `$ p9 X4 s- i; h
    _FUNCCALLBACK callback_list[] ={    {cmd1, func_callback1},    {cmd2, func_callback2},    {cmd3, func_callback3},    {cmd4, func_callback41},    ...};
    " P, k. O- {+ T8 Qvoid poll_task(rt_uint8_t cmd, rt_uint8_t *msg, uint8_t len){    int cmd_indexmax = sizeof(callback_list) / sizeof(_FUNCCALLBACK);    int cmd_index = 0;
    6 x. y% G: P" C0 M" {5 s8 ^5 h    for (cmd_index = 0; cmd_index     {        if (callback_list[cmd_index].CMD == cmd)        {            if (callback_list[cmd_index])            {                /* 處理邏輯  */                callback_list[cmd_index].callback_func(cmd, msg, len);            }        }    }}除上述例子,表驅(qū)動法在UI界面中也有良好的應(yīng)用,如下
    0 S: H, g6 n' x; s/ [0 ]8 u) v結(jié)構(gòu)體封裝
  • typedef enum{    stage1 = 0,    stage2,    stage3,    stage4,    stage5,    stage6,    stage7,    stage8,    stage9,} SCENE;typedef struct{    void (*current_operate)(); //當(dāng)前場景的處理函數(shù)    SCENE Index;               //當(dāng)前場景的標(biāo)簽    SCENE Up;                  //按下Up鍵跳轉(zhuǎn)的場景    SCENE Down;                //按下Down鍵跳轉(zhuǎn)的場景    SCENE Right;               //按下Left鍵跳轉(zhuǎn)的場景    SCENE Left;                //按下Right鍵跳轉(zhuǎn)的場景} STAGE_TAB;函數(shù)映射表5 d+ S" S1 a6 O( J% v
  • STAGE_TAB stage_tab[] = {    //operate        Index   Up      Down    Left    Right    {Stage1_Handler, stage1, stage4, stage7, stage3, stage2},    {Stage2_Handler, stage2, stage5, stage8, stage1, stage3},    {Stage3_Handler, stage3, stage6, stage9, stage2, stage1},    {Stage4_Handler, stage4, stage7, stage1, stage6, stage5},    {Stage5_Handler, stage5, stage8, stage2, stage4, stage6},    {Stage6_Handler, stage6, stage9, stage3, stage5, stage4},    {Stage7_Handler, stage7, stage1, stage4, stage9, stage8},    {Stage8_Handler, stage8, stage2, stage5, stage7, stage9},    {Stage9_Handler, stage9, stage3, stage6, stage8, stage7},};定義兩個變量保存當(dāng)前場景和上一個場景3 I4 N* A: D7 @
  • char current_stage=stage1;char prev_stage=current_stage;按下Up按鍵 跳轉(zhuǎn)到指定場景current_stage的值根據(jù)映射表改變
    7 w6 E8 W/ P4 S: Y
  • current_stage =stage_tab[current_stage].Up;場景改變后 根據(jù)映射表執(zhí)行相應(yīng)的函數(shù)Handler
    / X) D0 S% T$ x, o: r" _% D8 ?
  • if(current_stage!=prev_stage){  stage_tab[current_stage].current_operate();  prev_stage=current_stage;}這是一個簡單的菜單操作,結(jié)合了表驅(qū)動法。在MCU中表驅(qū)動法有很多很多用處,本文的例子已經(jīng)過多了,如果在通勤路上用手機看到這里,已經(jīng)很難了。關(guān)于UI操作,大神figght在github開源了zBitsView倉庫,單片機實現(xiàn)屏幕界面,多層菜單。很牛,很優(yōu)秀的代碼,有興趣的同學(xué)可以學(xué)習(xí)一下。https://github.com/figght/zBitsView: @1 o- G6 T% Q0 Y

    ) L) d6 O3 ^' a后記  t3 [1 L5 T0 \# H3 y
    這篇文章我也看到網(wǎng)上一遍表驅(qū)動法的后總結(jié)的筆記,可能也有很多同學(xué)和我一樣,在自己的項目中熟練應(yīng)用了這種“技巧”,但今天才知道名字:表驅(qū)動法。* F5 z" b) c: M4 M- p
    這篇文章多數(shù)都是代碼示例,實在因為表驅(qū)動法大家應(yīng)該都熟練應(yīng)用了,這篇文章算是總結(jié)一下吧。
    ( y1 ~8 ^  @8 V6 v3 m7 h* h學(xué)習(xí)知識,可以像在學(xué)校從概念一點點學(xué)習(xí),也可以在工作中慢慢積累,然后總結(jié)記錄,回歸最初的概念,豐富自己的知識框架。
    , [" b; r1 `: `' f6 t祝大家變得更強!END& A' E- I- n& ]4 _. m. \" V- m

    * _" a6 v2 K! r+ q5 R) ]; T0 u" o+ F
    & R! \" J1 f: R0 t8 r1 `# K?測量代碼運行時間  必讀7 j; e- p; c4 a% V: Y$ V
    ?聊一聊const關(guān)鍵字
    ' g; X* k& O2 L& P0 u2 a?聯(lián)合體在單片機中的應(yīng)用  
    $ Z' F( r: x3 B$ v9 a?教你如何在STM32中使用DSP指令
    4 z. P" _5 Y/ E# {4 q+ R, R?STM32單片機中結(jié)構(gòu)體和枚舉的結(jié)合
  • 回復(fù)

    使用道具 舉報

    發(fā)表回復(fù)

    您需要登錄后才可以回帖 登錄 | 立即注冊

    本版積分規(guī)則


    聯(lián)系客服 關(guān)注微信 下載APP 返回頂部 返回列表