配置詳解)
簡介基于STM32F103C8T6高級定時器TIM1的查表法實現(xiàn)三相六路互補SPWM輸出信號相位互差120°、帶死區(qū)支持調(diào)頻調(diào)壓并采用恒壓頻比策略驅動三相電機。資源面向嵌入式與電機控制開發(fā)者配套KEIL5工程包含詳細注釋可在線仿真直接觀察六路PWM波形。包體內(nèi)共149個文件、約46.67MB以C/H源程序、HEX/AXF編譯與調(diào)試產(chǎn)物、PDF原理文檔為主另有正弦表生成工具和死區(qū)計算器覆蓋從原理到實踐的完整鏈路。目前已有8067人學習下載適合需要快速搭建三相SPWM驅動并深入理解死區(qū)與查表原理的工程師和學生。1. 一個定時器搞定的三相逆變STM32F103C8T6 的 SPWM 工程能干什么接手過一個三相變頻電源的預研任務最初的想法是至少三個定時器分工一個出頻率、一個出占空比、一個做保護。看完這份基于 STM32F103C8T6 的 SPWM 三相六路互補輸出工程后發(fā)現(xiàn)一塊最便宜的 C8T6 最小系統(tǒng)板加一個高級定時器 TIM1就把正弦波調(diào)制、互補輸出、死區(qū)插入、調(diào)頻調(diào)壓全做完了甚至還能在 KEIL5 里直接仿真跑波形。這工程適合兩類人一是剛接觸電機控制和逆變器、想把 SPWM 原理落到寄存器層面的初學者二是手上只有 C8T6 想快速出六路 PWM 波形做算法驗證的開發(fā)者。讀完這篇你能知道它的調(diào)制策略怎么選、調(diào)頻調(diào)壓各自動哪些寄存器、KEIL5 仿真怎么配以及把開環(huán)改成 V/F 曲線控制該從哪下手。2. 技術選型與整體結構為什么是高級定時器 TIM1 加軟件查表法2.1 硬件資源與引腳分配三對互補通道到底怎么布局STM32F103C8T6 的定時器資源里只有 TIM1 是高級定時器它帶 6 個獨立的比較/捕獲通道映射到 PA8、PA9、PA10 和 PB13、PB14、PB15 上天然形成三對互補輸出而且硬件層面直接支持死區(qū)插入和剎車功能。通用定時器 TIM2、TIM3、TIM4 沒有互補輸出要做三相橋必須外接反相器和延時電路可靠性差一截所以選 TIM1 不是偏好是性價比最優(yōu)解。引腳分配上本工程把三對互補通道按「U、V、W 三相」劃分PA8/PA13 對應 U 相上橋和下橋PB13/PB14 對應 V 相PB15/PA10 對應 W 相。這里有個容易被忽略的細節(jié)PA13、PA14、PA15 默認是 SWD 下載引腳本工程卻沒把它們當普通 IO 用把 U 相下橋放到 PA13 而不是復用通道對應的默認引腳就是為了燒錄調(diào)試時不被仿真器占用沖突。實際做板時如果要換成標準引腳硬件上必須把跳線改掉軟件里也要同步改 CCR 通道映射。TIM1 還有一個獨有的特征它的時基單元里 ARR、PSC、CCR 都有影子寄存器意味著這些寄存器不是寫入立即生效而是在更新事件到達時才被硬件鎖存。這個特征對 SPWM 有雙重意義好的方面是輸出不會有撕裂的毛刺壞的方面是調(diào)頻調(diào)壓時更新時刻沒對齊會導致周期跳變。后面核心代碼部分會專門講怎么規(guī)避。2.2 軟件結構從正弦表到三相橋的完整數(shù)據(jù)流整體軟件分四層最頂層是調(diào)制策略層它決定「當前要輸出多大頻率、多大電壓」這層就是調(diào)頻調(diào)壓算法的核心也是后續(xù)做 V/F 控制要擴展的地方。第二層是波形生成層負責把目標頻率換算成定時器的 PSC、ARR 值把目標電壓換算成比較寄存器 CCR 的值并寫入 TIM1。第三層是硬件適配層封裝了 GPIO 復用配置、TIM1 通道配置、死區(qū)計算等寄存器操作。最底層就是標準外設庫。我把這個工程下載下來后大概掃了一遍目錄結構是這樣分的STM32F103C8T6_SPWM/ │ main.c // 主循環(huán)啟動調(diào)制處理按鍵調(diào)頻調(diào)壓 │ stm32f10x_it.c // 中斷更新事件標志故障標志歸零 ├─ App/ │ ├─ spwm.c // SPWM 核心正弦表、調(diào)頻、調(diào)壓入口 │ └─ spwm.h // 對外接口SPWM_Init / SPWM_SetFreq / SPWM_SetDuty ├─ Driver/ │ ├─ tim1_config.c // TIM1 時基、互補輸出、死區(qū)、剎車配置 │ └─ gpio_config.c // PA/PB 復用推挽配置 ├─ StdPeriph/ │ ├─ src/ // 標準外設庫源碼 │ └─ inc/ └─ Output/ └─ SPWM.hex // 可直接燒錄的固件這個分層不算復雜但每個文件職責很清晰改調(diào)頻算法不會碰到底層定時器初始化換芯片平臺時只需重寫 Driver 層App 層算法代碼幾乎不用動。2.3 為什么不用硬件 DAC 和 SVPWM調(diào)制策略的選擇邏輯有人可能會問F103 也有 DAC直接把正弦波送到外部比較器不也能出 SPWM確實能但那是模擬方案溫漂和噪聲問題都很麻煩而且外部還需要電平轉換電路遠不如數(shù)字 PWM 直接驅動功率管方便。SVPWM 在母線電壓利用率上比 SPWM 高約 15%控制異步電機時優(yōu)勢明顯但它的扇區(qū)判斷和矢量作用時間計算比較復雜代碼量是查表法的三倍以上。這工程定位是「先把三相橋轉起來、把調(diào)頻調(diào)壓的框架跑通」用查表 SPWM 是投入產(chǎn)出比最高的選擇。查表法本身也有講究工程里采用的是規(guī)則采樣正弦表長度取 256 點和定時器 ARR 的 255 對齊一個載波周期里更新一次比較值。256 點對基波來說足夠平滑諧波分量主要集中在載波頻率及其邊帶對大多數(shù)電機負載不敏感。如果換成 512 點波形質(zhì)量提升有限但查表和比較中斷的負擔翻倍所以 256 是工程上權衡后的典型值。3. 定時器配置與死區(qū)計算六路 PWM 從初始化到輸出的完整鏈路3.1 TIM1 時基和通道初始化載波頻率怎么定先看最關鍵的一段初始化代碼它決定了 PWM 的載波頻率、計數(shù)模式以及六個通道的工作方式。工程里把載波頻率定為 8kHz對于 16 位的 ARR 來說余量很充足。void TIM1_SPWM_Init(uint16_t arr, uint16_t psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; TIM_BDTRInitTypeDef TIM_BDTRInitStructure; // 時基配置向上計數(shù)ARR 決定載波周期 TIM_TimeBaseStructure.TIM_Prescaler psc; // 預分頻72MHz 時鐘除以 psc1 TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period arr; // 自動重載值 TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); // PWM 模式OC1、OC2、OC3 各自配置為 PWM2 模式 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM2; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OutputNState TIM_OutputNState_Enable; TIM_OCInitStructure.TIM_Pulse arr / 2; // 初始 50% 占空比 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCNPolarity TIM_OCNPolarity_High; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_OC2Init(TIM1, TIM_OCInitStructure); TIM_OC3Init(TIM1, TIM_OCInitStructure); }這段代碼的要點是 PWM2 模式的選擇。PWM2 模式下當計數(shù)器值小于 CCR 時輸出有效電平大于 CCR 時輸出無效電平而 PWM1 模式正好相反。選 PWM2 是為了讓 CCR 數(shù)值增大時占空比增大調(diào)壓邏輯更直觀。另一個細節(jié)是 TIM_OutputNState_Enable它使能了互補輸出通道也就是 CH1N、CH2N、CH3N 這三路下橋 PWM這是普通定時器根本不存在的寄存器位。3.2 死區(qū)配置一個寄存器位搞錯就燒功率管死區(qū)插入是高級定時器最實用的功能它保證了同一橋臂上下管在切換瞬間不會同時導通。硬件實現(xiàn)原理是在互補輸出的上升沿插入一段延遲這段延遲由 BDTR 寄存器的 DTG 位決定。// 死區(qū)時間 2us72MHz 下 DTG 2us * 72MHz 144 TIM_BDTRInitStructure.TIM_OSSRState TIM_OSSRState_Enable; TIM_BDTRInitStructure.TIM_OSSIState TIM_OSSIState_Enable; TIM_BDTRInitStructure.TIM_LOCKLevel TIM_LOCKLevel_1; TIM_BDTRInitStructure.TIM_DeadTime 144; // 死區(qū) 2us TIM_BDTRInitStructure.TIM_Break TIM_Break_Disable; TIM_BDTRInitStructure.TIM_BreakPolarity TIM_BreakPolarity_High; TIM_BDTRInitStructure.TIM_AutomaticOutput TIM_AutomaticOutput_Enable; TIM_BDTRInit(TIM1, TIM_BDTRInitStructure);TIM_DeadTime 等于 144 時實際死區(qū)時間在 72MHz 主頻下正好是 2 微秒這個值對 IGBT 和 MOS 管都比較安全。值得留意的是 TIM_AutomaticOutput_Enable 這個位它是 MOE 主輸出使能的自動恢復機制一旦產(chǎn)生剎車事件 MOE 被硬件清零六路輸出全部進入高阻態(tài)而自動輸出使能會在下一個更新事件時自動恢復 MOE這在過流保護需要「跳閘后自動重啟」的場景下很有用。工程里把它關掉改為手動控制 MOE這樣故障復位時機由軟件決定而不是硬件自動恢復對電機控制更安全。3.3 急停與加載時刻SPWM 長期運行的穩(wěn)定性保障六路 PWM 不能一初始化就立刻輸出否則上電瞬間占空比是亂的MCU 的復位期間 IO 默認是浮空輸入功率管柵極可能被噪聲誤觸發(fā)。工程的做法是先用 GPIO 配置把六個通道全部拉低再初始化 TIM1最后才在主循環(huán)中置位 MOE 使能輸出。void SPWM_Start(void) { TIM_Cmd(TIM1, ENABLE); // 必須等第一個更新事件產(chǎn)生讓影子寄存器加載完初始值 while ((TIM1-SR TIM_FLAG_Update) 0); // 硬件控制主輸出使能此時六路 PWM 才開始實際輸出 TIM1-BDTR | TIM_BDTR_MOE; }這里有個容易翻車的地方如果在置位 MOE 之前就改 CCR寫入的是影子寄存器而實際輸出寄存器還是復位值導致第一波 PWM 波形的占空比和預期完全不同。所以我一般會在置 MOE 前先強制產(chǎn)生一個軟件更新事件讓所有影子寄存器一次性鎖存再開 MOE波形第一個周期就是完整的。4. 調(diào)頻調(diào)壓核心代碼拆解改哪幾個寄存器影響什么波形4.1 調(diào)頻原理改預分頻還是改重載值調(diào)頻的本質(zhì)是改變定時器的計數(shù)周期但存在兩種改法改 PSC 預分頻或改 ARR 自動重載值。PSC 的取值范圍是 12 位ARR 是 16 位兩者都能改變 PWM 頻率但影響方式不同。改 PSC 時計數(shù)時鐘步進變大每一拍的計數(shù)器增量不再是 1波形質(zhì)量會變差改 ARR 時計數(shù)步進不變只是周期長度變化更適合精調(diào)。工程里采用「粗調(diào) PSC、細調(diào) ARR」的組合方式避免單一寄存器調(diào)節(jié)范圍不足。// 目標頻率 5Hz 到 400Hz載波比固定為 32載波頻率隨基波同步變化 void SPWM_SetFreq(uint16_t freq_hz) { uint32_t carrier freq_hz * 32; // 載波頻率 基波頻率 x 載波比 uint16_t psc (72000000 / carrier / 256) - 1; uint16_t arr (72000000 / (psc 1) / carrier) - 1; TIM1-PSC psc; TIM1-ARR arr; // 手動觸發(fā)更新事件讓影子寄存器的值立即生效 TIM1-EGR | TIM_EGR_UG; }這段代碼的關鍵是 EGR 寄存器的 UG 位它在置 1 后會產(chǎn)生一個軟件更新事件把所有影子寄存器的值一次性鎖存到實際寄存器。如果不寫這行AR R和 PSC 的改動會等到下一個自然更新事件才生效調(diào)頻響應會慢半個載波周期在閉環(huán)控制里這是不能接受的延遲。載波比固定為 32 意味著基波頻率升高時載波頻率也跟著升高這屬于同步調(diào)制變頻變壓時載波比恒定諧波特性更穩(wěn)定。4.2 調(diào)壓原理正弦表幅值疊加整體縮放調(diào)壓的核心是把每個點的正弦表數(shù)值按目標電壓比例整體縮放然后寫入對應通道的 CCR。工程里正弦表幅值設計為 2400留出 250 的余量給死區(qū)補償防止調(diào)制比過高時上橋還沒關斷下橋就導通。// 電壓調(diào)節(jié)duty 范圍 0 到 1000對應 0% 到 100% 調(diào)制深度 void SPWM_SetDuty(uint16_t duty) { uint16_t i; uint32_t compare_value; for (i 0; i SPWM_TABLE_SIZE; i) { compare_value (uint32_t)sinTable[i] * duty / 1000; TIM_SetCompare1(TIM1, compare_value); // 只更新 CH1需配合通道映射 // CH2、CH3 相位差 120 度索引偏移需要在查表處處理 } }三相 SPWM 的關鍵在于相位偏移U 相索引從 0 開始V 相索引加 85256 的 1/3W 相索引加 171256 的 2/3。三個通道的 CCR 更新必須放在同一個中斷服務函數(shù)里連續(xù)執(zhí)行中間不能有任何延時或調(diào)度切換否則三相的相位關系會漂移。工程里沒有在 for 循環(huán)里逐點寫入而是把三相的查表索引放在同一個中斷里算好一次性寫三個 CCR 寄存器。4.3 KEIL5 仿真配置沒有開發(fā)板也能看波形這份工程能在 KEIL5 里直接仿真關鍵配置有兩處一是將調(diào)試器選擇為 ST-Link 或 DAP-Link并在 Settings 里勾選「Download to Flash」二是啟用 Simulator 模式下的波形窗口。實際用仿真模式時要注意 TIM1 的時鐘在仿真環(huán)境中默認是關閉的需要在 RCC 配置里手動使能 TIM1 時鐘否則波形窗口一片平。// main.c 里的時鐘初始化必須包含這一句 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); // 仿真時 TIM1 掛在 APB2 上主頻 72MHz分頻系數(shù) 1 // 如果仿真波形頻率偏高一倍檢查 APB2 預分頻是否被設為了 /2仿真器模式下外設庫的 RCC 配置是真實執(zhí)行的但示波器窗口的刷新率有限載波 8kHz 時建議把仿真時間軸拉長觀察基波周期而不是載波周期更容易確認調(diào)頻調(diào)壓是否正確生效。一個血淚經(jīng)驗是仿真模式看不出死區(qū)插入效果因為波形窗口只顯示邏輯電平不反映時間差死區(qū)還是要拿到示波器上看。5. 調(diào)試避坑記錄六路 PWM 工程里最容易翻車的幾個地方5.1 現(xiàn)象上電一瞬間上下橋同時導通電流猛增原因TIM1 初始化完成但 MOE 沒置位時六路輸出并非全是低電平而是保持上一次的輸出狀態(tài)如果功率管柵極有下拉電阻復位期間可能處于半導通狀態(tài)。解決GPIO 初始化階段先把六個通道全部配置為推挽輸出并輸出低電平等 TIM1 完成全部配置后再切換到復用功能模式。關鍵代碼如下// 初始化 GPIO 時先用通用推挽輸出拉低 GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_2MHz; GPIO_WriteBit(GPIOA, GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10, Bit_RESET); // 定時器初始化完成后再改成復用推挽 GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP;5.2 現(xiàn)象調(diào)頻瞬間輸出波形出現(xiàn)一個異常長周期原因直接修改 ARR 后如果恰好計數(shù)器已經(jīng)計過了新的 ARR 值計數(shù)器需要先計到最大值回繞多出一個完整的載波周期。解決調(diào)頻時必須先停計數(shù)寫入新 ARR 和 PSC 后再啟動。ST 庫的 TIM_Cmd 能實現(xiàn)開關但更干凈的做法是操作 CR1 寄存器的 CEN 位先清零再置位。TIM1-CR1 ~TIM_CR1_CEN; // 停止計數(shù) TIM1-ARR new_arr; TIM1-PSC new_psc; TIM1-EGR | TIM_EGR_UG; // 產(chǎn)生更新事件鎖存影子寄存器 TIM1-CR1 | TIM_CR1_CEN; // 重新啟動5.3 現(xiàn)象三相波形頻率對但相位差不是 120 度原因查表時索引偏移計算用了 256 除 3得到 85 和 171但 85 比理想值 85.33 少了 0.33累積若干點后相位漂移超過 1 度。解決索引不用整數(shù)加法改用浮點累加或查表時單獨計算浮點索引再取整。工程里為了省時間用了整型偏移做高精度電機控制時必須修正。// 相位差累加方式用浮點步進每點累加后再取整 float phase_v 256.0f / 3.0f * i; uint16_t index_v (uint16_t)phase_v % 256;5.4 現(xiàn)象KEIL5 仿真時波形窗口看不到任何輸出原因仿真模式下外設庫往 TIM1 寫寄存器是成功的但 Keil 的波形窗口默認只捕捉模擬引腳狀態(tài)引腳沒有連接虛擬示波器通道。解決在 Keil 的 View-System Viewer 里找到 TIM1手動把 CH1、CH1N、CH2、CH2N 等引腳添加到 Logic Analyzer然后設置合適的顯示比例。另外仿真時如果開了優(yōu)化選項部分寄存器寫操作會被編譯器優(yōu)化掉把 C 優(yōu)化級別設置為 -O0 是調(diào)試階段的標準姿勢。5.5 現(xiàn)象下載程序后板子不跑電流極小原因C8T6 的 BOOT0 引腳懸空或被外部電路拉高芯片進入了系統(tǒng)存儲器引導模式而不是用戶 Flash 引導模式。解決確認 BOOT0 接 10k 下拉電阻到地BOOT1 隨意。同時檢查啟動文件是不是選的 startup_stm32f10x_md.sC8T6 屬于中等容量選成 HD 的啟動文件會導致中斷向量表錯位程序直接跑飛。6. 二次開發(fā)技巧從固定載波比改成 V/F 曲線控制拿到這份工程后如果想把它改成真正的變頻器第一個要動的就是調(diào)頻調(diào)壓的聯(lián)動邏輯。現(xiàn)在的 SPWM_SetFreq 和 SPWM_SetDuty 是獨立接口實際電機控制里電壓和頻率必須按 V/F 曲線同步變化否則低頻段磁通過飽和、高頻段力矩不足。常見的做法是把頻率和電壓的對應關系做成一張查找表主循環(huán)里按目標頻率查表得到電壓百分比再同時調(diào)用兩個接口。// V/F 曲線表頻率(Hz) - 電壓百分比(%) const uint16_t vf_curve[][2] { { 0, 0 }, // 0Hz 輸出 0V { 5, 18 }, // 5Hz 輸出 18%低頻補償 { 25, 50 }, // 25Hz 輸出 50% { 50, 80 }, // 50Hz 輸出 80%預留恒壓區(qū) { 400, 100 }, // 400Hz 滿載 };查表時用線性插值可以得到任意頻率點的電壓值。低頻補償段0 到 5Hz的電壓不能直接按比例給否則電機啟動力矩不足這是變頻控制的常識。擴展后就有了完整的變頻器開環(huán)算法不需要改動 Driver 層任何代碼。如果想從查表法升級為實時運算把正弦表改成運行時計算也不難但 F103 做浮點 sin 運算在一個載波周期里要算 256 次CPU 占用偏高。折中方案是保持查表法只把幅值縮放從循環(huán)改成 DMA 搬運用 DMA 循環(huán)模式不停從正弦表搬運數(shù)據(jù)到 TIM1 的 CCR 寄存器CPU 占用幾乎為零。工程里預留了三相比較中斷要上 DMA 的話需要把查表索引改成硬件觸發(fā)方式這是一個值得動手的實驗方向。從那以后我每次改定時器參數(shù)都強制自己在紙上畫出計數(shù)器的波形圖標清楚寫入時刻和影子寄存器鎖存時刻再上電實測。硬件控制沒有「我覺得應該沒問題」這種說法一個寄存器的時序錯了功率管就在那里等著你。希望幫到你。本文還有配套的精品資源點擊獲取