舵机的控制信号为周期是20ms 的脉宽调制(PWM)信号,其中脉冲宽度从0.5ms-2.5ms,相对应舵盘的位置为0—180度,呈线性变化。也就是说,给它提供一定的脉宽,它的输出轴就会保持在一个相对应的角度上,无论外界转矩怎样改变,直到给它提供一个另外宽度的脉冲信号,它才会改变输出角度到新的对应的位置上。舵机内部有一个基准电路,产生周期20ms,宽度1.5ms的基准信号,有一个比较器,将外加信号与基准信号相比较,判断出方向和大小,从而产生电机的转动信号。其舵机输出转角与信号脉冲宽度的关系见下图。
最近在使用STC89C52单片机控制SG90舵机和MG90S舵机的过程中,为了保证脉冲信号宽度的精准性,采用了定时器中断来调节占空比产生PWM信号,也就是在定时器中断函数中去调节高低电平持续的时间,博主在此处选择定时器0中断来产生PWM信号,每0.1ms中断一次,在实际编写代码的过程中,由于定时器0中断的中断频率过高,导致主函数内其他功能代码的运行出现了较大的偏差,例如DS18B20温度传感器要求每次读取温度都要等待750ms以上,但是定时器0中断在每次读取温度数据的时候进入中断就会导致所读取的温度值不准确,因而需要在每次控制完舵机转动指定角度后就关闭定时器0。
在实际编写代码测试发现,如果是在向舵机发送完20ms的pwm脉冲之后就立马关闭定时器的话,则会导致舵机无法转动指定角度,反之如果不关闭定时器的话,pwm脉冲信号就会不断的反复发送,舵机就能转动到指定角度,针对这个现象,博主不断编写代码测试发现,在持续发送pwm脉冲信号一段时间后再关闭定时器,仍能达到控制舵机转动指定角度,且能保证主函数内其他代码块的正常运行,直到下一次通过发送控制指令到单片机时再开启定时器来产生pwm信号,反复如此,经测试,在7个脉冲周期(每个周期20ms)后关闭定时器(共140ms)可保证舵机的正常运行,且不影响主函数其他代码块运行,具体代码如下:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
unsigned char SEH_count = 5;//舵机转动角度,5-0度,10-45度,15-90度
unsigned char count = 0;//脉宽长度计数
unsigned char T0RH = 0xff; //T0重载的高字节
unsigned char T0RL = 0xa4; //T0Ö载的低字节
unsigned char RxdByte = 0; //串口接收到的数据
sbit SEH_PWM = P1^0; //舵机PWM脉冲信号数据口
uchar UartDataIsResolve = 0;//串口数据是否已处理标志位,0-未处理,1-已处理
uchar timer_count = 0;//舵机20ms脉冲发送次数
uchar flag0 = 0; //舵机转动位置标志位,0-90度,1-0度
//串口初始化
void UARTInit()
{
SM0 = 0;
SM1 = 1; //串口工作方式1
REN = 1; //允许串口接收
EA = 1; //开总中断
ES = 1; //开串口中断
TMOD |= 0x20;//定时器1 8位自动重装模式
TH1 = 0xfd;
TL1 = 0xfd; //9600波特率
TR1 = 1; //开定时器1
ET1 = 0; //禁止定时器1中断
}
/*定时器0初始化*/
void Timer0_Init()
{
TMOD |= 0x01; //定时器0工作方式模式1
TH0 = T0RH; //初值装填
TL0 = T0RL;
ET0 = 1; //开定时器0中断
TR0 = 1; //开定时器0
}
void PWM(unsigned char x)
{
SEH_count = x;
}
void main(){
Timer0_Init();//初始化定时器0,并将舵机复位至0°位置
UARTInit();
while(1){
switch(RxdByte){
case '0':{
if(!UartDataIsResolve){//通过串口发送字符'0'控制舵机转动到0°位置或90°位置,确保每次串口中断数据只处理一次
UartDataIsResolve = 1;
if(flag0 == 1){
flag0 = 0;
PWM(5);
}else{
flag0 = 1;
PWM(15);
}
TR0 = 1;//每次控制舵机转动时再开定时器。
}
};
break;
}
}
}
/* 串口中断函数*/
void InterruptUART() interrupt 4
{
EA = 0;
if(RI)
{
RI = 0;
RxdByte = SBUF;
UartDataIsResolve = 0;//串口数据处理标志
}
EA = 1;
}
/*定时器0中断函数,0.1ms中断一次*/
void InterruptTimer0() interrupt 1
{
TH0 = T0RH; //重新装填重载值
TL0 = T0RL;
TR0 = 0;//关闭定时器
if(count <= SEH_count)
{
SEH_PWM = 1;
}
else
{
SEH_PWM = 0;
}
TR0 = 1;//开启定时器
count++;
if(count >= 200){
count = 0;
timer_count++;
}
//7个脉冲周期后关闭定时器
if(timer_count == 7){
timer_count = 0;
TR0 = 0;
}
}