
簡介基于STM32G431的三相無刷電機驅動與比例積分PI調速完整工程源碼面向嵌入式電機控制方向的學習者與開發者用于掌握無刷電機換相邏輯、脈寬調制驅動以及閉環調速的實現方法。工程基于硬件抽象層HAL庫和官方數字信號處理庫搭建核心代碼涵蓋高分辨率定時器配置、三相逆變橋驅動、電流與速度采樣、比例積分調節器以及相應控制邏輯可作為無刷電機控制項目的移植參考或二次開發基礎。工程中保留了完整的構建中間文件和鏈接腳本便于理解嵌入式工程的組織方式。資源共1524個文件以C語言源碼和頭文件為主輔以匯編程序、鏈接腳本、編譯工程配置、圖形化配置工程以及編譯生成的燒錄文件便于對照完整工程結構進行重新編譯和驗證目錄結構清晰從啟動文件、硬件抽象層到底層驅動和上層算法均有對應代碼。壓縮包整體約43.48兆字節累計已有2063人下載學習適合具備一定單片機基礎、希望深入電機控制底層實現與調參實踐的讀者。1. 為什么stm32G431驅動三相無刷電機默認先做PI調速而不是一上來就上FOC拿STM32G431驅動三相無刷電機很多人的第一反應是直接上FOC。但實際量產項目里大量方案用的是六步換相加PI調速原因很直接代碼量小一個量級調試周期從幾周縮到一兩天而且在中低速、大扭矩的應用場景下換相邏輯清晰轉速閉環同樣穩定。G431這顆芯片自帶170MHz主頻、HRTIM、模擬比較器和高速ADC做六步換相屬于資源溢出根本不需要動用FOC那種復雜架構。這篇文章就按一線工程師最常用的路線走一遍用霍爾傳感器做轉子位置反饋TIM1輸出三路PWM驅動三相全橋PI調速閉環控制轉速。從換相表怎么建、PWM怎么配、轉速怎么算、PI參數怎么定到最容易踩的坑全部給出能直接抄的代碼和步驟。2. 六步換相原理與G431外設選型霍爾方案為什么夠用2.1 三相無刷電機的六步換相邏輯無刷直流電機BLDC的反電動勢近似梯形波所以驅動邏輯不必像永磁同步電機那樣追求正弦電流波形只需要在轉子轉到特定角度時以60°電角度為步進切換通電相電機就能持續輸出轉矩。這就是“六步換相”這個名字的來源。每步只有兩相導通一相進電流一相出電流第三相懸空。以常見的內轉子電機為例通電順序從AB-開始依次切到AC-、BC-、BA-、CA-、CB-六個狀態循環一圈轉子轉過360°電角度。極對數為p的電機電角度轉一圈對應機械角度360°/p所以六步換相恰好覆蓋一個電周期。霍爾傳感器安裝在定子上三個霍爾間隔120°電角度輸出三路高低電平組合一共8種狀態其中6種是有效位置對應轉子的6個扇區。控制器讀取霍爾組合后查表得到當前扇區再決定哪兩相通電。這就是霍爾換相的完整閉環。2.2 霍爾信號與通電相的映射表換相表是調試的第一道坎因為霍爾安裝方向、電機繞組接法不同同一張表在不同電機上表現完全不一樣。以下是一張最常用的默認表以霍爾輸出HA、HB、HC的順序拼成3位二進制數0代表A相、1代表B相、2代表C相。霍爾組合H3:H2:H1上橋PWM相下橋導通相實際通電狀態0b000/0b111無無無效狀態停止輸出0b001ACAC-0b010ABAB-0b011BCBC-0b100BABA-0b101CACA-0b110CBCB-注意這張表只是起點。如果電機通電后抖動不轉或者反轉優先做兩件事一是把表中6個有效狀態整體前移或后移一格相當于調整60°換相角二是任意交換兩路霍爾輸入線霍爾組合的編碼順序會整體重排。實際操作時我一般會在代碼里預留一個數組存這張表然后用串口把實時霍爾值打出來配合示波器看相電流波形最多10分鐘就能調對。2.3 stm32G431驅動三相無刷電機哪些外設真正干活ST特意在G431上堆了一組電機控制外設但六步換相方案真正用到的其實只有一小部分。核心是TIM1高級定時器它有3個通道可以輸出PWM且每個通道帶互補輸出、剎車輸入和死區插入。對六步換相來說三路PWM就夠驅動上橋余下的互補通道可以不用把引腳當普通GPIO控制下橋。HRTIM高分辨率定時器在FOC里是主角但在六步換相里反而用不上。它的價值在于能和比較器、ADC聯動產生精確的PWM中心對齊觸發信號做無感FOC電流采樣非常合適。如果項目下一步要升級FOCG431的HRTIM一秒切入只做PI調速TIM1完全夠。G431的另一組好料是內置運放和比較器不需要外接比較電路就能做無感反電動勢過零檢測。但霍爾方案本身不需要過零檢測比較器可以用來做過流保護。ADC的高速采樣能力同樣用不上全部性能每50μs采一次母線電流就足夠保護邏輯用了。2.4 PWM調制方式高側斬波與同步整流的取舍六步換相的PWM調制方式有好幾種最常見的是H_PWM-L_ON上橋PWM斬波下橋常通。這種方式控制邏輯簡單續流靠下橋MOSFET的體二極管缺點是關斷時電流經體二極管續流二極管損耗大大電流工況下發熱明顯。另一種是互補PWM同步整流上下橋互補輸出關斷期間電流走下橋MOSFET溝道導通損耗小但代碼里要處理死區、避免直通換相瞬間的邏輯也更復雜。G431的TIM1自帶死區生成器理論上可以直接用但需要時刻保證同一半橋上下管不能同時導通對六步狀態機的正確性要求更高。我一般選H_PWM-L_ON起步原因很實際先用最簡單的驅動邏輯確認換相表、轉速測量和PI調速跑通再考慮要不要加同步整流優化效率。PWM頻率取20kHz以上能避開可聽見的噪聲G431的TIM1時鐘170MHzARR值取8500就是20kHz占空比分辨率足夠細膩。3. stm32G431驅動三相無刷電機的硬件接線與PWM初始化3.1 三相逆變橋與G431的引腳分配硬件上需要三相全橋逆變電路最常見的是6顆N溝道MOSFET加柵極驅動芯片比如IR2101、EG2134這類自帶自舉電路的高低壓側驅動。柵極驅動芯片的輸入側只需要三路上橋PWM和三路下橋電平信號正好對應G431的一組TIM1輸出加三個GPIO。以下是全套接線參考按這個接法CubeMX里的引腳配置可以直接照抄。功能引腳外設/復用說明A相上橋PWMPA8TIM1_CH1接A相柵極驅動高側輸入B相上橋PWMPA9TIM1_CH2接B相柵極驅動高側輸入C相上橋PWMPA10TIM1_CH3接C相柵極驅動高側輸入A相下橋控制PB13GPIO輸出接A相柵極驅動低側輸入B相下橋控制PB14GPIO輸出接B相柵極驅動低側輸入C相下橋控制PB15GPIO輸出接C相柵極驅動低側輸入霍爾HAPC6EXTI6外部中斷輸入上拉霍爾HBPC7EXTI7外部中斷輸入上拉霍爾HCPC8EXTI8外部中斷輸入上拉母線電流采樣PA0ADC1_IN1經運放放大后進ADC要注意PB13、PB14、PB15雖然硬件上同時連接TIM1的互補通道但只要在CubeMX里配置成GPIO輸出就不會觸碰TIM1的PWM功能。霍爾引腳用外部中斷而不是輸入捕獲是因為換相動作需要在任意一路霍爾變化時立刻響應三路霍爾共用EXTI9_5中斷線在一次中斷里統一讀取三路電平效率更高。3.2 初始化定時器PWM和IO讓電機先轉起來初始化代碼用HAL庫寫結構清楚也方便CubeMX用戶直接對齊自己的工程。PWM頻率按20kHz設置ARM值8500初始占空比0。static void Motor_PWM_Init(void) { GPIO_InitTypeDef gpio {0}; TIM_OC_InitTypeDef oc {0}; __HAL_RCC_TIM1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); // PA8/PA9/PA10 復用為 TIM1 CH1/CH2/CH3接上橋驅動 gpio.Pin GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10; gpio.Mode GPIO_MODE_AF_PP; gpio.Pull GPIO_NOPULL; gpio.Speed GPIO_SPEED_FREQ_HIGH; gpio.Alternate GPIO_AF1_TIM1; HAL_GPIO_Init(GPIOA, gpio); // PB13/14/15 做普通GPIO控制下橋 gpio.Pin GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Alternate 0; HAL_GPIO_Init(GPIOB, gpio); htim1.Instance TIM1; htim1.Init.Prescaler 0; htim1.Init.CounterMode TIM_COUNTERMODE_UP; htim1.Init.Period 8500 - 1; // 170MHz / 8500 20kHz htim1.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter 0; htim1.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE; HAL_TIM_PWM_Init(htim1); oc.OCMode TIM_OCMODE_PWM1; oc.Pulse 0; oc.OCPolarity TIM_OCPOLARITY_HIGH; oc.OCFastMode TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(htim1, oc, TIM_CHANNEL_1); HAL_TIM_PWM_ConfigChannel(htim1, oc, TIM_CHANNEL_2); HAL_TIM_PWM_ConfigChannel(htim1, oc, TIM_CHANNEL_3); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_2); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_3); }Period取8500減一是因為計數器從0計數到8499正好8500個計數周期。PWM頻率等于定時器時鐘除以PeriodG431的TIM1掛在APB2上最高170MHzPrescaler為0時直接得到170MHz計數時鐘。HAL_TIM_PWM_Start只啟動三路主輸出下橋完全由GPIO控制不涉及互補通道死區問題留給柵極驅動芯片和軟件換相間隙解決。3.3 換相狀態機查詢霍爾并切換PWM通道換相函數是整套驅動的心臟。它根據霍爾組合查表確定當前步進應該PWM哪一相上橋、導通哪一相下橋。為了保證換相瞬間不出現上下橋直通我采用“先關下橋再恢復PWM”的序列中間留幾微秒間隔。static uint8_t cur_pwm_ch 0; // 當前上橋PWM通道 0/1/2 對應A/B/C static uint8_t cur_low_ph 0; // 當前下橋導通相 0/1/2 static uint16_t cur_duty 0; // 當前PWM比較值 // 換相表行霍爾組合列[上橋PWM相, 下橋導通相] // 0A, 1B, 2C, 0xFF不動作 const int8_t step_table[8][2] { {0xFF, 0xFF}, // 0b000 { 0, 2 }, // 0b001 { 0, 1 }, // 0b010 { 1, 2 }, // 0b011 { 1, 0 }, // 0b100 { 2, 0 }, // 0b101 { 2, 1 }, // 0b110 {0xFF, 0xFF}, // 0b111 }; static const uint16_t pwm_ch_map[3] { TIM_CHANNEL_1, TIM_CHANNEL_2, TIM_CHANNEL_3 }; static const uint16_t low_pin[3] { GPIO_PIN_13, GPIO_PIN_14, GPIO_PIN_15 }; void Motor_Set_Step(uint8_t hall) { const int8_t *step step_table[hall]; HAL_GPIO_WritePin(GPIOB, low_pin[0] | low_pin[1] | low_pin[2], GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htim1, pwm_ch_map[cur_pwm_ch], 0); if (step[0] 0xFF) { cur_pwm_ch 0xFF; return; } cur_pwm_ch step[0]; cur_low_ph step[1]; HAL_GPIO_WritePin(GPIOB, low_pin[cur_low_ph], GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htim1, pwm_ch_map[cur_pwm_ch], cur_duty); }這段邏輯里換相第一時間把舊上橋PWM比較值清零同時關閉全部下橋避免新舊兩步之間出現“舊下橋沒關、新上橋已開”的瞬間。之后設置新下橋導通再恢復新上橋PWM。cur_duty是PI調速輸出后寫入的全局占空比這樣換相時能立刻知道當前應該輸出多大占空比。3.4 霍爾外部中斷與轉速時間戳霍爾信號變化觸發EXTI9_5中斷在中斷服務函數里讀霍爾組合、調換相函數并用DWT計數器記錄兩次換相的時間間隔。DWT-CYCCNT是Cortex-M4內核自帶的周期計數器適合做微秒級時間測量比SysTick更輕量。volatile uint32_t hall_tick_us 0; volatile uint32_t comm_period_us 0; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { uint8_t hall; uint32_t now_us; if ((GPIO_Pin (GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8)) 0) return; hall (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_8) 2) | (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_7) 1) | HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_6); now_us DWT-CYCCNT / 170; comm_period_us now_us - hall_tick_us; hall_tick_us now_us; Motor_Set_Step(hall); }DWT-CYCCNT每周期加1170MHz下每微秒加170。需要在使用前使能DWT計數器開啟方式是在main里執行CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk然后DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk。霍爾中斷里做了兩件事測量換相周期執行換相。速度計算不在中斷里做除法只在需要時用comm_period_us算出轉速避免中斷里浮點運算拖慢響應。4. stm32G431的PI調速實現從換相周期計算轉速到閉環修正4.1 轉速計算公式為什么是十萬除以極對數乘換相周期霍爾換相周期和轉速之間是嚴格的幾何關系。每兩次換相之間轉子轉過60°電角度6次換相完成一個完整電周期。一個電周期對應機械角度為360°/p所以轉速和換相周期的關系如下。轉速RPM 60000000 / (6 * p * comm_period_us) 10000000 / (p * comm_period_us)極對數p在電機參數里通常標著比如2812電機通常是4對極或7對極。comm_period_us是霍爾中斷里記錄的相鄰兩次換相間隔。以p4、comm_period1000μs為例轉速就是10000000除以4000等于2500RPM。這個公式不依賴任何傳感器標定只要霍爾安裝正確算出來的轉速就是準的。用定時器固定1ms周期計算轉速并執行PI比在換相中斷里直接調PI更穩定。因為低速時換相間隔變長PI的執行頻率會跟著降低比例積分參數沒法固定。固定1ms采樣相當于把速度環采樣時間定死Kp和Ki的物理意義清晰調參也更符合直覺。#define MOTOR_POLE_PAIRS 4 volatile uint16_t motor_rpm 0; void Speed_Calc_Update(void) { uint32_t period comm_period_us; uint32_t rpm; if (period 0 || period 100000) { motor_rpm 0; return; } rpm 10000000UL / (MOTOR_POLE_PAIRS * period); motor_rpm (uint16_t)rpm; }period超過100ms說明電機已經接近停轉此時轉速記0更合理。如果電機高速轉period很小rpm可能超過uint16_t范圍但這個場景在常規電機驅動里很難出現工程上夠用。4.2 PI控制器結構位置式還是增量式PI調速輸出的是PWM比較值屬于絕對占空比所以用位置式PI最直接。位置式PI的公式是u(k)Kpe(k)KiΣe(i)其中誤差積分項需要單獨累加。增量式PI輸出的是每次調整量適合步進電機或輸出本身就是增量的執行器無刷電機的占空比是絕對值用位置式少一次換算限幅和積分飽和處理也更直觀。積分飽和是位置式PI在電機啟動階段最常見的問題。啟動時轉速誤差大積分項快速累加等轉速接近目標時積分還停留在高位導致超調甚至振蕩。對抗辦法是積分分離誤差超過一定范圍時停止積分進入穩態后再讓積分器工作。下面的代碼把積分分離閾值放在±200RPM區間內啟動階段積分器不會瘋狂累加。#define KP_DEFAULT 0.15f #define KI_DEFAULT 0.02f #define DUTY_MIN 0.10f #define DUTY_MAX 0.95f #define INTEGRAL_LIMIT 0.30f #define SPEED_ERR_BAND 200 static float duty_ratio 0.10f; static float integral 0.0f; static uint16_t target_rpm 1500; void PI_Update(void) { float err, out; err (float)target_rpm - (float)motor_rpm; if (err -SPEED_ERR_BAND err SPEED_ERR_BAND) { integral err; if (integral INTEGRAL_LIMIT) integral INTEGRAL_LIMIT; if (integral -INTEGRAL_LIMIT) integral -INTEGRAL_LIMIT; } out KP_DEFAULT * err KI_DEFAULT * integral; if (out DUTY_MAX) out DUTY_MAX; if (out DUTY_MIN) out DUTY_MIN; duty_ratio out; cur_duty (uint16_t)(out * 8500.0f); }積分限幅0.3對應的物理意義是積分項對占空比的貢獻最多30%比例項在啟動階段主導輸出避免積分器單獨把占空比推到極限。輸出下限10%是因為PWM占空比低于10%時電機往往無法克服靜摩擦占空比上不去會導致PI一直在積分實際表現為電機嗡嗡響但不轉。4.3 1ms定時器中斷里執行速度閉環PI放在TIM6的1ms周期中斷里。TIM6是基礎定時器只有中斷功能適合做系統節拍。在中斷里先計算最新轉速再執行PI更新換相則始終由霍爾中斷驅動兩者互不干擾。void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM6) { Speed_Calc_Update(); PI_Update(); } }這種結構的好處是速度環和換相環解耦。霍爾中斷頻率隨轉速變化最高可能到幾千赫茲速度環頻率固定1kHz穩定可設計。PI的參數整定基于1ms采樣周期Kp和Ki的量綱清晰。如果未來要調成5ms或2ms采樣只需改TIM6的周期再按比例縮Ki即可。4.4 PI參數整定先P后I的實操順序參數表給出起步值和調節方向但真正在現場調參時順序比數值更重要。參數作用起步值調節方向Kp比例增益決定響應速度0.10~0.15過小響應慢過大會持續振蕩Ki積分增益消除穩態誤差0.01~0.02過大會超調過小有靜差輸出上限限制最大占空比0.95限制最大電流時可調低輸出下限最小啟動占空比0.10低于該值電機無法轉動積分限幅防積分飽和0.30啟動無超調時可適當增大先把Ki設為0Kp從0.05開始目標轉速設到額定轉速的一半觀察電機是否能在1秒內接近目標并且不振蕩。如果轉速在目標上下反復擺動Kp減半。如果接近目標的弧線很緩、遲遲到不了Kp翻倍。找到一個“稍有超調但不持續振蕩”的Kp后再把Ki從0.005開始緩慢增大觀察穩態誤差是否歸零。這個階段Ki過大的特征很明顯轉速到達目標后出現周期性浪涌聲音粗細交替這時Ki立刻減半。5. 調試技巧開環驗證換相順序再閉環調PI參數5.1 第一階段固定占空比確認換相順序不要一上來就閉環。先把PI輸出的占空比寫死成30%用串口把霍爾值打印出來手動緩慢轉動電機軸觀察串口打印的霍爾組合是否按步進表的順序循環。如果霍爾組合跳變順序不是循環的說明霍爾接線或換相表次序不對交換任意兩路霍爾輸入再試。霍爾狀態正常后再上電給定30%占空比電機應該能平滑轉起來。如果抖一下停住說明換相表和霍爾組合錯位把step_table的6行數據整體平移一格。5.2 第二階段用示波器看PWM和相電壓波形確認直通風險接好示波器探頭到A相上橋PWM引腳和A相相電壓節點空載運行電機。正常狀態是PWM引腳輸出20kHz方波占空比隨負載變化相電壓在每個換相點附近呈現梯形波形狀。如果相電壓在換相瞬間出現尖峰或者母線電流明顯增大先檢查軟件換相間隙是否太短。我一般在Motor_Set_Step里加一個約2μs的延時再恢復PWM輸出成本低能有效避免上下橋直通。不要小看這一步很多電機燒MOSFET都是換相時序的問題不是過流保護沒做。5.3 第三階段閉環微調PI并記錄階躍響應曲線閉環啟動前把目標轉速設為200RPM先用極小的Kp跑起來。重點看電機能不能穩定停在目標轉速附近而不是看它多久到達。穩定后把目標轉速突變到1500RPM記錄轉速從突變到穩定的時間。理想的階躍響應是無超調或單次小超調穩定時間在200ms以內。如果轉速在目標下方爬升緩慢增加Kp如果出現等幅振蕩先減Kp再觀察是否恢復。PI調好后還要驗證最低轉速和最高轉速兩個極端點。最低轉速下換相周期長速度環采樣不會因為換相中斷太慢而跳變最高轉速下輸出占空比接近上限確認PI輸出沒有飽和過頭導致突然失速。調完參數后把轉速、PI輸出、母線電流三路信號同時用串口以1kHz頻率發出錄一段空載啟動到滿載突加的數據。保存這組曲線它就是這臺電機PI標定的最終記錄也是后續批量接線差異排查的對照基準。本文還有配套的精品資源點擊獲取