舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。目前在高档遥控玩具,如航模,包括飞机模型,潜艇模型;遥控机器人中已经使用得比较普遍。舵机是一种俗称,其实是一种伺服马达。
控制信号由接收机的通道进入信号调制芯片,获得直流偏置电压。它内部有一个基准电路,产生周期为20ms,宽度为1.5ms的基准信号,将获得的直流偏置电压与电位器的电压比较,获得电压差输出。最后,电压差的正负输出到电机驱动芯片决定电机的正反转。当电机转速一定时,通过级联减速齿轮带动电位器旋转,使得电压差为0,电机停止转动。当然我们可以不用去了解它的具体工作原理,知道它的控制原理就够了。就象我们使用晶体管一样,知道可以拿它来做开关管或放大管就行了,至于管内的电子具体怎么流动是可以完全不用去考虑的。
舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度控制脉冲部分。以180度角度伺服为例,那么对应的控制关系是这样的:
时间 | 角度 |
---|---|
0.5ms | 0度 |
1ms | 45度 |
1.5ms | 90度 |
2.0ms | 135度 |
2.5ms | 180度 |
单片机系统实现对舵机输出转角的控制,必须首先完成两个任务:首先是产生基本的PWM周期信号,本设计是产生20ms的周期信号;其次是脉宽的调整,即单片机模拟PWM信号的输出,并且调整占空比。
当系统中只需要实现一个舵机的控制,采用的控制方式是改变单片机的一个定时器中断的初值,将20ms分为两次中断执行,一次短定时中断和一次长定时中断。这样既节省了硬件电路,也减少了软件开销,控制系统工作效率和控制精度都很高。
具体的设计过程:例如想让舵机转向左极限的角度,它的正脉冲为2ms,则负脉冲为20ms-2ms=18ms,所以开始时在控制口发送高电平,然后设置定时器在2ms后发生中断,中断发生后,在中断程序里将控制口改为低电平,并将中断时间改为18ms,再过18ms进入下一次定时中断,再将控制口改为高电平,并将定时器初值改为2ms,等待下次中断到来,如此往复实现PWM信号输出到舵机。用修改定时器中断初值的方法巧妙形成了脉冲信号,调整时间段的宽度便可使伺服机灵活运动。
为保证软件在定时中断里采集其他信号,并且使发生PWM信号的程序不影响中断程序的运行(如果这些程序所占用时间过长,有可能会发生中断程序还未结束,下次中断又到来的后果),所以需要将采集信号的函数放在长定时中断过程中执行,也就是说每经过两次中断执行一次这些程序,执行的周期还是20ms。
舵机主要使用PWM信号进行控制,需要用到定时器输出PWM信号,这里使用的是定时器3和定时器4,没有使用复用功能,具体的引脚如下图:
sg90.c
#include "sg90.h"
#include "usart.h"
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO结构体 需要用结构体类型参数时候一定要先在前面定义
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义TIMx定时器结构体
TIM_OCInitTypeDef TIM_OCInitStructure; //定义定时器脉宽调制结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3|RCC_APB1Periph_TIM4,ENABLE); //使能TIM3、4时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);//使能GPIOA时钟和GPIOB时钟
// GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE); //TIM3部分重映射 TIM3_CH2->PB5
//设置该引脚为复用输出功能,输出TIM3、4 的PWM脉冲波形 GPIOB.0、1、6、7、8、9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; //配置输出速率
GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化GPIOB
//设置该引脚为复用输出功能,输出TIM3 的PWM脉冲波形 GPIOA 6、7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; //配置输出速率
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化GPIOA
//初始化TIM3
TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载寄存器周期的值 arr=value-1
TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置预分频值 psc=value-1
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure); //初始化TIMx时间基数
//初始化TIM4
TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载寄存器周期的值 arr=value-1
TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置预分频值 psc=value-1
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseStructure); //初始化TIMx时间基数
//初始化TIM3 Channel2 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1(改变占空比)
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能比较输出
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC1Init(TIM3,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC1
TIM_OC2Init(TIM3,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
TIM_OC3Init(TIM3,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
TIM_OC4Init(TIM3,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
//初始化TIM4 Channel2 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能比较输出
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC1Init(TIM4,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC1
TIM_OC2Init(TIM4,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC2
TIM_OC3Init(TIM4,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC2
TIM_OC4Init(TIM4,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC2
// TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIM3
TIM_Cmd(TIM4, ENABLE); //使能TIM4
}
main.c
int main(void)
{
vu8 key=0;
delay_init(); //延时函数初始化
LED_Init(); //初始化与LED连接的硬件接口
KEY_Init(); //初始化与按键连接的硬件接口
PWM_Init(199,7199); //舵机初始化 不分频。PWM频率=72000000/900=80Khz
LED0=0; //先点亮红灯
//计算,假设PWM周期设置为20ms
//公式为:溢出时间Tout=(arr+1)*(psc+1)/Tclk,Tclk为通用定时器的时钟,如果APB1没有分频,则就为系统时钟,72MHZ
while(1)
{
key=KEY_Scan(0); //得到键值
if(key)
{
switch(key)
{
case WKUP_PRES: //控制舵机
TIM_SetCompare1(TIM4,5);//定时器4通道1---0度
delay_ms(1000);
TIM_SetCompare1(TIM4,15);//定时器4通道1---90度
delay_ms(1000);
break;
case KEY1_PRES: //控制LED1翻转
LED1=!LED1;
break;
case KEY0_PRES: //同时控制LED0,LED1翻转
LED0=!LED0;
LED1=!LED1;
break;
}
}else delay_ms(10);
}
}
舵机工程代码