3 Star 15 Fork 4

建伟F4nniu / FN1895E-MCU101

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
MCU025.md 20.90 KB
一键复制 编辑 原始数据 按行查看 历史
建伟F4nniu 提交于 2017-11-10 10:07 . 修正 up_to_down 函数名。

#第二十五节:用LED灯和按键来模拟工业自动化设备的运动控制。

开场白:

前面三节讲了独立按键控制跑马灯的各种状态,这一节我们要做一个机械手控制程序,这个机械手可以左右移动,最左边有一个开关感应器,最右边也有一个开关感应器。它也可以上下移动,最下面有一个开关感应器。左右移动是通过一个气缸控制,上下移动也是通过一个气缸控制。而单片机控制气缸,本质上是通过三极管把信号放大,然后控制气缸上的电磁阀。这个系统机械手驱动部分的输出和输入信号如下:

  • 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_down();   //从上边移动到下边
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循环中驱动数码管的动态扫描程序。

(未完待续,下节更精彩,不要走开哦)

C
1
https://gitee.com/F4NNIU/FN1895E-MCU101.git
git@gitee.com:F4NNIU/FN1895E-MCU101.git
F4NNIU
FN1895E-MCU101
FN1895E-MCU101
Development

搜索帮助