表驅動法在STM32中的應用

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;

推薦閱讀