用LED灯和按键来模拟工业自动化设备的运动控制

/*

 开场白:
  前面讲了独立按键控制跑马灯的各种状态,这一例讲的是一个机械手控制程序,这个机械手可以左右移动,最左边有
  一个开关感应器,最右边也有一个开关感应器。它也可以上下移动,最下边有一个开关感应器。左右移动是通过一个
  气缸控制,上下移动也是通过一个气缸控制。而单片机控制气缸,本质上是通过三极管把信号放大,然后控制气缸上
  的电磁阀。这个系统机械手驱动部分的输出信号和输入信号如下:
  2个输出IO口,分别控制两个气缸。对于左右移动的气缸,IO口为0表示左移,当IO口为1表示右移。对于上下移动的
  气缸,当IO口为0往上跑,当IO口为1时往下跑。
  3个输入IO口,分别检测3个开关感应器。感应器没有被触发时,IO口检测到高电平1.被触发时,检测到低电平0
  
 
 实现功能:
  开机默认机械手在左上方的原点位置,按下启动按键后,机械手从左边开始往右边移动,当机械手移动到最右边时,
  机械手马上开始往下移动,当机械手移动到最右下角的时候,延时1秒,然后原路返回,一直返回到左上角的原点位置。
  请注意:按键必须等机械手处于左上角的位置时,启动按键的触发才有效。
S5和S1同时按下,启动,从左往右移动;S9从上往下移动;S13延时1S后由下往上移动,S9由右往左移动,知道S5触发停止。
*/
#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_down();   //从上边移动到下边
 void down_to_up();   //从下边返回到上边
 
 void run(); //设备自动控制程序
 void hc595_drive(unsigned char ucLedStatusTemp08_01);
 void led_update();  //LED更新函数
 void T0_time();  //定时中断函数
 void key_service(); //按键服务的应用程序
 void key_scan();  //按键扫描函数,放在定时中断里
 void sensor_scan(); //开关感应器软件抗干扰处理函数,放在定时中断里
sbit hc595_sh_dr=P3^6;  //上升沿时,数据寄存器数据移位
sbit hc595_st_dr=P3^5;  //上升沿时移位寄存器的数据进入数据寄存器,下降沿时数据不变。当移位结束后,会产生一个正脉冲,用于更新显示数据。
sbit hc595_ds_dr=P3^4;  //串行数据输入端,级联的话接上一级的Q7
sbit beep_dr=P1^5;  //蜂鸣器的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;
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_update=1; //刷新变量。每次更改LED灯的状态都要更新一次
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)
 {
  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)
 {
  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) //处于左上角原点位置,即S5触发
   {
    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&0xfe;    //确保第1位为0  亮
  else
   ucLedStatus08_01=ucLedStatus08_01|0x01;  //确保第1位为1 灭
 
  if(ucLed_dr2==1)
   ucLedStatus08_01=ucLedStatus08_01&0xfd;   
  else
   ucLedStatus08_01=ucLedStatus08_01|0x02; 
 
  if(ucLed_dr3==1)
   ucLedStatus08_01=ucLedStatus08_01&0xfb;   
  else
   ucLedStatus08_01=ucLedStatus08_01|0x04;
 
  if(ucLed_dr4==1)
   ucLedStatus08_01=ucLedStatus08_01&0xf7;   
  else
   ucLedStatus08_01=ucLedStatus08_01|0x08;
 
  if(ucLed_dr5==1)
   ucLedStatus08_01=ucLedStatus08_01&0xef;   
  else
   ucLedStatus08_01=ucLedStatus08_01|0x10;
 
  if(ucLed_dr6==1)
   ucLedStatus08_01=ucLedStatus08_01&0xdf;   
  else
   ucLedStatus08_01=ucLedStatus08_01|0x20;
 
  if(ucLed_dr7==1)
   ucLedStatus08_01=ucLedStatus08_01&0xbf;   
  else
   ucLedStatus08_01=ucLedStatus08_01|0x40;
 
  if(ucLed_dr8==1)
   ucLedStatus08_01=ucLedStatus08_01&0x7f;   
  else
   ucLedStatus08_01=ucLedStatus08_01|0x80;
 
  hc595_drive(ucLedStatus08_01);  //74HC595底层驱动程序
 }
}
void hc595_drive(unsigned char ucLedStatusTemp08_01)
{
 unsigned char i;
 unsigned char ucTempData;
 hc595_sh_dr=0;
 hc595_st_dr=0;
 
 ucTempData=ucLedStatusTemp08_01; //先送高8位
 for(i=0;i<8;i++)
 {
  if(ucTempData>=0x80) //更新一次数据,移一次位
   hc595_ds_dr=1;//串行数据输入,如果是多片联级的话,更新一次,输入一次数据。(我的单片机只用了一个74HC595芯片)
  else
   hc595_ds_dr=0;
 
  hc595_sh_dr=0;
  delay_short(15);
  hc595_sh_dr=1;//SH引脚的上升沿把数据送入寄存器
  delay_short(15);
 
  ucTempData=ucTempData<<1;//左移一位
 }
 
 hc595_st_dr=0;
 delay_short(15);
 hc595_st_dr=1;  //ST引脚负责把寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来。上升沿时更新显示数据。
 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;
}
void up_to_down() //从上边移动到下边
{
 ucLed_dr2=1; //1代表上下气缸从上边移动到下边
 ucLed_update=1;
}
void down_to_up() //从下边返回到上边
{
 ucLed_dr2=0; //0代表上下气缸从下边返回到上边
 ucLed_update=1;
}
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:
   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) //设定这个条件,防止超范围
 {
  uiRunTimeCnt++; //延时计数器
 }
 if(uiVoiceCnt!=0)
 {
  uiVoiceCnt--;
  beep_dr=0;  //PNP三极管控制,低电平开始鸣叫
 }
 else
 {
  ;
  beep_dr=1;
 } 
 
 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=0;
 beep_dr=1;
 
 TMOD=0x01;  //设置定时器0工作方式为1
 TH0=0xf8;
 TL0=0x2f;
}
void initial_peripheral()
{
 EA=1; //开总中断
 ET0=1; //允许定时中断
 TR0=1; //启动定时中断
}
 
/*最近忙着写开题报告,之后会晚点更新。之后更新如何与上位机通讯等代码*/
posted @ 2019-12-26 21:21  烟火流沙  阅读(463)  评论(0编辑  收藏  举报