1.舵机到底要的是什么信号?
想象舵机就像一个"听秒表的工人":
这个工人每隔 20ms 就抬头看看秒表一次。
秒表上的 高电平持续多久,他就把这个时间当成"指令角度"。
高 1ms → 转到最左(0°)
高 1.5ms → 转到中间(90°)
高 2ms → 转到最右(180°)
所以:舵机只关心"脉宽多少微秒",不关心占空比,也不关心电平是多少次。
它就像在量高电平的时间长度。
为什么舵机用 PWM 控制?
舵机的内部
常见 3 线舵机(信号、VCC、电源地)内部是:电机 + 减速齿轮 + 电位器反馈 + 控制器。
外部只给它一个周期固定、脉宽可变的控制脉冲,舵机内部会把"脉宽"当作目标角度,做闭环去转到位。
舵机"吃"的信号
帧周期:通常 20 ms(50 Hz)最通用;很多舵机允许 40--300 Hz,数字舵机还能更高,但50 Hz 是最安全默认。
脉宽:常见 1.0 ms ≈ 0°、1.5 ms ≈ 90°、2.0 ms ≈ 180°(具体要以舵机说明书/实测为准;有些是 0.5--2.5 ms)。
注意:这是"定周期的脉宽控制",不是单纯追求占空比。我们用定时器 PWM 正是为了精准到微秒地生成这个脉宽。
为什么用 PWM?
PWM(脉宽调制)就是 MCU 内部"定时器"自动帮我们生成一个方波信号:
周期固定:20ms 一次。
高电平可调:可以是 1000us、1500us、2000us......
不用自己 while(1) 里 delay 搞波形,定时器硬件自动输出,非常精准。
所以我们用 定时器 + PWM 模式,就是为了让 MCU 硬件替我们"定点报时"。
2.在 STM32 里怎么做?
MCU 的"定时器"就像一个秒表:
PSC(预分频器):决定秒表走得快还是慢。
ARR(自动重装载):决定多少数后清零 → 就是周期。
CCR(比较寄存器):决定在哪个数的时候翻转电平 → 就是脉宽。
举例(假设 STM32 主频 72MHz):
让秒表每 1 微秒加 1(PSC=71)。
设置 ARR=20000 → 秒表数到 20000 就清零 → 周期 20ms。
设置 CCR=1500 → 秒表数到 1500 的时候输出翻转 → 高电平 1.5ms。
这样我们就得到了一个"周期 20ms、高电平 1.5ms"的 PWM,舵机看到它,就会乖乖转到 90°。
把角度变成脉宽
角度和脉宽是线性映射:
脉宽(us)=1000+(角度/180)×(2000−1000),比如:
0° → 1000us
90° → 1500us
180° → 2000us
所以我们只要给定一个角度,就能算出对应的脉宽,然后设置到 CCR 寄存器。
为什么必须这样做?
因为舵机内部的电路就是按照"高电平时间长度"来解码的。
如果你给它随便一个 PWM(比如频率 1kHz,占空比 10%),它根本不懂,会乱抖。
只有20ms 周期 + 特定的脉宽,舵机才能正确理解。
举个直观的比喻
想象你和舵机在玩手电筒信号:每 20 秒我都给你亮一次手电筒。如果亮 1 秒,你就走到左边。如果亮 1.5 秒,你就走到中间。如果亮 2 秒,你就走到右边。舵机就是这么傻,但很稳定。
3.举例具体代码案例
主函数 main.c
cpp
OLED_Init(); // 初始化 OLED 显示屏
Servo_Init(); // 初始化舵机(其实就是初始化 PWM)
Key_Init(); // 初始化按键
OLED_ShowString(1,1,"Angle:");
while (1)
{
KeyNum = Key_GetNum(); // 读按键
if(KeyNum == 1)
{
Angle += 30; // 每按一下 +30°
if(Angle > 180)
{
Angle = 0; // 超过180°回到0°
}
}
Servo_SetAngle(Angle); // 把角度换成 PWM 脉宽
OLED_ShowNum(1, 7, Angle, 3); // 显示角度
}
按一次按键 → Angle += 30 → Servo_SetAngle(Angle) → 改 PWM 脉宽 → 舵机转动。
PWM_Init()
cpp
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // TIM2_CH2 -> PA1
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
STM32 主频 72 MHz
PSC=72-1 → 72 分频 → 定时器时钟 = 1 MHz(1 tick = 1us)
ARR=20000-1 → 每数到 20000 清零 → 周期 = 20000us = 20ms 20ms 就是舵机需要的 PWM 周期。
cpp
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM1 模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; // CCR 初值
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
输出 PWM1 模式:计数器 < CCR → 高电平
计数器 ≥ CCR → 低电平
这样一来,高电平的长度就取决于 CCR。
PWM_SetCompare2()
cpp
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2, Compare);
}
直接改 CCR2,控制高电平时间。
比如 Compare=1500 → 高电平 1500us → 舵机转到 90°。
Servo_SetAngle()
cpp
void Servo_SetAngle(float Angle)
{
PWM_SetCompare2(Angle/180.0f*2000 + 500);
}
把角度(0°~180°)映射到脉宽:
Angle=0 → CCR=500 → 500us
Angle=180 → CCR=2500 → 2500us
这就是一个 500~2500us 的范围。
(有些舵机要求 1000~2000us,我这里用了 500~2500us,表示支持的角度范围更大,实际可能会超出舵机物理限制,要注意一下)。
总结一下
参数/函数 | 作用 | 对应舵机需求 |
---|---|---|
PSC=72-1 | 定时器分频,得到 1MHz 计数频率 | 1 tick = 1us |
ARR=20000-1 | 自动重装载,周期 20000us | 20ms 周期 |
CCR2 | 捕获比较寄存器 | 控制高电平时间(us) |
PWM1 模式 | 输出模式 | 高电平持续到 CCR,符合舵机信号 |
Servo_SetAngle() | 角度→脉宽换算 | 0°=500us,180°=2500us |
PA1 (TIM2_CH2) | PWM 输出引脚 | 信号送到舵机 |
4.注意:普通舵机(角度型) vs 连续旋转舵机(360°型)
特性 | 普通舵机(角度舵机) | 连续旋转舵机(360°舵机) |
---|---|---|
控制信号周期 | 20ms(50Hz) | 20ms(50Hz) |
PWM脉宽含义 | 表示目标角度 | 表示旋转方向和速度 |
典型控制范围 | 0.5ms ~ 2.5ms ≈ 0°~180° | 1.0ms ~ 2.0ms = 速度控制(1.5ms停止) |
1.0ms信号 | 接近 0°位置 | 反转(中速) |
1.5ms信号 | 中间角度(≈90°) | 停止 |
2.0ms信号 | 接近 180°位置 | 正转(中速) |
反馈机制 | 内部电位器 → 有角度反馈 | 无反馈,类似直流电机 |
能否定位 | 可以,转到角度后会自动停 | 不可以,只能连续旋转 |
控制目标 | 绝对角度 | 转速 + 方向 |
主要用途 | 机械臂、云台、模型舵面 | 小车驱动轮、电机替代 |
代码实现公式 | PWM = (Angle/180)*2000 + 500 |
PWM = 1500 + speed*5 (speed=-100~100) |
具体连续旋转舵机,之后会详细说明(这边可以留意一下)