zoukankan      html  css  js  c++  java
  • 用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; //启动定时中断
    }
     
    /*最近忙着写开题报告,之后会晚点更新。之后更新如何与上位机通讯等代码*/
  • 相关阅读:
    笨办法42物以类聚(补充40)
    笨办法41来自 Percal 25 号行星的哥顿人-randint-exit(0)
    nginx模块1--索引autoindex_基于ip_监控模块status
    selinux
    nginx安装与配置--基础
    svn安装
    docker-registry私有registry
    zabbix钉钉报警
    zabbix字体
    zabbix告警
  • 原文地址:https://www.cnblogs.com/TheFly/p/12104488.html
Copyright © 2011-2022 走看看