動SG90舵機:TIM PWM頻率與占空比映射實戰(zhàn))
簡介這份源碼面向嵌入式初學者與需要快速驗證舵機控制的開發(fā)者圍繞STM32F103C8T6驅(qū)動SG90舵機這一典型場景提供可直接參考的PWM控制實現(xiàn)。資源包共4個文件以2個C源文件與1個頭文件為核心分別承擔舵機角度控制邏輯、初始化配置與接口聲明另附1張舵機接口圖輔助接線理解壓縮包約24KB體量輕便、便于移植到自己的工程中。內(nèi)容涉及定時器PWM模式配置、GPIO復用推挽輸出、預分頻與自動重裝載寄存器設置以及按角度換算脈寬占空比等關鍵環(huán)節(jié)并給出初始化與角度設定函數(shù)的組織方式方便讀者對照修改周期與CCR值。目前已有5074人學習下載適合作為機器人、遙控模型等項目中舵機驅(qū)動的入門參考也能幫助讀者理解PWM信號與角度之間的對應關系并在此基礎上擴展多路舵機或中斷更新方案。1. 從一次舵機抖動的排查說起STM32F103C8T6 驅(qū)動 SG90 到底該怎么寫很多人第一次用 STM32F103C8T6 點 SG90 舵機都會遇到同一個玄學現(xiàn)象明明代碼里寫了 50Hz 的 PWM舵機要么紋絲不動要么像抽風一樣來回抖偶爾還伴隨電源電壓被拉低導致單片機復位。問題往往不在舵機本身而在于 PWM 頻率、占空比映射和供電這三件事沒對齊。這份源碼就是圍繞這三個痛點展開的用 TIM 定時器輸出 50Hz PWM把 0.5ms2.5ms 脈寬映射成 0°180°再配合獨立的 5V 供電讓 SG90 穩(wěn)定工作。它適合剛上手 STM32 標準外設庫、想快速把舵機跑起來或者準備做機械臂、云臺、智能小車這類需要多路舵機控制的開發(fā)者。2. 項目定位與技術選型為什么是 TIM PWM 而不是軟件延時2.1 硬件與軟件棧的搭配邏輯STM32F103C8T6 是 Cortex-M3 內(nèi)核主頻 72MHz有 4 個通用定時器 TIM1TIM4其中 TIM1 是高級定時器TIM2TIM4 是通用定時器。SG90 屬于模擬舵機控制信號是周期 20ms、高電平 0.5ms2.5ms 的 PWM 波。用硬件定時器輸出 PWMCPU 只需要初始化一次之后完全不占用主循環(huán)這是它比軟件延時或軟件 PWM 更靠譜的根本原因。源碼采用 STM32 標準外設庫不是 HAL 庫。標準庫的寄存器操作更直接代碼體積小對 F103C8T6 這種 64KB Flash、20KB RAM 的芯片更友好。常見做法是把 TIM2 或 TIM3 的某個通道映射到 PA0PA3 或 PB0PB1 這類支持定時器復用的引腳上通過 GPIO 復用推挽輸出把 PWM 送出去。選型上還有幾個細節(jié)值得說清楚。第一SG90 的工作電壓是 4.8V6V而 STM32 的 IO 輸出是 3.3VPWM 信號線可以直接接但舵機電源必須單獨供 5V不能從單片機 3.3V 穩(wěn)壓器上取電。第二多路舵機時最好用同一個定時器的不同通道這樣四路 PWM 的頻率和相位天然一致代碼也好維護。第三如果項目里還要跑串口、I2C 等外設注意定時器通道和這些外設的引腳不要沖突。2.2 目錄結構與文件職責這份源碼的目錄結構比較典型按標準庫工程的慣例組織SG90_STM32F103/ ├── CMSIS/ # 內(nèi)核啟動文件與系統(tǒng)初始化 ├── FWLIB/ │ ├── inc/ # 標準外設庫頭文件 │ └── src/ # 標準外設庫源文件 ├── USER/ │ ├── main.c # 主程序舵機角度控制邏輯 │ ├── stm32f10x_it.c # 中斷服務函數(shù) │ └── system_stm32f10x.c # 系統(tǒng)時鐘配置 ├── HARDWARE/ │ ├── TIMER/ │ │ ├── timer.c # PWM 初始化與占空比設置 │ │ └── timer.h │ └── LED/ # 可選用于狀態(tài)指示 └── OBJ/ # 編譯輸出目錄USER 目錄放業(yè)務邏輯HARDWARE 目錄放外設驅(qū)動這種分層讓改舵機引腳或換定時器時只需要動 HARDWARE/TIMER 下的文件main.c 里的角度控制邏輯基本不用改。如果你要加第二路、第三路舵機擴展點也集中在這里。2.3 關鍵參數(shù)速查參數(shù)取值說明PWM 周期20ms對應 50Hz舵機標準控制頻率高電平范圍0.5ms2.5ms對應 0°180°定時器時鐘72MHzAPB1 定時器倍頻后預分頻系數(shù)72-1得到 1MHz 計數(shù)頻率自動重裝載值20000-11MHz 下計 20000 次為 20ms比較值范圍5002500對應 0.5ms2.5ms這張表是后面代碼里所有計算的依據(jù)改頻率或改角度范圍時對著它調(diào)就行。3. 核心模塊拆解TIM PWM 初始化與角度映射的完整鏈路3.1 定時器初始化從時鐘使能到 PWM 輸出先看 TIM3 通道 1 的初始化代碼這是整個項目最核心的一段#include timer.h // 通用定時器3通道1的PWM初始化 // arr: 自動重裝載值 // psc: 時鐘預分頻數(shù) void TIM3_PWM_Init(u16 arr, u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 1. 使能TIM3和GPIOA時鐘 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 2. 配置PA6為復用推挽輸出TIM3_CH1默認映射在PA6 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 3. 定時器基礎配置 TIM_TimeBaseStructure.TIM_Period arr; // 自動重裝載值 TIM_TimeBaseStructure.TIM_Prescaler psc; // 預分頻值 TIM_TimeBaseStructure.TIM_ClockDivision 0; // 時鐘分割 TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; // 向上計數(shù) TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); // 4. PWM模式配置通道1 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; // PWM模式1 TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; // 初始比較值 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; // 高電平有效 TIM_OC1Init(TIM3, TIM_OCInitStructure); // 5. 使能預裝載寄存器 TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM3, ENABLE); // 6. 啟動定時器 TIM_Cmd(TIM3, ENABLE); }這段代碼的邏輯鏈條是先開時鐘再配 GPIO 復用然后設定時器時基接著配 PWM 通道最后啟動。每一步的順序不能亂尤其是時鐘使能必須放在 GPIO 和定時器配置之前否則配置寫不進去。參數(shù)說明arr和psc由調(diào)用方傳入。在 main 里調(diào)用TIM3_PWM_Init(19999, 71)代入公式PWM頻率 72MHz / ((psc1) * (arr1))得到72000000 / (72 * 20000) 50Hz正好是舵機需要的頻率。TIM_Pulse就是比較值 CCR決定高電平時間后面改角度就是改這個值。3.2 角度到占空比的映射函數(shù)初始化完成后控制角度就是改 CCR 的值。寫一個映射函數(shù)把 0°180° 轉(zhuǎn)成 5002500 的比較值// 設置舵機角度范圍0~180度 // 角度0度對應0.5ms高電平180度對應2.5ms高電平 void SG90_SetAngle(float angle) { u16 ccr; // 限幅防止超出舵機機械行程 if (angle 0) angle 0; if (angle 180) angle 180; // 線性映射angle / 180 * (2500 - 500) 500 ccr (u16)(500 angle * (2000.0f / 180.0f)); TIM_SetCompare1(TIM3, ccr); }映射公式是線性的0° 對應 500180° 對應 2500中間按比例插值。用浮點算是為了角度分辨率更細如果項目對 Flash 和速度敏感可以改成整數(shù)運算ccr 500 angle * 2000 / 180效果一樣。限幅這一步別省。SG90 的實際機械行程可能略小于標稱的 180°如果直接給 2500 以上的值舵機會頂?shù)较尬话l(fā)出咔咔聲長時間堵轉(zhuǎn)容易燒。我一般會在調(diào)試階段先用 10° 和 170° 試確認不頂限位再放開到 0° 和 180°。3.3 主程序里的調(diào)用節(jié)奏main.c 里的邏輯很直白初始化之后循環(huán)改變角度#include stm32f10x.h #include timer.h #include delay.h int main(void) { delay_init(); // 延時函數(shù)初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); TIM3_PWM_Init(19999, 71); // 50Hz PWM while (1) { SG90_SetAngle(0); // 轉(zhuǎn)到0度 delay_ms(1000); SG90_SetAngle(90); // 轉(zhuǎn)到90度 delay_ms(1000); SG90_SetAngle(180); // 轉(zhuǎn)到180度 delay_ms(1000); } }這里有個容易被忽略的點舵機從 0° 轉(zhuǎn)到 180° 需要時間SG90 的規(guī)格是 0.1s/60°轉(zhuǎn) 180° 大約 0.3s。如果延時太短比如 100ms 就切下一個角度舵機還沒到位就收到新指令表現(xiàn)出來就是抖動或不到位。延時給 500ms 以上比較穩(wěn)妥具體看你的舵機批次。3.4 多路舵機的擴展方式如果要控制四路舵機用 TIM3 的四個通道就行PA6、PA7、PB0、PB1 分別對應 CH1CH4。初始化時把四個通道都配上然后封裝一個按通道設角度的函數(shù)// 按通道設置角度ch范圍1~4 void SG90_SetAngleCh(u8 ch, float angle) { u16 ccr; if (angle 0) angle 0; if (angle 180) angle 180; ccr (u16)(500 angle * (2000.0f / 180.0f)); switch (ch) { case 1: TIM_SetCompare1(TIM3, ccr); break; case 2: TIM_SetCompare2(TIM3, ccr); break; case 3: TIM_SetCompare3(TIM3, ccr); break; case 4: TIM_SetCompare4(TIM3, ccr); break; default: break; } }四路共用一個定時器頻率天然同步不會出現(xiàn)某路 PWM 周期漂移的問題。注意 GPIO 初始化時要把四個引腳都配上別只配一個。4. 編譯、燒錄與上電驗證從零把舵機跑起來4.1 工程編譯與下載配置源碼是標準庫工程用 Keil MDK 打開 .uvprojx 文件即可。編譯前確認兩件事一是 Options for Target 里的 Device 選的是 STM32F103C8二是 Debug 選項卡里選對了下載器常見的是 ST-Link 或 J-Link。如果用的是串口下載需要把 BOOT0 拉高、BOOT1 拉低下載完再改回 BOOT0 拉低運行。編譯輸出在 OBJ 目錄生成的 .hex 文件可以直接用下載器燒錄。如果編譯報錯找不到 stm32f10x.h檢查 C/C 選項卡里的 Include Paths 是否包含了 FWLIB/inc、CMSIS 和 USER 目錄。4.2 接線與供電接線是新手翻車最多的地方列一下SG90 線色接到說明棕色/黑色GND與單片機共地紅色5V 外部電源正極不要接單片機 3.3V橙色/黃色PA6PWM 信號共地這一步必須做否則 PWM 信號沒有參考電平舵機會亂動。5V 電源的電流能力至少 1ASG90 空載電流約 100mA堵轉(zhuǎn)能到 700mA 以上用單片機板上的 3.3V 或 USB 口供電很容易觸發(fā)復位。4.3 上電后的驗證步驟燒錄完成后按這個順序驗證先不接舵機用示波器或邏輯分析儀看 PA6 有沒有 50Hz、高電平 1.5ms 的波形初始角度 90° 時。沒有示波器的話接一個 LED 加電阻到 PA6能看到微弱閃爍說明 PWM 在輸出。然后接上舵機上電后舵機應該先轉(zhuǎn)到 90° 附近再按 main 里的節(jié)奏在 0°、90°、180° 之間循環(huán)。如果舵機不動先量 PA6 電壓萬用表測平均值應該在 0.25V1.25V 之間變化。如果電壓不變說明 PWM 沒輸出回去查定時器初始化和 GPIO 復用配置。如果電壓在變但舵機不動查供電和共地。5. 避坑與常見問題排查舵機抖動、復位、角度不準的根因5.1 舵機持續(xù)抖動或嗡嗡響現(xiàn)象舵機停在某個角度附近小幅抖動伴隨嗡嗡聲。原因PWM 信號不穩(wěn)定或者供電電壓波動導致舵機內(nèi)部比較電路反復調(diào)整。常見的是電源電流不足舵機一動電壓就掉舵機又往回找位置。解決換獨立 5V 2A 電源電源正負極到舵機之間并一個 470μF1000μF 電解電容信號線盡量短。如果還抖檢查定時器配置里TIM_OCPolarity是不是設成了 High設反了會導致脈寬邏輯顛倒。5.2 單片機上電就復位現(xiàn)象接上舵機后單片機反復重啟串口打印亂碼或程序跑飛。原因舵機啟動瞬間電流沖擊把 5V 或 3.3V 拉低觸發(fā)單片機欠壓復位。解決舵機電源和單片機電源分開供只在 GND 上共地。如果必須共用加大濾波電容并在軟件里避免上電瞬間就讓舵機大角度轉(zhuǎn)動可以先給 90° 再慢慢轉(zhuǎn)。5.3 角度和預期對不上現(xiàn)象寫 0° 舵機轉(zhuǎn)到 20°寫 180° 只到 160°。原因SG90 個體差異不同批次的脈寬范圍不完全一致或者機械安裝時舵盤沒對準。解決先做校準寫一個循環(huán)從 500 到 2500 每次加 100記錄舵機實際能到的兩個極限位置把映射公式里的 500 和 2500 換成實測值。機械上把舵盤拆下來讓舵機先轉(zhuǎn)到 90°再把舵盤裝到機械中位。5.4 多路舵機互相干擾現(xiàn)象單獨控制一路正常四路一起動就有的不動或抖動。原因四路同時啟動電流疊加電源扛不住或者 GPIO 初始化漏配了某個通道的引腳。解決電源按每路 1A 余量配四路至少 4A。軟件上避免四路同時大角度跳變可以錯開幾十毫秒依次給指令。檢查 GPIO 初始化里四個引腳是否都設成了GPIO_Mode_AF_PP。5.5 改了定時器或引腳后不工作現(xiàn)象把 TIM3 換成 TIM2或者把 PA6 換成 PA0代碼編譯通過但沒波形。原因不同定時器通道對應的默認復用引腳不同TIM2_CH1 默認在 PA0TIM3_CH1 默認在 PA6換定時器必須同時換引腳。另外 TIM2 掛在 APB1 上時鐘使能要用RCC_APB1PeriphClockCmd別寫成 APB2。解決查芯片參考手冊的復用功能映射表確認定時器通道和引腳的對應關系。如果要用重映射還得開 AFIO 時鐘并調(diào)GPIO_PinRemapConfig。6. 二次開發(fā)改造從單路舵機到多路聯(lián)動與角度平滑拿到這份源碼后最常做的改造有三個方向加路數(shù)、加平滑、加聯(lián)動。加路數(shù)前面已經(jīng)講了用同一 TIM 的多通道把SG90_SetAngle擴展成帶通道參數(shù)的版本就行。這里重點說平滑和聯(lián)動這兩個是讓舵機從「能轉(zhuǎn)」到「轉(zhuǎn)得好看」的關鍵。平滑的核心是別讓 CCR 一步跳到位而是分多次小步逼近。寫一個帶速度參數(shù)的函數(shù)// 平滑轉(zhuǎn)動到目標角度step為每步角度delay_ms為每步間隔 void SG90_MoveSmooth(float target, float step, u16 interval) { static float current 90.0f; // 記錄當前角度 if (target 0) target 0; if (target 180) target 180; while (current target - step) { current step; SG90_SetAngle(current); delay_ms(interval); } while (current target step) { current - step; SG90_SetAngle(current); delay_ms(interval); } current target; SG90_SetAngle(current); }step控制平滑度給 12 度比較自然給 10 度以上就接近直接跳。interval控制速度給 1020ms 時轉(zhuǎn)動看起來連續(xù)。注意current用 static 保存多路舵機時每路要各自維護一個當前角度變量不能共用一個。聯(lián)動改造適合機械臂或云臺場景。比如做一個兩軸云臺X 軸和 Y 軸要同時從當前位置轉(zhuǎn)到目標位置如果分別調(diào)用平滑函數(shù)會一個轉(zhuǎn)完另一個再轉(zhuǎn)看起來別扭。做法是把兩個軸的目標和當前值都拿出來在一個循環(huán)里同時更新void Gimbal_MoveTo(float tx, float ty, float step, u16 interval) { float cx 90.0f, cy 90.0f; // 當前角度實際項目里應保存為全局 while (fabs(cx - tx) step || fabs(cy - ty) step) { if (cx tx) cx step; else if (cx tx) cx - step; if (cy ty) cy step; else if (cy ty) cy - step; SG90_SetAngleCh(1, cx); SG90_SetAngleCh(2, cy); delay_ms(interval); } SG90_SetAngleCh(1, tx); SG90_SetAngleCh(2, ty); }這樣兩個軸同步逼近運動軌跡是直線插補的近似視覺效果比先后轉(zhuǎn)動好很多。如果要做圓弧插補就在循環(huán)里按角度算坐標原理一樣。驗證改造效果時我習慣用邏輯分析儀抓兩路 PWM 的波形看高電平時間是不是同步變化、有沒有跳變。沒有邏輯分析儀就用兩個舵機裝在一個十字架上肉眼觀察運動是否協(xié)調(diào)。改完平滑參數(shù)后重點看低速時有沒有卡頓、高速時有沒有過沖過沖就減小 step 或加大 interval。從那以后我每次改舵機代碼都強制先用示波器確認 50Hz 和脈寬范圍再上舵機避免把機械結構擰壞。這套流程幫我省了不少返工。希望幫到你。本文還有配套的精品資源點擊獲取