不需要stm32,使用51单片机就能够控制sg90舵机基本的旋转(内涵未模块化的代码,复制粘贴就能用)

由于模块化之后各位的复制粘贴就麻烦了,所以这次没有模块化,即粘即用。

这里用的是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站瞧瞧。

  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值