關(guān)注公眾號,回復“入門資料”獲取單片機入門到高級開掛教程
?開發(fā)板帶你入門,我們帶你飛
文 | 無際(微信:2777492857)
全文約3287字,閱讀大約需要?10?分鐘
雷猴啊~我是無際,一個即將禿頂?shù)募夹g(shù)朋友。
剛做開發(fā)那幾年,每次領(lǐng)導或客戶提新需求,我都心慌慌,表面點頭,心里已經(jīng)在算要重寫多少代碼了。
? ? ? ? ?? ?
那會沒有程序架構(gòu)的概念,寫程序都是if-else無限套娃,隔一段時間再回去看,想改個地方得先花大量時間理清頭緒,邊改代碼邊罵項目經(jīng)理。
? ? ? ? ?? ?
后面像撿垃圾一樣,大佬代碼撿一點,網(wǎng)上撿一點,慢慢才有了系統(tǒng)的程序架構(gòu)思維,寫程序也有框架了。
? ? ? ? ?? ?
今天就來介紹一個我經(jīng)常用的架構(gòu)技巧:表驅(qū)動法。
有幸之前看過一個大佬的代碼,可惜我去的時候,他已經(jīng)離職了,我接受了他的項目進行維護,看到了大量表驅(qū)動的用法,比如矩陣按鍵,矩陣LED控制。
我第一次看到這代碼,都震驚了,啥玩意?這么復雜?
? ? ? ? ?
這玩意兒聽起來像是高大上的學術(shù)名詞,但其實就是個“查表解決問題”的騷操作,能讓你的代碼從一團亂麻變成整齊劃一的藝術(shù)品。
? ? ? ? ?? ?
別慌,我保證用最接地氣的語言帶你搞懂這東西,全篇3000字以上,咬碎嚼爛喂你嘴里,保管你看完能直接上手,寫代碼時嘴角都不自覺上揚!
? ? ? ? ?? ?
1.你是不是也煩透了亂糟糟的代碼?
先說個場景,咱們假設你在搞一個單片機項目,比如花式點燈。
? ? ? ? ?? ?
需求很簡單:按按鈕A,燈亮;按按鈕B,燈滅;按按鈕C,燈閃爍。
? ? ? ? ?? ?
你腦子一熱,立馬甩出一堆if-else或者switch-case,代碼可能是這樣的:? ??
if?(button ==?'A')?{? ??light_on();}?else?if?(button ==?'B')?{? ??light_off();}?else?if?(button ==?'C')?{? ??light_blink();}?else?{? ??handle_error();}
看著挺順眼,對吧?邏輯清晰,小項目里用用也沒啥毛病。但現(xiàn)實的需求總是變態(tài)腦洞大開的,項目經(jīng)理冷不丁跑過來說:“加個按鈕D,燈要漸變亮!”你咋整?硬著頭皮在else if后面再塞一行?
? ? ? ? ?? ?
然后改完還得測半天,生怕手抖改錯了把整個邏輯搞崩。更別提需求再變幾次,代碼里全是條件判斷,維護起來簡直是邊罵項目經(jīng)理,邊想砸鍵盤。
? ? ? ? ?? ?
所以,表驅(qū)動法,其實就能解決這些問題。
? ? ? ? ?? ?
2.表驅(qū)動法:從“碼農(nóng)”到“碼神”的捷徑
? ? ? ? ?? ?
啥是表驅(qū)動法?別被名字嚇尿,說白了就是把那些亂七八糟的邏輯判斷塞進一張表里,程序跑的時候直接查表辦事,就跟查字典一樣。
? ? ? ? ?? ?
單片機里用這招,能把復雜的代碼變得簡單到飛起,想加功能?改表就行,核心代碼穩(wěn)如泰山不動。這不比你手動改一堆if-else爽多了?
? ? ? ? ?? ?
這招的精髓在于:把容易變的東西(比如邏輯、數(shù)據(jù))抽出來,扔到表里隔離起來,程序就只管查表干活。既優(yōu)雅又實用,簡直是單片機開發(fā)里的降維打擊。
? ? ? ? ?? ?
3.啥時候使用表驅(qū)動法?
表驅(qū)動法不是萬能鑰匙,但有些場景用它那是真的香。咱們列幾個單片機開發(fā)里的常見情況,你看看是不是似曾相識:
?命令解析:比如串口收到不同指令,要執(zhí)行不同操作,像“ON”開燈,“OFF”關(guān)燈。
?狀態(tài)機:設備在“待機”“運行”“故障”之間跳來跳去,每次狀態(tài)切換還得干點啥。
?事件處理:根據(jù)不同事件調(diào)用不同函數(shù),比如按鍵觸發(fā)、傳感器報警。
?配置管理:一堆參數(shù)需要統(tǒng)一管理,查起來方便。
? ? ? ? ?? ?
這些場景有個共同點:邏輯多、容易變、改起來煩。表驅(qū)動法就像個“收納大師”,把這些亂七八糟的東西整整齊齊塞進表里,代碼瞬間清爽,維護起來也跟喝水一樣簡單。 ?
? ? ? ? ?? ?
4.表驅(qū)動法怎么玩?
表驅(qū)動法其實就三步:
1.搭個表:根據(jù)需求弄個數(shù)組或者結(jié)構(gòu)體數(shù)組,把邏輯和數(shù)據(jù)塞進去。
2.查表:程序跑的時候,根據(jù)輸入或者狀態(tài)去表里找對應的條目。
3.干活:找到后按表里的指示執(zhí)行操作,完事。
? ? ? ? ?? ?
聽起來是不是有點“so easy”?別光點頭,咱們直接上例子,無際單片機從不整虛的,直接帶你實操落地。
? ? ? ? ?? ?
實戰(zhàn)1:命令解析
還記得前面那個燈控系統(tǒng)嗎?咱們用表驅(qū)動法給它整一波優(yōu)化。先看看傳統(tǒng)的路子:
void?process_button(char?button)?{? ??switch?(button)?? ? {? ? ? ??case?'A': light_on();?break;? ? ? ??case?'B': light_off();?break;? ? ? ??case?'C': light_blink();?break;? ? ? ??default: handle_error();?break;? ? }}
這代碼小項目里湊合,但一加新功能就暴露短板。咱們換表驅(qū)動法試試:
? ? ? ? ?? ?
第一步:建表
定義一個結(jié)構(gòu)體數(shù)組,把按鈕和對應的操作塞進去,用函數(shù)指針來把逼裝起來:
// 定義函數(shù)指針類型typedef?void?(*ButtonHandler)(void);// 表結(jié)構(gòu)typedef?struct?{? ??char?button; ? ? ? ? ?// 按鈕? ? ButtonHandler handler;?// 操作函數(shù)} ButtonCommand;// 命令表ButtonCommand commands[] =?{? ? {'A', light_on}, ? ?// 按鈕A:開燈? ? {'B', light_off}, ??// 按鈕B:關(guān)燈? ? {'C', light_blink},?// 按鈕C:閃爍? ??// 加新功能?在這加一行就行!};
? ? ?
第二步:查表+執(zhí)行
寫個函數(shù),收到按鈕信號就去表里找,然后執(zhí)行:
void?process_button(char?button)?{? ??int?size =?sizeof(commands) /?sizeof(ButtonCommand);? ??for?(int?i =?0; i < size; i++) {? ? ? ??if?(commands[i].button == button) {? ? ? ? ? ? commands[i].handler();?// 找到就干活? ? ? ? ? ??return;? ? ? ? }? ? }? ? handle_error();?// 沒找到就報錯}
這招有啥好?
?擴展無壓力:加個按鈕D漸變亮?表里加一行{'D', light_fade},完事!
?代碼不亂套:核心邏輯不動,改需求只改表,維護起來跟玩兒似的。
?一目了然:所有命令都在表里列著,比一堆switch-case直觀多了。
? ? ? ? ?? ?
怎么樣,是不是有種“代碼瞬間高級了”的感覺?
? ? ? ? ?? ?
實戰(zhàn)2:狀態(tài)機也能這么絲滑
單片機里狀態(tài)機用得老多了,比如設備有“待機”“運行”“錯誤”三種狀態(tài),根據(jù)事件跳來跳去。傳統(tǒng)寫法是這樣的:
switch?(current_state)?{? ??case?IDLE:? ? ? ??if?(event?== START)?? ? ? ? {? ? ? ? ? ? start_running();? ? ? ? ? ? current_state = RUNNING;? ? ? ? }? ? ? ??break;? ??case?RUNNING:? ? ? ??if?(event?== STOP)?? ? ? ? {? ? ? ? ? ? stop_running();? ? ? ? ? ? current_state = IDLE;? ? ? ? }?else?if?(event?== ERROR)?? ? ? ? {? ? ? ? ? ? handle_error();? ? ? ? ? ? current_state = ERROR;? ? ? ? }? ? ? ??break;? ??// 后面還有一堆}
這代碼看著就頭暈,改起來更頭大,尤其是狀態(tài)和事件一多,嵌套得讓人想哭。咱們用表驅(qū)動法整一波:
? ? ? ? ?? ?
第一步:建表
定義狀態(tài)和事件的枚舉,再搞個狀態(tài)轉(zhuǎn)移表:? ??
// 狀態(tài)和事件typedef?enum?{ IDLE, RUNNING, ERROR } State;typedef?enum?{ START, STOP, ERROR_EVENT } Event;// 表結(jié)構(gòu)typedef?struct?{? ? State current; ?// 當前狀態(tài)? ? Event event; ? ?// 觸發(fā)事件? ? State next; ? ??// 下一狀態(tài)? ??void?(*action)(void);?// 執(zhí)行的操作} Transition;// 狀態(tài)轉(zhuǎn)移表Transition transitions[] =?{? ? {IDLE, START, RUNNING, start_running},? ? {RUNNING, STOP, IDLE, stop_running},? ? {RUNNING, ERROR_EVENT, ERROR, handle_error},? ??// 加新狀態(tài)?在這加一行}; ? ? ? ? ?
?
第二步:查表+執(zhí)行
寫個函數(shù),根據(jù)當前狀態(tài)和事件查表,更新狀態(tài)并干活:
void?process_event(Event?event)?{? ??int?size =?sizeof(transitions) /?sizeof(Transition);? ??for?(int?i =?0; i < size; i++)?? ? {? ? ? ??if?(transitions[i].current == current_state && transitions[i].event?==?event)?? ? ? ? {? ? ? ? ? ? current_state = transitions[i].next;? ? ? ? ? ??if?(transitions[i].action) transitions[i].action();? ? ? ? ? ??return;? ? ? ? }? ? }? ? invalid_transition();?// 沒找到就報個錯}
? ? ? ? ?? ?
這招有啥妙處?
?加狀態(tài)超輕松:新狀態(tài)新事件?表里加一行,邏輯自動生效。
?規(guī)則全在表里:一打開代碼,所有狀態(tài)轉(zhuǎn)移一清二楚。
?告別嵌套地獄:再也不用盯著層層switch找bug了。
? ? ? ? ?? ?
這狀態(tài)機一用表驅(qū)動法,絲滑得像剛抹了潤滑油,調(diào)試起來都心情舒暢。
? ? ? ? ?? ?
以上幾種我們無際單片機項目用的也非常多,比如自己任務調(diào)度、LED特效、按鍵檢測、菜單、幾種防盜報警模式等等。

? ? ? ? ?? ?

所以項目不是關(guān)鍵,關(guān)鍵是實現(xiàn)的水平,在什么高度。
? ? ? ? ?? ?
把這些實現(xiàn)細節(jié)講給面試官聽,內(nèi)行人能聽得出來你是有水平的,對于入行來說,完全夠了,哪怕做了嵌入式工程師4-5年,都不見得聽過表驅(qū)動法,更別說用了。
?
? ? ? ? ?? ?
5.用表驅(qū)動法的小心機
表驅(qū)動法這么好用,但也不是沒坑,下面的坑不要踩:
?表得全:漏了個情況,程序就懵了,查表查不到可不會自己猜。
?性能得掂量:單片機那點資源,像51單片機項目就沒必要用了,表太大了查找慢,必要時可以用二分查找或者哈希優(yōu)化。
?調(diào)試別偷懶:表里的邏輯不像if-else那么直白,多寫點注釋,不然回頭自己都看不懂。
? ? ? ? ?? ?
我第一次看到那些大佬寫的代碼,沒注釋,也邊看邊罵,寫這么復雜干吊?還是if-else看起來輕松。
? ? ? ? ?? ?
不過這些小毛病比起它的優(yōu)點,簡直不值一提。只要用得順手,表驅(qū)動法絕對是你代碼里的得力助手。
? ? ? ? ?? ?
6. 總結(jié)下
說到底,程序架構(gòu)這東西,它不僅是一種編程技術(shù),更是一種設計思維,同一個項目,不同段位的人去做,穩(wěn)定性各方面都不同,就取決于這種思維和技術(shù)的經(jīng)驗積累。
? ? ? ? ?? ?
而表驅(qū)動法只是其中之一,在單片機開發(fā)中,無論是命令解析、狀態(tài)機設計,還是配置管理,表驅(qū)動法都能幫你寫出更優(yōu)雅的代碼
? ? ? ? ?? ?
接下來,我強烈建議你親自下場,感受“改表不改碼”的快感。
? ? ? ? ?? ?
下次領(lǐng)導再改需求,你先苦巴巴說,這個功能不好做呀,可能要2周。然后暗地里笑笑瞇瞇地改張表,幾分鐘搞定,其它時間,當然是用來摸魚啦,以前我就經(jīng)常干這種事。
end

下面是更多無際原創(chuàng)的個人成長經(jīng)歷、行業(yè)經(jīng)驗、技術(shù)干貨。
1.電子工程師是怎樣的成長之路?10年5000字總結(jié)
2.如何快速看懂別人的代碼和思維
3.單片機開發(fā)項目全局變量太多怎么管理?
4.C語言開發(fā)單片機為什么大多數(shù)都采用全局變量的形式?
5.單片機怎么實現(xiàn)模塊化編程?實用程度讓人發(fā)指!
6.c語言回調(diào)函數(shù)的使用及實際作用詳解
7.手把手教你c語言隊列實現(xiàn)代碼,通俗易懂超詳細!
8.c語言指針用法詳解,通俗易懂超詳細!
? ? ? ? ?? ?