|
|
- 前言 -
⼤家,好久不⻅。
此⽂旨在于對2019年我發表在51黑論壇的貼⼦《請⼯程化,定制化你的單⽚機代碼》http://www.raoushi.com/bbs/dpj-162218-1.html 進⾏更新,升級和完善。
雖說現在回頭⼀看,那篇貼⼦在⼀些細節確實有⼀些問題,不過最⼤的問題是——它不是⼀個完整的⽂章,我匆忙的把⼀些編程要點塞進了帖⼦⾥,然后就⼀直沒有更新了。
如今⼀晃就是6年,機緣巧合下,我突然⼜找到了那篇貼⼦。在不知不覺之間,帖⼦已經被置頂,有了這么多的評論和閱讀量,這令我甚是慚愧。
感謝⼤家的⽀持。
我近期剛好有時間,那就把帖⼦重寫⼀下,并更完⽂章吧。
- 第⼀章:設計⼀個時鐘基線 -
在⼀些初級MCU編程⼊⻔書中,經常能夠⻅到類似
或者
- while(K1 == 0); //等待⽤戶釋放鍵盤上的K1按鍵
復制代碼 這樣的代碼,表⾯上看沒有什么問題,但是本質上跟下⾯的邏輯差不多:
“已知⼩明同學從教室跑去⼩賣部買糖再回來,⼤概需要1分鐘,所以,當我們需要計時12⼩時的時候,讓⼩明同學這樣來回往返個720次,應該就差不多了。”
“你戳⼩明⼀下,他就會叫⼀聲但是如果你⼀直戳著不松開,他就會由于腦⼦不夠⽤,不但叫不出來,就算你跟他說話他也沒法聽進去。”
2.小明燃盡了.jpg (76.5 KB, 下載次數: 0)
下載附件
2
2025-10-30 19:16 上傳
在這樣的代碼中,MCU的PC指針(這東⻄代表著程序當前在哪個代碼⾥運⾏)就如同悲催的⼩明同學,被困在那兩⾏代碼中。也許是巨⼤的資源浪費,也許是⽆法對外部的信息變化做出反應,都是很糟糕的事情。
那么,要怎么樣來編寫單⽚機程序,才可以⼜實現這些邏輯功能,⼜釋放PC指針呢這就是我們接下來要討論的內容。
1.1 帶有時基的主程序架構
正如“ 每個不得不跑去⼩賣部的⼩明,其實他去年買了個表,根本不⽤跑 ”的道理()。
即使是普通的51單⽚機,他也是有定時器的。
我們需要設置⼀個定時器,讓定時器會告訴單⽚機:“好的,現在已經過了1ms了(或者10ms這都沒關系)”單⽚機⼀拍腦袋:“啊,已經到了1ms了么,那我就把⼀些事情做⼀下吧”。就像下面這樣子:
3.定時器和主程序.png (25.09 KB, 下載次數: 0)
下載附件
3
2025-10-30 19:16 上傳
在這個框架下,我們的代碼看起來會像下⾯這個樣⼦:
- unsigned char flag1ms; //全局變量
-
- void main()
- {
- flag1ms = 0;
- mcuConfig(); //MCU的⼀些其他初始化,此處略過
- timer0Init(); //定時器0初始化,略過,需設置1ms定時周期
-
- while(1)
- {
- if(flag1ms)
- {
- flag1ms = 0;
- doAnything(); //單片機可以做任意事情
- }
- }
- }
-
- void timer0XINT() interrupt 1 //定時器T0中斷
- {
- flag1ms = 1;
- }
復制代碼 為了⽅便理解,這⾥的“⼀系列⼯作”在代碼中只⽤了⼀個doAnything()來表⽰,實際應⽤中,往往會放好⼏個⼦程序,沒什么問題。
在本程序中,單⽚機每隔1ms會將⼀系列⼯作執⾏⼀遍。從現在開始,你的⼦程序必須要摒棄那些“傻等⾏為”,重新設計你的程序邏輯。
1.2 ⼀個按鍵程序的例⼦
我們先拿⼀個⾮常惡⼼的按鍵函數來開⼑,如果沒有時基,我們可能會寫出這樣的⼀個按鍵程序:
- /* 在沒有時基的時候,代碼是這樣寫的 */
- #define K1_PRESSING() ((P1&0x01)==0) //按鍵位于P1^0引腳
- void keyPress()
- {
- unsigned int key_press_time = 0;
- while(K1_PRESSING()) //等待松開
- {
- ++key_press_time;
- if(key_press_time==300) //300*10ms = 3s
- {
- //⻓按3s時要做的事情
- }
- delay10ms(); //delay計時
- }
- if((key_press_time < 300) && (key_press_time >= 2))
- {
- //短按的處理
- }
- }
復制代碼 這個函數兼具delay()語句和while(KEY==0)語句,可以說是⼆毒俱全了。
如果是在新的代碼架構中,它可以修改成這樣的形式
- /* 本程序將在每個1ms時基中被調⽤ */
- #define JUST_INC(x) if(++x<=0) --x //x在不能溢出的前提下++
- #define K1_PRESSING() ((P1&0x01)==0) //按鍵位于P1^0引腳
- void keyPress()
- {
- static unsigned int key_press_time = 0; // ……請標為靜態變量
- if(K1_PRESSING())
- {
- JUST_INC(key_press_time);//計量按鍵時間,并避免數據溢出
- if(key_press_time==3000)
- {
- //在此寫下按鍵⻓按3s時要做的事情
- }
- }
- else
- {
- if((20<=key_press_time) && (key_press_time < 3000))
- {
- //20ms ~ 3s之間,視為短按,在此寫下寫短按的處理代碼
- }
- key_press_time=0;
- }
- }
復制代碼 功能是⼀樣的,但實際⽤起來區別很⼤——舊程序跑1次⻓按功能,新程序已經跑了3000次,且后者不會⼀直占⽤PC指針。
新的程序有些細節,可以展開說⼀說:
①key_press_time現在是⼀個靜態變量,static關鍵字可以讓該變量在每次重新進⼊函數的時候不會重新賦值為0,⽽是保留上次退出函數時的值。
②“JUST_INC(key_press_time)”這⼀句,看起來⽤“++key_press_time”就能搞定,但是,誰也不能保證⽤戶真的不會按按鍵超過65秒的啊萬⼀他真的按了65576ms單⽚機還就真的以為⽤戶“短按”了⼀次呢(65576-65536=40ms,屬于短按范疇),下⾯那個短按程序段也會被執⾏現在這樣寫,哪怕你按100年也沒關系了,反正單⽚機就每隔1ms進來看⼀次,K1這個按鈕你想按多久就按多久,掉在范圍內就處理,超出范圍就⽆視。
③“if(key_press_time==3000)”這⾥的3000只是隨便設置的⼀個3秒⻓按時間,如果需要做按鍵⻓短按功能,這⾥就是⻓按程序所放的位置也可以不⽤3000,⽤2000、50000都沒事,別超過65534就⾏。
④“if((20<=key_press_time) && (key_press_time < 3000))”這⾥,前⾯的>=20是短按的消抖設計——再強的⼈類也不可能1秒按⼀個按鍵超過20次,也就是不可能⼩于50ms的時間——這⾥⽤了20ms相當于兼容“超級快男”來按按鍵了。后⾯<3000是不能和⻓按的時間沖突,因為3s我們已經⼈為的設置成⻓按時間節點了。
⑤這⾥的參數遵循乘法原則。舉個例⼦,如果時基是10ms,那么這⾥的if(key_press_time==3000)就會代表30秒,如果想設置⻓按3秒的話,得把3000改成300。
1.3 關于時基的進階設置
回到剛剛討論的時鐘基線處,這⾥有⼏個⼩問題。
(1)假如,1ms的周期不夠把事情做完怎么辦
——那就把時基設置⼤⼀些,⽐如5ms,10ms,20ms,還不夠就該換芯⽚了,要不就是代碼還有很⼤的其他問題,需要好好排查。
(2)我該怎么⼤致估算代碼跑完“⼀系列⼯作”需要的時間,好讓我設置更合適的時基
——好問題這⾥有⼀個簡單且準確的⽅案任意找個閑置端⼝,⽐如P0^0,在⼲活之前先把這個端⼝拉⾼,⼲完活之后⻢上把它拉低,通過外接⽰波器或者邏輯分析儀,就能直觀的測量出耗時了。
4.工作時長推算.png (30.94 KB, 下載次數: 0)
下載附件
4
2025-10-30 19:16 上傳
(3)只有1個時基不⽅便,我的按鍵只需要10ms采集1次,但是LCD顯⽰我需要100ms才刷新⼀次。
——時基是可以擴展的!在1個時基的基礎上,只要你有需要,完全可以擴展出N多個時基匹配你的N個⼦程序。
⽐如下⾯的代碼,就在1ms的時基上分別擴展出了10ms和100ms的時基:
圖片式代碼1.png (26.01 KB, 下載次數: 0)
下載附件
2025-10-30 21:00 上傳
(4)我的各個函數應該放在哪個時基⾥⾯呢
——這要求我們要對⾃⼰的程序要有清楚的把握,以及⼀定的產品思維,以下是⼀⼰之⻅。
①⾸先,所有的函數都要寫得簡潔⼲凈,不要有任何模塊的delay()加起來超過0.2ms,⼦程序⾥⾯放⼏個nop倒是⽆傷⼤雅。
②按鍵,檢測,通信這類的⼦程序放到10ms時基⾥。輸出,顯⽰這類的放100ms時基就OK了。
③1ms時基⾥⾯應該放什么呢可以什么也別放,空著就好。或者把主程序的基礎時基換成10ms也可以,其實很少有東⻄需要刷新得這么快的。如果基礎時基打算⽤10ms的話,可以將定時器的中斷設置為每10ms觸發(這樣可以刪掉flag1ms變量,此處不展開)也可以偷個懶,原本的程序框架改成這樣即可
圖片式代碼2.png (21.47 KB, 下載次數: 0)
下載附件
2025-10-30 21:00 上傳
④如果有特別需要關照的部分,⽐如說步進電機的驅動啥的,請放到另⼀個定時器中斷⾥(單⽚機基本都⾄少有倆定時器的,不⽤⽩不⽤),按你需要的來設置。
⑤定時器的中斷觸發時間建議不要少于0.5ms,不然進中斷就太頻繁了。
現在,我們的代碼已經初具雛形,⼤家可以⾃⼰搭⼀下這個框架來體驗⼀下。
(當當當,現在是中場休息時間,喝杯⽔吧)
5.快寫完了.jpg (30.89 KB, 下載次數: 0)
下載附件
5
2025-10-30 19:16 上傳
|
評分
-
查看全部評分
|