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
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()
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 周期。
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()
void PWM_SetCompare2(uint16_t Compare)
{TIM_SetCompare2(TIM2, Compare);
}
直接改 CCR2,控制高電平時間。
比如 Compare=1500 → 高電平 1500us → 舵機轉到 90°。
Servo_SetAngle()
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) |
具體連續旋轉舵機,之后會詳細說明(這邊可以留意一下)