大家好,我叫吳堅鴻,從事單片機項目開發(fā)已經(jīng)有快十年了,目前跟小伙伴們在深圳開了個方案公司,專門承接項目開發(fā)。這些年我做的項目不計其數(shù),現(xiàn)在借電子信息網(wǎng)這個平臺把我認(rèn)為最有價值的東西分享給大家,我這個技術(shù)貼每個星期更新一兩篇,直到我江郎才盡為止。
第二節(jié):delay()延時實現(xiàn)LED燈的閃爍。
開場白:
上一節(jié)鴻哥列出了初學(xué)者七大誤區(qū),到底什么才是初學(xué)者關(guān)注的核心?那就是裸機奔跑的程序結(jié)構(gòu)。一個好的程序結(jié)構(gòu),本身就是一個微型的多任務(wù)操作系統(tǒng)。鴻哥教給大家的就是如何編寫這個簡單的操作系統(tǒng)。在main函數(shù)循環(huán)中用switch語句實現(xiàn)多任務(wù)并行處理的任務(wù)切換,再外加一個定時器中斷,這兩者的結(jié)合就是鴻哥多年來所有實戰(zhàn)項目的核心。鴻哥的程序結(jié)構(gòu)看似簡單,實際上就是那么簡單。大家不用著急,本篇連載文章現(xiàn)在才正式開始,這一節(jié)我要教會大家兩個知識點:
第一點:鴻哥首次提出的“三區(qū)一線”理論。此理論把程序代碼分成三個區(qū),一個延時分割線。
第二點:delay()延時的用途。
(1)硬件平臺:基于朱兆祺51單片機學(xué)習(xí)板。
(2)實現(xiàn)功能:讓一個LED閃爍。
(3)源代碼講解如下:
#include "REG52.H" void initial_myself(); void initial_peripheral(); void delay_short(unsigned int uiDelayshort); void delay_long(unsigned int uiDelaylong); void led_flicker(); /* 注釋一: * 吳堅鴻個人的命名風(fēng)格:凡是輸出后綴都是_dr,凡是輸入后綴都是_sr。 * dr代表drive驅(qū)動,sr代表sensor感應(yīng)器 */ sbit led_dr=P3^5; void main() //學(xué)習(xí)要點:深刻理解鴻哥首次提出的三區(qū)一線理論 { /* 注釋二: * initial_myself()函數(shù)屬于鴻哥三區(qū)一線理論的第一區(qū), * 專門用來初始化單片機自己的寄存器以及個別外圍要求響應(yīng)速度快的輸出設(shè)備, * 防止剛上電之后,由于輸出IO口電平狀態(tài)不確定而導(dǎo)致外圍設(shè)備誤動作, * 比如繼電器的誤動作等等。 */ initial_myself(); /* 注釋三: * 此處的delay_long()延時函數(shù)屬于第一區(qū)與第二區(qū)的分割線, * 延時時間一般是0.3秒到2秒之間,等待外圍芯片和模塊上電穩(wěn)定。 * 比如液晶模塊,AT24C02存儲芯片,DS1302時鐘芯片, * 這類芯片有個特點,一般都是跟單片機進行串口或并口通訊的, * 并且不要求上電立即處理的。 */ delay_long(100); /* 注釋四: * initial_peripheral()函數(shù)屬于鴻哥三區(qū)一線理論的第二區(qū), * 專門用來初始化不要求上電立即處理的外圍芯片和模塊. * 比如液晶模塊,AT24C02存儲芯片,DS1302時鐘芯片。 * 本程序基于朱兆祺51單片機學(xué)習(xí)板。 */ initial_peripheral(); /* 注釋五: * while(1){}主函數(shù)循環(huán)區(qū)屬于鴻哥三區(qū)一線理論的第三區(qū), * 專門用來編寫被循環(huán)掃描到的非中斷應(yīng)用程序 */ while(1) { led_flicker(); //LED閃爍應(yīng)用程序 } } void led_flicker() //LED閃爍應(yīng)用程序 { led_dr=1; //LED亮 delay_short(50000); //延時50000個空指令的時間 /* 注釋六: * delay_long(100)延時50000個空指令的時間,因為內(nèi)嵌了一個500次的for循環(huán) */ led_dr=0; //LED滅 delay_long(100); //延時50000個空指令的時間 } /* 注釋七: * delay_short(unsigned int uiDelayShort)是小延時函數(shù), * 專門用在時序驅(qū)動的小延時,一般uiDelayShort的數(shù)值取10左右, * 最大一般也不超過100.本例為了解釋此函數(shù)的特點,取值范圍超過100。 * 此函數(shù)的特點是時間的細分度高,延時時間不宜過長。uiDelayShort數(shù)值 * 的大小就代表里面執(zhí)行了多少條空指令的時間。數(shù)值越大,延時越長。 * 時間精度不要刻意去計算,感覺差不多就行。 */ void delay_short(unsigned int uiDelayShort) { unsigned int i; for(i=0;i
總結(jié)陳詞:
鴻哥首次提出的“三區(qū)一線”理論概況了各種項目程序的基本分區(qū)。我后續(xù)的程序就按此分區(qū)編寫。Delay()函數(shù)的長延時適用在上電初始化。Delay()函數(shù)的短延時適用在驅(qū)動時序的脈沖延時,此時的時間不能太長,本例中暫時沒有列出這方面的例子,在后面的章節(jié)中會提到。在本例源代碼中,在led_flicker()閃爍應(yīng)用程序里用到的兩個延時delay,它們的延時時間都太長了,在實戰(zhàn)項目中肯定不能用這種延時,因為消耗的時間太長了,其它任務(wù)根本沒有機會執(zhí)行。那怎么辦呢?我們應(yīng)該如何改善?欲知詳情,請聽下回分解-----累計主循環(huán)次數(shù)使LED燈閃爍。
(未完待續(xù),下節(jié)更精彩,不要走開哦)
第一節(jié):吳堅鴻談初學(xué)單片機的誤區(qū)。
(1)很難記住繁雜的寄存器?寄存器不用死記硬背,鴻哥我行走江湖多年,連一個寄存器都記不住。
需要配置寄存器的時候,直接在網(wǎng)上或者書本上參考別人現(xiàn)成的配置程序是上策,查找芯片數(shù)據(jù)手冊是中策,死記硬背寄存器是最最下策。
(2)很難記住繁雜的匯編語言指令?除非是在校學(xué)生要應(yīng)付考試或者少數(shù)工作中繞不開匯編,否則學(xué)匯編就是浪費時間。鴻哥我行走江湖多年,從來就沒有用匯編幫客戶做過一個項目。
(3)C語言很難學(xué)?你不用學(xué)指針,你不用學(xué)帶形參的函數(shù),你不用學(xué)結(jié)構(gòu)體,你不用學(xué)宏定義,你不用學(xué)文件操作,你也不用死記繁瑣的數(shù)據(jù)類型。你只要會:
5條指令語句switch語句,if else語句,while語句,for語句,=賦值語句。
7個運算符+,-,*,/,|,&,!。
4個邏輯關(guān)系符||,&&,!=,==.
3個數(shù)據(jù)類型unsigned char, unsigned int, unsigned long。
3個進制相互轉(zhuǎn)化,二進制,十六進制,十進制。
1個void函數(shù)。
1個一維數(shù)組code(或const) unsigned char array[]。
那么世界上任何一種邏輯功能的單片機軟件你都能做出來。
鴻哥我當(dāng)年剛畢業(yè)出來工作的時候才知道可以用C語言開發(fā)單片機,一開始只用if語句就把項目做出來了,沒有用指針,沒有用帶形參的函數(shù)等復(fù)雜的功能。再到后來才慢慢開始用C語言其他的高級功能,但是我發(fā)現(xiàn)C語言其他的高級功能,本質(zhì)上都是用我前面列舉出來的最基本功能集合而成,只是書寫更加簡單方便了一點,編譯后的機器碼都大同小異。
所以不會指針等高級功能你不用自卑,恰恰相反,當(dāng)你會最簡單的幾個語句,就把這些高級功能的程序都做出來了,你才發(fā)現(xiàn)你對底層了解得更加透切,再學(xué)那些高級功能輕而易舉。當(dāng)你裸機跑的程序都能夠協(xié)調(diào)得很好的時候,你才發(fā)現(xiàn)所謂高深的操作系統(tǒng)也不過如此,只要給你時間和金錢你也可以寫個操作系統(tǒng)來玩玩。
(4)很難記住精確時間的計算公式?經(jīng)常看到時間公式等于晶振,時鐘周期,執(zhí)行指令次數(shù)他們之間的乘除關(guān)系式。鴻哥我認(rèn)為這些都是浮云,不用糾結(jié)也不用去記,大概了解一下就可以了。不管你對公式掌握得有多精確,你都不可能做出非常精確的時間。想用單片機做一個非常精確的時間這種想法一開始就是錯的,不可能的。真想做一個比較精確的時間,應(yīng)該用外圍時鐘芯片或者FPGA和CPLD,而不是單片機。
(5)很難記住繁雜的各種通信協(xié)議?什么IIC,SPI,232串口通訊,CAN,USB等等。這些都是浮云,你不用記那么多,你只要理解兩種通訊方式就夠了,那就是串行通訊方式和并行通訊方式。不管世界上有多少種通訊協(xié)議,物理世界上只有這兩種通訊方式,其他各種名稱的通訊協(xié)議都基于此兩種方式演變而來。
(6)很難寫短小精悍的程序?初學(xué)者不要糾結(jié)于此。做項目開發(fā),程序容量不是刻意追求的目標(biāo),程序多一點少一點沒關(guān)系,現(xiàn)在大容量的單片機品種非常多,容量不會是寸土寸金的事情,我們更加要關(guān)注程序的運行效率,可讀性和可修改性。
既然鴻哥列出了那么多誤區(qū),那么什么才是初學(xué)者關(guān)注的核心?預(yù)知詳情,請聽下回分解----delay()延時實現(xiàn)LED燈的閃爍。(未完待續(xù),下節(jié)更精彩,不要走開哦)
1
第一節(jié)的前面沒基礎(chǔ)的嗎? 比如看什么書,之類
從入門開始行嗎
第三節(jié):累計主循環(huán)次數(shù)使LED燈閃爍。 開場白: 上一節(jié)鴻哥提到delay()延時函數(shù)消耗的時間太長了,其它任務(wù)根本沒有機會執(zhí)行,我們該怎么改善?本節(jié)教大家利用累計主循環(huán)次數(shù)的方法來解決這個問題。這一節(jié)要教會大家兩個知識點: 第一點:利用累計主循環(huán)次數(shù)的方法實現(xiàn)時間延時。 第二點:switch核心語句之初體驗。 鴻哥所有的實戰(zhàn)項目都是基于switch語句實現(xiàn)多任務(wù)并行處理。 (1)硬件平臺:基于朱兆祺51單片機學(xué)習(xí)板。 (2)實現(xiàn)功能:讓一個LED閃爍。 (3)源代碼講解如下:
#include "REG52.H" /* 注釋一: * const_time_level是統(tǒng)計循環(huán)次數(shù)的設(shè)定上限,數(shù)值越大,LED延時的時間越久 */ #define const_time_level 10000 void initial_myself(); void initial_peripheral(); void delay_long(unsigned int uiDelaylong); void led_flicker(); sbit led_dr=P3^5; /* 注釋二: * 吳堅鴻個人的命名風(fēng)格:凡是switch語句里面的步驟變量后綴都是Step. * 前綴帶uc,ui,ul分別表示此變量是unsigned char,unsigned int,unsigned long. */ unsigned char ucLedStep=0; //步驟變量 unsigned int uiTimeCnt=0; //統(tǒng)計循環(huán)次數(shù)的延時計數(shù)器 void main() { initial_myself(); delay_long(100); initial_peripheral(); while(1) { led_flicker(); } } void led_flicker() ////第三區(qū) LED閃爍應(yīng)用程序 { switch(ucLedStep) { case 0: /* 注釋三: * uiTimeCnt累加循環(huán)次數(shù),只有當(dāng)它的次數(shù)大于或等于設(shè)定上限const_time_level時, * 才會去改變LED燈的狀態(tài),否則CPU退出led_flicker()任務(wù),繼續(xù)快速掃描其他的任務(wù), * 這樣的程序結(jié)構(gòu)就可以達到多任務(wù)并行處理的目的。 * 本程序基于朱兆祺51單片機學(xué)習(xí)板 */ uiTimeCnt++; //累加循環(huán)次數(shù), if(uiTimeCnt>=const_time_level) //時間到 { uiTimeCnt=0; //時間計數(shù)器清零 led_dr=1; //讓LED亮 ucLedStep=1; //切換到下一個步驟 } break; case 1: uiTimeCnt++; //累加循環(huán)次數(shù), if(uiTimeCnt>=const_time_level) //時間到 { uiTimeCnt=0; //時間計數(shù)器清零 led_dr=0; //讓LED滅 ucLedStep=0; //返回到上一個步驟 } break; } } void delay_long(unsigned int uiDelayLong) { unsigned int i; unsigned int j; for(i=0;i |
第四節(jié):累計定時中斷次數(shù)使LED燈閃爍。
開場白:
上一節(jié)提到在累計主循環(huán)次數(shù)來實現(xiàn)計時,隨著主函數(shù)里任務(wù)量的增加,為了保證延時時間的準(zhǔn)確性,要不斷修正設(shè)定上限閥值const_time_level 。我們該怎么解決這個問題呢?本節(jié)教大家利用累計定時中斷次數(shù)的方法來解決這個問題。這一節(jié)要教會大家四個知識點:
第一點:利用累計定時中斷次數(shù)的方法實現(xiàn)時間延時。
第二點:展現(xiàn)鴻哥最完整的實戰(zhàn)程序框架。在主函數(shù)循環(huán)里用switch語句實現(xiàn)狀態(tài)機的切換,在定時中斷里累計中斷次數(shù),這兩個的結(jié)合就是我寫代碼最本質(zhì)的框架思想。
第三點:提醒大家C語言中的int ,long變量是由幾個字節(jié)構(gòu)成的數(shù)據(jù),凡是在main函數(shù)和中斷函數(shù)里有可能同時改變的變量,這個變量應(yīng)該在主函數(shù)中被更改之前,先關(guān)閉相應(yīng)的中斷,更改完了此變量,再打開中斷,否則會留下不宜察覺的漏洞。當(dāng)然在大部分的項目中可以不用這么操作,但是在一些要求非常高的項目中,有一些核心變量必須這么做。
第四點:定時中斷的初始值該怎么設(shè)置。不用嚴(yán)格按公式來計算時間,一般取個經(jīng)驗值是最大初始值減去1000就可以了。具體內(nèi)容,請看源代碼講解。
(1)硬件平臺:基于朱兆祺51單片機學(xué)習(xí)板。
(2)實現(xiàn)功能:讓一個LED閃爍。
(3)源代碼講解如下:
#include "REG52.H" #define const_time_level 200 void initial_myself(); void initial_peripheral(); void delay_long(unsigned int uiDelaylong); void led_flicker(); void T0_time(); //定時中斷函數(shù) sbit led_dr=P3^5; unsigned char ucLedStep=0; //步驟變量 unsigned int uiTimeCnt=0; //統(tǒng)計定時中斷次數(shù)的延時計數(shù)器 void main() { initial_myself(); delay_long(100); initial_peripheral(); while(1) { led_flicker(); } } void led_flicker() ////第三區(qū) LED閃爍應(yīng)用程序 { switch(ucLedStep) { case 0: /* 注釋一: * uiTimeCnt累加定時中斷的次數(shù),每一次定時中斷它都會在中斷函數(shù)里自加一。 * 只有當(dāng)它的次數(shù)大于或等于設(shè)定上限const_time_level時, * 才會去改變LED燈的狀態(tài),否則CPU退出led_flicker()任務(wù),繼續(xù)快速掃描其他的任務(wù), * 這樣的程序結(jié)構(gòu)就可以達到多任務(wù)并行處理的目的。這就是鴻哥在所有開發(fā)項目中的核心框架。 */ if(uiTimeCnt>=const_time_level) //時間到 { /* 注釋二: * ET0=0;uiTimeCnt=0;ET0=1;----在清零uiTimeCnt之前,為什么要先禁止定時中斷? * 因為uiTimeCnt是unsigned int類型,本質(zhì)上是由兩個字節(jié)組成。 * 在C語言中uiTimeCnt=0看似一條指令,實際上經(jīng)過編譯之后它不只一條匯編指令。 * 由于定時中斷函數(shù)里也對這個變量進行累加操作,如果不禁止定時中斷, * 那么uiTimeCnt這個變量在main()函數(shù)中還沒被完全清零的時候,如果這個時候 * 突然來一個定時中斷,并且在中斷里又更改了此變量,這種情況在某些要求高的 * 項目上會是一個不容易察覺的漏洞,為項目帶來隱患。當(dāng)然,大部分的普通項目, * 都可以不用那么嚴(yán)格,可以不用禁止定時中斷。在這里只是提醒各位初學(xué)者有這種情況。 */ ET0=0; //禁止定時中斷 uiTimeCnt=0; //時間計數(shù)器清零 ET0=1; //開啟定時中斷 led_dr=1; //讓LED亮 ucLedStep=1; //切換到下一個步驟 } break; case 1: if(uiTimeCnt>=const_time_level) //時間到 { ET0=0; //禁止定時中斷 uiTimeCnt=0; //時間計數(shù)器清零 ET0=1; //開啟定時中斷 led_dr=0; //讓LED滅 ucLedStep=0; //返回到上一個步驟 } break; } } /* 注釋三: * C51的中斷函數(shù)格式如下: * void 函數(shù)名() interrupt 中斷號 * { * 中斷程序內(nèi)容 * } * 函數(shù)名可以隨便取,只要不是編譯器已經(jīng)征用的關(guān)鍵字。 * 這里最關(guān)鍵的是中斷號,不同的中斷號代表不同類型的中斷。 * 定時中斷的中斷號是 1.至于其它中斷的中斷號,大家可以查找 * 相關(guān)書籍和資料。大家進入中斷時,必須先清除中斷標(biāo)志,并且 * 關(guān)閉中斷,然后再寫代碼,最后出來時,記得重裝初始值,并且 * 打開中斷。 */ void T0_time() interrupt 1 { TF0=0; //清除中斷標(biāo)志 TR0=0; //關(guān)中斷 if(uiTimeCnt<0xffff) //設(shè)定這個條件,防止uiTimeCnt超范圍。 { uiTimeCnt++; //累加定時中斷的次數(shù), } TH0=0xf8; //重裝初始值(65535-2000)=63535=0xf82f TL0=0x2f; TR0=1; //開中斷 } void delay_long(unsigned int uiDelayLong) { unsigned int i; unsigned int j; for(i=0;i總結(jié)陳詞:
本節(jié)程序麻雀雖小五臟俱全。在本節(jié)中已經(jīng)展示了我最完整的實戰(zhàn)程序框架。本節(jié)程序只有一個LED燈閃爍的單任務(wù),如果要多增加一個任務(wù)來并行處理,該怎么辦?欲知詳情,請聽下回分解-----蜂鳴器的驅(qū)動程序。
(未完待續(xù),下節(jié)更精彩,不要走開哦)
呵呵,從FSY一路追到這里,走到哪里都是焦點,崇拜中
第五節(jié):蜂鳴器的驅(qū)動程序。
開場白:
上一節(jié)講了利用累計定時中斷次數(shù)實現(xiàn)LED燈閃爍,這個例子同時也第一次展示了我最完整的實戰(zhàn)程序框架:用switch語句實現(xiàn)狀態(tài)機,外加定時中斷。這個框架看似簡單,實際上就是那么簡單。我做的所有開發(fā)項目都是基于這個簡單框架,但是非常好用。上一節(jié)只有一個單任務(wù)的LED燈在閃爍,這節(jié)開始,我們多增加一個蜂鳴器報警的任務(wù),要教會大家四個知識點:
第一點:蜂鳴器的驅(qū)動程序框架編寫。
第二點:多任務(wù)處理的程序框架。
第三點:如何控制蜂鳴器聲音的長叫和短叫。
第四點:如何知道1秒鐘需要多少個定時中斷,也就是如何按比例修正時間精度。具體內(nèi)容,請看源代碼講解。
(1)硬件平臺:基于朱兆祺51單片機學(xué)習(xí)板。
(2)實現(xiàn)功能:同時跑兩個任務(wù),第一個任務(wù)讓一個LED燈1秒鐘閃爍一次。第二個任務(wù)讓蜂鳴器在前面3秒發(fā)生一次短叫報警,在后面6秒發(fā)生一次長叫報警,反復(fù)循環(huán)。
(3)源代碼講解如下:
#include "REG52.H" /* 注釋一: * 如何知道1秒鐘需要多少個定時中斷? * 這個需要編寫一段小程序測試,得到測試的結(jié)果后再按比例修正。 * 步驟: * 第一步:在程序代碼上先寫入1秒鐘大概需要200個定時中斷。 * 第二步:基于以上1秒鐘的基準(zhǔn),編寫一個60秒的簡單測試程序(如果編寫超過 * 60秒的時間,這個精度還會更高)。比如,編寫一個用蜂鳴器的聲音來識別計時的 * 起始和終止的測試程序。 * 第三步:把程序燒錄進單片機后,上電開始測試,手上同步打開手機里的秒表。 * 如果單片機僅僅跑了27秒。 * 第四步:那么最終得出1秒鐘需要的定時中斷次數(shù)是:const_time_1s=(200*60)/27=444 */ #define const_time_05s 222 //0.5秒鐘的時間需要的定時中斷次數(shù) #define const_time_1s 444 //1秒鐘的時間需要的定時中斷次數(shù) #define const_time_3s 1332 //3秒鐘的時間需要的定時中斷次數(shù) #define const_time_6s 2664 //6秒鐘的時間需要的定時中斷次數(shù) #define const_voice_short 40 //蜂鳴器短叫的持續(xù)時間 #define const_voice_long 200 //蜂鳴器長叫的持續(xù)時間 void initial_myself(); void initial_peripheral(); void delay_long(unsigned int uiDelaylong); void led_flicker(); void alarm_run(); void T0_time(); //定時中斷函數(shù) sbit beep_dr=P2^7; //蜂鳴器的驅(qū)動IO口 sbit led_dr=P3^5; //LED燈的驅(qū)動IO口 unsigned char ucLedStep=0; //LED燈的步驟變量 unsigned int uiTimeLedCnt=0; //LED燈統(tǒng)計定時中斷次數(shù)的延時計數(shù)器 unsigned char ucAlarmStep=0; //報警的步驟變量 unsigned int uiTimeAlarmCnt=0; //報警統(tǒng)計定時中斷次數(shù)的延時計數(shù)器 unsigned int uiVoiceCnt=0; //蜂鳴器鳴叫的持續(xù)時間計數(shù)器 void main() { initial_myself(); delay_long(100); initial_peripheral(); while(1) { led_flicker(); //第一個任務(wù)LED燈閃爍 alarm_run(); //第二個任務(wù)報警器定時報警 } } void led_flicker() //第三區(qū) LED閃爍應(yīng)用程序 { switch(ucLedStep) { case 0: if(uiTimeLedCnt>=const_time_05s) //時間到 { uiTimeLedCnt=0; //時間計數(shù)器清零 led_dr=1; //讓LED亮 ucLedStep=1; //切換到下一個步驟 } break; case 1: if(uiTimeLedCnt>=const_time_05s) //時間到 { uiTimeLedCnt=0; //時間計數(shù)器清零 led_dr=0; //讓LED滅 ucLedStep=0; //返回到上一個步驟 } break; } } void alarm_run() //第三區(qū) 報警器的應(yīng)用程序 { switch(ucAlarmStep) { case 0: if(uiTimeAlarmCnt>=const_time_3s) //時間到 { uiTimeAlarmCnt=0; //時間計數(shù)器清零 /* 注釋二: * 只要變量uiVoiceCnt不為0,蜂鳴器就會在定時中斷函數(shù)里啟動鳴叫,并且自減uiVoiceCnt * 直到uiVoiceCnt為0時才停止鳴叫。因此控制uiVoiceCnt變量的大小就是控制聲音的長短。 */ uiVoiceCnt=const_voice_short; //蜂鳴器短叫 ucAlarmStep=1; //切換到下一個步驟 } break; case 1: if(uiTimeAlarmCnt>=const_time_6s) //時間到 { uiTimeAlarmCnt=0; //時間計數(shù)器清零 uiVoiceCnt=const_voice_long; //蜂鳴器長叫 ucAlarmStep=0; //返回到上一個步驟 } break; } } void T0_time() interrupt 1 { TF0=0; //清除中斷標(biāo)志 TR0=0; //關(guān)中斷 if(uiTimeLedCnt<0xffff) //設(shè)定這個條件,防止uiTimeLedCnt超范圍。 { uiTimeLedCnt++; //LED燈的時間計數(shù)器,累加定時中斷的次數(shù), } if(uiTimeAlarmCnt<0xffff) //設(shè)定這個條件,防止uiTimeAlarmCnt超范圍。 { uiTimeAlarmCnt++; //報警的時間計數(shù)器,累加定時中斷的次數(shù), } /* 注釋三: * 為什么不把驅(qū)動蜂鳴器這段代碼放到main函數(shù)的循環(huán)里去? * 因為放在定時中斷里,能保證蜂鳴器的聲音長度是一致的, * 如果放在main循環(huán)里,聲音的長度就有可能受到某些必須 * 一氣呵成的任務(wù)干擾,得不到及時響應(yīng),影響聲音長度的一致性。 */ if(uiVoiceCnt!=0) { uiVoiceCnt--; //每次進入定時中斷都自減1,直到等于零為止。才停止鳴叫 beep_dr=0; //蜂鳴器是PNP三極管控制,低電平就開始鳴叫。 } else { ; //此處多加一個空指令,想維持跟if括號語句的數(shù)量對稱,都是兩條指令。不加也可以。 beep_dr=1; //蜂鳴器是PNP三極管控制,高電平就停止鳴叫。 } TH0=0xf8; //重裝初始值(65535-2000)=63535=0xf82f TL0=0x2f; TR0=1; //開中斷 } void delay_long(unsigned int uiDelayLong) { unsigned int i; unsigned int j; for(i=0;i
總結(jié)陳詞:
本節(jié)程序已經(jīng)展示了一個多任務(wù)處理的基本思路,假如要實現(xiàn)一個獨立按鍵檢測,能不能也按照這種思路來處理呢?欲知詳情,請聽下回分解-----在主函數(shù)中利用累計主循環(huán)次數(shù)來實現(xiàn)獨立按鍵的檢測。
(未完待續(xù),下節(jié)更精彩,不要走開哦)
第六節(jié):在主函數(shù)中利用累計主循環(huán)次數(shù)來實現(xiàn)獨立按鍵的檢測。
開場白:
上一節(jié)講了多任務(wù)中蜂鳴器驅(qū)動程序的框架,這節(jié)繼續(xù)利用多任務(wù)處理的方式,在主函數(shù)中利用累計主循環(huán)次數(shù)來實現(xiàn)獨立按鍵的檢測。要教會大家四個知識點:
第一點:獨立按鍵的驅(qū)動程序框架。
第二點:用累計主循環(huán)次數(shù)來實現(xiàn)去抖動的延時。
第三點:靈活運用防止按鍵不松手后一直觸發(fā)的按鍵自鎖標(biāo)志。
第四點:在按鍵去抖動延時計時中,添加一個抗干擾的軟件監(jiān)控判斷。一旦發(fā)現(xiàn)瞬間雜波干擾,馬上把延時計數(shù)器清零。這種方法是我在復(fù)雜的工控項目中總結(jié)出來的。以后凡是用到開關(guān)感應(yīng)器的地方,都可以用類似的方法實現(xiàn)軟件上的抗干擾處理。具體內(nèi)容,請看源代碼講解。
(1)硬件平臺:基于朱兆祺51單片機學(xué)習(xí)板。用矩陣鍵盤中的S1和S5號鍵作為獨立按鍵,記得把輸出線P0.4一直輸出低電平,模擬獨立按鍵的觸發(fā)地GND。
(2)實現(xiàn)功能:有兩個獨立按鍵,每按一個獨立按鍵,蜂鳴器發(fā)出“滴”的一聲后就停。
(3)源代碼講解如下:
#include "REG52.H" #define const_voice_short 40 //蜂鳴器短叫的持續(xù)時間 /* 注釋一: * 調(diào)整抖動時間閥值的大小,可以更改按鍵的觸發(fā)靈敏度。 * 去抖動的時間本質(zhì)上等于累計主循環(huán)次數(shù)的時間。 */ #define const_key_time1 500 //按鍵去抖動延時的時間 #define const_key_time2 500 //按鍵去抖動延時的時間 void initial_myself(); void initial_peripheral(); void delay_long(unsigned int uiDelaylong); void T0_time(); //定時中斷函數(shù) void key_service(); //按鍵服務(wù)的應(yīng)用程序 void key_scan(); //按鍵掃描函數(shù) sbit key_sr1=P0^0; //對應(yīng)朱兆祺學(xué)習(xí)板的S1鍵 sbit key_sr2=P0^1; //對應(yīng)朱兆祺學(xué)習(xí)板的S5鍵 sbit key_gnd_dr=P0^4; //模擬獨立按鍵的地GND,因此必須一直輸出低電平 sbit beep_dr=P2^7; //蜂鳴器的驅(qū)動IO口 unsigned char ucKeySec=0; //被觸發(fā)的按鍵編號 unsigned int uiKeyTimeCnt1=0; //按鍵去抖動延時計數(shù)器 unsigned char ucKeyLock1=0; //按鍵觸發(fā)后自鎖的變量標(biāo)志 unsigned int uiKeyTimeCnt2=0; //按鍵去抖動延時計數(shù)器 unsigned char ucKeyLock2=0; //按鍵觸發(fā)后自鎖的變量標(biāo)志 unsigned int uiVoiceCnt=0; //蜂鳴器鳴叫的持續(xù)時間計數(shù)器 void main() { initial_myself(); delay_long(100); initial_peripheral(); while(1) { key_scan(); //按鍵掃描函數(shù) key_service(); //按鍵服務(wù)的應(yīng)用程序 } } void key_scan()//按鍵掃描函數(shù) { /* 注釋二: * 獨立按鍵掃描的詳細過程: * 第一步:平時沒有按鍵被觸發(fā)時,按鍵的自鎖標(biāo)志和去抖動延時計數(shù)器一直被清零。 * 第二步:一旦有按鍵被按下,去抖動延時計數(shù)器開始累加,在還沒累加到 * 閥值const_key_time1時,如果在這期間由于受外界干擾或者按鍵抖動,而使 * IO口突然瞬間觸發(fā)成高電平,這個時候馬上又把延時計數(shù)器uiKeyTimeCnt1 * 清零了,這個過程非常巧妙,非常有效地去除瞬間的雜波干擾。這是我實戰(zhàn)中摸索出來的。 * 以后凡是用到開關(guān)感應(yīng)器的時候,都可以用類似這樣的方法去干擾。 * 第三步:如果按鍵按下的時間超過了閥值const_key_time1,則觸發(fā)按鍵,把編號ucKeySec賦值。 * 同時,馬上把自鎖標(biāo)志ucKeyLock1置位,防止按住按鍵不松手后一直觸發(fā)。 * 第四步:等按鍵松開后,自鎖標(biāo)志ucKeyLock1及時清零,為下一次自鎖做準(zhǔn)備。 * 第五步:以上整個過程,就是識別按鍵IO口下降沿觸發(fā)的過程。 */ if(key_sr1==1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標(biāo)志位 { ucKeyLock1=0; //按鍵自鎖標(biāo)志清零 uiKeyTimeCnt1=0;//按鍵去抖動延時計數(shù)器清零,此行非常巧妙,是我實戰(zhàn)中摸索出來的。 } else if(ucKeyLock1==0)//有按鍵按下,且是第一次被按下 { ++uiKeyTimeCnt1; //延時計數(shù)器 if(uiKeyTimeCnt1>const_key_time1) { uiKeyTimeCnt1=0; ucKeyLock1=1; //自鎖按鍵置位,避免一直觸發(fā) ucKeySec=1; //觸發(fā)1號鍵 } } if(key_sr2==1) { ucKeyLock2=0; uiKeyTimeCnt2=0; } else if(ucKeyLock2==0) { ++uiKeyTimeCnt2; if(uiKeyTimeCnt2>const_key_time2) { uiKeyTimeCnt2=0; ucKeyLock2=1; ucKeySec=2; //觸發(fā)2號鍵 } } } void key_service() //第三區(qū) 按鍵服務(wù)的應(yīng)用程序 { switch(ucKeySec) //按鍵服務(wù)狀態(tài)切換 { case 1:// 1號鍵 對應(yīng)朱兆祺學(xué)習(xí)板的S1鍵 uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。 ucKeySec=0; //響應(yīng)按鍵服務(wù)處理程序后,按鍵編號清零,避免一致觸發(fā) break; case 2:// 2號鍵 對應(yīng)朱兆祺學(xué)習(xí)板的S5鍵 uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。 ucKeySec=0; //響應(yīng)按鍵服務(wù)處理程序后,按鍵編號清零,避免一致觸發(fā) break; } } void T0_time() interrupt 1 { TF0=0; //清除中斷標(biāo)志 TR0=0; //關(guān)中斷 if(uiVoiceCnt!=0) { uiVoiceCnt--; //每次進入定時中斷都自減1,直到等于零為止。才停止鳴叫 beep_dr=0; //蜂鳴器是PNP三極管控制,低電平就開始鳴叫。 } else { ; //此處多加一個空指令,想維持跟if括號語句的數(shù)量對稱,都是兩條指令。不加也可以。 beep_dr=1; //蜂鳴器是PNP三極管控制,高電平就停止鳴叫。 } TH0=0xf8; //重裝初始值(65535-2000)=63535=0xf82f TL0=0x2f; TR0=1; //開中斷 } void delay_long(unsigned int uiDelayLong) { unsigned int i; unsigned int j; for(i=0;i總結(jié)陳詞:
本節(jié)程序已經(jīng)展示了在主函數(shù)中,利用累計主循環(huán)次數(shù)來實現(xiàn)獨立按鍵的檢測。這種方法我經(jīng)常在實戰(zhàn)用應(yīng)用,但是它也有一個小小的不足,隨著在主函數(shù)循環(huán)中任務(wù)量的增加,為了保證去抖動延時的時間一致性,要適當(dāng)調(diào)整一下去抖動的閥值const_key_time1。如何解決這個問題呢?欲知詳情,請聽下回分解-----在主函數(shù)中利用累計定時中斷的次數(shù)來實現(xiàn)獨立按鍵的檢測。
(未完待續(xù),下節(jié)更精彩,不要走開哦)
第七節(jié):在主函數(shù)中利用累計定時中斷的次數(shù)來實現(xiàn)獨立按鍵的檢測。
開場白:
上一節(jié)講了在主函數(shù)中利用累計主循環(huán)次數(shù)來實現(xiàn)獨立按鍵的檢測,但是它也有一個小小的不足,隨著在主函數(shù)中任務(wù)量的增加,為了保證去抖動延時的時間一致性,要適當(dāng)調(diào)整一下去抖動的時間閥值const_key_time1。如何解決這個問題呢?這一節(jié)教大家在主函數(shù)中利用累計定時中斷的次數(shù)來實現(xiàn)獨立按鍵的檢測,可以有效地避免這個問題。要教會大家一個知識點:如何在上一節(jié)的基礎(chǔ)上,略作修改,就可以在主函數(shù)中,利用累計定時中斷的次數(shù)來實現(xiàn)去抖動的延時。
具體內(nèi)容,請看源代碼講解。
(1)硬件平臺:基于朱兆祺51單片機學(xué)習(xí)板。用矩陣鍵盤中的S1和S5號鍵作為獨立按鍵,記得把輸出線P0.4一直輸出低電平,模擬獨立按鍵的觸發(fā)地GND。
(2)實現(xiàn)功能:有兩個獨立按鍵,每按一個獨立按鍵,蜂鳴器發(fā)出“滴”的一聲后就停。
(3)源代碼講解如下:
#include "REG52.H" #define const_voice_short 40 //蜂鳴器短叫的持續(xù)時間 /* 注釋一: * 調(diào)整抖動時間閥值的大小,可以更改按鍵的觸發(fā)靈敏度。 * 去抖動的時間本質(zhì)上等于累計定時中斷次數(shù)的時間。 */ #define const_key_time1 30 //按鍵去抖動延時的時間 #define const_key_time2 30 //按鍵去抖動延時的時間 void initial_myself(); void initial_peripheral(); void delay_long(unsigned int uiDelaylong); void T0_time(); //定時中斷函數(shù) void key_service(); //按鍵服務(wù)的應(yīng)用程序 void key_scan(); //按鍵掃描函數(shù) sbit key_sr1=P0^0; //對應(yīng)朱兆祺學(xué)習(xí)板的S1鍵 sbit key_sr2=P0^1; //對應(yīng)朱兆祺學(xué)習(xí)板的S5鍵 sbit key_gnd_dr=P0^4; //模擬獨立按鍵的地GND,因此必須一直輸出低電平 sbit beep_dr=P2^7; //蜂鳴器的驅(qū)動IO口 unsigned char ucKeySec=0; //被觸發(fā)的按鍵編號 unsigned char ucKeyStartFlag1=0; //啟動定時中斷計數(shù)的開關(guān) unsigned int uiKeyTimeCnt1=0; //按鍵去抖動延時計數(shù)器 unsigned char ucKeyLock1=0; //按鍵觸發(fā)后自鎖的變量標(biāo)志 unsigned char ucKeyStartFlag2=0; //啟動定時中斷計數(shù)的開關(guān) unsigned int uiKeyTimeCnt2=0; //按鍵去抖動延時計數(shù)器 unsigned char ucKeyLock2=0; //按鍵觸發(fā)后自鎖的變量標(biāo)志 unsigned int uiVoiceCnt=0; //蜂鳴器鳴叫的持續(xù)時間計數(shù)器 void main() { initial_myself(); delay_long(100); initial_peripheral(); while(1) { key_scan(); //按鍵掃描函數(shù) key_service(); //按鍵服務(wù)的應(yīng)用程序 } } void key_scan()//按鍵掃描函數(shù) { /* 注釋二: * 獨立按鍵掃描的詳細過程: * 第一步:平時沒有按鍵被觸發(fā)時,按鍵的自鎖標(biāo)志,計時器開關(guān)和去抖動延時計數(shù)器一直被清零。 * 第二步:一旦有按鍵被按下,啟動計時器,去抖動延時計數(shù)器開始在定時中斷函數(shù)里累加,在還沒累加到 * 閥值const_key_time1時,如果在這期間由于受外界干擾或者按鍵抖動,而使 * IO口突然瞬間觸發(fā)成高電平,這個時候馬上停止計時,并且把延時計數(shù)器uiKeyTimeCnt1 * 清零了,這個過程非常巧妙,非常有效地去除瞬間的雜波干擾。這是我實戰(zhàn)中摸索出來的。 * 以后凡是用到開關(guān)感應(yīng)器的時候,都可以用類似這樣的方法去干擾。 * 第三步:如果按鍵按下的時間超過了閥值const_key_time1,則觸發(fā)按鍵,把編號ucKeySec賦值。 * 同時,馬上把自鎖標(biāo)志ucKeyLock1置位,防止按住按鍵不松手后一直觸發(fā)。 * 第四步:等按鍵松開后,自鎖標(biāo)志ucKeyLock1及時清零,為下一次自鎖做準(zhǔn)備。 * 第五步:以上整個過程,就是識別按鍵IO口下降沿觸發(fā)的過程。 */ if(key_sr1==1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標(biāo)志位 { ucKeyLock1=0; //按鍵自鎖標(biāo)志清零 ucKeyStartFlag1=0; //停止計數(shù)器 uiKeyTimeCnt1=0;//按鍵去抖動延時計數(shù)器清零,此行非常巧妙,是我實戰(zhàn)中摸索出來的。 } else if(ucKeyLock1==0)//有按鍵按下,且是第一次被按下 { ucKeyStartFlag1=1; //啟動計數(shù)器 if(uiKeyTimeCnt1>const_key_time1) { ucKeyStartFlag1=0; //停止計數(shù)器 uiKeyTimeCnt1=0; ucKeyLock1=1; //自鎖按鍵置位,避免一直觸發(fā) ucKeySec=1; //觸發(fā)1號鍵 } } if(key_sr2==1) { ucKeyLock2=0; ucKeyStartFlag2=0; //停止計數(shù)器 uiKeyTimeCnt2=0; } else if(ucKeyLock2==0) { ucKeyStartFlag2=1; //啟動計數(shù)器 if(uiKeyTimeCnt2>const_key_time2) { ucKeyStartFlag2=0; //停止計數(shù)器 uiKeyTimeCnt2=0; ucKeyLock2=1; ucKeySec=2; //觸發(fā)2號鍵 } } } void key_service() //第三區(qū) 按鍵服務(wù)的應(yīng)用程序 { switch(ucKeySec) //按鍵服務(wù)狀態(tài)切換 { case 1:// 1號鍵 對應(yīng)朱兆祺學(xué)習(xí)板的S1鍵 uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。 ucKeySec=0; //響應(yīng)按鍵服務(wù)處理程序后,按鍵編號清零,避免一致觸發(fā) break; case 2:// 2號鍵 對應(yīng)朱兆祺學(xué)習(xí)板的S5鍵 uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。 ucKeySec=0; //響應(yīng)按鍵服務(wù)處理程序后,按鍵編號清零,避免一致觸發(fā) break; } } void T0_time() interrupt 1 { TF0=0; //清除中斷標(biāo)志 TR0=0; //關(guān)中斷 if(ucKeyStartFlag1==1)//啟動計數(shù)器 { if(uiKeyTimeCnt1<0xffff) //防止計數(shù)器超范圍 { uiKeyTimeCnt1++; } } if(ucKeyStartFlag2==1)//啟動計數(shù)器 { if(uiKeyTimeCnt2<0xffff) //防止計數(shù)器超范圍 { uiKeyTimeCnt2++; } } if(uiVoiceCnt!=0) { uiVoiceCnt--; //每次進入定時中斷都自減1,直到等于零為止。才停止鳴叫 beep_dr=0; //蜂鳴器是PNP三極管控制,低電平就開始鳴叫。 } else { ; //此處多加一個空指令,想維持跟if括號語句的數(shù)量對稱,都是兩條指令。不加也可以。 beep_dr=1; //蜂鳴器是PNP三極管控制,高電平就停止鳴叫。 } TH0=0xf8; //重裝初始值(65535-2000)=63535=0xf82f TL0=0x2f; TR0=1; //開中斷 } void delay_long(unsigned int uiDelayLong) { unsigned int i; unsigned int j; for(i=0;i
總結(jié)陳詞:
本節(jié)程序已經(jīng)展示了在主函數(shù)中,利用累計定時中斷次數(shù)來實現(xiàn)獨立按鍵的檢測。這種方法我也經(jīng)常在實戰(zhàn)用應(yīng)用,但是如果在某些項目中,需要在主函數(shù)里間歇性地執(zhí)行一些一氣呵成的耗時任務(wù),這種方法就不是很實用,因為當(dāng)主函數(shù)正在處理一氣呵成的耗時任務(wù)時,這個時候如果有按鍵按下來,就有可能沒有及時被響應(yīng)到而遺漏了。那有什么方法可以解決這類項目中遇到的問題?欲知詳情,請聽下回分解-----在定時中斷函數(shù)里執(zhí)行獨立按鍵的掃描程序。
(未完待續(xù),下節(jié)更精彩,不要走開哦)
第八節(jié):在定時中斷函數(shù)里執(zhí)行獨立按鍵的掃描程序。
開場白:
上一節(jié)講了在主函數(shù)中利用累計定時中斷的次數(shù)來實現(xiàn)獨立按鍵的檢測,但是如果在某些項目中,需要在主函數(shù)里間歇性地執(zhí)行一些一氣呵成的耗時任務(wù),當(dāng)主函數(shù)正在處理一氣呵成的耗時任務(wù)時(前提是沒有關(guān)閉定時器中斷),這個時候如果有按鍵按下來,就有可能沒有及時被響應(yīng)到而遺漏了。在定時中斷函數(shù)里處理獨立按鍵的掃描程序,可以避免這個問題。要教會大家一個知識點:如何在上一節(jié)的基礎(chǔ)上,略作修改,就可以在定時中斷函數(shù)里處理獨立按鍵的掃描程序。具體內(nèi)容,請看源代碼講解。
(1)硬件平臺:基于朱兆祺51單片機學(xué)習(xí)板。用矩陣鍵盤中的S1和S5號鍵作為獨立按鍵,記得把輸出線P0.4一直輸出低電平,模擬獨立按鍵的觸發(fā)地GND。
(2)實現(xiàn)功能:有兩個獨立按鍵,每按一個獨立按鍵,蜂鳴器發(fā)出“滴”的一聲后就停。
(3)源代碼講解如下:
#include "REG52.H" #define const_voice_short 40 //蜂鳴器短叫的持續(xù)時間 /* 注釋一: * 調(diào)整抖動時間閥值的大小,可以更改按鍵的觸發(fā)靈敏度。 * 去抖動的時間本質(zhì)上等于累計定時中斷次數(shù)的時間。 */ #define const_key_time1 20 //按鍵去抖動延時的時間 #define const_key_time2 20 //按鍵去抖動延時的時間 void initial_myself(); void initial_peripheral(); void delay_long(unsigned int uiDelaylong); void T0_time(); //定時中斷函數(shù) void key_service(); //按鍵服務(wù)的應(yīng)用程序 void key_scan(); //按鍵掃描函數(shù) 放在定時中斷里 sbit key_sr1=P0^0; //對應(yīng)朱兆祺學(xué)習(xí)板的S1鍵 sbit key_sr2=P0^1; //對應(yīng)朱兆祺學(xué)習(xí)板的S5鍵 sbit key_gnd_dr=P0^4; //模擬獨立按鍵的地GND,因此必須一直輸出低電平 sbit beep_dr=P2^7; //蜂鳴器的驅(qū)動IO口 unsigned char ucKeySec=0; //被觸發(fā)的按鍵編號 unsigned int uiKeyTimeCnt1=0; //按鍵去抖動延時計數(shù)器 unsigned char ucKeyLock1=0; //按鍵觸發(fā)后自鎖的變量標(biāo)志 unsigned int uiKeyTimeCnt2=0; //按鍵去抖動延時計數(shù)器 unsigned char ucKeyLock2=0; //按鍵觸發(fā)后自鎖的變量標(biāo)志 unsigned int uiVoiceCnt=0; //蜂鳴器鳴叫的持續(xù)時間計數(shù)器 void main() { initial_myself(); delay_long(100); initial_peripheral(); while(1) { key_service(); //按鍵服務(wù)的應(yīng)用程序 } } void key_scan()//按鍵掃描函數(shù) 放在定時中斷里 { /* 注釋二: * 獨立按鍵掃描的詳細過程: * 第一步:平時沒有按鍵被觸發(fā)時,按鍵的自鎖標(biāo)志,去抖動延時計數(shù)器一直被清零。 * 第二步:一旦有按鍵被按下,去抖動延時計數(shù)器開始在定時中斷函數(shù)里累加,在還沒累加到 * 閥值const_key_time1時,如果在這期間由于受外界干擾或者按鍵抖動,而使 * IO口突然瞬間觸發(fā)成高電平,這個時候馬上把延時計數(shù)器uiKeyTimeCnt1 * 清零了,這個過程非常巧妙,非常有效地去除瞬間的雜波干擾。這是我實戰(zhàn)中摸索出來的。 * 以后凡是用到開關(guān)感應(yīng)器的時候,都可以用類似這樣的方法去干擾。 * 第三步:如果按鍵按下的時間超過了閥值const_key_time1,則觸發(fā)按鍵,把編號ucKeySec賦值。 * 同時,馬上把自鎖標(biāo)志ucKeyLock1置位,防止按住按鍵不松手后一直觸發(fā)。 * 第四步:等按鍵松開后,自鎖標(biāo)志ucKeyLock1及時清零,為下一次自鎖做準(zhǔn)備。 * 第五步:以上整個過程,就是識別按鍵IO口下降沿觸發(fā)的過程。 */ if(key_sr1==1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標(biāo)志位 { ucKeyLock1=0; //按鍵自鎖標(biāo)志清零 uiKeyTimeCnt1=0;//按鍵去抖動延時計數(shù)器清零,此行非常巧妙,是我實戰(zhàn)中摸索出來的。 } else if(ucKeyLock1==0)//有按鍵按下,且是第一次被按下 { uiKeyTimeCnt1++; //累加定時中斷次數(shù) if(uiKeyTimeCnt1>const_key_time1) { uiKeyTimeCnt1=0; ucKeyLock1=1; //自鎖按鍵置位,避免一直觸發(fā) ucKeySec=1; //觸發(fā)1號鍵 } } if(key_sr2==1) { ucKeyLock2=0; uiKeyTimeCnt2=0; } else if(ucKeyLock2==0) { uiKeyTimeCnt2++; //累加定時中斷次數(shù) if(uiKeyTimeCnt2>const_key_time2) { uiKeyTimeCnt2=0; ucKeyLock2=1; ucKeySec=2; //觸發(fā)2號鍵 } } } void key_service() //第三區(qū) 按鍵服務(wù)的應(yīng)用程序 { switch(ucKeySec) //按鍵服務(wù)狀態(tài)切換 { case 1:// 1號鍵 對應(yīng)朱兆祺學(xué)習(xí)板的S1鍵 uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。 ucKeySec=0; //響應(yīng)按鍵服務(wù)處理程序后,按鍵編號清零,避免一致觸發(fā) break; case 2:// 2號鍵 對應(yīng)朱兆祺學(xué)習(xí)板的S5鍵 uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。 ucKeySec=0; //響應(yīng)按鍵服務(wù)處理程序后,按鍵編號清零,避免一致觸發(fā) break; } } void T0_time() interrupt 1 { TF0=0; //清除中斷標(biāo)志 TR0=0; //關(guān)中斷 key_scan(); //按鍵掃描函數(shù) if(uiVoiceCnt!=0) { uiVoiceCnt--; //每次進入定時中斷都自減1,直到等于零為止。才停止鳴叫 beep_dr=0; //蜂鳴器是PNP三極管控制,低電平就開始鳴叫。 } else { ; //此處多加一個空指令,想維持跟if括號語句的數(shù)量對稱,都是兩條指令。不加也可以。 beep_dr=1; //蜂鳴器是PNP三極管控制,高電平就停止鳴叫。 } TH0=0xf8; //重裝初始值(65535-2000)=63535=0xf82f TL0=0x2f; TR0=1; //開中斷 } void delay_long(unsigned int uiDelayLong) { unsigned int i; unsigned int j; for(i=0;i
總結(jié)陳詞:
本節(jié)程序已經(jīng)展示了在定時中斷函數(shù)里執(zhí)行獨立按鍵的掃描程序。這節(jié)和前面兩節(jié)所講的掃描方式,我都在項目上用過,具體跟項目的側(cè)重點不同來選擇不同的方式,我本人用得最多的就是當(dāng)前這種方式。假如要獨立按鍵實現(xiàn)類似鼠標(biāo)的雙擊功能,我們改怎么寫程序?欲知詳情,請聽下回分解-----獨立按鍵的雙擊按鍵觸發(fā)。
(未完待續(xù),下節(jié)更精彩,不要走開哦)
第九節(jié):獨立按鍵的雙擊按鍵觸發(fā)。
開場白:
上一節(jié)講了在定時中斷函數(shù)里處理獨立按鍵的掃描程序,這種結(jié)構(gòu)的程序我用在了很多項目上。這一節(jié)教大家如何實現(xiàn)按鍵雙擊觸發(fā)的功能,這種功能類似鼠標(biāo)的雙擊。要教會大家一個知識點:如何在上一節(jié)的基礎(chǔ)上,略作修改,就可以實現(xiàn)按鍵的雙擊功能。
具體內(nèi)容,請看源代碼講解。
(1)硬件平臺:基于朱兆祺51單片機學(xué)習(xí)板。用矩陣鍵盤中的S1和S5號鍵作為獨立按鍵,記得把輸出線P0.4一直輸出低電平,模擬獨立按鍵的觸發(fā)地GND。
(2)實現(xiàn)功能:有兩個獨立按鍵,每雙擊一個獨立按鍵,蜂鳴器發(fā)出“滴”的一聲后就停。
(3)源代碼講解如下:
#include "REG52.H" #define const_voice_short 40 //蜂鳴器短叫的持續(xù)時間 /* 注釋一: * 調(diào)整抖動時間閥值的大小,可以更改按鍵的觸發(fā)靈敏度。 * 去抖動的時間本質(zhì)上等于累計定時中斷次數(shù)的時間。 */ #define const_key_time1 20 //按鍵去抖動延時的時間 #define const_key_time2 20 //按鍵去抖動延時的時間 /* 注釋二: * 有效時間差,是指連續(xù)兩次按鍵觸發(fā)的最大有效間隔時間。 * 如果雙擊的兩個按鍵按下的時間間隔太長,則視為無效雙擊。 */ #define const_interval_time1 200 //連續(xù)兩次按鍵之間的有效時間差 #define const_interval_time2 200 //連續(xù)兩次按鍵之間的有效時間差 void initial_myself(); void initial_peripheral(); void delay_long(unsigned int uiDelaylong); void T0_time(); //定時中斷函數(shù) void key_service(); //按鍵服務(wù)的應(yīng)用程序 void key_scan(); //按鍵掃描函數(shù) 放在定時中斷里 sbit key_sr1=P0^0; //對應(yīng)朱兆祺學(xué)習(xí)板的S1鍵 sbit key_sr2=P0^1; //對應(yīng)朱兆祺學(xué)習(xí)板的S5鍵 sbit key_gnd_dr=P0^4; //模擬獨立按鍵的地GND,因此必須一直輸出低電平 sbit beep_dr=P2^7; //蜂鳴器的驅(qū)動IO口 unsigned char ucKeySec=0; //被觸發(fā)的按鍵編號 unsigned int uiKeyTimeCnt1=0; //按鍵去抖動延時計數(shù)器 unsigned char ucKeyLock1=0; //按鍵觸發(fā)后自鎖的變量標(biāo)志 unsigned char ucKeyTouchCnt1=0; //按鍵按下的次數(shù)記錄 unsigned int uiKeyIntervalCnt1=0; //按鍵間隔的時間計數(shù)器 unsigned int uiKeyTimeCnt2=0; //按鍵去抖動延時計數(shù)器 unsigned char ucKeyLock2=0; //按鍵觸發(fā)后自鎖的變量標(biāo)志 unsigned char ucKeyTouchCnt2=0; //按鍵按下的次數(shù)記錄 unsigned int uiKeyIntervalCnt2=0; //按鍵間隔的時間計數(shù)器 unsigned int uiVoiceCnt=0; //蜂鳴器鳴叫的持續(xù)時間計數(shù)器 void main() { initial_myself(); delay_long(100); initial_peripheral(); while(1) { key_service(); //按鍵服務(wù)的應(yīng)用程序 } } void key_scan()//按鍵掃描函數(shù) 放在定時中斷里 { /* 注釋三: * 獨立雙擊按鍵掃描的詳細過程: * 第一步:平時沒有按鍵被觸發(fā)時,按鍵的自鎖標(biāo)志,去抖動延時計數(shù)器一直被清零。 * 如果之前已經(jīng)有按鍵觸發(fā)過一次,那么啟動時間間隔計數(shù)器uiKeyIntervalCnt1, * 在這個允許的時間差范圍內(nèi),如果一直沒有第二次按鍵觸發(fā),則把累加按鍵觸發(fā)的 * 次數(shù)ucKeyTouchCnt1也清零。 * 第二步:一旦有按鍵被按下,去抖動延時計數(shù)器開始在定時中斷函數(shù)里累加,在還沒累加到 * 閥值const_key_time1時,如果在這期間由于受外界干擾或者按鍵抖動,而使 * IO口突然瞬間觸發(fā)成高電平,這個時候馬上把延時計數(shù)器uiKeyTimeCnt1 * 清零了,這個過程非常巧妙,非常有效地去除瞬間的雜波干擾。這是我實戰(zhàn)中摸索出來的。 * 以后凡是用到開關(guān)感應(yīng)器的時候,都可以用類似這樣的方法去干擾。 * 第三步:如果按鍵按下的時間超過了閥值const_key_time1,馬上把自鎖標(biāo)志ucKeyLock1置位, * 防止按住按鍵不松手后一直觸發(fā)。與此同時,累加一次按鍵次數(shù),如果按鍵次數(shù)累加有兩次以上, * 則認(rèn)為觸發(fā)雙擊按鍵,并把編號ucKeySec賦值。 * 第四步:等按鍵松開后,自鎖標(biāo)志ucKeyLock1及時清零,為下一次自鎖做準(zhǔn)備。并且累加間隔時間, * 防止兩次按鍵的間隔時間太長。 * 第五步:以上整個過程,就是識別按鍵IO口下降沿觸發(fā)的過程。 */ if(key_sr1==1)//IO是高電平,說明按鍵沒有被按下,這時要及時清零一些標(biāo)志位 { ucKeyLock1=0; //按鍵自鎖標(biāo)志清零 uiKeyTimeCnt1=0;//按鍵去抖動延時計數(shù)器清零,此行非常巧妙,是我實戰(zhàn)中摸索出來的。 if(ucKeyTouchCnt1>0) //之前已經(jīng)有按鍵觸發(fā)過一次,再來一次就構(gòu)成雙擊 { uiKeyIntervalCnt1++; //按鍵間隔的時間計數(shù)器累加 if(uiKeyIntervalCnt1>const_interval_time1) //超過最大允許的間隔時間 { uiKeyIntervalCnt1=0; //時間計數(shù)器清零 ucKeyTouchCnt1=0; //清零按鍵的按下的次數(shù) } } } else if(ucKeyLock1==0)//有按鍵按下,且是第一次被按下 { uiKeyTimeCnt1++; //累加定時中斷次數(shù) if(uiKeyTimeCnt1>const_key_time1) { uiKeyTimeCnt1=0; ucKeyLock1=1; //自鎖按鍵置位,避免一直觸發(fā) uiKeyIntervalCnt1=0; //按鍵有效間隔的時間計數(shù)器清零 ucKeyTouchCnt1++; if(ucKeyTouchCnt1>1) //連續(xù)被按了兩次以上 { ucKeyTouchCnt1=0; //統(tǒng)計按鍵次數(shù)清零 ucKeySec=1; //觸發(fā)1號鍵 } } } if(key_sr2==1) { ucKeyLock2=0; uiKeyTimeCnt2=0; if(ucKeyTouchCnt2>0) { uiKeyIntervalCnt2++; //按鍵間隔的時間計數(shù)器累加 if(uiKeyIntervalCnt2>const_interval_time2) //超過最大允許的間隔時間 { uiKeyIntervalCnt2=0; //時間計數(shù)器清零 ucKeyTouchCnt2=0; //清零按鍵的按下的次數(shù) } } } else if(ucKeyLock2==0) { uiKeyTimeCnt2++; //累加定時中斷次數(shù) if(uiKeyTimeCnt2>const_key_time2) { uiKeyTimeCnt2=0; ucKeyLock2=1; uiKeyIntervalCnt2=0; //按鍵有效間隔的時間計數(shù)器清零 ucKeyTouchCnt2++; if(ucKeyTouchCnt2>1) //連續(xù)被按了兩次以上 { ucKeyTouchCnt2=0; //統(tǒng)計按鍵次數(shù)清零 ucKeySec=2; //觸發(fā)2號鍵 } } } } void key_service() //第三區(qū) 按鍵服務(wù)的應(yīng)用程序 { switch(ucKeySec) //按鍵服務(wù)狀態(tài)切換 { case 1:// 1號鍵 雙擊 對應(yīng)朱兆祺學(xué)習(xí)板的S1鍵 uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。 ucKeySec=0; //響應(yīng)按鍵服務(wù)處理程序后,按鍵編號清零,避免一致觸發(fā) break; case 2:// 2號鍵 雙擊 對應(yīng)朱兆祺學(xué)習(xí)板的S5鍵 uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。 ucKeySec=0; //響應(yīng)按鍵服務(wù)處理程序后,按鍵編號清零,避免一致觸發(fā) break; } } void T0_time() interrupt 1 { TF0=0; //清除中斷標(biāo)志 TR0=0; //關(guān)中斷 key_scan(); //按鍵掃描函數(shù) if(uiVoiceCnt!=0) { uiVoiceCnt--; //每次進入定時中斷都自減1,直到等于零為止。才停止鳴叫 beep_dr=0; //蜂鳴器是PNP三極管控制,低電平就開始鳴叫。 } else { ; //此處多加一個空指令,想維持跟if括號語句的數(shù)量對稱,都是兩條指令。不加也可以。 beep_dr=1; //蜂鳴器是PNP三極管控制,高電平就停止鳴叫。 } TH0=0xf8; //重裝初始值(65535-2000)=63535=0xf82f TL0=0x2f; TR0=1; //開中斷 } void delay_long(unsigned int uiDelayLong) { unsigned int i; unsigned int j; for(i=0;i
總結(jié)陳詞:
假如要兩個獨立按鍵實現(xiàn)組合按鍵的功能,我們該怎么寫程序?欲知詳情,請聽下回分解-----獨立按鍵的組合按鍵觸發(fā)。
(未完待續(xù),下節(jié)更精彩,不要走開哦)
第十節(jié):兩個獨立按鍵的組合按鍵觸發(fā)。
開場白:
上一節(jié)講了按鍵雙擊觸發(fā)功能的程序,這一節(jié)講類似電腦鍵盤組合按鍵觸發(fā)的功能,要教會大家一個知識點:如何在上一節(jié)的基礎(chǔ)上,略作修改,就可以實現(xiàn)兩個獨立按鍵的組合按鍵觸發(fā)功能。具體內(nèi)容,請看源代碼講解。
(1)硬件平臺:基于朱兆祺51單片機學(xué)習(xí)板。用矩陣鍵盤中的S1和S5號鍵作為獨立按鍵,記得把輸出線P0.4一直輸出低電平,模擬獨立按鍵的觸發(fā)地GND。
(2)實現(xiàn)功能:有兩個獨立按鍵,當(dāng)把兩個獨立按鍵都按下后,蜂鳴器發(fā)出“滴”的一聲后就停。直到松開任一個按鍵后,才能重新進行下一次的組合按鍵觸發(fā)。
(3)源代碼講解如下:
#include "REG52.H" #define const_voice_short 40 //蜂鳴器短叫的持續(xù)時間 /* 注釋一: * 調(diào)整抖動時間閥值的大小,可以更改按鍵的觸發(fā)靈敏度。 * 去抖動的時間本質(zhì)上等于累計定時中斷次數(shù)的時間。 */ #define const_key_time12 20 //按鍵去抖動延時的時間 void initial_myself(); void initial_peripheral(); void delay_long(unsigned int uiDelaylong); void T0_time(); //定時中斷函數(shù) void key_service(); //按鍵服務(wù)的應(yīng)用程序 void key_scan(); //按鍵掃描函數(shù) 放在定時中斷里 sbit key_sr1=P0^0; //對應(yīng)朱兆祺學(xué)習(xí)板的S1鍵 sbit key_sr2=P0^1; //對應(yīng)朱兆祺學(xué)習(xí)板的S5鍵 sbit key_gnd_dr=P0^4; //模擬獨立按鍵的地GND,因此必須一直輸出低電平 sbit beep_dr=P2^7; //蜂鳴器的驅(qū)動IO口 unsigned char ucKeySec=0; //被觸發(fā)的按鍵編號 unsigned int uiKeyTimeCnt12=0; //按鍵去抖動延時計數(shù)器 unsigned char ucKeyLock12=0; //按鍵觸發(fā)后自鎖的變量標(biāo)志 unsigned int uiVoiceCnt=0; //蜂鳴器鳴叫的持續(xù)時間計數(shù)器 void main() { initial_myself(); delay_long(100); initial_peripheral(); while(1) { key_service(); //按鍵服務(wù)的應(yīng)用程序 } } void key_scan()//按鍵掃描函數(shù) 放在定時中斷里 { /* 注釋二: * 獨立組合按鍵掃描的詳細過程: * 第一步:平時只要兩個按鍵中有一個沒有被按下時,按鍵的自鎖標(biāo)志,去抖動延時計數(shù)器一直被清零。 * 第二步:一旦兩個按鍵都被按下,去抖動延時計數(shù)器開始在定時中斷函數(shù)里累加,在還沒累加到 * 閥值const_key_time12時,如果在這期間由于受外界干擾或者按鍵抖動,而使 * IO口突然瞬間觸發(fā)成高電平,這個時候馬上把延時計數(shù)器uiKeyTimeCnt12 * 清零了,這個過程非常巧妙,非常有效地去除瞬間的雜波干擾。這是我實戰(zhàn)中摸索出來的。 * 以后凡是用到開關(guān)感應(yīng)器的時候,都可以用類似這樣的方法去干擾。 * 第三步:如果按鍵按下的時間超過了閥值const_key_time12,馬上把自鎖標(biāo)志ucKeyLock12置位, * 防止按住按鍵不松手后一直觸發(fā)。并把編號ucKeySec賦值。 組合按鍵觸發(fā) * 第四步:等按鍵松開后,自鎖標(biāo)志ucKeyLock12及時清零,為下一次自鎖做準(zhǔn)備。 * 第五步:以上整個過程,就是識別按鍵IO口下降沿觸發(fā)的過程。 */ if(key_sr1==1||key_sr2==1)//IO是高電平,說明兩個按鍵沒有全部被按下,這時要及時清零一些標(biāo)志位 { ucKeyLock12=0; //按鍵自鎖標(biāo)志清零 uiKeyTimeCnt12=0;//按鍵去抖動延時計數(shù)器清零,此行非常巧妙,是我實戰(zhàn)中摸索出來的。 } else if(ucKeyLock12==0)//有按鍵按下,且是第一次被按下 { uiKeyTimeCnt12++; //累加定時中斷次數(shù) if(uiKeyTimeCnt12>const_key_time12) { uiKeyTimeCnt12=0; ucKeyLock12=1; //自鎖按鍵置位,避免一直觸發(fā) ucKeySec=1; //觸發(fā)1號鍵 } } } void key_service() //第三區(qū) 按鍵服務(wù)的應(yīng)用程序 { switch(ucKeySec) //按鍵服務(wù)狀態(tài)切換 { case 1:// 1號鍵 組合按鍵 對應(yīng)朱兆祺學(xué)習(xí)板的S1鍵和S5鍵 uiVoiceCnt=const_voice_short; //按鍵聲音觸發(fā),滴一聲就停。 ucKeySec=0; //響應(yīng)按鍵服務(wù)處理程序后,按鍵編號清零,避免一致觸發(fā) break; } } void T0_time() interrupt 1 { TF0=0; //清除中斷標(biāo)志 TR0=0; //關(guān)中斷 key_scan(); //按鍵掃描函數(shù) if(uiVoiceCnt!=0) { uiVoiceCnt--; //每次進入定時中斷都自減1,直到等于零為止。才停止鳴叫 beep_dr=0; //蜂鳴器是PNP三極管控制,低電平就開始鳴叫。 } else { ; //此處多加一個空指令,想維持跟if括號語句的數(shù)量對稱,都是兩條指令。不加也可以。 beep_dr=1; //蜂鳴器是PNP三極管控制,高電平就停止鳴叫。 } TH0=0xf8; //重裝初始值(65535-2000)=63535=0xf82f TL0=0x2f; TR0=1; //開中斷 } void delay_long(unsigned int uiDelayLong) { unsigned int i; unsigned int j; for(i=0;i
總結(jié)陳詞:
以前尋呼機流行的時候,尋呼機往往只有一個設(shè)置按鍵,它要求用一個按鍵來設(shè)置不同的參數(shù),這個時候就要用到同一個按鍵來實現(xiàn)短按和長按的區(qū)別觸發(fā)功能。要現(xiàn)實這種功能,我們該怎么寫程序?欲知詳情,請聽下回分解-----同一個按鍵短按與長按的區(qū)別觸發(fā)。
(未完待續(xù),下節(jié)更精彩,不要走開哦)