第一次接触舵机时,我盯着那个三线接口的小盒子完全摸不着头脑。直到拆开一个报废的SG90,才发现里面的结构比想象中简单:核心就是直流电机+减速齿轮组+电位器反馈电路。这个精巧的机械结构决定了它和普通电机的本质区别——舵机不是靠持续通电转动,而是通过脉冲信号精确定位。
SG90的PWM控制有个黄金法则:50Hz频率+脉宽调制。实测下来,0.5ms脉宽对应0度,1.5ms对应90度,2.5ms对应180度(不同品牌可能有微小差异)。这里有个新手容易误解的点:占空比计算要基于20ms周期。比如1.5ms脉宽的实际占空比是7.5%,而不是直接看脉宽绝对值。
我用示波器抓取了典型控制信号:
注意:舵机供电电压会影响扭矩但不会改变角度映射关系,实测5V供电时扭矩约1.6kg·cm,降到4.8V时扭矩衰减明显
STM32的定时器就像瑞士军刀,TIM1/TIM8这类高级定时器更是功能强大。以TIM8为例,配置PWM输出要搞懂三个核心参数:
计算周期有个实用公式:
code复制PWM周期 = (Prescaler+1) × (Period+1) / 时钟频率
拿20ms周期举例,如果时钟72MHz:
实际调试时我习惯先用STM32CubeMX可视化配置,再微调代码。比如发现TIM8的刹车功能会影响输出时,需要特别关注BDTR寄存器的MOE位。
先分享一个我优化过的初始化函数,增加了错误处理和参数校验:
c复制void TIM8_PWM_Init(uint32_t pulse_width_us) {
// 参数安全检查
assert_param(pulse_width_us <= 2500 && pulse_width_us >=500);
TIM_TimeBaseInitTypeDef TIM_BaseStruct = {0};
TIM_OCInitTypeDef TIM_OCStruct = {0};
GPIO_InitTypeDef GPIO_Struct = {0};
// 开启外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8 | RCC_APB2Periph_GPIOC, ENABLE);
// 配置PC6为复用推挽输出
GPIO_Struct.GPIO_Pin = GPIO_Pin_6;
GPIO_Struct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_Struct);
// 定时器基础配置
TIM_BaseStruct.TIM_Prescaler = 7199; // 72MHz/7200=10kHz
TIM_BaseStruct.TIM_Period = 199; // 200个计数=20ms
TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM8, &TIM_BaseStruct);
// PWM通道配置
TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCStruct.TIM_Pulse = (pulse_width_us/100); // 转换为计数单位
TIM_OC1Init(TIM8, &TIM_OCStruct);
// 使能预装载和主输出
TIM_OC1PreloadConfig(TIM8, TIM_OCPreload_Enable);
TIM_CtrlPWMOutputs(TIM8, ENABLE);
TIM_Cmd(TIM8, ENABLE);
}
主函数里我推荐使用角度映射函数,比直接写占空比更直观:
c复制float map_angle_to_pulse(uint8_t angle) {
// 角度限幅
angle = angle > 180 ? 180 : angle;
// 线性映射:0.5ms(0°) ~ 2.5ms(180°)
return 500 + angle * (2000/180);
}
int main(void) {
HAL_Init();
TIM8_PWM_Init(map_angle_to_pulse(90)); // 初始位置90度
while(1) {
for(uint8_t ang=0; ang<=180; ang+=45) {
uint32_t pulse = map_angle_to_pulse(ang);
TIM_SetCompare1(TIM8, pulse/100);
HAL_Delay(1000);
}
}
}
烧录代码后舵机没反应?别急,按这个checklist逐步排查:
电源问题:
信号问题:
代码问题:
遇到舵机抖动时,可以尝试:
有一次我遇到舵机只能单向转动,最终发现是TIM_OCPolarity配置错误。这种硬件问题最有效的调试方法就是用已知正常的代码逐步替换现有代码。
当需要控制多个舵机时,直接扩展定时器通道是最可靠的方式。以TIM1为例,它可以同时输出4路PWM:
c复制// 同时初始化通道1-4
TIM_OC1Init(TIM1, &TIM_OCStruct);
TIM_OC2Init(TIM1, &TIM_OCStruct);
TIM_OC3Init(TIM1, &TIM_OCStruct);
TIM_OC4Init(TIM1, &TIM_OCStruct);
如果定时器资源紧张,可以考虑PWM+延时方案。但实测下来这种方法会导致舵机响应变慢,建议优先级:
对于机械臂这类多自由度系统,我习惯用结构体管理舵机状态:
c复制typedef struct {
uint8_t current_angle;
uint8_t target_angle;
TIM_TypeDef* timer;
uint32_t channel;
} Servo_Instance;
void servo_update(Servo_Instance* s) {
uint32_t pulse = map_angle_to_pulse(s->target_angle);
switch(s->channel) {
case 1: TIM_SetCompare1(s->timer, pulse); break;
case 2: TIM_SetCompare2(s->timer, pulse); break;
// 其他通道...
}
s->current_angle = s->target_angle;
}
最后提醒:长时间保持舵机在极限角度会加速齿轮磨损,实际项目中建议设置5°-175°的安全区间。遇到堵转情况要立即切断电源,我在一次机械臂项目中烧毁了三个舵机才学会这个教训