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