1. 舵机控制基础与硬件准备
SG90舵机是最常见的微型舵机之一,价格亲民且性能稳定,广泛用于机器人关节、遥控模型等场景。它的工作电压通常在4.8V-6V之间,扭矩约为1.6kg·cm,转动角度范围是0-180度。我实测过市面上几种不同厂家的SG90,发现虽然外观略有差异,但控制协议都是通用的PWM信号。
舵机的控制原理其实很简单:通过20ms周期的PWM信号,用高电平的宽度来指定角度。具体来说:
- 0.5ms高电平对应0度
- 1.5ms高电平对应90度
- 2.5ms高电平对应180度
这个关系是线性的,所以1.0ms对应45度,2.0ms对应135度,依此类推。实际使用中我发现,有些廉价舵机可能存在±10度的误差,这时需要通过校准来修正。
硬件连接非常简单:
- 舵机红线接STM32开发板的5V输出
- 棕线接GND
- 橙线(信号线)接任意GPIO口(我们后面会配置为PWM输出)
特别注意:如果同时驱动多个舵机,建议使用外部电源供电,避免开发板稳压芯片过热。我之前用F103C8T6同时驱动4个舵机时,就遇到过板载稳压器发烫的问题。
2. CubeMX工程创建与时钟配置
打开CubeMX新建工程,选择STM32F103C8T6芯片。这个蓝色小开发板应该是国内最流行的STM32入门板了,价格不到20元但功能齐全。在Pinout视图下,我们先做几个关键配置:
RCC配置:
- High Speed Clock (HSE) 选择 Crystal/Ceramic Resonator
- 这样可以使用外部8MHz晶振获得更精确的时钟
SYS配置:
- Debug 选择 Serial Wire
- 这样可以通过ST-Link进行调试
时钟树配置:
- 将HSE设为时钟源
- PLLCLK使能,倍频到72MHz
- APB1 Prescaler设为2(36MHz)
- APB2 Prescaler设为1(72MHz)
这里有个容易踩的坑:定时器挂在APB1总线上,但APB1最大时钟是36MHz。如果设置错误,会导致PWM频率计算完全不对。我刚开始学习时就因为这个问题调试了半天。
3. 定时器PWM配置详解
SG90需要50Hz的PWM信号(周期20ms),我们使用TIM2的Channel1来生成。在CubeMX中:
- 找到TIM2,选择Clock Source为Internal Clock
- 将Channel1设为PWM Generation CH1
- 参数配置:
- Prescaler: 72-1 (将72MHz分频为1MHz)
- Counter Period: 20000-1 (1MHz时钟下计数20000次正好20ms)
- Pulse: 初始设为1500 (1.5ms,对应90度)
- CH Polarity: High
关键计算:
- 输入时钟 = 72MHz
- 分频后 = 72MHz / (72) = 1MHz
- 周期 = (20000) / 1MHz = 20ms
- 频率 = 1/20ms = 50Hz
这样配置后,改变Pulse值就能控制舵机角度:
- Pulse=500 → 0.5ms → 0度
- Pulse=2500 → 2.5ms → 180度
建议在CubeMX的Configuration标签下开启TIM2全局中断,方便后续添加控制逻辑。
4. 代码实现与角度控制函数
生成代码后,在main.c中添加我们的控制逻辑。首先在main函数初始化后启动PWM:
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);然后添加角度控制函数:
void SetServoAngle(uint16_t angle) { // 将角度(0-180)转换为Pulse值(500-2500) uint16_t pulse = 500 + (angle * 2000) / 180; __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse); }这个函数比原始文章的更加健壮,做了数值范围保护。实际使用时可以这样调用:
while(1) { SetServoAngle(0); // 转到0度 HAL_Delay(1000); SetServoAngle(90); // 转到90度 HAL_Delay(1000); SetServoAngle(180); // 转到180度 HAL_Delay(1000); }如果发现舵机转动不顺畅,可以尝试在角度变化时添加缓动效果:
void SmoothMove(uint16_t start, uint16_t end, uint16_t step) { if(start < end) { for(uint16_t i=start; i<=end; i+=step) { SetServoAngle(i); HAL_Delay(20); } } else { for(uint16_t i=start; i>=end; i-=step) { SetServoAngle(i); HAL_Delay(20); } } }5. 常见问题排查与优化
问题1:舵机抖动或不转动
- 检查电源是否足够(建议用示波器看5V电压)
- 确认PWM信号频率确实是50Hz
- 检查信号线连接是否牢固
问题2:角度不准确
- 可能是舵机本身精度问题,尝试校准:
// 找到实际的0度和180度对应Pulse值 #define PWM_MIN 480 // 实测0度值 #define PWM_MAX 2520 // 实测180度值
问题3:多个舵机控制
- 每个舵机需要单独的定时器通道
- 或者使用一个定时器的多个通道
- 注意总电流不要超过电源供应能力
性能优化建议:
- 使用DMA方式更新PWM值,减少CPU开销
- 对于需要快速响应的应用,可以适当提高PWM频率(但不要超过100Hz)
- 在电池供电场景下,可以在舵机到达目标位置后关闭PWM输出以省电
6. 进阶应用:外部控制与反馈
实际项目中,我们通常需要通过串口、ADC或按键来控制舵机。这里给出一个串口控制的例子:
void UART_Control() { uint8_t rxData[10]; if(HAL_UART_Receive(&huart1, rxData, 1, 100) == HAL_OK) { uint8_t angle = atoi((char*)rxData); if(angle <= 180) { SetServoAngle(angle); printf("Set angle to: %d\n", angle); } } }然后在main循环中调用这个函数即可。通过这种扩展,你可以轻松实现:
- 电位器实时控制(ADC读取)
- 手机蓝牙控制(HC-05模块)
- 多舵机协同动作(存储动作序列)
我在一个机械臂项目中就使用了类似方案,通过JSON指令控制6个舵机协同工作,效果非常稳定。关键是要确保每次PWM更新之间有足够的时间间隔,避免电源电压骤降。