由于模块化之后各位的复制粘贴就麻烦了,所以这次没有模块化,即粘即用。
这里用的是11.0592MHz的晶振,如果是12MHz的晶振需要修改红外接收部分。
pwm信号其实就是一段很长的掺杂着高低电平的信号。舵机接收pwm信号的周期为20毫秒,在两毫秒内收到的电平信号,有多少高电平,有多少低电(也就是平占空比决定舵机旋转角度),直接决定了舵机的旋转,注意这里舵机的旋转是转到pwm信号代表的角度,转到之后就会停下,并且会固定在这个位置,用手都无法使其转动(在未通电时是可以随意转动舵机的)。
stm32控制舵机十分的容易,因为其内部有pwm模块,能够直接产生想要的pwm信号,这样一来控制舵机就再容易不过了。
使用52单片机控制舵机的困难之处在于要人为的产生pwm信号
在2毫秒内
有0.5毫秒的高电平,舵机转到0°位置
有1.0.毫秒的高电平,舵机转到45°位置
有1.5毫秒的高电平,舵机转到90°位置
有2.0毫秒的高电平,舵机转到135°位置
有2.5毫秒的高电平,舵机转到180°位置
在这20毫秒的时间里,需要根据想要的角度输出高电平,compare因此而生。
我的方案是找到两个数比较大小,如果A>B则输出高电平。所以设置一个compare和count去比较,得到两种结果刚好对应高低电平。而高低电平所占的时间就成为了舵机如何旋转的信号。但是这里要确定的一点,A与B必须有一个数是变化的,可以自增也可自减,如果不能变化那就只能有一个输出结果,那就没办法搞了。
我在此定义了compare和count,它们是用于发出pwm信号的,其中compare是根据舵机旋转所需高电平时间所决定的,count则是用于计数的,定时器1定时500微秒,但pwm信号的周期位20毫秒,也就是说需要定时40个500微秒才能够构成一个周期,所以count的最大值为40。于是count就成为了那个会变化的数,拿compare和它去比较就可以根据count的不同值得到不同的结果,可此时虽然得到了不同的高低电平,但compare变,我们的pwm信号还是不会变的。所以用户再自己给compare赋不同的值,那是不是就可以得到几种占空比不同的pwm信号呢?
#include <REGX52.H>
#define IR_PREVIOUS 0x40
#define IR_NEXT 0x43
unsigned char angle;
unsigned char compare;
unsigned char count;
unsigned char Command;
unsigned int IR_Time;
unsigned char IR_State;//默认状态是0
unsigned char IR_Data[4];//存储数据,四个数据区分别表示四个字节
unsigned char IR_pData;//收到32位数据就结束了
unsigned char IR_DataFlag;//用来输出数据,比如解码完成了就把它置1,然后跳出程序
unsigned char IR_RepeatFlag;//用来重发数据Flag是标志位
unsigned char IR_Address;//将IR_Data[]的数据存储在这,以免IR_Data既接收数据又输出数据
unsigned char IR_Command;//命令码
sbit pwm =P2^7; //PWM信号输出,接舵机的数据线
void Timer1_Init(void) //定时500微秒@11.0592MHz
{
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0x33; //设置定时初值
TH1 = 0xFE; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1;
EA = 1;
PT1 = 0;
}
void Timer1_Routine() interrupt 3
{
TL1 = 0x33; //设置定时初值
TH1 = 0xFE; //设置定时初值
count++;//每来一次都自增一次,用来和compare进行比较
count%=40;//这是由周期设定的
if(count<compare)
{
pwm=1;
}
else
{
pwm=0;
}
}
//Timer0
void Timer0_Init(void)//设置定时器的初始值
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0=0;
TH0=0;
TF0=0;
TR0=0;
}
void Timer0_SetCounter(unsigned int Value)
{
TH0=Value/256;
TL0=Value%256;
}
unsigned int Timer0_GetCounter(void)
{
return (TH0<<8)|TL0;
}
void Timer0_Run(unsigned char Flag)
{
TR0=Flag;
}
//Int0
void Int0_Init(void)
{
IT0=1;
IE0=0;
EX0=1;
EA=1;
PX0=1;
}
//IR
void IR_Init(void)
{
Timer0_Init();//对定时器初始化
Int0_Init();//对中断函数初始化
}
unsigned char IR_GetDataFlag(void)
{
if(IR_DataFlag)
{
IR_DataFlag=0;
return 1;
}
return 0;
}
unsigned char IR_GetRepeatFlag(void)
{
if(IR_RepeatFlag)
{
IR_RepeatFlag=0;
return 1;
}
return 0;
}
unsigned char IR_GetAddress(void)
{
return IR_Address;
}
unsigned char IR_GetCommand(void)
{
return IR_Command;
}
void Int0_Routine(void) interrupt 0
{//信号最初是高电平的,,变成低电代表
if(IR_State==0)//设置初始状态(空闲状态),是起点
{
Timer0_SetCounter(0);//将计数器内的数据清零
Timer0_Run(1);//计数器开始计数
IR_State=1;//State0不需要停留,直接进入下一阶段
}
else if (IR_State==1)//从上面到这里,接收的信号是一个下降沿到另外一个下降沿
{//无论是start还是repeat,都是一个下降沿到上升沿到下降沿,截取两个下降沿之间所用的时间就能得出start和repeat
IR_Time=Timer0_GetCounter();//读取计数器内的数据,之后对信息时长的比较就不需要用Timer0_GetCounter
Timer0_SetCounter(0);//读取完第一个数据,将计数器清零
if(IR_Time>12442-500&&IR_Time<12442+500)//对两个下降沿之间的时长进行判断
{
IR_State=2;//State2代表解码阶段
}
else if(IR_Time>10368-500&&IR_Time<10368+500)
{
IR_RepeatFlag=1;//IR_RepeatFlag就一个作用,跳出程序进入输出部分
IR_State=0;
}
else
{
IR_State=1;//State1用来搜寻起始信号
}
}
else if(IR_State==2)//开始对数据进行解码
{
IR_Time=Timer0_GetCounter();
Timer0_SetCounter(0);
if(IR_Time>1032-500&&IR_Time<1032+500)
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8));
IR_pData++;
}
else if(IR_Time>2074-500&&IR_Time<2074+500)
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));
IR_pData++;
}
else
{
IR_pData=0;
IR_State=1;
}
if(IR_pData>=32)
{
IR_pData=0;
if((IR_Data[0]==~IR_Data[1])&&(IR_Data[2]==~IR_Data[3]))
{
IR_Address=IR_Data[0];
IR_Command=IR_Data[2];
IR_DataFlag=1;
}
Timer0_Run(0);
IR_State=0;
}
}
}
void main()
{
angle=2;
count=0;
compare=2;
IR_Init();
Timer0_Init();
Int0_Init();
Timer1_Init();
while(1)
{
if(IR_GetDataFlag())
{
Command=IR_GetCommand();
if(Command==IR_PREVIOUS){Timer1_Init();angle--;}
if(Command==IR_NEXT){Timer1_Init();angle++;}
if(angle==0){angle=2;}
if(angle==1){compare=1;}
if(angle==2){compare=2;}
if(angle==3){compare=3;}
if(angle==4){angle=2;}
}
}
}
在主函数的部分,我们首先将定时给初始化,或者在发送指令前将定时器1初始化,并且我们给compare的初始值是2,这样能保证舵机不是从0°开始转动,而是45°开始,此时它在一开始就可以反向转的,如果上来就是0°,那就无法转到-45°的。
angle=2是初始位置(这样留出反向转的45°能更好开关灯),但不是舵机开始转向的位置,而是45°的位置,但可以手动将angle=2的时候转到水平位置,以便于angle=1和angle=3的时候刚好开关灯。angle=0和angle=4都是不需要的情况,所以将这两种情况的输出都设定为初始位置也就是angle=2。
其余红外部分有需要的可以上b站瞧瞧。