第一篇:51單片機(jī)舵機(jī)程序
51單片機(jī)舵機(jī)程序不用定時器:自己整理的不用定時器調(diào)舵機(jī)向左,中,右三個方向擺動的51單片機(jī)程序
#include
for(y=110;y>0;y--);}
void delayus2x(unsigned char t){
while(--t);} void delay750us(){ delayus2x(245);delayus2x(122);} void delay1500us(){
delayus2x(245);
delayus2x(245);
delayus2x(245);} void delay2300us(){
delayus2x(245);
delayus2x(245);
delayus2x(245);
delayus2x(245);
delayus2x(147);
} void main()
//a=~a和delay順序不能反 { while(1){
uint i=50;while(--i)
//中
{
a=1;
delay1500us();
a=0;
delay(20);
}
i=50;
while(--i)
{
a=1;
delay2300us();
a=0;
delay(20);
}
i=50;
while(--i)
{
a=1;
delay750us();
a=0;
delay(20);
}
}
}
//左
//右
第二篇:89C51單片機(jī)時鐘程序
SECOND EQU 40H
;給內(nèi)存RAM空間中40H單元起名SECOND MINUTE EQU 41H
;給內(nèi)存RAM空間中41H單元起名MINUTE HOUR EQU 42H
;給內(nèi)存RAM空間中42H單元起名HOUR SECONDGEWEI EQU 43H
;給43H單元起名SECONDGEWEI存秒的個位 SECONDSHIWEI EQU 44H
;給44H單元起名SECONDSHIWEI存秒的十位 MINUTEGEWEI EQU 45H
;給45H單元起名MINUTEGEWEI存分的個位 MINUTESHIWEI EQU 46H
;給46H單元起名MINUTESHIWEI存分的十位 HOURGEWEI EQU 47H
;給47H單元起名HOURGEWEI存小時的個位 HOURSHIWEI EQU 48H
;給48H單元起名HOURSHIWEI存小時的十位 ORG 0000H
;復(fù)位時程序從此開始 SJMP START
;跳到START進(jìn)行初始化 ORG 000BH
;定時器 0中斷入口 AJMP TIMER0
;跳轉(zhuǎn)到TIMER0處
ORG 0030H
;初始化程序從30H開始;---------------初始化START------------------------------START:
MOV SECOND, #0
;給秒存儲單元SECOND賦初始值0 MOV MINUTE, #0
;給分存儲單元MINUTE賦初始值0 MOV HOUR , #12
;給小時存儲單元HOUR賦初始值12 MOV DPTR , #TAB
;給數(shù)據(jù)指針賦值,將DPTR指向TAB數(shù)據(jù)表頭處 MOV 30H, #0
;給30H單元賦初始值0(用于計20次的50ms中斷)MOV TH0,#3CH
;給計數(shù)容器的高8位TH0賦初始值3CH MOV TL0,#0B0H
;給計數(shù)容器的低8位TL0賦初始值B0H MOV TMOD,#00000001B
;C/T位設(shè)置為0,M1M0設(shè)置位10,即模式1定時 MOV TCON,#00010000B
;TR0設(shè)置為1,即啟動定時器0開始工作 SETB ET0
;IE中的ET0位設(shè)置為1,開定制器中斷0 SETB EA
;IE中的EA位設(shè)置為1,開總中斷;-----------------------主程序MAIN-----------------------------MAIN:CALL KEY
;調(diào)按鍵子程序KEY CALL PROCESS
;調(diào)數(shù)據(jù)處理子程序PROCESS CALL DISPLAY
;調(diào)顯示子程序DISPLAY SJMP MAIN
;跳轉(zhuǎn)到MAIN標(biāo)號處;------------------------------按鍵子程序KEY調(diào)時-------------------KEY:MOV P1,#0FEH
;行掃描 LCALL DELAY
;JNB P1.4,HOURJIA
;P1.4引腳如果是低電平就跳到HOURJIA處
JNB P1.5,HOURJIAN
;P1.5引腳如果是低電平就跳到HOURJIAN處 JNB P1.6,MINUTEJIA
;P1.6引腳如果是低電平就跳到MIMUTEJIA處 JNB P1.7,MINUTEJIAN
;P1.7引腳如果是低電平就跳到MIMUTEJIAN處 FANHUI:RET
;子程序返回(如果沒有按鍵按下)
HOURJIA:CALL DELAY
;調(diào)延時程序目的是跳過按鍵抖動期(去抖)JB P1.4,FANHUI
;P1.4如果是高電平就跳到FANHUI處(沒鍵按)JNB P1.4,$
;如果P1.4是低電平就停在當(dāng)前位置等鍵釋放 MOV R4,HOUR CJNE R4,#23,A1
;判斷時數(shù)字是否為23 AJMP A2
A1:INC HOUR
;把小時位加1 MOV SECOND, #0
;小時進(jìn)位,秒歸0
RET
A2:MOV HOUR,#0
;小時數(shù)為23時加一為0
MOV SECOND, #0
;小時進(jìn)位,秒歸0
RET
;子程序返回
HOURJIAN:CALL DELAY
;調(diào)延時程序目的是跳過按鍵抖動期(去抖)JB P1.5,FANHUI
JNB P1.5,$
MOV R5,HOUR CJNE R5,#0,A3
AJMP A4 A3:DEC HOUR
MOV SECOND, #0
RET A4:MOV HOUR,#23
MOV SECOND, #0 RET
MINUTEJIA:CALL DELAY
JB P1.6,FANHUI
JNB P1.6,$
MOV R6,MINUTE
CJNE R6,#59,A5
AJMP A6 A5:INC MINUTE
MOV SECOND, #0
RET A6:MOV SECOND, #0
MOV MINUTE, #0
MOV R4,HOUR CJNE R4,#23,A10
MOV HOUR,#0
RET A10:INC HOUR
RET
MINUTEJIAN:CALL DELAY
JB P1.7,FANHUI
JNB P1.7,$
MOV R7,MINUTE CJNE R7,#0,A7
AJMP A8 A7:DEC MINUTE
;P1.5如果是高電平就跳到FANHUI處(沒鍵按)
;如果P1.5是低電平就停在當(dāng)前位置等鍵釋放
;判斷時數(shù)字是否為23
;把小時位減1
;小時數(shù)為0時減一為23
;子程序返回
;調(diào)延時程序目的是跳過按鍵抖動期(去抖)
;P1.6如果是高電平就跳到FANHUI處(沒鍵按)
;如果P1.6是低電平就停在當(dāng)前位置等鍵釋放
;判斷分鐘數(shù)是否為59
;把分鐘位加1
;給秒存儲單元SECOND賦初始值0
;分鐘數(shù)為59則分鐘歸0
;判斷時數(shù)字是否為23
;23時增1歸0
;分鐘數(shù)為59 自增1后小時增1
;子程序返回
;調(diào)延時程序目的是跳過按鍵抖動期(去抖)
;P1.7如果是高電平就跳到FANHUI處(沒鍵按)
;如果P1.7是低電平就停在當(dāng)前位置等鍵釋放
;判斷分鐘數(shù)是否為0
;分鐘不為0把分鐘位減1
MOV SECOND, #0
RET
A8:MOV MINUTE, #59
;分鐘數(shù)為0時減一為59 MOV R4,HOUR CJNE R4,#0,A9
;判斷時鐘數(shù)是否為0 MOV HOUR,#23
;時鐘數(shù)為0減1為23 MOV SECOND, #0 RET
A9:DEC HOUR
;時鐘數(shù)不為0則減1 MOV SECOND, #0
RET
;子程序返回;-------------------處理子程序PROCESS-----------------------PROCESS:MOV A, SECOND
;把SECOND中的秒值拷貝給A MOV B, #10
;給寄存器B賦值10 DIV AB
;A除以B,結(jié)果存入A中,余數(shù)存入B中 MOV SECONDSHIWEI , A
;結(jié)果即秒的十位數(shù)拷貝給SECONDSHIWEI MOV SECONDGEWEI , B
;余數(shù)即秒的個位拷貝給SECONDGEWEI MOV A, MINUTE
;把MINUTE中的分值拷貝給A MOV B, #10
;給寄存器B賦值10 DIV AB
;A除以B,結(jié)果存入A中,余數(shù)存入B中 MOV MINUTESHIWEI , A
;結(jié)果即分的十位拷貝給MINUTESHIWEI MOV MINUTEGEWEI , B
;余數(shù)即分的個位拷貝給MINUTEGEWEI MOV A, HOUR
;把HOUR中的小時值拷貝給A MOV B, #10
;給寄存器B賦值10 DIV AB
;A除以B,結(jié)果存入A中,余數(shù)存入B中 MOV HOURSHIWEI , A
;結(jié)果即小時的十位拷貝給HOURSHIWEI MOV HOURGEWEI , B
;余數(shù)即小時的個位拷貝給HOURGEWEI RET
;子程序結(jié)束返回到主程序;-----------------顯示子程序DISPLAY--------------DISPLAY:MOV A, HOURSHIWEI
;小時的十位拷貝給A MOVC A, @A+DPTR
;到A+DPRT這個數(shù)對應(yīng)的地方找顯示段碼拷貝給A MOV P0, A
;把顯示段碼(小時的十位)送到P0 CLR P2.0
;將P2.0置低電平,對應(yīng)的三極管導(dǎo)通 CALL DELAY
;調(diào)延時(讓顯示小時十位的數(shù)碼管持續(xù)亮一段時間)SETB P2.0
;將P2.0置高電平,對應(yīng)三極管截止,對應(yīng)數(shù)碼管滅 MOV A, HOURGEWEI
;小時的個位拷貝給A MOVC A,@A+DPTR
;到A+DPRT這個數(shù)對應(yīng)的地方找顯示段碼拷貝給A MOV P0, A
;把顯示段碼(小時的個位)送到P0 CLR P2.1
;將P2.1置低電平,對應(yīng)的三極管導(dǎo)通
CALL DELAY
;調(diào)延時(讓顯示小時個位的數(shù)碼管持續(xù)亮一段時間)SETB P2.1 MOV P0,#7FH CLR P2.1 CALL DELAY SETB P2.1
;將P2.1置高電平,對應(yīng)三極管截止,對應(yīng)數(shù)碼管滅 MOV A, MINUTESHIWEI
;分鐘的十位拷貝給A MOVC A,@A+DPTR
;到A+DPRT這個數(shù)對應(yīng)的地方找顯示段碼拷貝給A MOV P0, A
;把顯示段碼(分鐘的十位)送到P0 CLR P2.2
;將P2.2置低電平,對應(yīng)的三極管導(dǎo)通 CALL DELAY
;調(diào)延時(讓顯示分鐘十位的數(shù)碼管持續(xù)亮一段時間)SETB P2.2
;將P2.2置高電平,對應(yīng)三極管截止,對應(yīng)數(shù)碼管滅 MOV A, MINUTEGEWEI
;分鐘的個位拷貝給A MOVC A,@A+DPTR
;到A+DPRT這個數(shù)對應(yīng)的地方找顯示段碼拷貝給A MOV P0, A
;把顯示段碼(分鐘的個位)送到P0 CLR P2.3
;將P2.3置低電平,對應(yīng)的三極管導(dǎo)通
CALL DELAY
;調(diào)延時(讓顯示分鐘個位的數(shù)碼管持續(xù)亮一段時間)SETB P2.3
;將P2.3置高電平,對應(yīng)三極管截止,對應(yīng)數(shù)碼管滅
MOV P0,#7FH CLR P2.3 CALL DELAY SETB P2.3
MOV A, SECONDSHIWEI
;秒的十位拷貝給A MOVC A,@A+DPTR
;到A+DPRT這個數(shù)對應(yīng)的地方找顯示段碼拷貝給A MOV P0, A
;把顯示段碼(秒鐘的十位)送到P0 CLR P2.4
;將P2.4置低電平,對應(yīng)的三極管導(dǎo)通 CALL DELAY
;調(diào)延時(讓顯示秒鐘十位的數(shù)碼管持續(xù)亮一段時間)SETB P2.4
;將P2.4置高電平,對應(yīng)三極管截止,對應(yīng)數(shù)碼管滅 MOV A, SECONDGEWEI
;秒的個位拷貝給A MOVC A,@A+DPTR
;到A+DPRT這個數(shù)對應(yīng)的地方找顯示段碼拷貝給A MOV P0, A
;把顯示段碼(秒鐘的個位)送到P0 CLR P2.5
;將P2.5置低電平,對應(yīng)的三極管導(dǎo)通
CALL DELAY
;調(diào)延時(讓顯示秒鐘個位的數(shù)碼管持續(xù)亮一段時間)SETB P2.5
;將P2.5置高電平,對應(yīng)三極管截止,對應(yīng)數(shù)碼管滅 RET
;顯示子程序結(jié)束返回主程序;--------------------中斷服務(wù)子程序----------------------------TIMER0:MOV R3, A
;把A中的數(shù)據(jù)送入R3保護(hù)起來 INC 30H
;30H單元中的數(shù)加1 MOV A, 30H
;30H單元中的數(shù)據(jù)拷貝給A CJNE A,#20,JIXU
;A中的數(shù)據(jù)與20比較不相等就跳轉(zhuǎn)到JIXU處 MOV 30H,#0
;(如果30H單元計滿20了)給30H賦值0 INC SECOND
;把SECOND中的秒鐘數(shù)加1 MOV A,SECOND
;把SECOND中的數(shù)據(jù)拷貝給A CJNE A, #60, JIXU
;A中的數(shù)據(jù)與60比較不相等就跳轉(zhuǎn)到JIXU處 MOV SECOND, #0
;給秒SECOND賦值0 INC MINUTE
;把MINUTE中的分鐘數(shù)加1 MOV A, MINUTE
;把MINUTE中的數(shù)據(jù)拷貝給A CJNE A, #60, JIXU
;A中的數(shù)據(jù)與60比較不相等就跳轉(zhuǎn)到JIXU處 MOV MINUTE, #0
;給分鐘MINUTE賦值0 INC HOUR
;把HOUR中的小時數(shù)據(jù)加1 MOV A, HOUR
;把HOUR中的數(shù)據(jù)拷貝給A CJNE A, #24, JIXU
;A中的數(shù)據(jù)與24比較不相等就跳轉(zhuǎn)到JIXU處 MOV HOUR, #0
;給小時HOUR賦值0 JIXU: MOV A,R3
;把剛才送入R3中的數(shù)據(jù)還給A MOV TH0,#3CH
;給計數(shù)容器的高8位TH0賦初始值3CH MOV TL0,#0B0H
;給計數(shù)容器的低8位TL0賦初始值B0H RETI
;中斷子程序返回主程序;---------------------------延時子程序----------------------------DELAY:MOV R0, #50
;給R0賦值50 D2:MOV R1, #10
;給R1賦值10 D1:DJNZ R1, D1
;R1減1不等于0跳到D1處 DJNZ R0, D2
;R0減1不等于0跳到D2處
RET
;延時子程序結(jié)束返回調(diào)用該程序的下一條;---------------下面的數(shù)據(jù)表中存儲的是顯示段碼(共陽)-------------------TAB:DB 0C0H,0F9H,0A4H,0B0H,99H
;從TAB處開始存儲0、1、2、3、4
DB 92H ,82H ,0F8H,80H ,90H
;5、6、7、8、9對應(yīng)的顯示段碼 END
;程序結(jié)束
第三篇:單片機(jī)經(jīng)典長短按程序
新型的按鍵掃描程序 不過我在網(wǎng)上游逛了很久,也看過不少源程序了,沒有發(fā)現(xiàn)這種按鍵處理辦法的蹤跡,所以,我將他共享出來,和廣大同僚們共勉。我非常堅信這種按鍵處理辦法的便捷和高效,你可以移植到任何一種嵌入式處理器上面,因為C語言強大的可移植性。
同時,這里面用到了一些分層的思想,在單片機(jī)當(dāng)中也是相當(dāng)有用的,也是本文的另外一個重點。
對于老鳥,我建議直接看那兩個表達(dá)式,然后自己想想就會懂的了,也不需要聽我后面的自吹自擂了,我可沒有班門弄斧的意思,hoho~~但是對于新手,我建議將全文看完。因為這是實際項目中總結(jié)出來的經(jīng)驗,學(xué)校里面學(xué)不到的東西。
以下假設(shè)你懂C語言,因為純粹的C語言描述,所以和處理器平臺無關(guān),你可以在MCS-51,AVR,PIC,甚至是ARM平臺上面測試這個程序性能。當(dāng)然,我自己也是在多個項目用過,效果非常好的。
好了,工程人員的習(xí)慣,廢話就應(yīng)該少說,開始吧。以下我以AVR的MEGA8作為平臺講解,沒有其它原因,因為我手頭上只有AVR的板子而已沒有51的。用51也可以,只是芯片初始化部分不同,還有寄存器名字不同而已。核心算法:
unsigned char Trg;unsigned char Cont;void KeyRead(void){ unsigned char ReadData = PINB^0xff;// 1 Trg = ReadData &(ReadData ^ Cont);// 2 Cont = ReadData;// 3 } 完了。有沒有一種不可思議的感覺?當(dāng)然,沒有想懂之前會那樣,想懂之后就會驚嘆于這算法的精妙!下面是程序解釋:
Trg(triger)代表的是觸發(fā),Cont(continue)代表的是連續(xù)按下。
1:讀PORTB的端口數(shù)據(jù),取反,然后送到ReadData 臨時變量里面保存起來。2:算法1,用來計算觸發(fā)變量的。一個位與操作,一個異或操作,我想學(xué)過C語言都應(yīng)該懂吧?Trg為全局變量,其它程序可以直接引用。3:算法2,用來計算連續(xù)變量。
看到這里,有種“知其然,不知其所以然”的感覺吧?代碼很簡單,但是它到底是怎么樣實現(xiàn)我們的目的的呢?好,下面就讓我們繞開云霧看青天吧。
我們最常用的按鍵接法如下:AVR是有內(nèi)部上拉功能的,但是為了說明問題,我是特意用外部上拉電阻。那么,按鍵沒有按下的時候,讀端口數(shù)據(jù)為1,如果按鍵按下,那么端口讀到0。下面就看看具體幾種情況之下,這算法是怎么一回事。
(1)沒有按鍵的時候
端口為0xff,ReadData讀端口并且取反,很顯然,就是 0x00 了。
Trg = ReadData &(ReadData ^ Cont);(初始狀態(tài)下,Cont也是為0的)很簡單的數(shù)學(xué)計算,因為ReadData為0,則它和任何數(shù)“相與”,結(jié)果也是為0的。
Cont = ReadData;保存Cont 其實就是等于ReadData,為0; 結(jié)果就是:
ReadData = 0; Trg = 0; Cont = 0;
(2)第一次PB0按下的情況
端口數(shù)據(jù)為0xfe,ReadData讀端口并且取反,很顯然,就是 0x01 了。Trg = ReadData &(ReadData ^ Cont);因為這是第一次按下,所以Cont是上次的值,應(yīng)為為0。那么這個式子的值也不難算,也就是 Trg = 0x01 &(0x01^0x00)= 0x01 Cont = ReadData = 0x01; 結(jié)果就是:
ReadData = 0x01;
Trg = 0x01;Trg只會在這個時候?qū)?yīng)位的值為1,其它時候都為0 Cont = 0x01;
(3)PB0按著不松(長按鍵)的情況
端口數(shù)據(jù)為0xfe,ReadData讀端口并且取反是 0x01 了。
Trg = ReadData &(ReadData ^ Cont);因為這是連續(xù)按下,所以Cont是上次的值,應(yīng)為為0x01。那么這個式子就變成了 Trg = 0x01 &(0x01^0x01)= 0x00 Cont = ReadData = 0x01; 結(jié)果就是:
ReadData = 0x01; Trg = 0x00; Cont = 0x01;
因為現(xiàn)在按鍵是長按著,所以MCU會每個一定時間(20ms左右)不斷的執(zhí)行這個函數(shù),那么下次執(zhí)行的時候情況會是怎么樣的呢? ReadData = 0x01;這個不會變,因為按鍵沒有松開
Trg = ReadData &(ReadData ^ Cont)= 0x01 &(0x01 ^ 0x01)= 0,只要按鍵沒有松開,這個Trg值永遠(yuǎn)為 0??!
Cont = 0x01;只要按鍵沒有松開,這個值永遠(yuǎn)是0x01?。?)按鍵松開的情況
端口數(shù)據(jù)為0xff,ReadData讀端口并且取反是 0x00 了。
Trg = ReadData &(ReadData ^ Cont)= 0x00 &(0x00^0x01)= 0x00 Cont = ReadData = 0x00; 結(jié)果就是:
ReadData = 0x00; Trg = 0x00; Cont = 0x00;
很顯然,這個回到了初始狀態(tài),也就是沒有按鍵按下的狀態(tài)??偨Y(jié)一下,不知道想懂了沒有?其實很簡單,答案如下:
Trg 表示的就是觸發(fā)的意思,也就是跳變,只要有按鍵按下(電平從1到0的跳變),那么Trg在對應(yīng)按鍵的位上面會置一,我們用了PB0則Trg的值為0x01,類似,如果我們PB7按下的話,Trg 的值就應(yīng)該為 0x80,這個很好理解,還有,最關(guān)鍵的地方,Trg 的值每次按下只會出現(xiàn)一次,然后立刻被清除,完全不需要人工去干預(yù)。所以按鍵功能處理程序不會重復(fù)執(zhí)行,省下了一大堆的條件判斷,這個可是精粹哦!Cont代表的是長按鍵,如果PB0按著不放,那么Cont的值就為 0x01,相對應(yīng),PB7按著不放,那么Cont的值應(yīng)該為0x80,同樣很好理解。
如果還是想不懂的話,可以自己演算一下那兩個表達(dá)式,應(yīng)該不難理解的。因為有了這個支持,那么按鍵處理就變得很爽了,下面看應(yīng)用: 應(yīng)用一:一次觸發(fā)的按鍵處理
假設(shè)PB0為蜂鳴器按鍵,按一下,蜂鳴器beep的響一聲。這個很簡單,但是大家以前是怎么做的呢?對比一下看誰的方便? #define KEY_BEEP 0x01 void KeyProc(void){ if(Trg & KEY_BEEP)// 如果按下的是KEY_BEEP { Beep();// 執(zhí)行蜂鳴器處理函數(shù) } } 怎么樣?夠和諧不?記得前面解釋說Trg的精粹是什么?精粹就是只會出現(xiàn)一次。所以你按下按鍵的話,Trg & KEY_BEEP 為“真”的情況只會出現(xiàn)一次,所以處理起來非常的方便,蜂鳴器也不會沒事亂叫,hoho~~~ 或者你會認(rèn)為這個處理簡單,沒有問題,我們繼續(xù)。應(yīng)用2:長按鍵的處理
項目中經(jīng)常會遇到一些要求,例如:一個按鍵如果短按一下執(zhí)行功能A,如果長按2秒不放的話會執(zhí)行功能B,又或者是要求3秒按著不放,計數(shù)連加什么什么的功能,很實際。不知道大家以前是怎么做的呢?我承認(rèn)以前做的很郁悶。但是看我們這里怎么處理吧,或許你會大吃一驚,原來程序可以這么簡單 這里具個簡單例子,為了只是說明原理,PB0是模式按鍵,短按則切換模式,PB1就是加,如果長按的話則連加(玩過電子表吧?沒錯,就是那個?。?define KEY_MODE 0x01 // 模式按鍵 #define KEY_PLUS 0x02 // 加 void KeyProc(void){ if(Trg & KEY_MODE)// 如果按下的是KEY_MODE,而且你常按這按鍵也沒有用,{ //它是不會執(zhí)行第二次的哦,必須先松開再按下 Mode++;// 模式寄存器加1,當(dāng)然,這里只是演示,你可以執(zhí)行你想
// 執(zhí)行的任何代碼 } if(Cont & KEY_PLUS)// 如果“加”按鍵被按著不放 { cnt_plus++;// 計時 if(cnt_plus > 100)// 20ms*100 = 2S 如果時間到 { Func();// 你需要的執(zhí)行的程序 } } } 不知道各位感覺如何?我覺得還是挺簡單的完成了任務(wù),當(dāng)然,作為演示用代碼。
應(yīng)用3:點觸型按鍵和開關(guān)型按鍵的混合使用
點觸形按鍵估計用的最多,特別是單片機(jī)。開關(guān)型其實也很常見,例如家里的電燈,那些按下就不松開,除非關(guān)。這是兩種按鍵形式的處理原理也沒啥特別,但是你有沒有想過,如果一個系統(tǒng)里面這兩種按鍵是怎么處理的?我想起了我以前的處理,分開兩個非常類似的處理程序,現(xiàn)在看起來真的是笨的不行了,但是也沒有辦法啊,結(jié)構(gòu)決定了程序。不過現(xiàn)在好了,用上面介紹的辦法,很輕松就可以搞定。
原理么?可能你也會想到,對于點觸開關(guān),按照上面的辦法處理一次按下和長按,對于開關(guān)型,我們只需要處理Cont就OK了,為什么?很簡單嘛,把它當(dāng)成是一個長按鍵,這樣就找到了共同點,屏蔽了所有的細(xì)節(jié)。程序就不給了,完全就是應(yīng)用2的內(nèi)容,在這里提為了就是說明原理~~
好了,這個好用的按鍵處理算是說完了??赡軙信笥褧?,為什么不說延時消抖問題?哈哈,被看穿了。果然不能偷懶。下面談?wù)勥@個問題,順便也就非常簡單的談?wù)勎易约河脮r間片輪辦法,以及是如何消抖的。
延時消抖的辦法是非常傳統(tǒng),也就是 第一次判斷有按鍵,延時一定的時間(一般習(xí)慣是20ms)再讀端口,如果兩次讀到的數(shù)據(jù)一樣,說明了是真正的按鍵,而不是抖動,則進(jìn)入按鍵處理程序。
當(dāng)然,不要跟我說你delay(20)那樣去死循環(huán)去,真是那樣的話,我衷心的建議你先放下手上所有的東西,好好的去了解一下操作系統(tǒng)的分時工作原理,大概知道思想就可以,不需要詳細(xì)看原理,否則你永遠(yuǎn)逃不出“菜鳥”這個圈子。當(dāng)然我也是菜鳥。我的意思是,真正的單片機(jī)入門,是從學(xué)會處理多任務(wù)開始的,這個也是學(xué)校程序跟公司程序的最大差別。當(dāng)然,本文不是專門說這個的,所以也不獻(xiàn)丑了。
我的主程序架構(gòu)是這樣的:
volatile unsigned char Intrcnt;void InterruptHandle()// 中斷服務(wù)程序 { Intrcnt++;// 1ms 中斷1次,可變 } void main(void){ SysInit();while(1)// 每20ms 執(zhí)行一次大循環(huán) { KeyRead();// 將每個子程序都掃描一遍 KeyProc();Func1();Funt2();?
?
while(1){ if(Intrcnt>20)// 一直在等,直到20ms時間到 { Intrcnt=“0”;break;// 返回主循環(huán) } } } } 貌似扯遠(yuǎn)了,回到我們剛才的問題,也就是怎么做按鍵消抖處理。我們將讀按鍵的程序放在了主循環(huán),也就是說,每20ms我們會執(zhí)行一次KeyRead()函數(shù)來得到新的Trg 和 Cont 值。好了,下面是我的消抖部分:很簡單
基本架構(gòu)如上,我自己比較喜歡的,一直在用。當(dāng)然,和這個配合,每個子程序必須執(zhí)行時間不長,更加不能死循環(huán),一般采用有限狀態(tài)機(jī)的辦法來實現(xiàn),具體參考其它資料咯。懂得基本原理之后,至于怎么用就大家慢慢思考了,我想也難不到聰明的工程師們。例如還有一些處理,怎么判斷按鍵釋放?很簡單,Trg 和Cont都為0 則肯定已經(jīng)釋放了。
這個需要有定時(按鍵間隔)調(diào)用函數(shù),完成去抖,區(qū)別單次和長按,好的思路。我想矩陣鍵盤也可以處理,只有鍵盤返回的碼是唯一的,把PINB 換成 getkey之類的函數(shù)。我想這個可能用來分析脈沖信號,比如紅外遙控信號
最簡單矩陣鍵盤掃描程序
這是站長初學(xué)者寫的最簡單、最詳細(xì)、效率最高的矩陣鍵盤掃描程序,只用了四條常用命令(MOV/送數(shù)、JB/高電平轉(zhuǎn)移、JMP/直接轉(zhuǎn)移、RET/子程序返回),保證初學(xué)者一看就懂!本程序已經(jīng)在本站電子實驗板上驗證通過,占用CPU時間少,效率高,被選作單片機(jī)的測試程序!
矩陣按鍵掃描程序是一種節(jié)省IO口的方法,按鍵數(shù)目越多節(jié)省IO口就越可觀,本程序的思路跟書上一樣:先判斷某一列(行)是否有按鍵按下,再判斷該行(列)是那一只鍵按下。但是,在程序的寫法上,站長采用了最簡單的方法,使得程序效率最高。
本程序中,如果檢測到某鍵按下了,就不再檢測其它的按鍵,這完全能滿足絕大多數(shù)需要,又能節(jié)省大量的CPU時間。另外,本人認(rèn)為鍵盤用延時程序來消除抖動,完全是浪費時間。試想,如果不用中斷執(zhí)行(用中斷執(zhí)行需要更多的硬件資源)的方法來掃描鍵盤,每秒鐘掃描20-100次,每次都要延時10-20MS的話,我們的單片機(jī)還有多少時間做正事呢?
其實,延時的這段時間,CPU可以做其它的事呀。所以,本鍵盤掃描程序的前面后面都可以加入少少代碼,既可以達(dá)到完美的消抖動效果,又可以擴(kuò)展其它的功能(例如按鍵封鎖、按鍵長按等按鍵功能復(fù)用!)字串2
本鍵盤掃描子程序名叫key,每次要掃描時用call key調(diào)用即可。以下子程序內(nèi)容:
key:mov p0,#00001111b;上四位和下四位分別為行和列,所以送出高低電壓檢查有沒有按鍵按下
jmp k10;跳到K10處開始掃描,這里可以改成其它條件轉(zhuǎn)移指令來決定本次掃描是否要繼續(xù),例如減1為0轉(zhuǎn)移或者位為1或0才轉(zhuǎn)移,這主要用來增加功能,確認(rèn)上一按鍵功能是否完成?是否相當(dāng)于經(jīng)過了延時?是否要封鎖鍵盤?
goend:jmp kend;如果上面判斷本次不執(zhí)行鍵盤掃描程序,則立即轉(zhuǎn)到程序尾部,不要浪費CPU的時間
k10:jb p0.0,k20;掃描正式開始,先檢查列1四個鍵是否有鍵按下,如果沒有,則跳到K20檢查列2 k11:mov p0,#11101111b;列1有鍵按下時,P0.0變低,到底是那一個鍵按下?現(xiàn)在分別輸出各行低電平
jb p0.0,k12;該行的鍵不按下時,p0.0為高電平,跳到到K12,檢查其它的行 mov r1,#1;如果正好是這行的鍵按下,將寄存器R0寫下1,表示1號鍵按下了 k12:mov p0,#11011111b jb p0.0,k13 mov r1,#2;如果正好是這行的鍵按下,將寄存器R0寫下2,表示2號鍵按下了 k13:mov p0,#10111111b jb p0.0,k14 mov r1,#3;如果正好是這行的鍵按下,將寄存器R0寫下3,表示3號鍵按下了 字串3 k14:mov p0,#01111111b jb p0.0,kend;如果現(xiàn)在四個鍵都沒有按下,可能按鍵松開或干擾,退出掃描(以后相同)mov r1,#4如果正好是這行的鍵按下,將寄存器R0寫下4,表示4號鍵按下了 jmp kend;已經(jīng)找到按下的鍵,跳到結(jié)尾吧
k20:jb p0.1,k30;列2檢查為高電平再檢查列3、4
k21:mov p0,#11101111b;列2有健按下時,P0.0會變低,到底是那一行的鍵按下呢?分別輸出行的低電平
jb p0.1,k22;該行的鍵不按下時p0.0為高電平,跳到到K22,檢查另外三行
mov r1,#5;如果正好是這行的鍵按下,將寄存器R0寫下5,表示5號鍵按下了(以后相同,不再重復(fù)了)
k22:mov p0,#11011111b jb p0.1,k23 mov r1,#6 k23:mov p0,#10111111b jb p0.1,k24 mov r1,#7 k24:mov p0,#01111111b jb p0.1,kend mov r1,#8 jmp kend;已經(jīng)找到按下的鍵,跳到結(jié)尾吧(以后相同,不要重復(fù)了)
k30:jb p0.2,k40 k31:mov p0,#11101111b jb p0.2,k32 mov r1,#9 k32:mov p0,#11011111b jb p0.2,k33 mov r1,#10 k33:mov p0,#10111111b jb p0.2,k34 mov r1,#11 k34:mov p0,#01111111b jb p0.2,kend 字串6
mov r1,#12 jmp kend
k40:jb p0.3,kend k41:mov p0,#11101111b jb p0.3,k42 mov r1,#13 k42:mov p0,#11011111b jb p0.3,k43 mov r1,#14 k43:mov p0,#10111111b jb p0.3,k44 mov r1,#15 k44:mov p0,#01111111b jb p0.3,kend mov r1,#16 kend: ret
鍵盤掃描結(jié)束了,寄存器R1的值就直接表示了是那個鍵按下的,根據(jù)不同的鍵值去執(zhí)行不同的程序,從而實現(xiàn)了十六個矩陣鍵盤掃描,同樣原理,最多可以識別255個按鍵的矩陣掃描。
我們可以每次鍵盤掃描開始時檢查R0的值是否為0,只有在為0才掃描鍵盤,不為0就證明剛剛掃描過鍵值,相應(yīng)的按鍵工作還沒有完成。但是必須記得,每個按鍵命令執(zhí)行完成后,要給R0寫上0,表示可以掃描鍵盤。
本鍵盤掃描程序的優(yōu)點在于:不用專門的按鍵延時程序,提高了CPU效率,也不用中斷來掃描鍵盤,節(jié)省了硬件資源。另外,本鍵盤掃描程序,每次掃描占用CPU時最短,不論有鍵按下或者無鍵按下都可以在很短的時間完成一次掃描。
還有,本程序只使用幾條最常用的匯編命令,MOV/JB/JMP/RET,而這幾條命令是最常用、最易懂、最好學(xué)的命令!有的鍵盤掃描程序還用與呀、或呀、移位呀、查表呀,我都還沒有看懂。字串5
當(dāng)然,以上只是站長初學(xué)單片機(jī)的一點個人見解,歡迎廣大單片機(jī)愛好者指正,希望大家將自己最認(rèn)可的鍵盤掃描程序公布出來,讓大家一起分享!最后,祝愿大家學(xué)習(xí)進(jìn)步!工作順利!
說明:本站數(shù)顯FM無線發(fā)射板中雖然不是用矩陣掃描,但是按鍵消抖動原理和上面相同,按鍵功能復(fù)用原理也和上面相同,用起來感覺很好!在鍵盤的10MS延時過程中,CPU剛好可以去做幾件事并在10MS左右做完。所以,產(chǎn)品中凡是要用到按鍵掃描的,都可以讓CPU去做別的事情,鍵盤延時消抖動唯一的好處就是,程序?qū)懫饋頃奖阋稽c。
經(jīng)典的矩陣鍵盤掃描程序
鍵盤是單片機(jī)常用輸入設(shè)備,在按鍵數(shù)量較多時,為了節(jié)省I/O口等單片機(jī)資源,一般采取掃描的方式來識別到底是哪一個鍵被按下。即通過確定被按下的鍵處在哪一行哪一列來確定該鍵的位置,獲取鍵值以啟動相應(yīng)的功能程序。
4*4矩陣鍵盤的結(jié)構(gòu)如圖1(實物參考見萬用板矩陣鍵盤制作技巧)。在本例中,矩陣鍵盤的四列依次接到單片機(jī)的P1.0~P1.3,四行依次接到單片機(jī)的P1.4~P1.7;同時,將列線上拉,通過10K電阻接電源。
圖1 查找哪個按鍵被按下的方法為:一個一個地查找。
先第一行輸出0,檢查列線是否非全高;
否則第二行輸出0,檢查列線是否非全高;
否則第三行輸出0,檢查列線是否非全高;
如果某行輸出0時,查到列線非全高,則該行有按鍵按下;
根據(jù)第幾行線輸出0與第幾列線讀入為0,即可判斷在具體什么位置的按鍵按下。下面是具體程序:
void Check_Key(void){ unsigned char row,col,tmp1,tmp2;tmp1 = 0x10;//tmp1用來設(shè)置P1口的輸出,取反后使P1.4~P1.7中有一個為0 for(row=0;row<4;row++)// 行檢測 { P1 = 0x0f;// 先將p1.4~P1.7置高
P1 =~tmp1;// 使P1.4~p1.7中有一個為0 tmp1*=2;// tmp1左移一位
if((P1 & 0x0f)< 0x0f)// 檢測P1.0~P1.3中是否有一位為0,只要有,則說明此行有鍵按下,進(jìn)入列檢測 { tmp2 = 0x01;// tmp2用于檢測出哪一列為0 for(col =0;col<4;col++)// 列檢測 { if((P1 & tmp2)==0x00)// 該列如果為低電平則可以判定為該列 { key_val =key_Map[ row*4 +col ];// 獲取鍵值,識別按鍵;key_Map為按鍵的定義表
return;// 退出循環(huán) } tmp2*=2;// tmp2左移一位 } } } } //結(jié)束
這是一種比較經(jīng)典的矩陣鍵盤識別方法,實現(xiàn)起來較為簡單,程序短小精煉。
一種新的矩陣鍵盤掃描方式
成都 李偉
矩陣鍵盤的按鍵越多,所節(jié)約的10口就越多,如8×8的矩陣鍵盤只需要16根IO口線。如果用單線鍵盤。則需要64根10口線。
矩陣鍵盤最常用的鍵盤掃描方式(以行掃描為例),是對行10口一行一行地置高(低),同時讀取列的數(shù)據(jù),如果判定有鍵按下,先調(diào)用按鍵消抖程序,然后再讀取列數(shù)據(jù),最后確定按鍵的位置。
但這種方式也存在問題,首先是程序比較復(fù)雜。其次是按鍵消抖延時對在實時性要求特別強的場合工作會有一定影響。下面介紹一種新型的掃描方式。
其總體思路是:行列掃描線都接下拉電阻,先將行掃描全置高,讀取列信號,如果列信號全為低,說明沒有鍵按下,如果列信號不全為低,則記錄此數(shù)據(jù),然后將列掃描全置高,讀取行掃描的數(shù)據(jù),兩次讀得的數(shù)據(jù)分別是所按鍵所在的列、行位置。
這種掃描方式思路清晰、程序簡單。下面以C51單片機(jī)為例,用C語言編寫一個8×8的鍵盤掃描程序。
函數(shù)名稱:keylook()種新的矩陣鍵盤掃描方式函數(shù)功能:查尋鍵盤按鍵情況人口參數(shù):無出口參數(shù):按鍵的編碼(1~
上述程序只能識別有一個鍵按下的情況。返回的是按鍵的編號。無鍵按下時,返回值為0。有兩個或以上的鍵按下時。返回值為0xFF。
在按鍵較多時,也可使用專門的鍵盤接口芯片,如ZLG7289、ZLG7290、CH451等。另外,這些芯片還具有其他功能,如可以驅(qū)動多位LED數(shù)碼管等。
第四篇:單片機(jī)實驗三 雙機(jī)通信實驗程序
實驗三 雙機(jī)通信實驗
一、實驗?zāi)康?/p>
UART 串行通信接口技術(shù)應(yīng)用
二、實驗實現(xiàn)的功能
用兩片核心板之間實現(xiàn)串行通信,將按鍵信息互發(fā)到對方數(shù)碼管顯示。
三、系統(tǒng)硬件設(shè)計
實驗所需硬件:電腦一臺;
開發(fā)板一塊;
串口通信線一根; USB線一根;
四、系統(tǒng)軟件設(shè)計
實驗所需軟件:編譯軟件:keil uvision3;
程序下載軟件:STC_ISP_V480; 試驗程序:
#include
L1=1;L2=1;L3=1;
H1=0;if(L1==0)
return 1;else if(L2==0)
return 2;else if(L3==0)
return 3;
H1=1;H2=0;if(L1==0)
return 4;else if(L2==0)
return 5;else if(L3==0)
return 6;H2=1;return 0;
} unsigned char keyscan(){ static unsigned int ct=0;static unsigned char lastkey=0;unsigned char key;key=getkey();
if(key==lastkey){
ct++;
if(ct==900)
{
ct=0;
lastkey=0;
return key;
} } else {
第五篇:基于單片機(jī)的數(shù)字鐘課程設(shè)計程序
#include
#define uchar unsigned char #define uint unsigned int
uchar code table[]=“I LOVE YOU!”;uchar code table1[]=“2014:06:14”;sbit lcden=P3^5;
sbit lcdrs=P3^4;uchar num;
void delayms(uint z)
{
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);}
void write_com(uchar com){
lcdrs=0;
P0=com;
delayms(5);
lcden=0;
}
void write_data(uchar date){
lcdrs=1;
P0=date;
delayms(5);
lcden=1;
delayms(5);
lcden=0;
}
void init()
{
lcden=0;
write_com(0x38);
write_com(0x0c);
write_com(0x06);
write_com(0x01);
}
void main()//定義使能端、命令選擇端//延時函數(shù)//寫入命令函數(shù)//寫入數(shù)據(jù) //初始化LCD
{}init();write_com(0x80);for(num=0;num<11;num++){write_data(table[num]);delayms(5);} write_com(0x80+0x40);for(num=0;num<13;num++){write_data(table[num]);delayms(5);} while(1);