表驅動法在STM32中的應用

2022-11-06 06:01:40

1、概念

所謂表驅動法(Table-Driven Approach)簡而言之就是用查表的方法獲取資料。此處的「表」通常為陣列,但可視為資料庫的一種體現。根據字典中的部首檢字表查詢讀音未知的漢字就是典型的表驅動法,即以每個字的字形為依據,計算出一個索引值,並對映到對應的頁數。相比一頁一頁地順序翻字典查字,部首檢字法效率極高。

具體到程式設計方面,在資料不多時可用邏輯判斷語句(if…else或switch…case)來獲取值;但隨著資料的增多,邏輯語句會越來越長,此時表驅動法的優勢就開始顯現。

2、簡單範例

上面講概念總是枯燥的,我們簡單寫一個C語言的例子。下面例子功能:傳入不同的數位列印不同字串。

使用if…else逐級判斷的寫法如下

void fun(int day)
{
    if (day == 1)
    {
        printf("Monday\n");
    }
    else if (day == 2)
    {
        printf("Tuesday\n");
    }
    else if (day == 3)
    {
        printf("Wednesday\n");
    }
    else if (day == 4)
    {
        printf("Thursday\n");
    }
    else if (day == 5)
    {
        printf("Friday\n");
    }
    else if (day == 6)
    {
        printf("Saturday\n");
    }
    else if (day == 7)
    {
        printf("Sunday\n");
    }
}

使用switch…case的方法寫

void fun(int day)
{
    switch (day)
    {
    case 1:
        printf("Monday\n");
        break;
    case 2:
        printf("Tuesday\n");
        break;
    case 3:
        printf("Wednesday\n");
        break;
    case 4;
        printf("Thursday\n");
        break;
        case 5:
        printf("Friday\n");
        break;
    case 6:
        printf("Saturday\n");
        break;
    case 7:printf("Sunday\n");
        break;
    default:
        break;
    }
}

使用表驅動法實現

char weekDay[] = {Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};
void fun(int day)
{
  printf("%s\n",weekDay[day]);
}

看完範例,可能「恍然大悟」,一拍大腿,原來表驅動法就是這麼簡單啊。是的,它的核心原理就是這個簡單,如上面例子一樣。

如果上面的例子還沒get這種用法的好處,那麼再舉一個栗子。

統計使用者輸入的一串數位中每個數位出現的次數。

常規寫法

int32_t aDigitCharNum[10] = {0}; /* 輸入字串中各數位字元出現的次數 */
int32_t dwStrLen = strlen(szDigits);

int32_t dwStrIdx = 0;
for (; dwStrIdx < dwStrLen; dwStrIdx++)
{
    switch (szDigits[dwStrIdx])
    {
    case '1':
        aDigitCharNum[0]++;
        break;
    case '2':
        aDigitCharNum[1]++;
        break;
    //... ...
    case '9':
        aDigitCharNum[8]++;
        break;
    }
}

表驅動法

for(; dwStrIdx < dwStrLen; dwStrIdx++)
{
    aDigitCharNum[szDigits[dwStrIdx] - '0']++;
}

偶爾在一些開源專案中看到類似的操作,驚呼「騷操作」,其實他們有規範的叫法:表驅動法。

3、在MCU中應用

在MCU中的應用範例,怎麼少的了點燈大師操作呢?首先來點一下流水LED燈吧。

常規寫法

void LED_Ctrl(void)
{
    static uint32_t sta = 0;

    if (0 == sta)
    {
        LED1_On();
    }
    else
    {
        LED1_Off();
    }

    if (1 == sta)
    {
        LED2_On();
    }
    else
    {
        LED2_Off();
    }

    /* 兩個燈,最大不超過2 */
    sta = (sta + 1) % 2;
}

/* 主函數執行 */
int main(void)
{
    while (1)
    {
        LED_Ctrl();
        os_delay(200);
    }
}

表驅動法

extern void LED1_On(void);
extern void LED1_Off(void);
extern void LED2_On(void);
extern void LED2_Off(void);

/* 把同一個燈的操作封裝起來 */
struct tagLEDFuncCB
{
    void (*LedOn)(void);
    void (*LedOff)(void);
};

/* 定義需要操作到的燈的表 */
const static struct tagLEDFuncCB LedOpTable[] =
{
        {LED1_On, LED1_Off},
        {LED2_On, LED2_Off},
};

void LED_Ctrl(void)
{
    static uint32_t sta = 0;
    uint8_t i;

    for (i = 0; i < sizeof(LedOpTable) / sizeof(LedOpTable[0]); i++)
    {
        (sta == i) ? (LedOpTable[i].LED_On()) : (LedOpTable[i].LED_Off());
    }

    /* 跑下個燈 */
    sta = (sta + 1) % (sizeof(LedOpTable) / sizeof(LedOpTable[0]));
}

int main(void)
{
    while (1)
    {
        LED_Ctrl();
        os_delay(200);
    }
}

這樣的程式碼結構緊湊,因為和結構體結合起來了,方便新增下一個LED燈到流水燈序列中,這其中涉及到函數指標,詳細請看《回撥函數》,只需要修改LedOpTable如下

const static struct tagLEDFuncCB LedOpTable[] =
{
    {LED1_On, LED1_Off},
    {LED2_On, LED2_Off},
    {LED3_On, LED3_Off},
};

這年頭誰還把流水燈搞的這麼花裡胡哨的啊,那麼就舉例在串列埠解析中的應用,之前的文章推播過《回撥函數在命令解析中的應用》,下面只貼一下程式碼

typedef struct
{
    rt_uint8_t CMD;
    rt_uint8_t (*callback_func)(rt_uint8_t cmd, rt_uint8_t *msg, uint8_t len);
} _FUNCCALLBACK;

_FUNCCALLBACK callback_list[] =
{
    {cmd1, func_callback1},
    {cmd2, func_callback2},
    {cmd3, func_callback3},
    {cmd4, func_callback41},
    ...
};

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;

    for (cmd_index = 0; cmd_index < cmd_indexmax; cmd_index++)
    {
        if (callback_list[cmd_index].CMD == cmd)
        {
            if (callback_list[cmd_index])
            {
                /* 處理邏輯  */
                callback_list[cmd_index].callback_func(cmd, msg, len);
            }
        }
    }
}

除上述例子,表驅動法在UI介面中也有良好的應用,如下

結構體封裝

typedef enum
{
    stage1 = 0,
    stage2,
    stage3,
    stage4,
    stage5,
    stage6,
    stage7,
    stage8,
    stage9,
} SCENE;
typedef struct
{
    void (*current_operate)(); //當前場景的處理常式
    SCENE Index;               //當前場景的標籤
    SCENE Up;                  //按下Up鍵跳轉的場景
    SCENE Down;                //按下Down鍵跳轉的場景
    SCENE Right;               //按下Left鍵跳轉的場景
    SCENE Left;                //按下Right鍵跳轉的場景
} STAGE_TAB;

函數對映表

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},
};

定義兩個變數儲存當前場景和上一個場景

char current_stage=stage1;
char prev_stage=current_stage;

按下Up按鍵 跳轉到指定場景current_stage的值根據對映表改變

current_stage =stage_tab[current_stage].Up;

場景改變後 根據對映表執行相應的函數Handler

if(current_stage!=prev_stage)
{
  stage_tab[current_stage].current_operate();
  prev_stage=current_stage;
}

這是一個簡單的選單操作,結合了表驅動法。在MCU中表驅動法有很多很多用處,本文的例子已經過多了,如果在通勤路上用手機看到這裡,已經很難了。關於UI操作,大神figght在github開源了zBitsView倉庫,微控制器實現螢幕介面,多層選單。很牛,很優秀的程式碼,有興趣的同學可以學習一下。https://github.com/figght/zBitsView

4、後記

這篇文章我也看到網上一遍表驅動法的後總結的筆記,可能也有很多同學和我一樣,在自己的專案中熟練應用了這種「技巧」,但今天才知道名字:表驅動法。

這篇文章多數都是程式碼範例,實在因為表驅動法大家應該都熟練應用了,這篇文章算是總結一下吧。

學習知識,可以像在學校從概念一點點學習,也可以在工作中慢慢積累,然後總結記錄,迴歸最初的概念,豐富自己的知識框架。

祝大家變得更強!

 

點選檢視:C語言進階專輯