一、SG90舵机介绍
SG90是有三个版本的,90度版、180度版和360度版,前两个只是舵机旋转角度的范围不一样,可以在这个范围内任意的控制舵机所转的角度,而360版本的是一直旋转的,我们不能控制它旋转的角度,只能控制它旋转的速度,这里我们在买的时候就要注意一点,根据自己的需求选取不同的版本,以免买错。这里我们介绍180度版本的。三个控制原理基本相同。
单片机系统实现对舵机输出转角的控制,必须首先完成两个任务:首先是产生基本的PWM周期信号,本设计是产生20ms的周期信号;其次是脉宽的调整,即单片机模拟PWM信号的输出,并且调整占空比。
脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度控制脉冲部分,所以是利用一个占空比来控制舵机转动的角度。PWM占空比是指在一个周期内,信号处于高电平的时间占据整个信号周期的百分比。占空比 = t / T 相关参数如下:
t = 0.5ms——————-舵机会转动 0 °
t = 1.0ms——————-舵机会转动 45°
t = 1.5ms——————-舵机会转动 90°
t = 2.0ms——————-舵机会转动 135°
t = 2.5ms——————-舵机会转动180°
当你需要转动135°时,他的占空比就是2.0ms/20ms=10%,所以 TIMx 捕获比较寄存器值就为200-200*10% = 180。
二、与单片机的连接:
SG90分别有三根线。棕色,红色,橙色
棕色接地(GND)
红色接电源(+5V)
橙色接输出PWM信号的引脚
三、PWM
因为舵机的控制主要用到的就是定时器的pwm功能,所以我们先聊一聊pwm
PWM的输出其实就是对外输出脉宽可调(即占空比调节)的方波信号,信号频率是由自动重装寄存器 ARR 的值决定,占空比由比较寄存器CCR的值决定。
当TIMx_CR1寄存器中的 DIR 位为低时执行递增计数,计数器CNT从0计数到自动重载值(TIMx_ARR 寄存器的内容),然后重新从 0 开始数并生成计数器上溢事件。
以PWM 模式 1 为例。只要TIMx_CNT < TIMx_CCRx, PWM 参考信号OCxREF 便为有高电平,否则为无效的低电平。如果 TIMx_CCRx 中的比较值大于自动重载值(TIMx_ARR 中),则 OCxREF 保持为“ 1”。果比较值为 0, 则 OCxREF 保持为“ 0”。
我们控制舵机主要是产生一个以20ms为周期的pwm信号,然后通过改变pwm的占空比就可以控制舵机的转角。
四、程序设计
主函数如下:
int main()
{
u8 i;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组
LED_Init();
Sg90_Init();
USART1_Init();
while(1)
{
i++;
// TIM_SetCompare2(TIM3, 195); // 0度 t = 0.5ms 修改TIMx_CCRx的值控制占空比
// delay_ms(1000);
// TIM_SetCompare2(TIM3, 190); //45度 t = 1.0ms——————-舵机会转动 45° 占空比就应该为1ms/20ms = 5%,所以TIM_SetCompare1的 TIMx 捕获比较 1 寄存器值就为200-200*5% = 190
// delay_ms(1000);
// TIM_SetCompare2(TIM3, 185); //90度 t = 1.5ms
// delay_ms(1000);
// TIM_SetCompare2(TIM3, 180); //135度 t = 2.0ms
// delay_ms(1000);
// TIM_SetCompare2(TIM3, 175); //180度 t = 2.5ms
// delay_ms(1000);
// Sg90_Return(0);
// delay_ms(1000);
// Sg90_Return(45);
// delay_ms(1000);
// Sg90_Return(90);
// delay_ms(1000);
// Sg90_Return(135);
// delay_ms(1000);
// Sg90_Return(180);
// delay_ms(1000);
if(i%20==0)
{
led1=!led1;
}
delay_ms(10);
}
}
在主函数中我们控制舵机以此转过45、90、135、180度,这里之所以会注销掉这些,是因为我在工程文件中增添了通过串口控制舵机的功能,我们可以直接在串口中向单片机发送控制数据,舵机就会转过相应的角度。
先看看sg90.c文件:
void Sg90_Init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure; //输出比较结构体
GPIO_InitTypeDef GPIO_InitStructure;
/* 开启时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//初始化GPIOB端口使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//开启定时器3的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
/* 配置GPIO的模式和IO口 */
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7; // 选择你要设置的IO口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //选择输出模式(复用推挽输出)
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStructure);
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE); //设置管脚复用映射(部分重映射)
TIM_TimeBaseInitStructure.TIM_Period=199; //自动装载值 定时器定时时间计算公式
TIM_TimeBaseInitStructure.TIM_Prescaler=7199; //分频系数 ((per)*(psc+1))/Tclk
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//时钟分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //计数模式(向上计数模式)
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low; //设置输出极性(底)
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable; // 比较输出使能
TIM_OC2Init(TIM3,&TIM_OCInitStructure); //输出比较通道2初始化
TIM_Cmd(TIM3,ENABLE); //开启定时器
TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
TIM_ARRPreloadConfig(TIM3,ENABLE); //使能TIM3在ARR上的预装载寄存器允许位
}
void Sg90_Return(u8 angle)
{
u8 per;
if(angle==45)
{
per=190;
TIM_SetCompare2(TIM3, per);
}
else if(angle==90)
{
per=185;
TIM_SetCompare2(TIM3, per);
}
else if(angle==135)
{
per=180;
TIM_SetCompare2(TIM3, per);
}
else if(angle==180)
{
per=175;
TIM_SetCompare2(TIM3, per);
}
else
{
per=195;
TIM_SetCompare2(TIM3, per);
}
}
程序比较简单,就一个定时器初始化和直接输入角度的控制函数,(第二个是函数是我自己写的,比较烂,有能力的可以完善完善一起学习)主要思想还是通过TIM_SetCompare2()设置CCR的数值。
在看看如何通过串口控制:
void USART1_IRQHandler(void) //中断服务函数
{
u8 r;
u16 num;
static char pwm[4];
static int i=0;
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET) //检查串口1的接收中断是否发生
{
r=USART_ReceiveData(USART1); //返回 USART1 最近接收到的数据
if(r!='g'&&r!='m')
{
pwm[i]=r;
i++;
}
else
{
i=0;
num=(atoi(pwm));
TIM_SetCompare2(TIM3, num);
}
USART_SendData(USART1,r); //通过外设 USART1 发送单个数据
while (USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET); //检查 USART1的 发送完成标志位 设置与否
}
USART_ClearFlag(USART1,USART_FLAG_TC);
}
这里主要就是数据转化的问题,通过atoi()函数将字符串转化为整数然后再用于控制舵机。串口传输数据的时候要特别注意不同数据格式之间的转化。