飞道的博客

STM32 --- 舵机使用笔记(含角度控制代码)

639人阅读  评论(0)

 

一 、舵机概述

上周,抽个闲做枪手,给群友写了一份两路舵机的控制代码。感谢老铁的信任,方案双方都很满意。

代码测试时,用的两款180°舵机:MG996R 和 MG995 。 

1、舵机的分类

  • 按旋转角度

180°舵机:能给定角度、固定转速。 只能在0度到180度之间运动,超过范围,舵机轻则齿轮打坏,重则烧坏内部电路.

 360°舵机:能360度转动, 能控制转速。但不能调节转动的角度。

  • 按控制信号

模拟舵机:要持续提供PWM信号才能固定角度。优点:便宜。

数字舵机:只要给一次PWM信号就能固定角度。优点:高精度,响就快,抖动小,更大的角度固定力。

2、几个重要参数

  • 最大扭矩:如上面的MG996, 最大扭矩: 13KG/cm. 这个是在舵机堵转时测得的, 其时距离轴中心1cm处能挂起的重量.
  • 工作电压:如常用的3.0V~7.2V, 接入电压不同, 所能产生的工作扭矩自然不同. 直接影响角度固定力。

3、引脚接线说明

  • 红色:供电;电流比较大,除非测试, 真不建议在开发板上取电;

  • 粽色:地线;必须与控制器, 如SMT32芯片共地;

  • 橙色:PWM信号线;接芯片的TIMx外设的CHx脚;


二、舵机控制原理

以本次测试的180度模拟舵机为例进行笔记讲解。

1、原理简述

舵机接收的是PWM信号,能使舵机内部电路产生一个偏置电压,触发电机通过减速齿轮带动电位器移动,当电压差为零时,电机停转,从而达到伺服的效果。

即,给舵机提供一个特定的PWM信号,舵机就可以旋转到指定的位置。

2、PWM信号、角度

舵机接收的PWM信号频率为50HZ,即周期为20ms。当高电平的脉宽在0.5ms-2.5ms之间时舵机就可以对应旋转到不同的角度。

          

为了更好地理解其信号,和编写代码,把PWM关键点转换如下:

  1. PWM信号周期:       20000us 
  2. 0度时,高电平时长:    500us
  3. 180度时, 高电平时长:2500us
  4. 每增加1 °,需增加高电平时长:(2500-500)÷180 = 11.1us
  5. 某角度值A,需要的总高电平时长:(A x11.1 +500)us 

特别地说明: 把所有ms值, 转换为us值, 是为了方便代码的编写和理解. 


三、STM32代码实现

工程代码: STM32F103RC + 标准库函数v3.5;

1、所用引脚的宏定义


  
  1. / **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *
  2. ** 移植配置
  3. ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **/
  4. // 舵机_1
  5. #define SERVO_1 _GPIO GPIOB // GPIO
  6. #define SERVO_1 _PIN GPIO_Pin _8 // PIN
  7. #define SERVO_1 _TIM_PORT TIM4 // 定时器: TIMx
  8. #define SERVO_1_TIM_CH 3 // 通道: CHx
  9. #define SERVO_1 _ANGLE_RESET 90 // 上电复位后的角度
  10. // 时基配置, 适用20ms周期的舵机
  11. #define SERVO_TIM_PSC 72 // 计数器时钟=72000000/72=1000000次/秒=1us/次
  12. #define SERVO_TIM_ARR 20000 // 周期=20000*1us=20ms

为了加大代码移植的方便,取消时钟的宏定义,在初始化函数里根据所用端口做判断后使能各时钟.

2、GPIO初始化


  
  1. // GPIO配置
  2. GPIO_InitTypeDef GPIO_InitStructure;
  3. GPIO_InitStructure.GPIO_Pin = PINx;
  4. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  5. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  6. GPIO_Init(GPIOx, &GPIO_InitStructure);

引脚配置要点:复用推挽模式(GPIO_Mode_AF_PP); 

2、TIM初始化


  
  1. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  2. TIM_OCInitTypeDef TIM_OCInitStructure;
  3. // TIM时基配置
  4. TIM_TimeBaseStructure.TIM_Prescaler= (psc -1); // 驱动CNT计数器的时钟 = Fck_int/(psc+1)
  5. TIM_TimeBaseStructure.TIM_Period= (arr -1); // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
  6. TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 时钟分频因子 ,用于配置死区时间,没用到,随意
  7. TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数器计数模式,设置为向上计数
  8. TIM_TimeBaseStructure.TIM_RepetitionCounter= 0; // 重复计数器的值,没用到,可以随意设置
  9. TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure); // 初始化定时器
  10. // 输出比较模式
  11. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; // 配置为PWM模式2
  12. TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能
  13. TIM_OCInitStructure.TIM_Pulse = ccr; // 设置占空比大小
  14. TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // 输出通道电平极性配置
  15. if(CHx== 1) TIM_OC1Init(TIMx, &TIM_OCInitStructure);
  16. if(CHx== 2) TIM_OC2Init(TIMx, &TIM_OCInitStructure);
  17. if(CHx== 3) TIM_OC3Init(TIMx, &TIM_OCInitStructure);
  18. if(CHx== 4) TIM_OC4Init(TIMx, &TIM_OCInitStructure);
  19. // CCR预装载
  20. if(CHx== 1) TIM_OC1PreloadConfig(TIMx, TIM_OCPreload_Enable);
  21. if(CHx== 2) TIM_OC1PreloadConfig(TIMx, TIM_OCPreload_Enable);
  22. if(CHx== 3) TIM_OC1PreloadConfig(TIMx, TIM_OCPreload_Enable);
  23. if(CHx== 4) TIM_OC1PreloadConfig(TIMx, TIM_OCPreload_Enable);
  24. TIM_Cmd(TIMx, ENABLE); // 使能计数器
  25. //TIM_CtrlPWMOutputs(TIMx, ENABLE); // 主输出使能,当使用的是通用定时器时,这句不需要

如果没有这几行 if 语句,代码简洁。

使用这些 if 进行判断的原因, 是因为我想把PWM的初始化函数, 做成更容易复用的代码, 这样以后要初始化某个TIM做PWM输出, 那只要传入相应的参数即可: 

void TIM_PwmInit(GPIO_TypeDef* GPIOx, u16 PINx, TIM_TypeDef* TIMx, u8 CHx, u16 PSC, u16 ARR, u16 CCR);

上面的TIM初始化,是经这个函数传入参数的。

重点解释一下:PSC, ARR, CCR, CNT

  1. PSC:TIM时钟的分频系数:72;内部时钟经PSC值分频后, 传给CNT计数器使用;
  2. CNT:计数器,CNT每计数一次的脉冲时长为:1÷(CLK÷PSC)  =  1÷(72000000÷72) = 0.000001s/次 = 1us/次;
  3. ARR:自动重装载值:20000,CNT计数器经过多少次脉冲就重新开始计数。用这个值可控制需要的PWM信号周期:1us x 20000次  = 20 000us = 20ms
  4. CCR:用于控制周期内高电平时长, 当CNT<CCR时, 为有效电平. 而有效电平的高低, 则是通过CCER寄存器设置的, 默认的有效电平为高电平.

3、角度输出函数


  
  1. // 舵机1#配置角度
  2. // 【可选参数】 [angle]0.0°~180.0°;
  3. void AnalogServo_1_Angle( float angle)
  4. {
  5. u16 ccr_1= 0;
  6. xServo.angleNow_1 = angle;
  7. if(xServo.angleNow_1> 180) xServo.angleNow_1 = 180; // 限制最大值, 防止出错
  8. if(xServo.angleNow_1< 0) xServo.angleNow_1 = 2; // 限制最小值, 防止出错
  9. ccr_1 = xServo.angleNow_1* 11.11 + 500; // 把角度值, 换算成CCR值
  10. TIM_SetCCR(SERVO_1_TIM_PORT, SERVO_1_TIM_CH, ccr_1); // 配置TIMx的CCR寄存器值
  11. }
  12. // 控制TIMx的CCRx的值;【可选参数】 [TIMx]TIM1~8, [CHx]1~4, [CCR]0~65535;
  13. void TIM_SetCCR(TIM_TypeDef* TIMx, u8 CHx, u16 CCR)
  14. {
  15. if(CHx== 1) TIMx->CCR1 = CCR;
  16. if(CHx== 2) TIMx->CCR2 = CCR;
  17. if(CHx== 3) TIMx->CCR3 = CCR;
  18. if(CHx== 4) TIMx->CCR4 = CCR;
  19. }

哈,自己有个死穴,不管学习某个方面的知识时,理解得有多深刻,过一段时间后,总是会淡忘得一干二净的。

所以,这个代码做成调用时,直接传入角度即可,以后就不用回忆高电平与角度的关系了。


四、完整代码下载

工程所用STM32F103RC的核心版,完整代码,已分到CSDN的资料库。

完整代码下载: STM32_舵机PWM控制代码.zip

同时也分享了一份Q群 887199504 的文件夹,可自由下载的。


转载:https://blog.csdn.net/zhouml_msn/article/details/116142124
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场