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

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

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

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

[復(fù)制鏈接]

485

主題

485

帖子

1623

積分

三級(jí)會(huì)員

Rank: 3Rank: 3

積分
1623
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 2022-10-10 08:30:00 | 只看該作者 |只看大圖 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
概念+ M) k7 p) i* f+ \- e
所謂表驅(qū)動(dòng)法(Table-Driven Approach)簡(jiǎn)而言之就是用查表的方法獲取數(shù)據(jù)。此處的“表”通常為數(shù)組,但可視為數(shù)據(jù)庫(kù)的一種體現(xiàn)。根據(jù)字典中的部首檢字表查找讀音未知的漢字就是典型的表驅(qū)動(dòng)法,即以每個(gè)字的字形為依據(jù),計(jì)算出一個(gè)索引值,并映射到對(duì)應(yīng)的頁(yè)數(shù)。相比一頁(yè)一頁(yè)地順序翻字典查字,部首檢字法效率極高。具體到編程方面,在數(shù)據(jù)不多時(shí)可用邏輯判斷語(yǔ)句(if…else或switch…case)來(lái)獲取值;但隨著數(shù)據(jù)的增多,邏輯語(yǔ)句會(huì)越來(lái)越長(zhǎng),此時(shí)表驅(qū)動(dòng)法的優(yōu)勢(shì)就開始顯現(xiàn)。
; _5 F/ ]; F; i簡(jiǎn)單示例: r5 A& f4 M& n. u0 z1 Q2 ]) }( I
上面講概念總是枯燥的,我們簡(jiǎn)單寫一個(gè)C語(yǔ)言的例子。下面例子功能:傳入不同的數(shù)字打印不同字符串。使用if…else逐級(jí)判斷的寫法如下7 G( a$ W! a8 f
  • void fun(int day){    if (day == 1)    {        printf("Monday2 V! [, a7 o; `, _2 ]/ Y+ p  X
    ");    }    else if (day == 2)    {        printf("Tuesday
    ; P/ G4 }& f/ s: }7 |( S. K");    }    else if (day == 3)    {        printf("Wednesday. i0 _9 P. W; @7 L, t% S6 J4 s
    ");    }    else if (day == 4)    {        printf("Thursday
    / F- c+ U" y) ^/ w: l" v7 C; _");    }    else if (day == 5)    {        printf("Friday
    ! k5 A! g; [) C9 N) q7 w");    }    else if (day == 6)    {        printf("Saturday" j& n/ c3 F3 p2 s0 a( Y
    ");    }    else if (day == 7)    {        printf("Sunday
    1 X" T4 H& o6 c. W9 G9 i7 [");    }}使用switch…case的方法寫6 O/ S! N$ `5 W  z' H& c
  • void fun(int day){    switch (day)    {    case 1:        printf("Monday* K4 m% d8 I0 z4 B" g& @- q
    ");        break;    case 2:        printf("Tuesday
    ' Z% c. o0 w0 N3 o");        break;    case 3:        printf("Wednesday3 L; c6 m6 K; V2 q$ ^: U( T: v
    ");        break;    case 4;        printf("Thursday$ g0 l. k6 T" l; b8 ^
    ");        break;        case 5:        printf("Friday/ D. D) X6 X6 K/ A4 k" U
    ");        break;    case 6:        printf("Saturday) F( _7 j9 W& I' G
    ");        break;    case 7:printf("Sunday
    # p# ]) q& n0 p, K");        break;    default:        break;    }}使用表驅(qū)動(dòng)法實(shí)現(xiàn)( G* d0 _$ Y3 y; f$ i
  • char weekDay[] = {Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};void fun(int day){  printf("%s+ g8 x  j) y6 }- v
    ",weekDay[day]);}看完示例,可能“恍然大悟”,一拍大腿,原來(lái)表驅(qū)動(dòng)法就是這么簡(jiǎn)單啊。是的,它的核心原理就是這個(gè)簡(jiǎn)單,如上面例子一樣。
    6 \2 G) |1 x1 p, v4 O' C如果上面的例子還沒(méi)get這種用法的好處,那么再舉一個(gè)栗子。
    4 d$ ~$ z' I! [7 O  A5 u統(tǒng)計(jì)用戶輸入的一串?dāng)?shù)字中每個(gè)數(shù)字出現(xiàn)的次數(shù)。! k7 L' b% @% P5 G* ^# ~* Z
    常規(guī)寫法
  • int32_t aDigitCharNum[10] = {0}; /* 輸入字符串中各數(shù)字字符出現(xiàn)的次數(shù) */int32_t dwStrLen = strlen(szDigits);
    8 s/ ?! z9 R" S0 ~& Vint32_t dwStrIdx = 0;for (; dwStrIdx {    switch (szDigits[dwStrIdx])    {    case '1':        aDigitCharNum[0]++;        break;    case '2':        aDigitCharNum[1]++;        break;    //... ...    case '9':        aDigitCharNum[8]++;        break;    }}表驅(qū)動(dòng)法
    $ n  F3 x) n, T  }; K+ }* P
  • for(; dwStrIdx {    aDigitCharNum[szDigits[dwStrIdx] - '0']++;}偶爾在一些開源項(xiàng)目中看到類似的操作,驚呼“騷操作”,其實(shí)他們有規(guī)范的叫法:表驅(qū)動(dòng)法。- {0 j3 O" v" a) r7 W5 S9 o
    在MCU中應(yīng)用. x4 b9 I% j$ \+ k& Z0 M
    在MCU中的應(yīng)用示例,怎么少的了點(diǎn)燈大師操作呢?首先來(lái)點(diǎn)一下流水LED燈吧。
    & q8 ]" ~6 K) h/ S" F常規(guī)寫法/ x0 r! G! t5 E
  • void LED_Ctrl(void){    static uint32_t sta = 0;1 p" j  X- k4 x7 M3 K6 c( m
        if (0 == sta)    {        LED1_On();    }    else    {        LED1_Off();    }
    6 C0 B8 }) D; q5 X+ I    if (1 == sta)    {        LED2_On();    }    else    {        LED2_Off();    }1 Y( E* e% A+ D9 v
        /* 兩個(gè)燈,最大不超過(guò)2 */    sta = (sta + 1) % 2;}
    & q$ Y5 b! ?. _) V* M, t/* 主函數(shù)運(yùn)行 */int main(void){    while (1)    {        LED_Ctrl();        os_delay(200);    }}表驅(qū)動(dòng)法
    + f' }1 W5 }* K( v; h
  • extern void LED1_On(void);extern void LED1_Off(void);extern void LED2_On(void);extern void LED2_Off(void);& \) v1 g4 S6 B
    /* 把同一個(gè)燈的操作封裝起來(lái) */struct tagLEDFuncCB{    void (*LedOn)(void);    void (*LedOff)(void);};5 \& t! S+ ]3 r
    /* 定義需要操作到的燈的表 */const static struct tagLEDFuncCB LedOpTable[] ={        {LED1_On, LED1_Off},        {LED2_On, LED2_Off},};9 c2 v' j" D# n/ q6 K+ _( P
    void LED_Ctrl(void){    static uint32_t sta = 0;    uint8_t i;8 ?- f! t5 U" v
        for (i = 0; i sizeof(LedOpTable) / sizeof(LedOpTable[0]); i++)    {        (sta == i) ? (LedOpTable.LED_On()) : (LedOpTable.LED_Off());    }
    , D- n7 `: |' F& L    /* 跑下個(gè)燈 */    sta = (sta + 1) % (sizeof(LedOpTable) / sizeof(LedOpTable[0]));}
    " I$ l* c. l* Nint main(void){    while (1)    {        LED_Ctrl();        os_delay(200);    }}這樣的代碼結(jié)構(gòu)緊湊,因?yàn)楹徒Y(jié)構(gòu)體結(jié)合起來(lái)了,方便添加下一個(gè)LED燈到流水燈序列中,這其中涉及到函數(shù)指針,詳細(xì)請(qǐng)看《回調(diào)函數(shù)》,只需要修改LedOpTable如下
    ; ?/ B- e7 S1 ^2 N5 B5 h8 V( V
  • const static struct tagLEDFuncCB LedOpTable[] ={    {LED1_On, LED1_Off},    {LED2_On, LED2_Off},    {LED3_On, LED3_Off},};這年頭誰(shuí)還把流水燈搞的這么花里胡哨的啊,那么就舉例在串口解析中的應(yīng)用,之前的文章推送過(guò)《回調(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;
      @9 r0 G! h: s0 n) O& T_FUNCCALLBACK callback_list[] ={    {cmd1, func_callback1},    {cmd2, func_callback2},    {cmd3, func_callback3},    {cmd4, func_callback41},    ...};: i: O6 }& ]5 _0 }0 g' [
    void 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;
    & ]$ q6 r3 r$ {. L$ [) L    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ū)動(dòng)法在UI界面中也有良好的應(yīng)用,如下+ @& ]) J  Y8 p$ b' V" l
    結(jié)構(gòu)體封裝
  • typedef enum{    stage1 = 0,    stage2,    stage3,    stage4,    stage5,    stage6,    stage7,    stage8,    stage9,} SCENE;typedef struct{    void (*current_operate)(); //當(dāng)前場(chǎng)景的處理函數(shù)    SCENE Index;               //當(dāng)前場(chǎng)景的標(biāo)簽    SCENE Up;                  //按下Up鍵跳轉(zhuǎn)的場(chǎng)景    SCENE Down;                //按下Down鍵跳轉(zhuǎn)的場(chǎng)景    SCENE Right;               //按下Left鍵跳轉(zhuǎn)的場(chǎng)景    SCENE Left;                //按下Right鍵跳轉(zhuǎn)的場(chǎng)景} STAGE_TAB;函數(shù)映射表
    : a# _7 f& C* n& H% y
  • 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},};定義兩個(gè)變量保存當(dāng)前場(chǎng)景和上一個(gè)場(chǎng)景
    ! t; T- i/ l+ V0 V
  • char current_stage=stage1;char prev_stage=current_stage;按下Up按鍵 跳轉(zhuǎn)到指定場(chǎng)景current_stage的值根據(jù)映射表改變) b/ m: `! J; s+ T2 k
  • current_stage =stage_tab[current_stage].Up;場(chǎng)景改變后 根據(jù)映射表執(zhí)行相應(yīng)的函數(shù)Handler9 |3 C6 r7 J8 r& V7 }
  • if(current_stage!=prev_stage){  stage_tab[current_stage].current_operate();  prev_stage=current_stage;}這是一個(gè)簡(jiǎn)單的菜單操作,結(jié)合了表驅(qū)動(dòng)法。在MCU中表驅(qū)動(dòng)法有很多很多用處,本文的例子已經(jīng)過(guò)多了,如果在通勤路上用手機(jī)看到這里,已經(jīng)很難了。關(guān)于UI操作,大神figght在github開源了zBitsView倉(cāng)庫(kù),單片機(jī)實(shí)現(xiàn)屏幕界面,多層菜單。很牛,很優(yōu)秀的代碼,有興趣的同學(xué)可以學(xué)習(xí)一下。https://github.com/figght/zBitsView
    " S# f* A3 O$ G
    ! Y( [% X8 z' w  {- ]4 q# X  B后記: V7 @% Z& w: L# e7 N1 U0 U
    這篇文章我也看到網(wǎng)上一遍表驅(qū)動(dòng)法的后總結(jié)的筆記,可能也有很多同學(xué)和我一樣,在自己的項(xiàng)目中熟練應(yīng)用了這種“技巧”,但今天才知道名字:表驅(qū)動(dòng)法。
      ~9 l4 a3 v7 F1 G. _5 D這篇文章多數(shù)都是代碼示例,實(shí)在因?yàn)楸眚?qū)動(dòng)法大家應(yīng)該都熟練應(yīng)用了,這篇文章算是總結(jié)一下吧。
    7 G" d! \# x/ q5 ~* Q學(xué)習(xí)知識(shí),可以像在學(xué)校從概念一點(diǎn)點(diǎn)學(xué)習(xí),也可以在工作中慢慢積累,然后總結(jié)記錄,回歸最初的概念,豐富自己的知識(shí)框架。& \! p: u% v- y" u$ {
    祝大家變得更強(qiáng)!END
    " r, I; c! v: p" ~( F) K. J6 R $ f/ P4 Y% [7 y3 d' T% y$ Z

    2 x& g. Z  K' `2 J?測(cè)量代碼運(yùn)行時(shí)間  必讀
    ! p$ X8 i' N0 a$ t% D% L5 ]) A0 J?聊一聊const關(guān)鍵字
    & i  D( e  N8 B$ h?聯(lián)合體在單片機(jī)中的應(yīng)用  4 ]! f( r8 f. a' C' u: I! x. r! k
    ?教你如何在STM32中使用DSP指令
    1 w1 L  M. P; I3 f* D5 L" f?STM32單片機(jī)中結(jié)構(gòu)體和枚舉的結(jié)合
  • 發(fā)表回復(fù)

    本版積分規(guī)則


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