使用STM32驱动舵机sg90
一、舵机原理
舵机(Servo Motor)驱动原理基于其内部的反馈控制系统,通常使用PWM(脉宽调制)信号来控制舵机的位置、速度或者扭矩。简单来说,舵机会根据输入的PWM信号的脉宽(占空比)来调整其转动角度。以下是详细的解释:
1. PWM信号控制
舵机的控制原理通常依赖于PWM信号的占空比(脉冲宽度与周期的比例)。PWM信号的周期一般固定为20ms(50Hz),但脉宽可以变化,从而控制舵机的转动角度。
- 周期:通常为20ms,即50Hz。
- 脉宽(占空比):通常在1ms到2ms之间变化,表示舵机的转动角度。
- 1ms:舵机转到0度位置。
- 1.5ms:舵机转到90度位置(中立位置)。
- 2ms:舵机转到180度位置。
2. 舵机内部结构
舵机内部通常包括以下几个关键部分:
- 电机:提供转动力矩。
- 齿轮系统:将电机的转速转换为适当的扭矩并限制角度。
- 位置传感器:通常是电位计,用来检测舵机的当前位置。
- 控制电路:接收PWM信号,并通过反馈控制系统调整电机的转动。
3. 舵机的反馈控制
舵机通过反馈系统(通常是一个电位计)将当前角度反馈给控制电路。当舵机接收到PWM信号时,内部控制系统会判断目标位置和当前角度之间的误差,并根据误差调整电机的转动,直到达到目标位置。这个过程是一个闭环控制系统。
- 目标角度:由输入的PWM信号的脉宽决定(比如1ms表示0度,1.5ms表示90度,2ms表示180度)。
- 实际角度:通过位置传感器获取并反馈给控制电路。
- 误差:目标角度和实际角度之间的差异。
- 调整:舵机根据误差调整电机的运动,直到实际角度与目标角度一致。
4. 舵机的工作过程
- 当你向舵机输入一个PWM信号时,舵机会根据脉冲宽度来判断目标位置。
- 如果舵机当前的角度与目标角度不一致,舵机会驱动电机旋转,直到角度匹配。
- 一旦到达目标位置,舵机就会停止转动并维持当前位置,直到接收到新的PWM信号。
5. 常见的舵机类型
- 模拟舵机:根据PWM信号控制角度变化,一旦电机达到目标位置就停止旋转,并维持静止状态。适用于精确的角度控制,但通常不能连续旋转(仅有固定的转动范围,通常是0°到180°)。
- 数字舵机:具有更高的精度和速度,内部控制电路更为复杂,通常能提供更高的扭矩和更精细的角度控制。
二、舵机控制(不考虑死区)
2.1 舵机控制原理(180°舵机)
PWM信号控制
舵机的控制原理通常依赖于PWM信号的占空比(脉冲宽度与周期的比例)。PWM信号的周期一般固定为20ms(50Hz),但脉宽可以变化,从而控制舵机的转动角度。
周期:通常为20ms,即50Hz。
脉宽(占空比):通常在1ms到2ms之间变化,表示舵机的转动角度。
1ms:舵机转到0度位置。
1.5ms:舵机转到90度位置(中立位置)。
总结:通过单片机设定一个20ms的PWM周期,然后通过不同的PWM占空比,让舵机转到指定位置
三、舵机死区
3.1 死区介绍
先简单看一下产品参数,这里以mg90为例,产品参数显示,死区是5微秒
5微秒死区:意味着舵机控制系统在信号输入的时间变化小于5微秒时,舵机不会做出任何响应。当PWM信号的脉宽变化不到5微秒时,舵机内部的反馈控制系统认为这是噪声或微小的波动,忽略它,不会做出位置调整
如何理解死区的存在?
死区简单理解为一个特别微小的变化,可能是主动的驱动或者被动的震动的幅度太小,不足以使舵机转动,我是把他理解为推动一个静止物体的最大静摩擦力即可,当驱动力不足以克服静止物体的最大静摩擦力时候,物体是推不动的。无论死区的原因是什么导致,尽可能大角度的控制才能让舵机精准的转动指定的角度。
3.2 软件减少死区方案
3.2.1 软件滤波(最简单)
死区通常是由于微小的PWM信号变化未能触发舵机响应。可以通过在软件中对输入信号进行平滑处理(如滤波)来减少微小信号的变化,以确保只有足够大的信号变化才会引起舵机动作。
方案:
软件滤波:使用简单的低通滤波器(如滑动平均滤波器)来平滑输入的PWM信号。这样,如果输入信号的变化小于某个阈值,舵机就不会执行任何动作。
比如,可以维护一个平滑后的PWM值列表,只有当新信号与历史平均信号有较大差异时,才调整舵机。
#define DEADZONE_THRESHOLD 10 // 死区阈值
uint16_t prev_pwm = 1500; // 之前的PWM信号
void SmoothPWM(uint16_t pwm_input) {
if (abs(pwm_input - prev_pwm) > DEADZONE_THRESHOLD) {
prev_pwm = pwm_input;
Servo_SetPWM(pwm_input); // 只有超出死区阈值时才设置新的PWM信号
}
}
优点:
能有效减少微小的信号波动对舵机的影响。
可以减少舵机不必要的调整和振动。
3.2.2 死区补偿算法
动态调整目标位置:当控制信号接近死区时,可以设定一个“死区宽容区”,使得当输入信号处于死区时,舵机不会做出响应,直到输入信号超出该范围。
#define DEADZONE_MIN 1400 // 死区下限
#define DEADZONE_MAX 1600 // 死区上限
void Servo_SetAngleWithDeadzone(uint16_t pwm_input) {
if (pwm_input < DEADZONE_MIN || pwm_input > DEADZONE_MAX) {
Servo_SetPWM(pwm_input); // 超过死区范围时才设置PWM
}
}
优点:
死区宽容区使得舵机不会响应微小的信号变化,从而避免频繁的调整和可能的振动。
提高了系统的稳定性,避免了无效的角度调整。
3.2.3 PDI反馈
PID(比例-积分-微分)控制器广泛用于舵机控制,特别是在需要精准控制位置时。为了减少死区带来的影响,可以通过调整PID参数来增加系统对微小变化的响应度,或引入反向积分等技术来避免死区效应。
做过飞思卡尔竞赛的同学应该都了解,车模车头就是一个精密舵机,通过舵机,完成快速转向,如下图车模上部是一个舵机。
想要精确控制需要对舵机增肌PID控制,通过PID补偿current角度和target的差值,这里不做详细分析。
float Kp = 1.0f; // 比例增益
float Ki = 0.0f; // 积分增益
float Kd = 0.1f; // 微分增益
void PIDControl(uint16_t target_angle, uint16_t current_angle) {
float error = target_angle - current_angle;
if (fabs(error) > DEADZONE_MIN) { // 如果误差大于死区范围才做调整
// 计算PID控制
// ...
uint16_t pwm_output = Kp * error + Ki * integral + Kd * derivative;
Servo_SetPWM(pwm_output);
}
}
四、总体代码
下面是基于stm32f1的简单舵机代码
#include "stm32f10x.h"
void TIM2_PWM_Init(void) {
// 1. 打开TIM2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 2. 打开GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 3. 配置PA0为复用推挽输出
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 4. 配置定时器2生成PWM信号
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 20000 - 1; // 频率20ms (50Hz)
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 1MHz计数频率
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 5. 配置PWM输出
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = 1500; // 初始占空比,1500us对应90°位置
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
// 6. 启动定时器
TIM_Cmd(TIM2, ENABLE);
TIM_CtrlPWMOutputs(TIM2, ENABLE); // 启用PWM输出
}
void Servo_SetAngle(uint16_t angle) {
// 将角度映射到1ms到2ms的脉冲宽度
// 0°对应1000us,180°对应2000us
uint16_t pulse_width = (uint16_t)(1000 + (angle * 1000 / 180)); // 范围1ms - 2ms
// 设置PWM脉冲宽度
TIM_SetCompare1(TIM2, pulse_width);
}
int main(void) {
// 初始化系统时钟
SystemInit();
// 初始化PWM
TIM2_PWM_Init();
while (1) {
// 设定舵机角度为90°
Servo_SetAngle(90);
// 延时
for (volatile int i = 0; i < 1000000; i++);
// 设定舵机角度为45°
Servo_SetAngle(45);
// 延时
for (volatile int i = 0; i < 1000000; i++);
// 设定舵机角度为135°
Servo_SetAngle(135);
// 延时
for (volatile int i = 0; i < 1000000; i++);
}
}
附录视频
等我拍了补上。。。。