1. STM32与SG90舵机控制基础
在嵌入式开发领域,舵机控制是最基础也最实用的技能之一。SG90作为市面上最常见的小型舵机,价格低廉且性能可靠,是学习PWM控制的理想选择。我使用STM32系列单片机驱动各类舵机已有五年多经验,今天就来详细分享从硬件连接到软件调参的全套实战经验。
SG90的工作电压范围为4.8V-6V,这意味着我们可以直接用USB供电(5V)或者两节锂电池(7.4V降压到6V)来驱动。但要注意,当使用STM32开发板时,切勿直接从MCU引脚取电!舵机启动瞬间的电流峰值可能达到500mA,这会导致MCU复位甚至损坏。正确的做法是外接独立电源,仅将STM32的PWM信号线连接到舵机。
2. PWM原理深度解析
2.1 定时器工作机制
STM32的PWM生成依赖于定时器模块。以常用的TIM2为例,其时钟源通常来自APB1总线。在72MHz系统时钟下,APB1分频后为36MHz。但STM32有个精妙设计:当APB1分频系数不为1时,定时器时钟会自动×2。因此TIM2实际获得的是72MHz时钟源。
关键参数计算公式:
code复制PWM频率 = 定时器时钟 / [(PSC+1) × (ARR+1)]
占空比 = CCR / (ARR+1) × 100%
例如要生成50Hz PWM(周期20ms):
- 预分频PSC设为71(72MHz/(71+1)=1MHz)
- 自动重载ARR设为19999(1MHz×20ms-1)
这样每个计数周期为1μs,20000个周期正好20ms。
2.2 脉宽与角度换算
SG90的控制信号要求:
- 0.5ms脉宽 → 0°位置
- 1.5ms脉宽 → 90°位置
- 2.5ms脉宽 → 180°位置
转换公式:
code复制CCR值 = 500 + (目标角度/180) × 2000
比如要让舵机转到45°:
500 + (45/180)×2000 = 1000
即设置CCR=1000
3. 硬件设计要点
3.1 典型连接方案
code复制SG90 STM32
棕色线(GND) → GND
红色线(VCC) → 5V外接电源
黄色线(SIG) → PA0(TIM2_CH1)
强烈建议在电源正负极之间并联一个100μF的电解电容,这能有效抑制舵机运动时产生的电压波动。我在早期项目中曾因忽略这点,导致系统频繁复位,后来用示波器才捕捉到电源线上的毛刺。
3.2 多舵机扩展方案
当需要控制多个舵机时:
- 每个舵机单独供电,共地处理
- 信号线可复用同一个定时器的不同通道
- TIM2有4个通道(CH1-CH4)
- 对应引脚:PA0-PA3
- 若需要更多通道,可启用TIM3、TIM4等
警告:同时驱动多个舵机时,务必确保电源功率足够。我曾测试过三个SG90同时转动,峰值总电流可达1.5A!
4. 软件实现详解
4.1 定时器初始化代码
c复制void PWM_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_OCInitTypeDef TIM_OCStruct;
// 时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// GPIO配置
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 时基配置
TIM_TimeBaseStruct.TIM_Period = 19999; // ARR
TIM_TimeBaseStruct.TIM_Prescaler = 71; // PSC
TIM_TimeBaseStruct.TIM_ClockDivision = 0;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
// PWM模式配置
TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCStruct.TIM_Pulse = 1500; // 初始1.5ms(90°)
TIM_OC1Init(TIM2, &TIM_OCStruct);
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
TIM_CtrlPWMOutputs(TIM2, ENABLE);
}
4.2 角度控制函数
c复制void Set_Servo_Angle(uint16_t angle)
{
// 角度限幅
if(angle > 180) angle = 180;
// 计算CCR值
uint16_t ccr = 500 + (angle * 2000) / 180;
// 更新捕获比较寄存器
TIM_SetCompare1(TIM2, ccr);
}
5. 调试技巧与常见问题
5.1 舵机无反应排查步骤
- 检查电源电压(4.8-6V)
- 用万用表测量信号线电压(应有5V PWM)
- 确认定时器配置正确(示波器观察波形)
- 检查舵机线序是否接反
5.2 运动不平稳优化方案
- 在代码中添加渐变函数,避免角度突变
c复制void Smooth_Move(uint16_t target_angle, uint16_t steps) { uint16_t current = TIM_GetCapture1(TIM2); int16_t increment = (target_angle - current) / steps; while(steps--) { current += increment; Set_Servo_Angle(current); Delay_ms(20); } } - 在机械结构上加装减震垫片
- 提高PWM刷新率(但不超过100Hz)
5.3 位置精度校准
由于舵机存在个体差异,建议进行终点校准:
- 发送0°信号,标记实际位置
- 发送180°信号,标记实际位置
- 在代码中调整CCR范围补偿误差
6. 进阶应用实例
6.1 机械臂控制
通过多路PWM协调控制:
c复制void Arm_Set_Position(uint16_t base, uint16_t elbow, uint16_t wrist)
{
Set_Servo_Angle(TIM2_CH1, base); // 底座旋转
Set_Servo_Angle(TIM3_CH1, elbow); // 肘关节
Set_Servo_Angle(TIM3_CH2, wrist); // 腕关节
Delay_ms(500); // 等待运动完成
}
6.2 轨迹规划算法
实现圆弧插补运动:
c复制void Circular_Move(uint16_t center_x, uint16_t center_y, uint16_t radius, uint16_t duration)
{
for(uint16_t t=0; t<duration; t++) {
float angle = 2*PI*t/duration;
uint16_t x = center_x + radius * cos(angle);
uint16_t y = center_y + radius * sin(angle);
Set_Servo_Angle(TIM2_CH1, x);
Set_Servo_Angle(TIM2_CH2, y);
Delay_ms(20);
}
}
7. 性能优化建议
- 使用DMA自动更新CCR值,减轻CPU负担
- 启用定时器预装载功能,避免PWM抖动
- 对于需要快速响应的应用,可以:
- 提高PWM频率到100Hz
- 减小运动角度范围(如0-90°)
- 选用更高性能的舵机(如MG996R)
经过这些优化后,在我的一个工业分拣项目中,舵机响应时间从原来的200ms缩短到了80ms。