|
概念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% jchar 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 sfor(; 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 vSTAGE_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 Rcurrent_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 \
2uhazf4qq1t64014187353.gif (170.56 KB, 下載次數(shù): 1)
下載附件
保存到相冊(cè)
2uhazf4qq1t64014187353.gif
2024-9-1 11:48 上傳
0 P, x& L" O" G1 P2 v: D* A' V# J
t0h4nm5vm1i64014187453.gif (66.66 KB, 下載次數(shù): 1)
下載附件
保存到相冊(cè)
t0h4nm5vm1i64014187453.gif
2024-9-1 11:48 上傳
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é)合 |
|