结合按键程序,我们设计这样一个功能程序:按数字键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();//调用按键驱动函数
}
}
voidStartMotor(signedlongangle){
//在计算前关闭中断,完成后再打开,以避免中断打断计算过程而造成错误
EA=0;
beats=(angle*4076)/360;//实测为4076拍转动一圈
EA=1;
}
voidStopMotor(){
EA=0;
beats=0;
EA=1;
}
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];//刷新前一次的备份值
}
}
}
}
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;
}
}
voidInterruptTimer0()interrupt1{
staticbitp=0;
TH0=0xFC;//重新加载初值
TL0=0x67;
KeyScan();//执行按键扫描
//用一个静态bit变量实现二分频,即2ms定时,用于控制电机
p=~p;
if(p==1){
TurnMotor();
}
}这个程序是第8章和本章知识的一个综合——用按键控制步进电机转动。程序中有这么几点值得注意,我们分述如下:
针对电机要完成正转和反转两个不同的操作,我们并没有使用正转启动函数和反转启动函数这么两个函数来完成,也没有在启动函数定义的时候增加一个形式参数来指明其方向。我们这里的启动函数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型,然后对它们递增,判断到哪个值该归零,就可以了。这就是在硬件定时器的基础上实现准确的软件定时,其实类似的操作我们在讲数码管的时候也用过了,回想一下吧。