結合按鍵程序,我們設計這樣一個功能程序:按數字鍵1~9,控制電機轉過1~9圈;配合上下鍵改變轉動方向,按向上鍵后正向轉1~9圈,向下鍵則反向轉1~9圈;左鍵固定正轉90度,右鍵固定反轉90;Esc鍵終止轉動。通過這個程序,我們也可以進一步體會到如何用按鍵來控制程序完成復雜的功能,以及控制和執行模塊之間如何協調工作,而你的編程水平也可以在這樣的實踐練習中得到鍛煉和提升。
#include
sbitKEY_IN_1=P2^4;
sbitKEY_IN_2=P2^5;
sbitKEY_IN_3=P2^6;
sbitKEY_IN_4=P2^7;
sbitKEY_OUT_1=P2^3;
sbitKEY_OUT_2=P2^2;
sbitKEY_OUT_3=P2^1;
sbitKEY_OUT_4=P2^0;
unsignedcharcodeKeyCodeMap[4][4]={//矩陣按鍵編號到標準鍵盤鍵碼的映射表
{0x31,0x32,0x33,0x26},//數字鍵1、數字鍵2、數字鍵3、向上鍵
{0x34,0x35,0x36,0x25},//數字鍵4、數字鍵5、數字鍵6、向左鍵
{0x37,0x38,0x39,0x28},//數字鍵7、數字鍵8、數字鍵9、向下鍵
{0x30,0x1B,0x0D,0x27}//數字鍵0、ESC鍵、回車鍵、向右鍵
};
unsignedcharKeySta[4][4]={//全部矩陣按鍵的當前狀態
{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}
};
signedlongbeats=0;//電機轉動節拍總數
voidKeyDriver();
voidmain(){
EA=1;//使能總中斷
TMOD=0x01;//設置T0為模式1
TH0=0xFC;//為T0賦初值0xFC67,定時1ms
TL0=0x67;
ET0=1;//使能T0中斷
TR0=1;//啟動T0
while(1){
KeyDriver();//調用按鍵驅動函數
}
}
/*步進電機啟動函數,angle-需轉過的角度*/
voidStartMotor(signedlongangle){
//在計算前關閉中斷,完成后再打開,以避免中斷打斷計算過程而造成錯誤
EA=0;
beats=(angle*4076)/360;//實測為4076拍轉動一圈
EA=1;
}
/*步進電機停止函數*/
voidStopMotor(){
EA=0;
beats=0;
EA=1;
}
/*按鍵動作函數,根據鍵碼執行相應的操作,keycode-按鍵鍵碼*/
voidKeyAction(unsignedcharkeycode){
staticbitdirMotor=0;//電機轉動方向
//控制電機轉動1-9圈
if((keycode>=0x30)&&(keycode<=0x39)){
if(dirMotor==0){
StartMotor(360*(keycode-0x30));
}else{
StartMotor(-360*(keycode-0x30));
}
}elseif(keycode==0x26){//向上鍵,控制轉動方向為正轉
dirMotor=0;
}elseif(keycode==0x28){//向下鍵,控制轉動方向為反轉
dirMotor=1;
}elseif(keycode==0x25){//向左鍵,固定正轉90度
StartMotor(90);
}elseif(keycode==0x27){//向右鍵,固定反轉90度
StartMotor(-90);
}elseif(keycode==0x1B){//Esc鍵,停止轉動
StopMotor();
}
}
/*按鍵驅動函數,檢測按鍵動作,調度相應動作函數,需在主循環中調用*/
voidKeyDriver(){
unsignedchari,j;
staticunsignedcharbackup[4][4]={//按鍵值備份,保存前一次的值
{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}
};
for(i=0;i<4;i++){//循環檢測4*4的矩陣按鍵
for(j=0;j<4;j++){
if(backup[i][j]!=KeySta[i][j]){//檢測按鍵動作
if(backup[i][j]!=0){//按鍵按下時執行動作
KeyAction(KeyCodeMap[i][j]);//調用按鍵動作函數
}
backup[i][j]=KeySta[i][j];//刷新前一次的備份值
}
}
}
}
/*按鍵掃描函數,需在定時中斷中調用,推薦調用間隔1ms*/
voidKeyScan(){
unsignedchari;
staticunsignedcharkeyout=0;//矩陣按鍵掃描輸出索引
staticunsignedcharkeybuf[4][4]={//矩陣按鍵掃描緩沖區
{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF}
};
//將一行的4個按鍵值移入緩沖區
keybuf[keyout][0]=(keybuf[keyout][0]<<1)|KEY_IN_1;
keybuf[keyout][1]=(keybuf[keyout][1]<<1)|KEY_IN_2;
keybuf[keyout][2]=(keybuf[keyout][2]<<1)|KEY_IN_3;
keybuf[keyout][3]=(keybuf[keyout][3]<<1)|KEY_IN_4;
//消抖后更新按鍵狀態
for(i=0;i<4;i++){//每行4個按鍵,所以循環4次
if((keybuf[keyout][i]&0x0F)==0x00){
//連續4次掃描值為0,即4*4ms內都是按下狀態時,可認為按鍵已穩定的按下
KeySta[keyout][i]=0;
}elseif((keybuf[keyout][i]&0x0F)==0x0F){
//連續4次掃描值為1,即4*4ms內都是彈起狀態時,可認為按鍵已穩定的彈起
KeySta[keyout][i]=1;
}
}
//執行下一次的掃描輸出
keyout++;//輸出索引遞增
keyout=keyout&0x03;//索引值加到4即歸零
//根據索引,釋放當前輸出引腳,拉低下次的輸出引腳
switch(keyout){
case0:KEY_OUT_4=1;KEY_OUT_1=0;break;
case1:KEY_OUT_1=1;KEY_OUT_2=0;break;
case2:KEY_OUT_2=1;KEY_OUT_3=0;break;
case3:KEY_OUT_3=1;KEY_OUT_4=0;break;
default:break;
}
}
/*電機轉動控制函數*/
voidTurnMotor(){
unsignedchartmp;//臨時變量
staticunsignedcharindex=0;//節拍輸出索引
unsignedcharcodeBeatCode[8]={//步進電機節拍對應的IO控制代碼
0xE,0xC,0xD,0x9,0xB,0x3,0x7,0x6
};
if(beats!=0){//節拍數不為0則產生一個驅動節拍
if(beats>0){//節拍數大于0時正轉
index++;//正轉時節拍輸出索引遞增
index=index&0x07;//用&操作實現到8歸零
beats--;//正轉時節拍計數遞減
}else{//節拍數小于0時反轉
index--;//反轉時節拍輸出索引遞減
index=index&0x07;//用&操作同樣可以實現到-1時歸7
beats++;//反轉時節拍計數遞增
}
tmp=P1;//用tmp把P1口當前值暫存
tmp=tmp&0xF0;//用&操作清零低4位
tmp=tmp|BeatCode[index];//用|操作把節拍代碼寫到低4位
P1=tmp;//把低4位的節拍代碼和高4位的原值送回P1
}else{//節拍數為0則關閉電機所有的相
P1=P1|0x0F;
}
}
/*T0中斷服務函數,用于按鍵掃描與電機轉動控制*/
voidInterruptTimer0()interrupt1{
staticbitp=0;
TH0=0xFC;//重新加載初值
TL0=0x67;
KeyScan();//執行按鍵掃描
//用一個靜態bit變量實現二分頻,即2ms定時,用于控制電機
p=~p;
if(p==1){
TurnMotor();
}
}針對電機要完成正轉和反轉兩個不同的操作,我們并沒有使用正轉啟動函數和反轉啟動函數這么兩個函數來完成,也沒有在啟動函數定義的時候增加一個形式參數來指明其方向。我們這里的啟動函數voidStartMotor(signedlongangle)與單向正轉時的啟動函數唯一的區別就是把形式參數angle的類型從unsignedlong改為了signedlong,我們用有符號數固有的正負特性來區分正轉與反轉,正數表示正轉angle度,負數就表示反轉angle度,這樣處理是不是很簡潔又很明了呢?而你對有符號數和無符號數的區別用法是不是也更有體會了?
針對終止電機轉動的操作,我們定義了一個單獨的StopMotor函數來完成,盡管這個函數非常簡單,盡管它也只在Esc按鍵分支內被調用了,但我們仍然把它單獨提出來作為了一個函數。而這種做法就是基于這樣一條編程原則:盡可能用單獨的函數來完成硬件的某種操作,當一個硬件包含多個操作時,把這些操作函數組織在一起,形成一個對上層的統一接口。這樣的層次化處理,會使得整個程序條理清晰,既有利于程序的調試維護,又有利于功能的擴充。
中斷函數中要處理按鍵掃描和電機驅動兩件事情,而為了避免中斷函數過于復雜,我們就又分出了按鍵掃描和電機驅動兩個函數(這也同樣符合上述2的編程原則),而中斷函數的邏輯就變得簡潔而清晰了。這里還有個矛盾,就是按鍵掃描我們選擇的定時時間是1ms,而本章之前的實例中電機節拍持續時間都是2ms;很顯然,用1ms的定時可以定出2ms的間隔,而用2ms的定時卻得不到準確的1ms間隔;所以我們的做法就是,定時器依然定時1ms,然后用一個bit變量做標志,每1ms改變一次它的值,而我們只選擇值為1的時候執行一次動作,這樣就是2ms的間隔了;如果我要3ms、4ms??呢,把bit改為char或int型,然后對它們遞增,判斷到哪個值該歸零,就可以了