控制入門:從硬件連接到PWM驅動實踐)
1. 從零到一為什么選擇開環(huán)控制作為單片機小車的第一步如果你剛拿到一塊STM32開發(fā)板和一套小車底盤看著一堆電機、輪子和杜邦線腦子里第一個冒出來的想法多半是“怎么讓它動起來”。很多教程會直接上PID閉環(huán)控制講得天花亂墜但對于一個初學者來說這就像還沒學會走路就想跑馬拉松。我的建議是別急我們先從“開環(huán)控制”開始。這不僅是讓小車動起來的起點更是你理解整個嵌入式控制系統(tǒng)底層邏輯的基石。開環(huán)控制說白了就是“我說什么你就做什么至于你做沒做到位我不關心”。比如我讓電機以50%的占空比轉3秒它就轉3秒至于這3秒里小車是不是因為地面不平、電池電壓下降而跑偏了控制器不管。聽起來很“傻”對吧但正是這種“傻”讓它成為了入門的最佳選擇。它剝離了復雜的反饋和糾錯環(huán)節(jié)讓你能集中精力解決幾個核心問題如何用STM32的定時器產(chǎn)生PWM波驅動電機如何通過程序邏輯控制小車的“前進”、“后退”、“左轉”、“右轉”如何搭建一個最基礎的硬件系統(tǒng)這個過程你會親手打通從代碼到物理運動的整個鏈路。當你第一次通過幾行代碼讓輪子按照你的意愿轉動時那種“掌控感”是無與倫比的。這比一上來就面對誤差、積分、微分這些抽象概念要直觀得多。開環(huán)控制是“骨架”先把骨架搭起來后續(xù)的PID閉環(huán)控制、傳感器融合才是往上添加的“肌肉”和“神經(jīng)”。跳過骨架直接堆肌肉系統(tǒng)是立不起來的。所以咱們今天就踏踏實實從給STM32小車搭建這個最基礎的“開環(huán)骨架”開始。2. 硬件清單與核心電路連接別在第一步就“翻車”在寫代碼之前硬件連接是重中之重。很多新手在這里栽跟頭不是電機不轉就是單片機冒煙。我結合自己焊板子、調(diào)車的經(jīng)驗給你捋一個清晰且避坑的連接清單。2.1 核心部件清單主控STM32F103C8T6俗稱“藍橋杯”或“最小系統(tǒng)板”。它性價比高資源足夠社區(qū)資料海量是學習的不二之選。電機驅動模塊L298N或TB6612FNG。強烈推薦TB6612FNG原因有三效率高、發(fā)熱小、外圍電路簡單。L298N是老將但需要外接散熱片邏輯電壓和驅動電壓要處理好對新手不友好。TB6612是MOSFET橋更現(xiàn)代。直流減速電機帶編碼器的那種。雖然開環(huán)用不到編碼器反饋但買帶編碼器的電機是為未來升級閉環(huán)預留了硬件接口避免重復投資。通常工作電壓在3-6V注意與驅動模塊匹配。電源這是最大的坑點。務必準備兩路獨立電源一路給STM32開發(fā)板供電5V Micro USB或3.3V引腳供電。一路給電機驅動模塊供電根據(jù)電機額定電壓選擇比如4節(jié)5號電池約6V或2節(jié)18650鋰電池約7.4V。嚴禁只用USB給整個系統(tǒng)供電USB的500mA電流帶不動兩個電機瞬間就會導致電壓被拉低單片機不斷復位現(xiàn)象就是程序跑飛、電機抽搐。小車底盤四輪或兩輪差分驅動底盤。四輪的后兩輪通常是萬向輪靠前兩輪差分驅動決定方向和速度。杜邦線公對公、母對母、公對母都需要用于連接。2.2 電路連接詳解與避坑指南以TB6612FNG驅動模塊為例連接STM32F103C8T6。一個TB6612可以驅動兩個電機我們假設控制小車的左輪電機MOTOR A和右輪電機MOTOR B。注意接線前確保所有電源處于斷開狀態(tài)養(yǎng)成好習慣。電源連接關鍵VM引腳接電機驅動電源正極如電池盒的6V。這是電機的“力氣”來源。VCC引腳接邏輯電源正極接STM32的3.3V或5V輸出引腳。這是給驅動芯片內(nèi)部邏輯電路供電的必須接。GND引腳共地共地共地將電池的負極、STM32的GND、TB6612的GND三者用杜邦線全部連接在一起。這是保證信號基準一致的基礎否則控制信號會亂。電機輸出A01和A02引腳分別接左輪電機的兩根線。B01和B02引腳分別接右輪電機的兩根線。如果電機轉向與預期相反交換這兩根線即可。STM32控制信號連接核心 TB6612每個電機需要3個控制信號AIN1/AIN2方向、PWMA速度PWM。我們將其連接到STM32的通用定時器如TIM2的通道上以實現(xiàn)硬件PWM輸出。左輪電機AIN1- STM32PA0(TIM2_CH1)AIN2- STM32PA1(TIM2_CH2)PWMA- STM32PA2(TIM2_CH3)右輪電機BIN1- STM32PA6(TIM3_CH1)BIN2- STM32PA7(TIM3_CH2)PWMB- STM32PB0(TIM3_CH3)STBY待機引腳直接接VCC邏輯電源讓驅動模塊始終處于工作狀態(tài)。這里為什么選擇TIM2和TIM3因為STM32F103C8T6的通用定時器TIM2、TIM3、TIM4都支持4路PWM輸出且引腳分布方便布線。你也可以根據(jù)自己板子的引腳定義靈活選擇但原則是一個電機的方向和PWM信號盡量來自同一個定時器方便程序管理。2.3 上電前最后檢查電壓確認用萬用表量一下VM和GND之間的電壓是否在電機額定電壓附近比如6V左右VCC和GND之間是否是3.3V或5V短路檢查快速目視檢查所有杜邦線頭有沒有金屬部分裸露接觸導致短路的風險。連接牢固度輕輕拉扯一下杜邦線確保都插緊了。接觸不良是調(diào)試中最詭異的問題來源。硬件連接就像蓋房子的地基這里扎實了后面的軟件調(diào)試才能順風順水。3. STM32定時器與PWM配置讓電機“聽令行事”的核心硬件通了現(xiàn)在要讓STM32發(fā)出正確的控制信號。開環(huán)控制的核心就是通過PWM脈沖寬度調(diào)制波來控制電機的平均電壓從而控制轉速。PWM的頻率和占空比是兩個關鍵參數(shù)。3.1 PWM頻率的選擇不是越高越好對于普通的直流減速電機PWM頻率通常在1kHz到20kHz之間。頻率太低如幾十Hz你會聽到電機發(fā)出刺耳的嘯叫聲這是脈沖間隔太長電機線圈在不斷地“啟動-停止”造成的對電機壽命和控制平滑度都不好。頻率太高如上百kHz雖然電機聲音會變得很安靜人耳聽不到但會對電機驅動模塊的開關損耗提出更高要求有些低端驅動芯片可能響應不過來導致實際效果變差。經(jīng)驗值我通常選擇5kHz到10kHz。這個區(qū)間對于TB6612和L298N都工作良好電機運行平穩(wěn)噪音在可接受范圍內(nèi)。我們以10kHz為例進行配置。3.2 使用CubeMX進行圖形化配置HAL庫對于初學者我強烈推薦使用STM32CubeMX工具配合HAL庫。它通過圖形界面配置時鐘和引腳自動生成初始化代碼能避免大量底層寄存器操作的錯誤。時鐘樹配置確保系統(tǒng)時鐘SYSCLK被正確設置為72MHzSTM32F103的最高主頻。這是計算定時器計數(shù)值的基準。定時器配置以TIM2為例在Pinout視圖找到PA0、PA1、PA2分別設置為TIM2_CH1、TIM2_CH2、TIM2_CH3。在左側配置欄找到TIM2。Clock Source選擇Internal Clock。Parameter Settings選項卡Prescaler預分頻器PSC設置為71。定時器時鐘 72MHz / (711) 1MHz。Counter ModeUp向上計數(shù)。Counter Period自動重裝載值ARR設置為99。PWM頻率 定時器時鐘 / ((PSC1) * (ARR1)) 1MHz / (100) 10kHz。公式一定要理解Fpwm Fclk / ((PSC1) * (ARR1))。auto-reload preloadEnable使能自動重裝載。切換到PWM Generation CHx對應通道子選項卡ModePWM mode 1。Pulse初始脈沖值先設為0。這個值就是后續(xù)控制占空比的關鍵范圍是0到ARR即99。占空比 Pulse / (ARR1)。Fast ModeDisable。CH PolarityHigh。這意味著當計數(shù)器小于Pulse值時輸出高電平。這個極性要和你的驅動模塊邏輯匹配。TB6612一般是高電平有效。生成代碼在Project Manager里設置好工程名、路徑、IDE如MDK-ARM V5然后生成代碼。3.3 在代碼中操控PWM與方向CubeMX生成的代碼已經(jīng)完成了定時器和GPIO的初始化。我們只需要在main.c的用戶代碼區(qū)調(diào)用HAL庫函數(shù)即可。// 在main函數(shù)初始化部分之后啟動PWM輸出 HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); // 啟動TIM2 CH1 (PA0, AIN1的PWM? 不這里注意) HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_2); // 啟動TIM2 CH2 (PA1, AIN2的PWM? 不這里注意) HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_3); // 啟動TIM2 CH3 (PA2, PWMA) // 同理啟動TIM3的各個通道 HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_2); HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_3);這里有一個非常重要的細節(jié)我們連接時AIN1/AIN2、BIN1/BIN2接的是普通GPIO口雖然我們用了定時器通道引腳但可以配置為純輸出模式而PWMA/PWMB接的才是真正的PWM輸出引腳。在CubeMX里PA0和PA1應該配置為GPIO_Output而不是TIM2_CH1/2。只有PA2PWMA需要配置為TIM2_CH3的PWM模式。所以正確的做法是PA0(AIN1),PA1(AIN2) 配置為GPIO Output。PA2(PWMA) 配置為TIM2_CH3的PWM Generation。在代碼中通過HAL_GPIO_WritePin來控制PA0和PA1的高低電平組合決定電機方向通過__HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_3, pulse)來改變PA2的PWM占空比控制電機速度。方向控制邏輯以左輪為例AIN1 (PA0)AIN2 (PA1)電機狀態(tài)10正轉01反轉00剎車停止11剎車停止我們可以封裝一個函數(shù)來控制一個電機// 控制左輪電機 void Left_Motor_Control(int16_t speed) // speed范圍-100 ~ 100 { if(speed 0) { HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_SET); // 正轉 HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_3, speed); // 假設ARR99 speed就是占空比 } else if(speed 0) { HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET); // 反轉 HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_3, -speed); } else { HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET); // 剎車 HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_3, 0); } }對于右輪電機同理封裝Right_Motor_Control函數(shù)操作BIN1、BIN2和TIM3_CH3。4. 基礎運動函數(shù)封裝與調(diào)試讓小車“走”起來有了單個電機的控制函數(shù)我們就可以組合出小車的基本動作了。封裝這些動作函數(shù)能讓主程序邏輯變得非常清晰。4.1 運動函數(shù)封裝// 小車運動函數(shù)封裝 void Car_Stop(void) { Left_Motor_Control(0); Right_Motor_Control(0); } void Car_Forward(uint8_t speed) { Left_Motor_Control(speed); Right_Motor_Control(speed); } void Car_Backward(uint8_t speed) { Left_Motor_Control(-speed); Right_Motor_Control(-speed); } void Car_TurnLeft(uint8_t speed) // 原地左轉左輪后退右輪前進 { Left_Motor_Control(-speed); Right_Motor_Control(speed); } void Car_TurnRight(uint8_t speed) // 原地右轉 { Left_Motor_Control(speed); Right_Motor_Control(-speed); } void Car_SpinLeft(uint8_t speed) // 左輪不動或慢速右輪快速前進實現(xiàn)弧線左轉 { Left_Motor_Control(speed / 3); // 比例可調(diào) Right_Motor_Control(speed); } void Car_SpinRight(uint8_t speed) // 弧線右轉 { Left_Motor_Control(speed); Right_Motor_Control(speed / 3); }4.2 上電調(diào)試與常見問題排查將代碼編譯下載到STM32后先別急著讓小車跑。按步驟調(diào)試靜態(tài)測試車輪懸空在main函數(shù)的while(1)循環(huán)里寫一個簡單的測試序列。while (1) { Car_Forward(50); // 50%速度前進 HAL_Delay(2000); Car_Stop(); HAL_Delay(500); Car_TurnLeft(60); // 原地左轉 HAL_Delay(1000); Car_Stop(); HAL_Delay(2000); // ... 其他動作測試 }觀察車輪是否按預期轉動。如果不動按以下順序排查電源指示燈STM32板和驅動板上的電源LED亮了嗎信號測量用萬用表電壓檔或示波器如果有測量PWMA引腳對地電壓。在電機轉動時你應該能看到一個平均電壓比如3.3V * 50% ≈ 1.65V。用萬用表頻率檔可以粗略看PWM頻率。方向信號測量AIN1和AIN2的電壓是否根據(jù)代碼變化0V或3.3V。電機驅動輸出測量AO1和AO2之間的電壓。當電機轉動時這里應該有接近VM電源電壓的PWM輸出。注意安全避免短路。代碼檢查確認Left_Motor_Control函數(shù)里的引腳名和CubeMX配置的一致。確認__HAL_TIM_SET_COMPARE函數(shù)的第一個參數(shù)定時器句柄正確。動態(tài)測試小車落地靜態(tài)測試通過后將小車放在空曠平整的地面上遠離桌子邊緣。再次運行測試程序。觀察小車運動軌跡。典型問題小車不走直線而是偏向一邊。這是100%會出現(xiàn)的現(xiàn)象也是開環(huán)控制的固有缺陷。原因有兩個電機差異性即使是同一批次的電機其空載轉速、負載特性也有微小差異。車輪安裝與摩擦車輪安裝的同心度、與地面的摩擦力不可能完全一致。開環(huán)階段的“土辦法”校準我們無法感知誤差但可以“預補償”。通過實驗找到一個偏差值。比如你發(fā)現(xiàn)小車總是向右偏那么可以讓左輪的速度略高于右輪。void Car_Forward_Adjusted(uint8_t speed) { uint8_t left_adj speed 5; // 左輪增加5個“單位”的補償 uint8_t right_adj speed; if(left_adj 100) left_adj 100; // 防止溢出 Left_Motor_Control(left_adj); Right_Motor_Control(right_adj); }這個補償值需要你在實際場地上反復測試獲得。這就是開環(huán)控制基于事先的“經(jīng)驗”或“校準”來發(fā)指令無法應對運行中的新變化。5. 構建開環(huán)運動序列設計你的第一條“巡線”開環(huán)控制并非一無是處。在已知的、結構化的環(huán)境中我們可以通過精心設計的時序控制讓小車完成一些固定任務。最經(jīng)典的例子就是讓小車走一個正方形軌跡。5.1 “走正方形”程序設計與陷阱思路很簡單前進一段距離 - 右轉90度 - 前進 - 右轉... 循環(huán)4次。但這里的關鍵是“一段距離”和“90度”如何用時間和速度來定義void Car_Run_Square(void) { uint8_t side_speed 60; uint16_t forward_time 1000; // 前進時間單位ms uint16_t turn_time 500; // 旋轉90度時間單位ms for(int i 0; i 4; i) { Car_Forward(side_speed); HAL_Delay(forward_time); // 前進 Car_Stop(); HAL_Delay(100); // 停頓一下讓動作更清晰 Car_TurnRight(side_speed); HAL_Delay(turn_time); // 右轉 Car_Stop(); HAL_Delay(100); } Car_Stop(); }你會發(fā)現(xiàn)這個小車畫出來的絕對不是一個標準的正方形可能是一個歪歪扭扭的平行四邊形甚至是個螺旋形。原因如下速度-時間與距離的非線性Car_Forward(side_speed)指令發(fā)出后小車從靜止加速到穩(wěn)定速度需要時間停止時也有減速過程。HAL_Delay(forward_time)延遲的這1秒鐘里包含了加速和減速段真正以穩(wěn)定速度前進的距離小于速度 * 時間。地面摩擦影響不同地面的摩擦系數(shù)不同加速和減速時間也不同。轉彎角度的不確定性Car_TurnRight是差速轉彎其角速度受電機性能、電池電壓、地面摩擦等多重影響。HAL_Delay(turn_time)延遲0.5秒轉過的角度不一定是90度。5.2 從“開環(huán)”到“開環(huán)”的改進思路純粹的延時開環(huán)控制魯棒性極差。為了讓它更可靠一點我們可以引入一些非常簡單的“準反饋”或更精細的控制模型這可以看作是從純開環(huán)到閉環(huán)的一個過渡練習。思路一分段速度控制。模仿工業(yè)上的“S曲線加減速”讓啟動和停止更平滑減少打滑提高時間-距離關系的可重復性。void Car_Forward_Smooth(uint8_t target_speed, uint16_t distance_time) { // 加速段 for(int s 30; s target_speed; s5) { Car_Forward(s); HAL_Delay(20); // 每20ms增加一點速度 } // 勻速段 Car_Forward(target_speed); HAL_Delay(distance_time); // 減速段 for(int s target_speed; s 30; s-5) { Car_Forward(s); HAL_Delay(20); } Car_Stop(); }通過勻加速/減速電機負載變化更平緩電池電壓波動更小兩次執(zhí)行的一致性會稍好一些。思路二利用簡單的傳感器進行事件觸發(fā)。比如在小車底盤前端安裝一個微動開關作為“碰撞傳感器”。我們可以寫一個程序“一直前進直到碰到障礙物開關被壓下然后后退一點右轉90度繼續(xù)前進”。這就構成了一個非常初級的“隨機避障”行為。雖然它沒有測量距離只是感知了“碰撞”這個事件但已經(jīng)引入了外部反饋系統(tǒng)的智能性上了一個臺階。這需要你學習STM32的外部中斷EXTI編程。5.3 開環(huán)控制的價值總結通過以上實踐你應該深刻體會到開環(huán)控制的局限性它無法應對擾動精度差。但它的價值同樣巨大驗證了硬件系統(tǒng)電機、驅動、電源、單片機連接全部正確。掌握了最底層的控制API你知道了如何讓輪子正轉、反轉、變速。建立了系統(tǒng)性的調(diào)試思維從電源、信號、到軟件邏輯的逐層排查。直觀理解了“為什么需要閉環(huán)”你親眼看到了小車跑不直、轉彎不準這為你后續(xù)學習編碼器測速、PID控制提供了最強烈的學習動機——你知道了你要解決的問題到底是什么。開環(huán)控制是起點而不是終點。它就像學騎自行車時的輔助輪幫你建立起平衡感和操控感但最終你要拆掉它去面對更真實、更復雜的路況。當你成功實現(xiàn)小車的開環(huán)控制后下一步自然就是為電機裝上編碼器開始探索“閉環(huán)控制”的世界那時你的小車才能真正做到“指哪打哪”抵抗各種外部干擾。