开场白:
前面三节讲了独立按键控制跑马灯的各种状态,这一节我们要做一个机械手控制程序,这个机械手可以左右移动,最左边有一个开关感应器,最右边也有一个开关感应器。它也可以上下移动,最下面有一个开关感应器。左右移动是通过一个气缸控制,上下移动也是通过一个气缸控制。而单片机控制气缸,本质上是通过三极管把信号放大,然后控制气缸上的电磁阀。这个系统机械手驱动部分的输出和输入信号如下:
2个输出IO口,分别控制2个气缸。对于左右移动的气缸,当IO口为0时往左边跑,当IO口为1时往右边跑。对于上下移动的气缸,当IO口为0时往上边跑,当IO口为1时往下边跑。
3个输入IO口,分别检测3个开关感应器。感应器没有被触发时,IO口检测为高电平1。被触发时,IO口检测为低电平0。
这一节继续要教会大家两个知识点:
第一点:如何用软件进行开关感应器的抗干扰处理。
第二点:如何用Switch语句搭建工业自动控制的程序框架。还是那句话,我们只要以Switch语句为支点,再复杂再繁琐的程序都可以轻松地编写出来。
具体内容,请看源代码讲解。
(1)硬件平台:基于朱兆祺51单片机学习板。用矩阵键盘中的S1键作为启动独立按键,用S5按键模拟左边的开关感应器,用S9按键模拟右边的开关感应器,用S13按键模拟下边的开关感应器。记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。
(2)实现功能:
开机默认机械手在左上方的原点位置。按下启动按键后,机械手从左边开始往右边移动,当机械手移动到最右边时,机械手马上开始往下移动,最后机械手移动到最右下角的位置时,延时1秒,然后原路返回,一直返回到左上角的原点位置。注意:启动按键必须等机械手处于左上角原点位置时,启动按键的触发才有效。
(3)源代码讲解如下:
#include "REG52.H" #define const_voice_short 40 //蜂鸣器短叫的持续时间 #define const_key_time1 20 //按键去抖动延时的时间 #define const_sensor 20 //开关感应器去抖动延时的时间 #define const_1s 500 //1秒钟大概的定时中断次数 void initial_myself(); void initial_peripheral(); void delay_short(unsigned int uiDelayShort); void delay_long(unsigned int uiDelaylong); void left_to_right(); //从左边移动到右边 void right_to_left(); //从右边返回到左边 void up_to_dowm(); //从上边移动到下边 void down_to_up(); //从下边返回到上边 void run(); //设备自动控制程序 void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01); void led_update(); //LED更新函数 void T0_time(); //定时中断函数 void key_service(); //按键服务的应用程序 void key_scan(); //按键扫描函数 放在定时中断里 void sensor_scan(); //开关感应器软件抗干扰处理函数,放在定时中断里。 sbit hc595_sh_dr=P2^3; sbit hc595_st_dr=P2^4; sbit hc595_ds_dr=P2^5; sbit beep_dr=P2^7; //蜂鸣器的驱动IO口 sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键 sbit left_sr=P0^1; //左边的开关感应器 对应朱兆祺学习板的S5键 sbit right_sr=P0^2; //右边的开关感应器 有对应朱兆祺学习板的S9键 sbit down_sr=P0^3; //下边的开关感应器 对应朱兆祺学习板的S13键 sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平 unsigned char ucKeySec=0; //被触发的按键编号 unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器 unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志 unsigned char ucLeftSr=0; //左边感应器经过软件抗干扰处理后的状态标志 unsigned char ucRightSr=0; //右边感应器经过软件抗干扰处理后的状态标志 unsigned char ucDownSr=0; //下边感应器经过软件抗干扰处理后的状态标志 unsigned int uiLeftCnt1=0; //左边感应器软件抗干扰所需的计数器变量 unsigned int uiLeftCnt2=0; unsigned int uiRightCnt1=0; //右边感应器软件抗干扰所需的计数器变量 unsigned int uiRightCnt2=0; unsigned int uiDownCnt1=0; //下边软件抗干扰所需的计数器变量 unsigned int uiDownCnt2=0; unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器 unsigned char ucLed_dr1=0; //代表16个灯的亮灭状态,0代表灭,1代表亮 unsigned char ucLed_dr2=0; unsigned char ucLed_dr3=0; unsigned char ucLed_dr4=0; unsigned char ucLed_dr5=0; unsigned char ucLed_dr6=0; unsigned char ucLed_dr7=0; unsigned char ucLed_dr8=0; unsigned char ucLed_dr9=0; unsigned char ucLed_dr10=0; unsigned char ucLed_dr11=0; unsigned char ucLed_dr12=0; unsigned char ucLed_dr13=0; unsigned char ucLed_dr14=0; unsigned char ucLed_dr15=0; unsigned char ucLed_dr16=0; unsigned char ucLed_update=1; //刷新变量。每次更改LED灯的状态都要更新一次。 unsigned char ucLedStatus16_09=0; //代表底层74HC595输出状态的中间变量 unsigned char ucLedStatus08_01=0; //代表底层74HC595输出状态的中间变量 unsigned int uiRunTimeCnt=0; //运动中的时间延时计数器变量 unsigned char ucRunStep=0; //运动控制的步骤变量 void main() { initial_myself(); delay_long(100); initial_peripheral(); while(1) { run(); //设备自动控制程序 led_update(); //LED更新函数 key_service(); //按键服务的应用程序 } } /* 注释一: * 开关感应器的抗干扰处理,本质上类似按键的去抖动处理。唯一的区别是: * 按键去抖动关注的是IO口的一种状态,而开关感应器关注的是IO口的两种状态。 * 当开关感应器从原来的1状态切换到0状态之前,要进行软件滤波处理过程,一旦成功地 * 切换到0状态了,再想从0状态切换到1状态的时候,又要经过软件滤波处理过程,符合 * 条件后才能切换到1的状态。通俗的话来说,按键的去抖动从1变成0难,从0变成1容易。 * 开关感应器从1变成0难,从0变成1也难。这里所说的"难"是指要经过去抖处理。 */ void sensor_scan() //开关感应器软件抗干扰处理函数,放在定时中断里。 { if(left_sr==1) //左边感应器是高电平,说明有可能没有被接触 对应朱兆祺学习板的S5键 { uiLeftCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零 uiLeftCnt2++; //类似独立按键去抖动的软件抗干扰处理 if(uiLeftCnt2>const_sensor) { uiLeftCnt2=0; ucLeftSr=1; //说明感应器确实没有被接触 } } else //左边感应器是低电平,说明有可能被接触到了 { uiLeftCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零 uiLeftCnt1++; if(uiLeftCnt1>const_sensor) { uiLeftCnt1=0; ucLeftSr=0; //说明感应器确实被接触到了 } } if(right_sr==1) //右边感应器是高电平,说明有可能没有被接触 对应朱兆祺学习板的S9键 { uiRightCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零 uiRightCnt2++; //类似独立按键去抖动的软件抗干扰处理 if(uiRightCnt2>const_sensor) { uiRightCnt2=0; ucRightSr=1; //说明感应器确实没有被接触 } } else //右边感应器是低电平,说明有可能被接触到了 { uiRightCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零 uiRightCnt1++; if(uiRightCnt1>const_sensor) { uiRightCnt1=0; ucRightSr=0; //说明感应器确实被接触到了 } } if(down_sr==1) //下边感应器是高电平,说明有可能没有被接触 对应朱兆祺学习板的S13键 { uiDownCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零 uiDownCnt2++; //类似独立按键去抖动的软件抗干扰处理 if(uiDownCnt2>const_sensor) { uiDownCnt2=0; ucDownSr=1; //说明感应器确实没有被接触 } } else //下边感应器是低电平,说明有可能被接触到了 { uiDownCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零 uiDownCnt1++; if(uiDownCnt1>const_sensor) { uiDownCnt1=0; ucDownSr=0; //说明感应器确实被接触到了 } } } void key_scan()//按键扫描函数 放在定时中断里 { if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { ucKeyLock1=0; //按键自锁标志清零 uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。 } else if(ucKeyLock1==0)//有按键按下,且是第一次被按下 { uiKeyTimeCnt1++; //累加定时中断次数 if(uiKeyTimeCnt1>const_key_time1) { uiKeyTimeCnt1=0; ucKeyLock1=1; //自锁按键置位,避免一直触发 ucKeySec=1; //触发1号键 } } } void key_service() //按键服务的应用程序 { switch(ucKeySec) //按键服务状态切换 { case 1:// 启动按键 对应朱兆祺学习板的S1键 if(ucLeftSr==0) //处于左上角原点位置 { ucRunStep=1; //启动 uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。 } ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发 break; } } void led_update() //LED更新函数 { if(ucLed_update==1) { ucLed_update=0; //及时清零,让它产生只更新一次的效果,避免一直更新。 if(ucLed_dr1==1) { ucLedStatus08_01=ucLedStatus08_01|0x01; } else { ucLedStatus08_01=ucLedStatus08_01&0xfe; } if(ucLed_dr2==1) { ucLedStatus08_01=ucLedStatus08_01|0x02; } else { ucLedStatus08_01=ucLedStatus08_01&0xfd; } if(ucLed_dr3==1) { ucLedStatus08_01=ucLedStatus08_01|0x04; } else { ucLedStatus08_01=ucLedStatus08_01&0xfb; } if(ucLed_dr4==1) { ucLedStatus08_01=ucLedStatus08_01|0x08; } else { ucLedStatus08_01=ucLedStatus08_01&0xf7; } if(ucLed_dr5==1) { ucLedStatus08_01=ucLedStatus08_01|0x10; } else { ucLedStatus08_01=ucLedStatus08_01&0xef; } if(ucLed_dr6==1) { ucLedStatus08_01=ucLedStatus08_01|0x20; } else { ucLedStatus08_01=ucLedStatus08_01&0xdf; } if(ucLed_dr7==1) { ucLedStatus08_01=ucLedStatus08_01|0x40; } else { ucLedStatus08_01=ucLedStatus08_01&0xbf; } if(ucLed_dr8==1) { ucLedStatus08_01=ucLedStatus08_01|0x80; } else { ucLedStatus08_01=ucLedStatus08_01&0x7f; } if(ucLed_dr9==1) { ucLedStatus16_09=ucLedStatus16_09|0x01; } else { ucLedStatus16_09=ucLedStatus16_09&0xfe; } if(ucLed_dr10==1) { ucLedStatus16_09=ucLedStatus16_09|0x02; } else { ucLedStatus16_09=ucLedStatus16_09&0xfd; } if(ucLed_dr11==1) { ucLedStatus16_09=ucLedStatus16_09|0x04; } else { ucLedStatus16_09=ucLedStatus16_09&0xfb; } if(ucLed_dr12==1) { ucLedStatus16_09=ucLedStatus16_09|0x08; } else { ucLedStatus16_09=ucLedStatus16_09&0xf7; } if(ucLed_dr13==1) { ucLedStatus16_09=ucLedStatus16_09|0x10; } else { ucLedStatus16_09=ucLedStatus16_09&0xef; } if(ucLed_dr14==1) { ucLedStatus16_09=ucLedStatus16_09|0x20; } else { ucLedStatus16_09=ucLedStatus16_09&0xdf; } if(ucLed_dr15==1) { ucLedStatus16_09=ucLedStatus16_09|0x40; } else { ucLedStatus16_09=ucLedStatus16_09&0xbf; } if(ucLed_dr16==1) { ucLedStatus16_09=ucLedStatus16_09|0x80; } else { ucLedStatus16_09=ucLedStatus16_09&0x7f; } hc595_drive(ucLedStatus16_09,ucLedStatus08_01); //74HC595底层驱动函数 } } void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01) { unsigned char i; unsigned char ucTempData; hc595_sh_dr=0; hc595_st_dr=0; ucTempData=ucLedStatusTemp16_09; //先送高8位 for(i=0;i<8;i++) { if(ucTempData>=0x80)hc595_ds_dr=1; else hc595_ds_dr=0; hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器 delay_short(15); hc595_sh_dr=1; delay_short(15); ucTempData=ucTempData<<1; } ucTempData=ucLedStatusTemp08_01; //再先送低8位 for(i=0;i<8;i++) { if(ucTempData>=0x80)hc595_ds_dr=1; else hc595_ds_dr=0; hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器 delay_short(15); hc595_sh_dr=1; delay_short(15); ucTempData=ucTempData<<1; } hc595_st_dr=0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来 delay_short(15); hc595_st_dr=1; delay_short(15); hc595_sh_dr=0; //拉低,抗干扰就增强 hc595_st_dr=0; hc595_ds_dr=0; } void left_to_right() //从左边移动到右边 { ucLed_dr1=1; // 1代表左右气缸从左边移动到右边 ucLed_update=1; //刷新变量。每次更改LED灯的状态都要更新一次。 } void right_to_left() //从右边返回到左边 { ucLed_dr1=0; // 0代表左右气缸从右边返回到左边 ucLed_update=1; //刷新变量。每次更改LED灯的状态都要更新一次。 } void up_to_down() //从上边移动到下边 { ucLed_dr2=1; // 1代表上下气缸从上边移动到下边 ucLed_update=1; //刷新变量。每次更改LED灯的状态都要更新一次。 } void down_to_up() //从下边返回到上边 { ucLed_dr2=0; // 0代表上下气缸从下边返回到上边 ucLed_update=1; //刷新变量。每次更改LED灯的状态都要更新一次。 } void run() //设备自动控制程序 { switch(ucRunStep) { case 0: //机械手处于左上角原点的位置,待命状态。此时触发启动按键ucRunStep=1,就触发后续一些列的连续动作。 break; case 1: //机械手从左边往右边移动 left_to_right(); ucRunStep=2; //这就是鸿哥传说中的怎样灵活控制步骤变量 break; case 2: //等待机械手移动到最右边,直到触发了最右边的开关感应器。 if(ucRightSr==0) //右边感应器被触发 { ucRunStep=3; //这就是鸿哥传说中的怎样灵活控制步骤变量 } break; case 3: //机械手从右上边往右下边移动,从上往下。 up_to_down(); ucRunStep=4; //这就是鸿哥传说中的怎样灵活控制步骤变量 break; case 4: //等待机械手从右上边移动到右下边,直到触发了右下边的开关感应器。 if(ucDownSr==0) //右下边感应器被触发 { uiRunTimeCnt=0; //时间计数器清零,为接下来延时1秒钟做准备 ucRunStep=5; //这就是鸿哥传说中的怎样灵活控制步骤变量 } break; case 5: //机械手在右下边延时1秒 if(uiRunTimeCnt>const_1s) //延时1秒 { ucRunStep=6; //这就是鸿哥传说中的怎样灵活控制步骤变量 } break; case 6: //原路返回,机械手从右下边往右上边移动。 down_to_up(); ucRunStep=7; //这就是鸿哥传说中的怎样灵活控制步骤变量 break; case 7: //原路返回,等待机械手移动到最右边的感应开关 if(ucRightSr==0) { ucRunStep=8; //这就是鸿哥传说中的怎样灵活控制步骤变量 } break; case 8: //原路返回,等待机械手从右边往左边移动 right_to_left(); ucRunStep=9; //这就是鸿哥传说中的怎样灵活控制步骤变量 break; case 9: //原路返回,等待机械手移动到最左边的感应开关,表示返回到了原点 if(ucLeftSr==0) //返回到左上角的原点位置 { ucRunStep=0; //这就是鸿哥传说中的怎样灵活控制步骤变量 } break; } } void T0_time() interrupt 1 { TF0=0; //清除中断标志 TR0=0; //关中断 sensor_scan(); //开关感应器软件抗干扰处理函数 key_scan(); //按键扫描函数 if(uiRunTimeCnt<0xffff) //不要超过最大int类型范围 { uiRunTimeCnt++; //延时计数器 } if(uiVoiceCnt!=0) { uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫 beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。 } else { ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。 beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。 } TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f TL0=0x2f; TR0=1; //开中断 } void delay_short(unsigned int uiDelayShort) { unsigned int i; for(i=0;i<uiDelayShort;i++) { ; //一个分号相当于执行一条空语句 } } void delay_long(unsigned int uiDelayLong) { unsigned int i; unsigned int j; for(i=0;i<uiDelayLong;i++) { for(j=0;j<500;j++) //内嵌循环的空指令数量 { ; //一个分号相当于执行一条空语句 } } } void initial_myself() //第一区 初始化单片机 { /* 注释二: * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平, * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。 * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。 */ key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平 beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。 TMOD=0x01; //设置定时器0为工作方式1 TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f TL0=0x2f; } void initial_peripheral() //第二区 初始化外围 { EA=1; //开总中断 ET0=1; //允许定时中断 TR0=1; //启动定时中断 }
总结陈词:
前面花了很多节内容在讲按键和跑马灯的关系,但是一直没涉及到人机界面,在大多数的实际项目中,人机界面是必不可少的。人机界面的程序框架该怎么样写?欲知详情,请听下回分解-----在主函数while循环中驱动数码管的动态扫描程序。