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

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

搜索
查看: 54|回復(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覽 |閱讀模式
概念6 v; i' X- P& j
所謂表驅(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ì)就開(kāi)始顯現(xiàn)。
8 N4 F/ w: b6 Y6 c: P0 y簡(jiǎn)單示例! N1 T! i" P  y* w6 b9 c
上面講概念總是枯燥的,我們簡(jiǎn)單寫(xiě)一個(gè)C語(yǔ)言的例子。下面例子功能:傳入不同的數(shù)字打印不同字符串。使用if…else逐級(jí)判斷的寫(xiě)法如下! j) L2 [% Z$ x5 k7 s
  • void fun(int day){    if (day == 1)    {        printf("Monday) O/ j- B# {8 o9 T4 ?
    ");    }    else if (day == 2)    {        printf("Tuesday( u( X8 M0 A9 h
    ");    }    else if (day == 3)    {        printf("Wednesday
    - t+ K8 h2 j3 y- ]- r2 Z, O& m");    }    else if (day == 4)    {        printf("Thursday, e3 E" q+ h; b, ?5 U6 a
    ");    }    else if (day == 5)    {        printf("Friday
    . B+ d2 g/ a* ^# N' `");    }    else if (day == 6)    {        printf("Saturday/ m/ {, @/ A3 o! Z2 t% l" G
    ");    }    else if (day == 7)    {        printf("Sunday2 p  [" `& Z2 l( A/ A
    ");    }}使用switch…case的方法寫(xiě)
    9 |! C+ P- y4 J1 Q. {
  • void fun(int day){    switch (day)    {    case 1:        printf("Monday9 T  z/ M9 [5 G
    ");        break;    case 2:        printf("Tuesday
      L/ m5 y) Z; X# t* N0 I");        break;    case 3:        printf("Wednesday* {8 Z- N+ P% ?1 j' n' d& I5 ?& ~
    ");        break;    case 4;        printf("Thursday
    ' ?, A3 ^) N9 W# S- g9 B+ @2 F");        break;        case 5:        printf("Friday' T9 s* |- \' }
    ");        break;    case 6:        printf("Saturday4 P+ Q9 Y9 D4 k5 C
    ");        break;    case 7:printf("Sunday, b5 X6 m' e8 Y2 p
    ");        break;    default:        break;    }}使用表驅(qū)動(dòng)法實(shí)現(xiàn)
    0 E. j' j6 v, o% j
  • char weekDay[] = {Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};void fun(int day){  printf("%s- g7 N$ m: s/ A( ^: [& r
    ",weekDay[day]);}看完示例,可能“恍然大悟”,一拍大腿,原來(lái)表驅(qū)動(dòng)法就是這么簡(jiǎn)單啊。是的,它的核心原理就是這個(gè)簡(jiǎn)單,如上面例子一樣。
    * U( U4 |8 F/ f7 V: [如果上面的例子還沒(méi)get這種用法的好處,那么再舉一個(gè)栗子。7 H' l3 e4 t) b1 N& P
    統(tǒng)計(jì)用戶(hù)輸入的一串?dāng)?shù)字中每個(gè)數(shù)字出現(xiàn)的次數(shù)。3 {/ B) _$ y( F, o
    常規(guī)寫(xiě)法
  • int32_t aDigitCharNum[10] = {0}; /* 輸入字符串中各數(shù)字字符出現(xiàn)的次數(shù) */int32_t dwStrLen = strlen(szDigits);
    ! E# M, p6 |$ hint32_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)法
    6 b) J( T# k! q8 c8 {( @3 j2 s
  • for(; dwStrIdx {    aDigitCharNum[szDigits[dwStrIdx] - '0']++;}偶爾在一些開(kāi)源項(xiàng)目中看到類(lèi)似的操作,驚呼“騷操作”,其實(shí)他們有規(guī)范的叫法:表驅(qū)動(dòng)法。
    ( N. n5 w# z- H1 z& F" v: e在MCU中應(yīng)用8 x& F" o. i" L2 k! S( b2 l9 Q
    在MCU中的應(yīng)用示例,怎么少的了點(diǎn)燈大師操作呢?首先來(lái)點(diǎn)一下流水LED燈吧。
    # |* N  Q* R! y. S" V常規(guī)寫(xiě)法: K. e4 Z2 I" c. K: W' S9 `8 c
  • void LED_Ctrl(void){    static uint32_t sta = 0;4 c" {  A: _& z6 H% R! }. [+ r
        if (0 == sta)    {        LED1_On();    }    else    {        LED1_Off();    }7 F7 {' o  [7 R; @6 x" M+ e
        if (1 == sta)    {        LED2_On();    }    else    {        LED2_Off();    }! B' T' ^5 n0 h3 q8 F: `
        /* 兩個(gè)燈,最大不超過(guò)2 */    sta = (sta + 1) % 2;}
    , w4 H3 B9 n* s, [/* 主函數(shù)運(yùn)行 */int main(void){    while (1)    {        LED_Ctrl();        os_delay(200);    }}表驅(qū)動(dòng)法! ?+ C( {" y+ g* f7 U
  • extern void LED1_On(void);extern void LED1_Off(void);extern void LED2_On(void);extern void LED2_Off(void);0 _/ }: M) k! `8 c" @# B5 p+ R6 |
    /* 把同一個(gè)燈的操作封裝起來(lái) */struct tagLEDFuncCB{    void (*LedOn)(void);    void (*LedOff)(void);};" y( @. I' q8 r& q, D2 ]
    /* 定義需要操作到的燈的表 */const static struct tagLEDFuncCB LedOpTable[] ={        {LED1_On, LED1_Off},        {LED2_On, LED2_Off},};+ q1 _( Z2 s, W
    void LED_Ctrl(void){    static uint32_t sta = 0;    uint8_t i;
    5 b+ r: t# i5 [) S3 h, [    for (i = 0; i sizeof(LedOpTable) / sizeof(LedOpTable[0]); i++)    {        (sta == i) ? (LedOpTable.LED_On()) : (LedOpTable.LED_Off());    }
    6 o0 M2 ]# {3 l" M' z8 V% A    /* 跑下個(gè)燈 */    sta = (sta + 1) % (sizeof(LedOpTable) / sizeof(LedOpTable[0]));}5 L5 q) s7 O: D% q* Z! ~
    int 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如下
    5 e" _" D5 Q: Y9 r- g5 [, g( k0 ?
  • 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;
    * ~% V! m8 N* Y_FUNCCALLBACK callback_list[] ={    {cmd1, func_callback1},    {cmd2, func_callback2},    {cmd3, func_callback3},    {cmd4, func_callback41},    ...};$ V6 o. h9 c) N  E& M6 X
    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;
    * ]) c: T, _: c( E: [    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)用,如下
    - q, [% n* k/ \- G6 R結(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ù)映射表
    & H* s' v; l# @0 {4 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},};定義兩個(gè)變量保存當(dāng)前場(chǎng)景和上一個(gè)場(chǎng)景) ^: I3 p# ~1 I7 ]% n
  • char current_stage=stage1;char prev_stage=current_stage;按下Up按鍵 跳轉(zhuǎn)到指定場(chǎng)景current_stage的值根據(jù)映射表改變
    + G; T& i# ~1 a. b* p# z4 R
  • current_stage =stage_tab[current_stage].Up;場(chǎng)景改變后 根據(jù)映射表執(zhí)行相應(yīng)的函數(shù)Handler3 w5 H% v1 L7 o, k0 p2 t; T  |
  • 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開(kāi)源了zBitsView倉(cāng)庫(kù),單片機(jī)實(shí)現(xiàn)屏幕界面,多層菜單。很牛,很優(yōu)秀的代碼,有興趣的同學(xué)可以學(xué)習(xí)一下。https://github.com/figght/zBitsView
    ) s& s. d- H+ ~; D- m  T6 l2 u4 ~7 t5 W
    后記3 M8 I( p! j) H7 H. e. S
    這篇文章我也看到網(wǎng)上一遍表驅(qū)動(dòng)法的后總結(jié)的筆記,可能也有很多同學(xué)和我一樣,在自己的項(xiàng)目中熟練應(yīng)用了這種“技巧”,但今天才知道名字:表驅(qū)動(dòng)法。
    / Y. _9 o# S* d+ K$ M/ W) U1 c$ f1 s這篇文章多數(shù)都是代碼示例,實(shí)在因?yàn)楸眚?qū)動(dòng)法大家應(yīng)該都熟練應(yīng)用了,這篇文章算是總結(jié)一下吧。
    & V5 L) w- d+ j, \4 O% D" y學(xué)習(xí)知識(shí),可以像在學(xué)校從概念一點(diǎn)點(diǎn)學(xué)習(xí),也可以在工作中慢慢積累,然后總結(jié)記錄,回歸最初的概念,豐富自己的知識(shí)框架。7 b+ }* T- O- k8 [: y
    祝大家變得更強(qiáng)!END1 z5 h# T/ x! s+ q. R8 \
    0 P, x& L" O" G1 P2 v: D* A' V# J

    5 ]: x( d" I1 q. P5 J?測(cè)量代碼運(yùn)行時(shí)間  必讀
    7 f! i, d: s' b8 L+ b3 q* t$ J?聊一聊const關(guān)鍵字
    4 D. v) \! N3 y8 o2 W( r9 c( [?聯(lián)合體在單片機(jī)中的應(yīng)用  
    ) X3 O' [! E% @# P% y' k& Q% U?教你如何在STM32中使用DSP指令6 ?2 `+ {3 O' w. G* k0 k2 p
    ?STM32單片機(jī)中結(jié)構(gòu)體和枚舉的結(jié)合
  • 發(fā)表回復(fù)

    本版積分規(guī)則


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