本帖最后由 jianhong_wu 于 2015-11-28 14:34 编辑
第九十节:针对行程开关感应器,分享一种既能及时响应,又能抗干扰处理的识别思路。 我今天在做数控三轴运动卡的项目时,正在考虑ARM单片机和FPGA如何检测步进电机回原点的问题,灵感一来,想到了一种我很满意的行程开关识别思路。为了说明此思路的优越性,我会先列出前面两种常用的思路作对比。并且举一个最简单的工控项目来说明。 此工控项目要求:上电后,一个普通电机控制一个滑块从左边往右边推,最右边有一个行程开关,滑块碰到行程开关后,电机停止,运动结束。 转化成单片机编程思路 :用1个IO输出高电平时电机运动,输出低电平时电机停止。另外再用1个IO口作输入,检测行程开关的电平状态,如果发现是高电平说明电机还没碰上行程开关感应器,如果发现是低电平就说明碰上了感应器,此时就可以发出停止电机的命令。 就是这么简单的过程,其实在识别开关感应器时暗藏玄机,现在一一解剖分析给大家: 第一种思路:直接判断行程感应器的电平状态,一旦发现低电平,就认为电机已经碰到了行程开关,马上停止电机。 优点:响应及时。 缺点:太灵敏了,以至于抗干扰能力非常差,在工控环境里,当电机正在行进的过程中,如果受到电源的波动或者外来的毛刺信号干扰,行程开关的输入信号可能会读取到瞬间的低电平,导致单片机误判断,提前把电机停止了,电机还没碰到行程开关就草率停机。 源码如下: #include "REG52.H"
#define CONT_TIME_20MS 10 //大概10次中断相当于20毫秒
void motor_run(); //电机往右边移动
void motor_stop(); //电机停止
void motor_pause(); //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样
void T0_time(); //定时中断函数
sbit motor_dr=P3^5; //控制电机的启动和停止的输出 对应朱兆祺学习板的独立LED灯
sbit right_sr=P0^2; //右边的行程开关检测输入 对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平
unsigned char u8MotorFlag=0; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
unsigned char u8MotorFlagBackup=0; //用来记录上一次的电机状态,用于判断电机的状态是否发生了改变。
unsigned char u8TimeFlag=0; //计时器的开关
unsigned int u16TimeCnt=0; //计时器的计时变量
unsigned char u8RunStep; //运动控制的步骤变量
void main()
{
key_gnd_dr=0; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平
TMOD=0x01; //设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f 大概每2ms产生一次中断,我没有详细计算。
TL0=0x2f;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
u8RunStep=1; //一上电,电机就开始从第1步开始启动
while(1)
{
switch(u8RunStep)
{
case 1:
motor_run(); //电机往右边移动
u8RunStep=2; //切换到下一个步骤
break;
case 2: //等待滑块移动到最右边,直到触发了最右边的行程开关感应器。
if(0==right_sr) //右边的行程感应器被触发
{
motor_stop(); //电机停止
u8RunStep=3; //马上切换到电机停止的步骤
}
break;
case 3: //电机处于停止的步骤,除非重新断电重启,否则就一直处于此步骤状态
break;
}
}
}
void T0_time() interrupt 1 //定时中断,大概每2ms就中断一次
{
TF0=0; //清除中断标志
TR0=0; //关中断
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f 大概每2ms产生一次中断,我没有详细计算。
TL0=0x2f;
TR0=1; //开中断
}
void motor_run() //电机往右边移动
{
motor_dr=1;
u8MotorFlag=1; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_stop() //电机停止
{
motor_dr=0;
u8MotorFlag=0; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_pause() //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样
{
motor_dr=0;
u8MotorFlag=0; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
第二种思路:在判断行程感应器的电平状态时,加入了软件的抗干扰处理,一旦发现低电平,一个计时器开始计时,在计时的期间,如果发现出现高电平就马上把计时器清零,如果一直是低电平,并且期间没有出现高电平,就认为是稳定的低电平,此时判定是碰到了行程开关。 优点:增加了抗干扰处理,几乎能百分百保证电机碰到了行程开关才停机,几乎不会误判了。 缺点:在软件抗干扰环节增加了一小段延时,而这一小段的延时,会导致电机碰到行程开关后没有马上停止,滑块继续往右边运动了一段时间才停止,时间长了容易把右边的限位机械结构压坏挤坏,因为有应力存在。 源码如下: #include "REG52.H"
#define CONT_TIME_20MS 10 //大概10次中断相当于20毫秒
void motor_run(); //电机往右边移动
void motor_stop(); //电机停止
void motor_pause(); //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样
void T0_time(); //定时中断函数
sbit motor_dr=P3^5; //控制电机的启动和停止的输出 对应朱兆祺学习板的单个LED灯
sbit right_sr=P0^2; //右边的行程开关检测输入 对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平
unsigned char u8MotorFlag=0; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
unsigned char u8MotorFlagBackup=0; //用来记录上一次的电机状态,用于判断电机的状态是否发生了改变。
unsigned char u8TimeFlag=0; //计时器的开关
unsigned int u16TimeCnt=0; //计时器的计时变量
unsigned char u8RunStep; //运动控制的步骤变量
void main()
{
key_gnd_dr=0; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平
TMOD=0x01; //设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f 大概每2ms产生一次中断,我没有详细计算。
TL0=0x2f;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
u8RunStep=1; //一上电,电机就开始从第1步开始启动
while(1)
{
switch(u8RunStep)
{
case 1:
motor_run(); //电机往右边移动
u8TimeFlag=0; //计时器关
u16TimeCnt=0; //计时器的计时变量清零
u8RunStep=2; //切换到下一个步骤
break;
case 2: //等待滑块移动到最右边,直到触发了最右边的行程开关感应器。
if(0==right_sr) //右边的行程感应器被触发
{
u8TimeFlag=1; //计时器开
if(u16TimeCnt>=CONT_TIME_20MS) //连续20ms内都是低电平,则认为是低电平
{
u8TimeFlag=0; //计时器关
u16TimeCnt=0; //计时器的计时变量及时清零
motor_stop(); //电机停止
u8RunStep=3; //马上切换到电机停止的步骤
}
}
else //如果是高电平
{
u8TimeFlag=0; //计时器关
u16TimeCnt=0; //计时器的计时变量及时清零
}
break;
case 3: //电机处于停止的步骤,除非重新断电重启,否则就一直处于此步骤状态
break;
}
}
}
void T0_time() interrupt 1 //定时中断,大概每2ms就中断一次
{
TF0=0; //清除中断标志
TR0=0; //关中断
if(1==u8TimeFlag)
{
u16TimeCnt++;
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f 大概每2ms产生一次中断,我没有详细计算。
TL0=0x2f;
TR0=1; //开中断
}
void motor_run() //电机往右边移动
{
motor_dr=1;
u8MotorFlag=1; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_stop() //电机停止
{
motor_dr=0;
u8MotorFlag=0; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_pause() //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样
{
motor_dr=0;
u8MotorFlag=0; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
第三种思路:本思路是结合了前面两种的优点,在判断行程感应器的电平状态时,当发现是低电平时(哪怕是干扰时出现的瞬间低电平),电机马上暂停(暂停和停止的概念不一样,虽然电机都是没有转),当发现是高电平时,电机继续运行,什么时候才认为碰到行程开关?当低电平像第二种思路那样连续持续低电平的时间超过某个值时,才认为碰到了行程开关。巧妙的是,在此判断低电平的小延时期间,电机是处于暂停的状态(没有转),所以不会过冲挤压右边的行程限位机构。 优点:既能及时响应,又增加了行程开关检测的抗干扰处理,又不会让电机过冲挤压右边的行程开关。 缺点:暂时还没想出来。 源码如下: #include "REG52.H"
#define CONT_TIME_20MS 10 //大概10次中断相当于20毫秒
void motor_run(); //电机往右边移动
void motor_stop(); //电机停止
void motor_pause(); //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样
void T0_time(); //定时中断函数
sbit motor_dr=P3^5; //控制电机的启动和停止的输出,对应朱兆祺学习板的单个LED灯
sbit right_sr=P0^2; //右边的行程开关检测输入,对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平
unsigned char u8MotorFlag=0; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
unsigned char u8RightSrFlagBackup=0; //用来记录上一次的行程开关电平状态,用来判断行程开关是否发生过电平变化
unsigned char u8TimeFlag=0; //计时器的开关
unsigned int u16TimeCnt=0; //计时器的计时变量
unsigned char u8RunStep; //运动控制的步骤变量
void main()
{
key_gnd_dr=0; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平
TMOD=0x01; //设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f 大概每2ms产生一次中断,我没有详细计算。
TL0=0x2f;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
u8RunStep=1; //一上电,电机就开始从第1步开始启动
while(1)
{
switch(u8RunStep)
{
case 1:
motor_run(); //电机往右边移动
u8RightSrFlagBackup=1; //用来判断行程开关是否发生过电平变化
u8TimeFlag=0; //计时器关
u16TimeCnt=0; //计时器的计时变量清零
u8RunStep=2; //切换到下一个步骤
break;
case 2: //等待滑块移动到最右边,直到触发了最右边的行程开关感应器。
if(0==right_sr) //右边的行程感应器被触发
{
if(u8RightSrFlagBackup!=0) //行程开关的电平发生过变化
{
u8RightSrFlagBackup=0; //及时更新,避免一直触发电机的命令操作
motor_pause(); //电机及时暂停
}
u8TimeFlag=1; //计时器开
if(u16TimeCnt>=CONT_TIME_20MS) //连续20ms内都是低电平,则认为是低电平
{
u8TimeFlag=0; //计时器关
u16TimeCnt=0; //计时器的计时变量及时清零
motor_stop(); //电机停止
u8RunStep=3; //马上切换到电机停止的步骤
}
}
else //如果是高电平
{
if(u8RightSrFlagBackup!=1) //行程开关的电平发生过变化
{
u8RightSrFlagBackup=1; //及时更新,避免一直触发电机的命令操作
motor_run(); //电机继续往右边移动
}
u8TimeFlag=0; //计时器关
u16TimeCnt=0; //计时器的计时变量及时清零
}
break;
case 3: //电机处于停止的步骤,除非重新断电重启,否则就一直处于此步骤状态
break;
}
}
}
void T0_time() interrupt 1 //定时中断,大概每2ms就中断一次
{
TF0=0; //清除中断标志
TR0=0; //关中断
if(1==u8TimeFlag)
{
u16TimeCnt++;
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f 大概每2ms产生一次中断,我没有详细计算。
TL0=0x2f;
TR0=1; //开中断
}
void motor_run() //电机往右边移动
{
motor_dr=1;
u8MotorFlag=1; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_stop() //电机停止
{
motor_dr=0;
u8MotorFlag=0; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_pause() //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样
{
motor_dr=0;
u8MotorFlag=0; //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
最后再提醒一下:凡是有行程限位开关的地方,速度都不宜太快,要控制好速度,因为高速会由于比较大的惯性产生碰击现象。如果需要高速的场合,最好能在到达终点前多增加一个感应器,在到达终点前做一些减速操作。如果是步进电机,也可以利用实时坐标信息来识别快到行程开关终点时做一些减速处理。 (本连载仅为初稿,我计划在2017年从头开始重写一次此连载,融入我最新的技术理解,在原来的基础上进一步提炼和补充)
|