1. 項(xiàng)目概述從零打造一臺(tái)“全能”智能小車搞嵌入式開(kāi)發(fā)尤其是玩單片機(jī)的朋友十個(gè)里有八個(gè)都繞不開(kāi)“智能小車”這個(gè)經(jīng)典項(xiàng)目。它就像電子工程師的“Hello World”但遠(yuǎn)比打印一行字復(fù)雜和有趣得多。今天我想分享的就是基于STM32微控制器整合了遠(yuǎn)程控制、自動(dòng)避障和循跡這三大核心功能的智能小車實(shí)現(xiàn)方案。這不僅僅是一個(gè)簡(jiǎn)單的玩具組裝更是一個(gè)涵蓋了硬件選型、電路設(shè)計(jì)、傳感器融合、電機(jī)控制、通信協(xié)議乃至簡(jiǎn)單控制算法的綜合性實(shí)戰(zhàn)項(xiàng)目。為什么選擇STM32對(duì)于這類需要實(shí)時(shí)處理多路傳感器數(shù)據(jù)、控制電機(jī)PWM、同時(shí)維持穩(wěn)定通信的項(xiàng)目STM32系列尤其是F1或F4系列在性能、外設(shè)豐富度和開(kāi)發(fā)生態(tài)上相比傳統(tǒng)的8位或16位單片機(jī)如51、AVR以及一些簡(jiǎn)易開(kāi)發(fā)板如Arduino UNO有著明顯優(yōu)勢(shì)。它的定時(shí)器資源可以輕松產(chǎn)生多路精準(zhǔn)的PWM波驅(qū)動(dòng)電機(jī)多個(gè)ADC通道能同時(shí)讀取模擬傳感器USART、SPI、I2C等接口為連接各種模塊提供了便利更重要的是其主頻和計(jì)算能力足以支撐一些實(shí)時(shí)性要求較高的任務(wù)比如PID循跡算法。這個(gè)項(xiàng)目適合有一定C語(yǔ)言和單片機(jī)基礎(chǔ)想通過(guò)一個(gè)綜合項(xiàng)目提升嵌入式系統(tǒng)設(shè)計(jì)、軟硬件聯(lián)調(diào)能力的開(kāi)發(fā)者。最終你將得到一臺(tái)能通過(guò)手機(jī)或電腦遠(yuǎn)程操控、遇到障礙自動(dòng)停下或繞開(kāi)、并能沿著預(yù)設(shè)黑色軌跡線自動(dòng)行駛的智能小車。2. 核心功能模塊設(shè)計(jì)與硬件選型解析一臺(tái)功能完善的智能小車可以看作一個(gè)微縮的移動(dòng)機(jī)器人系統(tǒng)。其硬件架構(gòu)通常包括控制核心、感知模塊、執(zhí)行模塊、通信模塊和能源模塊。下面我們來(lái)逐一拆解每個(gè)部分的設(shè)計(jì)思路和選型考量。2.1 控制核心STM32微控制器選型作為小車的大腦STM32的選型直接決定了項(xiàng)目的上限。對(duì)于這個(gè)多功能小車我推薦使用STM32F103C8T6俗稱“藍(lán)橋杯”或“最小系統(tǒng)板”或STM32F407VET6。前者性價(jià)比極高擁有72MHz主頻、64KB Flash、20KB RAM、3個(gè)USART、2個(gè)SPI、2個(gè)I2C、多達(dá)8個(gè)定時(shí)器完全能滿足基礎(chǔ)需求。如果你的算法更復(fù)雜比如打算上視覺(jué)識(shí)別K210或者未來(lái)想擴(kuò)展更多功能如OLED顯示、語(yǔ)音模塊那么F407168MHz主頻192KB RAM豐富的外設(shè)是更好的選擇它提供了更大的折騰空間。注意新手常犯的一個(gè)錯(cuò)誤是盲目追求高性能芯片。對(duì)于第一臺(tái)小車F103系列完全足夠。把有限的精力集中在軟件邏輯和調(diào)試上比糾結(jié)芯片型號(hào)更有價(jià)值。2.2 感知模塊讓小車“看見(jiàn)”和“感知”世界感知模塊是小車的眼睛和觸角負(fù)責(zé)收集環(huán)境信息。循跡傳感器用于讓小車沿著黑色軌跡線行走。常見(jiàn)的有紅外對(duì)管式和灰度傳感器。紅外對(duì)管發(fā)射紅外光接收反射光強(qiáng)度通過(guò)比較器輸出數(shù)字信號(hào)0或1。通常集成多路如5路安裝在小車底部前方用于檢測(cè)地面黑白變化。優(yōu)點(diǎn)是電路簡(jiǎn)單、響應(yīng)快、成本低。灰度傳感器類似紅外對(duì)管但通常輸出的是模擬電壓值能感知更豐富的灰度層次而不僅僅是黑白。通過(guò)STM32的ADC讀取可以實(shí)現(xiàn)更細(xì)膩的循跡控制比如判斷是否偏離中線。五路或八路灰度傳感器陣列是參加智能車競(jìng)賽的常見(jiàn)配置能提供更精確的路徑信息。選型建議對(duì)于學(xué)習(xí)和一般應(yīng)用五路數(shù)字紅外循跡模塊足矣。如果想挑戰(zhàn)更復(fù)雜的路徑如十字路口、環(huán)島或者想實(shí)踐PID控制算法那么選擇模擬輸出的灰度傳感器陣列更好。避障傳感器用于檢測(cè)前方障礙物。最常用的是HC-SR04超聲波模塊和紅外避障模塊。HC-SR04超聲波模塊通過(guò)發(fā)射和接收超聲波來(lái)測(cè)量距離精度在厘米級(jí)檢測(cè)距離遠(yuǎn)2cm-400cm不受光線影響。但檢測(cè)有盲區(qū)約2cm內(nèi)且對(duì)柔軟、吸音材料的物體檢測(cè)不佳。紅外避障模塊發(fā)射調(diào)制紅外光檢測(cè)是否有反射。輸出數(shù)字信號(hào)有障礙物輸出低電平。優(yōu)點(diǎn)是價(jià)格極其低廉響應(yīng)速度快。但檢測(cè)距離短一般幾厘米到幾十厘米且容易受環(huán)境光特別是太陽(yáng)光和物體顏色的干擾。方案選擇我推薦前方主要使用一個(gè)超聲波模塊用于中遠(yuǎn)距離障礙檢測(cè)在小車左右兩側(cè)或前側(cè)方輔以1-2個(gè)紅外避障模塊用于檢測(cè)近處或側(cè)面的障礙實(shí)現(xiàn)更全面的防護(hù)。這種多傳感器融合的思路在實(shí)際機(jī)器人中很常見(jiàn)。2.3 執(zhí)行模塊小車的“雙腿”——電機(jī)與驅(qū)動(dòng)小車的運(yùn)動(dòng)由電機(jī)驅(qū)動(dòng)輪子實(shí)現(xiàn)。最常用的是直流減速電機(jī)它集成了電機(jī)和減速齒輪箱輸出扭矩大轉(zhuǎn)速可控。電機(jī)驅(qū)動(dòng)芯片STM32的IO口電流不足以直接驅(qū)動(dòng)電機(jī)必須通過(guò)電機(jī)驅(qū)動(dòng)橋。最經(jīng)典、最皮實(shí)耐用的方案是L298N雙H橋驅(qū)動(dòng)模塊。它可以同時(shí)驅(qū)動(dòng)兩個(gè)直流電機(jī)支持正反轉(zhuǎn)和PWM調(diào)速。雖然效率不是最高但其結(jié)構(gòu)簡(jiǎn)單、驅(qū)動(dòng)能力強(qiáng)、資料豐富是新手入門的不二之選。進(jìn)階可以選擇效率更高的TB6612FNG等驅(qū)動(dòng)芯片。供電隔離務(wù)必注意電機(jī)是“用電大戶”啟動(dòng)和堵轉(zhuǎn)時(shí)會(huì)產(chǎn)生很大的電流尖峰和電壓波動(dòng)可能通過(guò)電源線干擾脆弱的單片機(jī)系統(tǒng)導(dǎo)致復(fù)位或程序跑飛。強(qiáng)烈建議為單片機(jī)控制系統(tǒng)和電機(jī)驅(qū)動(dòng)系統(tǒng)使用兩套獨(dú)立的電源如兩節(jié)18650電池并聯(lián)或者至少在電源入口處加入大電容和磁珠進(jìn)行濾波。這是保證系統(tǒng)穩(wěn)定的關(guān)鍵。2.4 通信模塊實(shí)現(xiàn)遠(yuǎn)程控制的“神經(jīng)”遠(yuǎn)程控制意味著小車需要與上位機(jī)手機(jī)/電腦進(jìn)行無(wú)線通信。藍(lán)牙模塊HC-05/HC-06最簡(jiǎn)單直接的方案。手機(jī)通過(guò)藍(lán)牙串口APP如“藍(lán)牙串口”發(fā)送字符命令HC-05模塊接收到后通過(guò)串口轉(zhuǎn)發(fā)給STM32。優(yōu)點(diǎn)是成本低、連接方便、功耗相對(duì)較低。缺點(diǎn)是通信距離短通常10米內(nèi)且一般只能一對(duì)一連接。Wi-Fi模塊ESP8266/ESP32更強(qiáng)大和靈活的方案。以ESP8266為例它可以設(shè)置為STA模式連接家庭路由器或者AP模式自建熱點(diǎn)。然后可以通過(guò)TCP/IP協(xié)議與手機(jī)或電腦通信。你可以編寫一個(gè)簡(jiǎn)單的手機(jī)APP或網(wǎng)頁(yè)來(lái)控制小車甚至可以實(shí)現(xiàn)視頻圖傳需搭配攝像頭。ESP32性能更強(qiáng)還支持藍(lán)牙。缺點(diǎn)是配置稍復(fù)雜功耗比藍(lán)牙高。2.4G無(wú)線模塊NRF24L01專為低速無(wú)線數(shù)據(jù)傳輸設(shè)計(jì)功耗低傳輸距離比藍(lán)牙遠(yuǎn)加功率放大后可達(dá)百米級(jí)。但它需要配對(duì)使用一收一發(fā)且通常需要自己定義簡(jiǎn)單的通信協(xié)議不如藍(lán)牙即插即用方便。我的方案建議對(duì)于初次嘗試想快速看到遠(yuǎn)程控制效果藍(lán)牙方案是首選。如果你希望有更遠(yuǎn)的控制距離、更豐富的交互比如網(wǎng)頁(yè)控制面板或者為未來(lái)擴(kuò)展如接入物聯(lián)網(wǎng)平臺(tái)留有余地那么使用ESP8266作為Wi-Fi協(xié)處理器通過(guò)串口與STM32通信是一個(gè)性價(jià)比極高的選擇。STM32負(fù)責(zé)底層控制電機(jī)、傳感器ESP8266負(fù)責(zé)網(wǎng)絡(luò)通信和協(xié)議解析分工明確。2.5 能源模塊小車的“心臟”電源系統(tǒng)必須穩(wěn)定可靠。通常使用18650鋰電池組兩節(jié)串聯(lián)7.4V或聚合物鋰電池。7.4V電壓經(jīng)過(guò)L298N驅(qū)動(dòng)模塊后可直接給電機(jī)供電。同時(shí)你需要一個(gè)降壓模塊如LM2596、MP1584將電池電壓降至5V給單片機(jī)、傳感器、通信模塊等供電。STM32核心板通常還需要3.3V這可以由5V再通過(guò)LDO如AMS1117-3.3轉(zhuǎn)換得到或者有些降壓模塊直接輸出3.3V和5V。3. 系統(tǒng)軟件架構(gòu)與模塊化編程思想硬件是骨架軟件是靈魂。面對(duì)遠(yuǎn)程控制、避障、循跡等多個(gè)功能如何組織代碼至關(guān)重要。切忌把所有代碼都堆在main.c的while(1)循環(huán)里。采用模塊化編程能極大提高代碼的可讀性、可維護(hù)性和可調(diào)試性。3.1 核心任務(wù)分解與調(diào)度我們可以將小車的功能分解為幾個(gè)相對(duì)獨(dú)立的任務(wù)通信任務(wù)負(fù)責(zé)通過(guò)串口連接藍(lán)牙/Wi-Fi模塊接收上位機(jī)指令并解析為具體的控制命令如前進(jìn)、后退、左轉(zhuǎn)、右轉(zhuǎn)、切換模式。傳感器數(shù)據(jù)采集任務(wù)定時(shí)或觸發(fā)式地讀取循跡傳感器陣列的值、超聲波測(cè)距值、紅外避障狀態(tài)等。決策與控制任務(wù)這是核心邏輯。根據(jù)當(dāng)前的工作模式手動(dòng)遙控模式、自動(dòng)避障模式、自動(dòng)循跡模式和傳感器數(shù)據(jù)計(jì)算出電機(jī)應(yīng)有的PWM占空比和方向。電機(jī)執(zhí)行任務(wù)根據(jù)決策任務(wù)給出的目標(biāo)通過(guò)定時(shí)器輸出PWM波控制L298N驅(qū)動(dòng)電機(jī)轉(zhuǎn)動(dòng)。對(duì)于STM32實(shí)現(xiàn)這種多任務(wù)有幾種常見(jiàn)方法超級(jí)循環(huán)Super Loop在while(1)中按順序調(diào)用各個(gè)任務(wù)函數(shù)。這是最簡(jiǎn)單的方法但需要精心設(shè)計(jì)每個(gè)任務(wù)的執(zhí)行時(shí)間避免某個(gè)任務(wù)阻塞導(dǎo)致其他任務(wù)無(wú)法及時(shí)響應(yīng)。可以通過(guò)狀態(tài)機(jī)和非阻塞延時(shí)來(lái)優(yōu)化。定時(shí)器中斷利用STM32的定時(shí)器產(chǎn)生固定周期如10ms的中斷在中斷服務(wù)函數(shù)中設(shè)置標(biāo)志位。主循環(huán)中檢查這些標(biāo)志位來(lái)執(zhí)行對(duì)應(yīng)的任務(wù)。這能提供更精確的時(shí)間基準(zhǔn)。實(shí)時(shí)操作系統(tǒng)RTOS如FreeRTOS。可以為每個(gè)任務(wù)創(chuàng)建一個(gè)獨(dú)立的線程由操作系統(tǒng)內(nèi)核進(jìn)行調(diào)度。這是最優(yōu)雅、最接近工程實(shí)踐的方式但學(xué)習(xí)曲線稍陡。對(duì)于初學(xué)者我建議先從**“超級(jí)循環(huán)狀態(tài)機(jī)”** 的方式入手理解任務(wù)調(diào)度的本質(zhì)后再嘗試RTOS。3.2 關(guān)鍵算法淺析PID循跡循跡的核心是讓小車始終沿著黑線中心行駛。當(dāng)傳感器檢測(cè)到小車偏離時(shí)需要調(diào)整左右輪的速度差來(lái)糾正方向。這里就可以引入經(jīng)典的PID控制算法。我們以五路灰度傳感器為例假設(shè)中間三個(gè)傳感器左2中右2用于循跡。我們可以定義一個(gè)“偏差”變量error。最簡(jiǎn)單的方法error 左2值 - 右2值。error為正表示偏左需要向右轉(zhuǎn)右輪加速或左輪減速。更精細(xì)的方法給五個(gè)傳感器賦予不同的權(quán)重計(jì)算加權(quán)中心位置。PID控制器根據(jù)這個(gè)error來(lái)計(jì)算輸出output即左右電機(jī)的速度差output Kp * error Ki * integral(error) Kd * derivative(error)比例P與當(dāng)前誤差成正比反應(yīng)“現(xiàn)在”的偏離程度。P越大糾正力度越大但可能引起振蕩。積分I累積歷史誤差用于消除靜態(tài)誤差比如小車始終無(wú)法完全對(duì)準(zhǔn)中心。但I(xiàn)太大會(huì)導(dǎo)致系統(tǒng)反應(yīng)遲鈍和超調(diào)。微分D預(yù)測(cè)誤差變化趨勢(shì)具有“阻尼”作用能抑制振蕩提高穩(wěn)定性。對(duì)于小車循跡很多時(shí)候一個(gè)純P控制或者PD控制就能取得不錯(cuò)的效果。調(diào)試時(shí)先將Ki和Kd設(shè)為0從小到大調(diào)整Kp直到小車能跟隨但略有抖動(dòng)然后加入較小的Kd來(lái)抑制抖動(dòng)。這個(gè)過(guò)程需要反復(fù)在實(shí)車上測(cè)試。3.3 通信協(xié)議設(shè)計(jì)即使是簡(jiǎn)單的藍(lán)牙串口控制也建議定義一個(gè)簡(jiǎn)單的協(xié)議而不是直接發(fā)送單個(gè)字符。這能提高通信的可靠性和可擴(kuò)展性。 例如可以定義一幀數(shù)據(jù)為幀頭(0xAA) 命令字(1字節(jié)) 數(shù)據(jù)長(zhǎng)度(1字節(jié)) 數(shù)據(jù)(N字節(jié)) 校驗(yàn)和(1字節(jié)) 幀尾(0x55)。 命令字可以定義0x01前進(jìn)數(shù)據(jù)為速度值0x02后退0x03停止0x10切換至手動(dòng)模式0x11切換至自動(dòng)避障模式等。 STM32端需要編寫一個(gè)串口數(shù)據(jù)解析函數(shù)根據(jù)幀結(jié)構(gòu)進(jìn)行接收、校驗(yàn)和解析。4. 硬件電路設(shè)計(jì)與集成要點(diǎn)有了各模塊的選型接下來(lái)就是將它們有機(jī)地連接在一起。這里不涉及復(fù)雜的PCB設(shè)計(jì)我們以使用現(xiàn)成模塊和杜邦線連接為例。4.1 最小系統(tǒng)與電源樹首先確保STM32最小系統(tǒng)板能正常工作連接好晶振、復(fù)位電路、Boot啟動(dòng)模式選擇通常Boot0接GND從主Flash啟動(dòng)并通過(guò)ST-Link或USB轉(zhuǎn)串口工具下載程序。 電源是重中之重。建議的供電方案如下[7.4V鋰電池組] ├── [降壓模塊] - 輸出5V │ ├── [LDO 3.3V] - STM32 VDD, 傳感器、模塊的3.3V引腳 │ └── 直接供給 - 藍(lán)牙/Wi-Fi模塊的5V引腳超聲波模塊VCC等 └── 直接供給 - L298N驅(qū)動(dòng)模塊的電源輸入(12V/5V端子具體看模塊說(shuō)明)重要提示務(wù)必確保所有模塊的“地”GND連接在一起共地是電路正常工作的基礎(chǔ)。電機(jī)驅(qū)動(dòng)模塊的“使能端”需要接高電平來(lái)啟用。L298N的邏輯供電為內(nèi)部邏輯電路供電如果獨(dú)立可以接5V如果與電機(jī)供電共用則接電池電壓具體請(qǐng)查閱模塊手冊(cè)。4.2 傳感器與STM32的接口連接五路循跡模塊通常輸出5個(gè)數(shù)字IO信號(hào)。將其VCC和GND接到STM32的3.3V和GND。將5個(gè)輸出引腳分別連接到STM32的5個(gè)GPIO口配置為上拉輸入或浮空輸入具體看模塊輸出電平。HC-SR04超聲波模塊Trig引腳觸發(fā)接STM32的一個(gè)GPIO配置為推挽輸出Echo引腳回響接另一個(gè)GPIO配置為輸入最好支持外部中斷或輸入捕獲功能以實(shí)現(xiàn)精確計(jì)時(shí)。VCC接5VGND接GND。紅外避障模塊輸出數(shù)字信號(hào)連接方式與循跡模塊類似。注意其檢測(cè)距離可通過(guò)電位器調(diào)節(jié)。L298N電機(jī)驅(qū)動(dòng)IN1,IN2控制電機(jī)A的方向IN3,IN4控制電機(jī)B的方向。將它們連接到STM32的4個(gè)GPIO推挽輸出。ENA,ENB是電機(jī)A和B的PWM使能端用于調(diào)速。將它們連接到STM32的兩個(gè)具有定時(shí)器PWM輸出功能的引腳如TIM2_CH1, TIM2_CH2。藍(lán)牙模塊HC-05TXD接STM32的RXD某個(gè)USART的接收腳RXD接STM32的TXD發(fā)送腳。VCC接5V或3.3V根據(jù)模塊版本GND接GND。注意藍(lán)牙模塊與STM32通信是交叉連接TX接RX。4.3 布局與走線建議雖然是用杜邦線連接良好的布局也能減少干擾強(qiáng)弱電分離電機(jī)驅(qū)動(dòng)的大電流線路電池到L298NL298N到電機(jī)盡量遠(yuǎn)離單片機(jī)、傳感器的細(xì)信號(hào)線。如果必須交叉盡量垂直交叉。電源線加粗給電機(jī)供電的導(dǎo)線盡可能短而粗以減少壓降和發(fā)熱。信號(hào)線捆扎同類信號(hào)線如傳感器數(shù)據(jù)線、電機(jī)控制線可以捆扎在一起使機(jī)艙內(nèi)整潔也便于調(diào)試。濾波電容在STM32的電源入口處靠近芯片的位置并聯(lián)一個(gè)10uF的電解電容和一個(gè)0.1uF的瓷片電容用于濾除高低頻噪聲。5. 固件開(kāi)發(fā)從外設(shè)驅(qū)動(dòng)到業(yè)務(wù)邏輯開(kāi)發(fā)環(huán)境通常選擇Keil MDK或STM32CubeIDE。我傾向于使用STM32CubeMX進(jìn)行圖形化引腳配置和中間件初始化然后生成代碼在生成的工程框架上添加業(yè)務(wù)邏輯。這能節(jié)省大量配置外設(shè)的時(shí)間并減少低級(jí)錯(cuò)誤。5.1 使用CubeMX進(jìn)行基礎(chǔ)配置選擇芯片型號(hào)創(chuàng)建新工程選擇你使用的具體STM32型號(hào)。配置時(shí)鐘樹Clock Configuration這是關(guān)鍵一步。通常選擇外部高速晶振HSE作為時(shí)鐘源然后配置PLL鎖相環(huán)將系統(tǒng)時(shí)鐘SYSCLK設(shè)置到芯片允許的最高頻率如F103為72MHz。更高的主頻意味著更快的程序執(zhí)行速度和更精確的定時(shí)。配置外設(shè)引腳GPIO將連接循跡、紅外避障、超聲波Trig、L298N方向控制INx的引腳設(shè)置為GPIO_Output。將連接超聲波Echo的引腳設(shè)置為GPIO_Input。定時(shí)器TIM配置兩個(gè)定時(shí)器通道用于產(chǎn)生PWM。例如選擇TIM2的Channel1和Channel2模式設(shè)為PWM Generation CHx。在Parameter Settings中設(shè)置預(yù)分頻器Prescaler和自動(dòng)重裝載值Counter Period以得到所需的PWM頻率。對(duì)于直流電機(jī)PWM頻率通常在1kHz到20kHz之間頻率太低電機(jī)會(huì)有嘯叫太高則驅(qū)動(dòng)芯片可能效率下降。可以設(shè)為10kHz左右。計(jì)算舉例系統(tǒng)時(shí)鐘72MHz預(yù)分頻器設(shè)為72-1則計(jì)數(shù)器時(shí)鐘為1MHz。設(shè)置自動(dòng)重裝載值為100-1則PWM頻率 1MHz / 100 10kHz。占空比通過(guò)設(shè)置Pulse值小于100來(lái)調(diào)整。USART配置用于連接藍(lán)牙模塊的串口如USART1。模式設(shè)為Asynchronous波特率設(shè)為9600或115200需與藍(lán)牙模塊配對(duì)時(shí)設(shè)置的波特率一致。ADC如果使用模擬灰度傳感器配置用于讀取傳感器模擬值的ADC通道設(shè)置為單次轉(zhuǎn)換或掃描轉(zhuǎn)換模式。生成代碼設(shè)置好工程路徑和工具鏈MDK-ARM生成代碼。5.2 編寫核心驅(qū)動(dòng)與控制函數(shù)在CubeMX生成的工程中我們主要修改main.c和添加自己的.c/.h文件。電機(jī)驅(qū)動(dòng)函數(shù)// motor.c #include motor.h // 初始化PWM void Motor_PWM_Init(TIM_HandleTypeDef *htim) { HAL_TIM_PWM_Start(htim, TIM_CHANNEL_1); // 啟動(dòng)通道1 PWM HAL_TIM_PWM_Start(htim, TIM_CHANNEL_2); // 啟動(dòng)通道2 PWM } // 設(shè)置左右電機(jī)速度speed范圍假設(shè)為-100~100負(fù)值代表反轉(zhuǎn) void Motor_SetSpeed(int16_t left_speed, int16_t right_speed) { // 限制速度范圍 left_speed constrain(left_speed, -100, 100); right_speed constrain(right_speed, -100, 100); // 根據(jù)速度正負(fù)設(shè)置方向引腳IN1, IN2, IN3, IN4 Set_Motor_Direction(left_speed, right_speed); // 將速度絕對(duì)值轉(zhuǎn)換為PWM占空比對(duì)應(yīng)的比較值 uint16_t left_pwm abs(left_speed) * (MAX_PULSE / 100); uint16_t right_pwm abs(right_speed) * (MAX_PULSE / 100); // 設(shè)置定時(shí)器比較寄存器值 __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, left_pwm); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_2, right_pwm); }其中Set_Motor_Direction函數(shù)根據(jù)速度正負(fù)控制L298N的INx引腳高低電平實(shí)現(xiàn)正反轉(zhuǎn)。例如對(duì)于一個(gè)電機(jī)IN1高、IN2低為正轉(zhuǎn)IN1低、IN2高為反轉(zhuǎn)同時(shí)為低則剎車同時(shí)為高則停止具體看手冊(cè)。超聲波測(cè)距函數(shù)// ultrasonic.c float Ultrasonic_GetDistance(void) { // 發(fā)送至少10us的高電平觸發(fā)信號(hào) HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(20); HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 等待回響引腳變高并開(kāi)始計(jì)時(shí) while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_RESET); uint32_t start_time HAL_GetTick(); // 使用毫秒計(jì)時(shí)精度要求高可用定時(shí)器輸入捕獲 while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_SET); uint32_t end_time HAL_GetTick(); // 計(jì)算高電平持續(xù)時(shí)間毫秒并轉(zhuǎn)換為距離厘米 // 聲音速度按340m/s計(jì)算距離 (時(shí)間 * 340 * 100) / (2 * 1000) ≈ 時(shí)間 * 0.017 float distance_cm (end_time - start_time) * 0.017f; // 注意HAL_GetTick()毫秒級(jí)計(jì)時(shí)精度有限適合演示。高精度需用定時(shí)器輸入捕獲模式測(cè)量微秒級(jí)時(shí)間。 return distance_cm; }循跡傳感器讀取與偏差計(jì)算// tracker.c #define TRACKER_NUM 5 uint8_t tracker_values[TRACKER_NUM]; // 存儲(chǔ)5路傳感器狀態(tài)0為白線1為黑線 int16_t Calculate_Tracker_Error(void) { // 讀取所有傳感器狀態(tài) for(int i0; iTRACKER_NUM; i) { tracker_values[i] HAL_GPIO_ReadPin(tracker_port[i], tracker_pin[i]); } // 簡(jiǎn)單的偏差計(jì)算給每個(gè)傳感器賦權(quán)重計(jì)算加權(quán)中心 // 假設(shè)傳感器從左到右編號(hào)0~4權(quán)重分別為 -2, -1, 0, 1, 2 int16_t error 0; int16_t weights[] {-2, -1, 0, 1, 2}; for(int i0; iTRACKER_NUM; i) { if(tracker_values[i] 1) { // 檢測(cè)到黑線 error weights[i]; } } return error; // error為負(fù)表示偏左需右轉(zhuǎn)為正表示偏右需左轉(zhuǎn)。 }5.3 主循環(huán)邏輯與模式切換在main.c的while(1)循環(huán)中實(shí)現(xiàn)狀態(tài)機(jī)來(lái)管理不同模式typedef enum { MODE_MANUAL 0, MODE_AVOIDANCE, MODE_TRACKING } SystemMode_t; SystemMode_t current_mode MODE_MANUAL; uint8_t uart_cmd 0; // 來(lái)自藍(lán)牙的指令 int main(void) { // HAL初始化外設(shè)初始化... Motor_PWM_Init(htim2); // 其他初始化... while (1) { // 1. 讀取傳感器數(shù)據(jù)非阻塞方式如定時(shí)中斷置標(biāo)志位后讀取 Update_Sensor_Data(); // 2. 處理串口接收到的指令在串口中斷中接收并解析主循環(huán)中處理 if(uart_cmd_received_flag) { Process_UART_Command(uart_cmd); uart_cmd_received_flag 0; // 根據(jù)指令可能改變 current_mode if(uart_cmd CMD_MODE_MANUAL) current_mode MODE_MANUAL; else if(uart_cmd CMD_MODE_AVOID) current_mode MODE_AVOIDANCE; // ... } // 3. 根據(jù)當(dāng)前模式執(zhí)行核心邏輯 switch(current_mode) { case MODE_MANUAL: // 手動(dòng)模式直接根據(jù)uart_cmd控制電機(jī) Manual_Control(uart_cmd); break; case MODE_AVOIDANCE: // 自動(dòng)避障模式 Auto_Avoidance(); break; case MODE_TRACKING: // 自動(dòng)循跡模式 Auto_Tracking(); break; default: Motor_SetSpeed(0, 0); // 停止 break; } // 4. 必要的延時(shí)或等待標(biāo)志位避免循環(huán)空跑耗電 HAL_Delay(10); // 主循環(huán)周期約10ms } }在Auto_Avoidance()函數(shù)中可以這樣實(shí)現(xiàn)void Auto_Avoidance(void) { float dist Ultrasonic_GetDistance(); uint8_t left_obstacle !HAL_GPIO_ReadPin(IR_LEFT_GPIO_Port, IR_LEFT_Pin); // 假設(shè)低電平有障礙 uint8_t right_obstacle !HAL_GPIO_ReadPin(IR_RIGHT_GPIO_Port, IR_RIGHT_Pin); if(dist AVOID_DISTANCE) { // 前方有障礙 Motor_SetSpeed(0, 0); // 先停一下 HAL_Delay(200); // 簡(jiǎn)單策略檢查左右哪邊沒(méi)障礙就向那邊轉(zhuǎn) if(!left_obstacle) { Motor_SetSpeed(-80, 80); // 原地左轉(zhuǎn) HAL_Delay(300); } else if(!right_obstacle) { Motor_SetSpeed(80, -80); // 原地右轉(zhuǎn) HAL_Delay(300); } else { // 左右都有障礙后退 Motor_SetSpeed(-60, -60); HAL_Delay(500); } } else { // 前方無(wú)障礙直行 Motor_SetSpeed(70, 70); } }在Auto_Tracking()函數(shù)中結(jié)合PIDvoid Auto_Tracking(void) { int16_t error Calculate_Tracker_Error(); // 簡(jiǎn)單的P控制 int16_t adjustment KP * error; // KP需要根據(jù)實(shí)測(cè)調(diào)整 // 限制調(diào)整量 adjustment constrain(adjustment, -MAX_ADJUST, MAX_ADJUST); int16_t base_speed 60; // 基礎(chǔ)速度 int16_t left_speed base_speed - adjustment; int16_t right_speed base_speed adjustment; Motor_SetSpeed(left_speed, right_speed); }6. 調(diào)試、優(yōu)化與常見(jiàn)問(wèn)題排查硬件組裝和軟件編寫只是第一步調(diào)試才是項(xiàng)目成功的關(guān)鍵也是最耗時(shí)、最能積累經(jīng)驗(yàn)的環(huán)節(jié)。6.1 分模塊調(diào)試法切勿一次性將所有代碼和硬件連接好再上電調(diào)試。應(yīng)采用“分而治之”的策略電源與最小系統(tǒng)先只連接STM32最小系統(tǒng)和電源用LED閃爍程序測(cè)試核心板是否正常工作。電機(jī)驅(qū)動(dòng)單獨(dú)測(cè)試電機(jī)驅(qū)動(dòng)。編寫一個(gè)簡(jiǎn)單程序讓兩個(gè)電機(jī)正轉(zhuǎn)、反轉(zhuǎn)、停止、調(diào)速。確認(rèn)L298N的邏輯控制和PWM調(diào)速功能正常。傳感器單獨(dú)測(cè)試每個(gè)傳感器。將循跡模塊放在黑白紙上通過(guò)串口打印其IO狀態(tài)讓超聲波模塊對(duì)著不同距離的物體打印測(cè)距值用手遮擋紅外避障模塊觀察其輸出變化。通信模塊單獨(dú)測(cè)試藍(lán)牙。編寫一個(gè)簡(jiǎn)單的串口回顯程序用手機(jī)藍(lán)牙串口APP發(fā)送數(shù)據(jù)看單片機(jī)是否能接收并原樣發(fā)回。功能集成當(dāng)每個(gè)模塊都獨(dú)立工作正常后再將它們的功能逐步整合。先實(shí)現(xiàn)手動(dòng)遙控再添加自動(dòng)避障最后集成循跡。6.2 常見(jiàn)問(wèn)題與解決方案速查表問(wèn)題現(xiàn)象可能原因排查步驟與解決方案上電后單片機(jī)無(wú)反應(yīng)或程序不運(yùn)行1. 電源問(wèn)題電壓不對(duì)、電流不足2. Boot引腳配置錯(cuò)誤3. 晶振未起振4. 復(fù)位電路問(wèn)題1. 用萬(wàn)用表測(cè)量各點(diǎn)電壓3.3V, 5V。2. 確認(rèn)Boot0和Boot1引腳電平通常Boot0接GND。3. 檢查晶振兩端電壓應(yīng)有1V左右交流分量或嘗試使用內(nèi)部晶振HSI啟動(dòng)。4. 檢查復(fù)位引腳是否為高電平按下復(fù)位鍵是否變?yōu)榈碗娖健k姍C(jī)不轉(zhuǎn)或只振動(dòng)1. 電機(jī)驅(qū)動(dòng)供電不足2. PWM頻率不合適3. 使能端未打開(kāi)4. 方向控制邏輯錯(cuò)誤1. 測(cè)量驅(qū)動(dòng)模塊輸入電壓確保電池電量充足。2. 嘗試調(diào)整PWM頻率1k-20k。3. 檢查L(zhǎng)298N的ENA、ENB是否接高電平或PWM信號(hào)。4. 用邏輯分析儀或示波器檢查INx引腳的電平變化是否符合預(yù)期。超聲波測(cè)距不準(zhǔn)或一直為01. 觸發(fā)信號(hào)寬度不夠2. 回響引腳未正確讀取3. 模塊與物體表面不平行4. 環(huán)境干擾多個(gè)超聲波同時(shí)工作1. 確保Trig高電平脈沖至少10us。2. 檢查Echo引腳連接和代碼中的等待邏輯考慮使用輸入捕獲模式提高精度。3. 確保模塊正對(duì)被測(cè)物體。4. 錯(cuò)開(kāi)不同模塊的觸發(fā)時(shí)間。循跡小車跑偏或沖出軌道1. 傳感器安裝高度不一致2. 傳感器閾值設(shè)置不當(dāng)3. 電機(jī)轉(zhuǎn)速不一致機(jī)械誤差4. PID參數(shù)不合適1. 調(diào)整所有傳感器距地面高度一致。2. 在黑白交界處測(cè)試并調(diào)整傳感器比較器電位器或代碼中的判斷閾值。3. 在代碼中為左右電機(jī)設(shè)置一個(gè)微調(diào)系數(shù)進(jìn)行補(bǔ)償。4. 耐心調(diào)試PID參數(shù)先從純P開(kāi)始。藍(lán)牙連接不穩(wěn)定或控制失靈1. 波特率不匹配2. 電源干擾3. 串口緩沖區(qū)溢出4. 手機(jī)APP兼容性問(wèn)題1. 確認(rèn)STM32串口波特率與藍(lán)牙模塊配置的波特率一致常用9600或115200。2. 給藍(lán)牙模塊的電源引腳并聯(lián)一個(gè)100uF電解電容。3. 在串口接收中斷中及時(shí)讀取數(shù)據(jù)或使用DMA。4. 換一個(gè)藍(lán)牙串口APP試試。程序運(yùn)行一段時(shí)間后死機(jī)或復(fù)位1. 電源被電機(jī)拉垮2. 軟件看門狗未處理3. 堆棧溢出4. 中斷沖突或處理時(shí)間過(guò)長(zhǎng)1.最可能的原因加強(qiáng)電源濾波電機(jī)電源與控制系統(tǒng)電源隔離或加大電容。2. 如果使能了IWDG獨(dú)立看門狗需定期喂狗。3. 增大啟動(dòng)文件中的堆棧大小。4. 優(yōu)化中斷服務(wù)函數(shù)只做標(biāo)志位設(shè)置耗時(shí)操作放到主循環(huán)。6.3 性能優(yōu)化與擴(kuò)展思路當(dāng)基礎(chǔ)功能穩(wěn)定后可以考慮以下優(yōu)化和擴(kuò)展速度閉環(huán)控制為電機(jī)增加編碼器實(shí)現(xiàn)精確的速度測(cè)量和PID閉環(huán)控制讓小車速度更穩(wěn)定不受電池電量和負(fù)載影響。更智能的避障算法結(jié)合超聲波和紅外傳感器數(shù)據(jù)實(shí)現(xiàn)更復(fù)雜的決策比如繞障、沿墻走等。上位機(jī)監(jiān)控通過(guò)Wi-Fi模塊將小車的傳感器數(shù)據(jù)速度、距離、圖像等實(shí)時(shí)發(fā)送到電腦上位機(jī)如用PythonPyQt5編寫進(jìn)行顯示和記錄。路徑規(guī)劃與SLAM這是更高級(jí)的領(lǐng)域需要更強(qiáng)的處理器如樹莓派STM32組合和激光雷達(dá)等傳感器實(shí)現(xiàn)地圖構(gòu)建和自主導(dǎo)航。這個(gè)基于STM32的智能小車項(xiàng)目就像一把鑰匙為你打開(kāi)了嵌入式系統(tǒng)、自動(dòng)控制、機(jī)器人技術(shù)的大門。從最初的硬件連線手忙腳亂到調(diào)試PID參數(shù)時(shí)小車在賽道上畫龍?jiān)俚阶罱K它能穩(wěn)定流暢地完成各項(xiàng)任務(wù)這個(gè)過(guò)程里踩過(guò)的每一個(gè)坑解決的每一個(gè)問(wèn)題都是實(shí)實(shí)在在的經(jīng)驗(yàn)積累。我個(gè)人的體會(huì)是硬件項(xiàng)目的成功三分在設(shè)計(jì)七分在調(diào)試。耐心、細(xì)致的調(diào)試以及遇到問(wèn)題時(shí)系統(tǒng)化的排查思路電源-信號(hào)-邏輯-代碼比任何華麗的算法都重要。最后一個(gè)小建議把所有調(diào)試過(guò)程中修改的參數(shù)、遇到的奇怪現(xiàn)象和解決方法都記錄在一個(gè)文檔里這將成為你最寶貴的財(cái)富。