轉(zhuǎn)向循跡小車:從硬件選型到PID控制實(shí)戰(zhàn))
簡(jiǎn)介面向智能車競(jìng)賽與嵌入式初學(xué)者的STM32循跡小車完整工程資料包。方案以72MHz主頻的STM32F103ZET6為核心控制器采用一字型灰度傳感器陣列識(shí)別賽道黑線配合舵機(jī)轉(zhuǎn)向與雙編碼電機(jī)差速驅(qū)動(dòng)并以位置式PID作為主要控制算法可應(yīng)對(duì)直道、環(huán)島、S彎和連續(xù)直角彎等典型賽道元素。壓縮包共207個(gè)文件大小約5.48MB內(nèi)含Keil工程源碼.c/.h、編譯生成的目標(biāo)文件.o/.d、工程配置文件.uvprojx/.uvoptx以及可直接燒錄的hex文件并保留了map、lst等鏈接與列表輸出便于核對(duì)編譯細(xì)節(jié)和程序結(jié)構(gòu)代碼按模塊組織方便閱讀與二次開發(fā)。該資源已吸引8435人瀏覽學(xué)習(xí)受歡迎程度較高。對(duì)想要快速搭建循跡小車、理解PID參數(shù)整定和差速控制邏輯的開發(fā)者來說這套資料既能提供完整代碼參考也能幫助排查工程配置問題是值得收藏的實(shí)戰(zhàn)項(xiàng)目無(wú)論用于備賽還是課程設(shè)計(jì)都能直接參考其完整閉環(huán)。1. 循跡小車為什么值得做成“舵機(jī)電機(jī)”的競(jìng)速底子做循跡小車很多同學(xué)第一個(gè)想到的是“四驅(qū)差速轉(zhuǎn)向”兩個(gè)后輪轉(zhuǎn)速不一樣就能拐彎結(jié)構(gòu)簡(jiǎn)單、代碼也好寫。但如果你要的是“循跡競(jìng)速”也就是在白色賽道上跑出盡量快的圈速差速方案很快就會(huì)撞到天花板轉(zhuǎn)向響應(yīng)慢、循跡精度靠輪子打滑來湊速度一上來就容易沖出賽道。這時(shí)候用“舵機(jī)控制前輪轉(zhuǎn)向 電機(jī)驅(qū)動(dòng)后輪”的模型車架構(gòu)反而更適合高速循跡這也是不少競(jìng)賽車隊(duì)實(shí)際采用的方案。本文圍繞基于STM32F103ZET6的循跡小車展開從硬件選型、傳感器布局、PID控制到賽道策略給出一個(gè)可以直接照著搭的完整設(shè)計(jì)。適合正在做課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)的學(xué)生也適合想從差速小車升級(jí)到高速循跡方案的開發(fā)者參考。2. 總體方案設(shè)計(jì)STM32F103ZET6的資源盤活與車體架構(gòu)選擇2.1 為什么選STM32F103ZET6做主控首先需要明確一件事做循跡小車主控不需要多高性能但外設(shè)資源要夠用。STM32F103ZET6屬于大容量型號(hào)144引腳封裝Flash有512KB、RAM有64KB主頻72MHz。循跡小車的傳感器讀取、舵機(jī)PWM輸出、電機(jī)PWM輸出、編碼器采集、藍(lán)牙調(diào)試這些任務(wù)它的外設(shè)完全扛得住而且?guī)旌瘮?shù)資料多遇到問題容易搜到參考。從資源分配角度我做方案時(shí)會(huì)這樣安排灰度傳感器模塊用GPIO讀取8路數(shù)字信號(hào)或者用ADC讀取模擬量取決于傳感器模塊本身是數(shù)字輸出還是模擬輸出。舵機(jī)需要一個(gè)定時(shí)器產(chǎn)生50Hz的PWM占空比在2.5%到12.5%之間對(duì)應(yīng)0度到180度。通常用TIM1的通道1。驅(qū)動(dòng)電機(jī)需要一個(gè)頻率高一點(diǎn)的PWM來控制速度比如20kHz減少電機(jī)嘯叫用TIM2或TIM3。還要配合兩個(gè)GPIO控制方向。編碼器測(cè)速如果要做速度閉環(huán)需要兩個(gè)定時(shí)器工作在編碼器模式TIM4和TIM5比較常用。調(diào)試接口USART1接藍(lán)牙模塊或者無(wú)線串口用來在跑車的時(shí)候看PID輸出和傳感器狀態(tài)。這套分配下F103ZET6的定時(shí)器和GPIO都還留有大量余量后續(xù)想加超聲波避障、OLED顯示狀態(tài)、按鍵切換模式都不用換主控。2.2 車體架構(gòu)舵機(jī)轉(zhuǎn)向和電機(jī)驅(qū)動(dòng)的職責(zé)劃分循跡小車的車體結(jié)構(gòu)直接決定了控制策略怎么寫。差速小車靠左右輪速差轉(zhuǎn)向轉(zhuǎn)向半徑由輪速差決定而舵機(jī)轉(zhuǎn)向車型的前輪有一個(gè)最大轉(zhuǎn)角舵機(jī)角度和轉(zhuǎn)向半徑呈近似線性關(guān)系。這個(gè)區(qū)別在控制上非常關(guān)鍵差速方案轉(zhuǎn)向相當(dāng)于一個(gè)積分量輪速差持續(xù)存在才會(huì)持續(xù)轉(zhuǎn)彎控制周期要求更高否則會(huì)左右擺動(dòng)手感很差。舵機(jī)方案舵機(jī)角度可以直接映射為目標(biāo)轉(zhuǎn)向角控制環(huán)只需要把當(dāng)前角度調(diào)整到目標(biāo)值天然適合PD控制響應(yīng)快且超調(diào)小。在競(jìng)賽場(chǎng)景下舵機(jī)轉(zhuǎn)向車型的另一個(gè)優(yōu)勢(shì)是支撐結(jié)構(gòu)穩(wěn)定。舵機(jī)裝在底盤前方通過連桿帶動(dòng)前輪轉(zhuǎn)向后輪由電機(jī)通過齒輪或直驅(qū)驅(qū)動(dòng)整車重心分布可以做得更靠后加速出彎時(shí)前輪不容易打滑。順帶說一個(gè)血淚經(jīng)驗(yàn)如果用差速小車跑高速后輪打滑是整個(gè)控制環(huán)路里最難解決的“玄學(xué)問題”而舵機(jī)車型可以把打滑問題主要局限在后輪驅(qū)動(dòng)輪上通過限幅和加速度控制來緩解。2.3 傳感器布局8路灰度探頭的安裝策略循跡小車最常用的傳感器是灰度傳感器核心是紅外對(duì)管——一組紅外發(fā)射管加接收管利用黑線和白底對(duì)紅外光的反射率差異來輸出不同的電平或電壓。典型的灰度傳感器模塊上有電位器可以調(diào)節(jié)靈敏度閾值也有比較器芯片做數(shù)字輸出。8路灰度探頭的安裝位置我一般建議這樣探頭排成一條直線橫向間距約1.5厘米覆蓋總寬度約12厘米剛好能覆蓋常見的白色賽道配黑色引導(dǎo)線引導(dǎo)線寬度通常在2厘米到3厘米之間。中間兩路探頭負(fù)責(zé)直線循跡左右各三路負(fù)責(zé)提前探測(cè)彎道方向。探頭離地高度控制在1厘米到2厘米之間太近容易刮到地面太遠(yuǎn)反射率下降紅外光會(huì)擴(kuò)散。安裝時(shí)要注意一個(gè)細(xì)節(jié)探頭不能完全垂直于地面稍微向前傾斜一個(gè)角度讓紅外光打到白底后反射回接收管的角度更穩(wěn)定。這個(gè)角度在1.5厘米離地高度下通常選20度到30度。為什么是8路而不是4路或16路4路的覆蓋寬度太窄小車稍微偏一點(diǎn)就丟失全部信號(hào)16路的信息確實(shí)更豐富但讀取和狀態(tài)判斷的復(fù)雜度上升對(duì)于51單片機(jī)可能有問題對(duì)于F103ZET6來說其實(shí)也能處理但布線難度更高。8路是競(jìng)速小車上最常見的選擇因?yàn)闋顟B(tài)組合可以比較完整地映射到轉(zhuǎn)向角度后續(xù)做補(bǔ)線策略也方便。3. 路徑檢測(cè)模塊實(shí)現(xiàn)灰度數(shù)據(jù)的讀取、閾值處理與中線計(jì)算3.1 灰度數(shù)據(jù)的采集與去抖無(wú)論傳感器模塊是數(shù)字輸出還是模擬輸出第一步都是把原始信號(hào)穩(wěn)定地讀進(jìn)主控。數(shù)字輸出的模塊內(nèi)部有比較器直接接GPIO輸入即可模擬輸出則需要接ADC通道。前者代碼簡(jiǎn)單后者可以靈活調(diào)節(jié)靈敏度適合在不同光照環(huán)境下使用。8路數(shù)字信號(hào)可以用GPIOA的低8位一次讀入然后做一個(gè)軟件去抖。常見的做法是連續(xù)讀取三次取中間值或者兩次相同的結(jié)果作為有效值。代碼示例uint8_t Gray_Read(void) { uint8_t raw GPIOA-IDR 0xFF; // 一次讀取PA0-PA7共8路 uint8_t stable; static uint8_t last_raw 0; uint8_t cnt 0; // 連續(xù)讀取3次統(tǒng)計(jì)每一位的穩(wěn)定結(jié)果 for (uint8_t i 0; i 3; i) { raw GPIOA-IDR 0xFF; cnt (cnt 8) | (raw last_raw ? 1 : 0); last_raw raw; } stable raw; return stable; }這個(gè)代碼的原理是連續(xù)三次讀取如果兩次讀取結(jié)果一致就認(rèn)為信號(hào)穩(wěn)定否則用最后一次結(jié)果。實(shí)際測(cè)試中灰度傳感器在黑白邊界附近會(huì)有幾十毫秒的抖動(dòng)區(qū)間軟件去抖能避免舵機(jī)在邊界處高頻擺動(dòng)。另一個(gè)更實(shí)用的辦法是識(shí)別“哪幾路壓線”用一個(gè)8位變量記錄狀態(tài)每一位代表一路探頭1表示壓到黑線0表示壓到白底。在控制循環(huán)里直接對(duì)這個(gè)狀態(tài)做映射即可。3.2 中線計(jì)算的兩種方式加權(quán)平均與狀態(tài)映射拿到8路壓線狀態(tài)后需要把它轉(zhuǎn)換成一個(gè)數(shù)值用來表示“黑線相對(duì)于車體的橫向位置”。這個(gè)數(shù)值就是中線偏差是PID控制器的輸入。方式一加權(quán)平均法。把每一路探頭的位置索引作為權(quán)重壓線的探頭索引加權(quán)求和再除以壓線數(shù)量。比如第3路和第4路壓線權(quán)重分別是3和4計(jì)算結(jié)果為3.5表示黑線在中線偏右半個(gè)探頭間距。這個(gè)方法的優(yōu)點(diǎn)是連續(xù)性好能輸出小數(shù)級(jí)別的偏差適合PID精細(xì)調(diào)節(jié)。方式二狀態(tài)映射法。直接把8路狀態(tài)的每一種組合對(duì)應(yīng)到一個(gè)預(yù)定義的角度增量。比如只第4路壓線正中偏差為0第3路壓線偏差為-1第2路壓線偏差為-2依此類推。這個(gè)方法的優(yōu)點(diǎn)是響應(yīng)快、確定性強(qiáng)缺點(diǎn)是組合數(shù)多8路全狀態(tài)有256種處理起來麻煩通常只映射單路壓線和雙路壓線的情況。我在競(jìng)速方案里推薦的做法是混合式正常循跡時(shí)用加權(quán)平均法計(jì)算連續(xù)偏差在彎道中如果出現(xiàn)多路壓線則切換到狀態(tài)映射法直接給定一個(gè)較大的轉(zhuǎn)向角預(yù)設(shè)值。這樣直線段控制細(xì)膩彎道段響應(yīng)迅速。3.3 閾值自適應(yīng)一個(gè)經(jīng)常被忽略的細(xì)節(jié)灰度傳感器的輸出很大程度上依賴賽道表面的反射率。白色啞光紙和白色高光紙的反射率差異很大室內(nèi)燈光和自然光的紅外干擾也不同。如果閾值固定不變換一個(gè)場(chǎng)地就可能出現(xiàn)整片誤判或整片丟失。通常的做法是在小車啟動(dòng)前做一個(gè)初始化校準(zhǔn)讓小車在賽道上掃一遍采集8路傳感器在白底和黑線上的電壓范圍取兩者的中間值作為判斷閾值。如果傳感器模塊是模擬輸出這個(gè)校準(zhǔn)過程還能順便記錄每路傳感器的基線偏差——不同探頭之間因?yàn)樵x散性會(huì)導(dǎo)致輸出電壓不完全一致需要做歸一化處理。typedef struct { uint16_t white_val[8]; uint16_t black_val[8]; uint16_t threshold[8]; } Gray_Calib; void Gray_Calibrate(Gray_Calib *calib) { // 每個(gè)通道采集10次白底最大值和黑線最小值 // 實(shí)際代碼里會(huì)先讓用戶按鍵觸發(fā)掃過黑白區(qū)域后取統(tǒng)計(jì)值 for (uint8_t i 0; i 8; i) { calib-threshold[i] (calib-white_val[i] calib-black_val[i]) / 2; } }這個(gè)初始化過程花不了兩秒鐘但能避免跑了十分鐘發(fā)現(xiàn)傳感器全飄了、數(shù)據(jù)從“正?!弊兂伞靶W(xué)”的尷尬。有些團(tuán)隊(duì)會(huì)直接把閾值做成可調(diào)參數(shù)通過藍(lán)牙下發(fā)修正在線調(diào)這在賽場(chǎng)上屬于后悔藥級(jí)別的功能建議預(yù)留。4. 控制核心實(shí)現(xiàn)舵機(jī)PD轉(zhuǎn)向環(huán)與電機(jī)速度環(huán)的協(xié)同調(diào)參4.1 舵機(jī)的PWM初始化與角度映射舵機(jī)控制看起來簡(jiǎn)單實(shí)際上第一步就容易翻車舵機(jī)的PWM頻率必須是50Hz左右也就是周期20ms占空比變化范圍約2.5%到12.5%。很多同學(xué)把PWM頻率配成幾百赫茲甚至幾千赫茲舵機(jī)要么瘋狂抖動(dòng)要么直接不轉(zhuǎn)。使用定時(shí)器生成50Hz PWM的配置要點(diǎn)定時(shí)器時(shí)鐘72MHz預(yù)分頻系數(shù)設(shè)為71得到1MHz計(jì)數(shù)頻率。自動(dòng)重裝載值設(shè)為19999得到50Hz的PWM頻率同時(shí)計(jì)數(shù)分辨率是1微秒。舵機(jī)中位對(duì)應(yīng)的占空比是7.5%即1500微秒左右極限分別是1000微秒和2000微秒。代碼片段如下void Steering_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 使能TIM1時(shí)鐘 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); // 72MHz / 71 / 20000 50Hz TIM_TimeBaseStructure.TIM_Prescaler 71; TIM_TimeBaseStructure.TIM_Period 19999; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); // PWM模式1輸出比較預(yù)裝載使能 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 1500; // 初始中位 TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_Cmd(TIM1, ENABLE); }舵機(jī)角度與PWM占空比不是嚴(yán)格的線性映射不同品牌的舵機(jī)線性度也有差異。實(shí)際使用中會(huì)讓小車放地上從小到大給舵機(jī)加角度觀察前輪是否在中位兩端對(duì)稱如果不對(duì)稱用軟件做一次角度補(bǔ)償。4.2 PD控制器的C語(yǔ)言實(shí)現(xiàn)與參數(shù)意義循跡小車轉(zhuǎn)向控制最常用的控制器是PD也就是比例加微分一般不加積分。原因很簡(jiǎn)單循跡的誤差是動(dòng)態(tài)變化的沒有穩(wěn)態(tài)殘差問題加積分反而會(huì)在彎道處造成超調(diào)和振蕩。PD控制器核心代碼typedef struct { float kp; float kd; float last_error; float output; float out_max; } PID_TypeDef; void Steering_PID(PID_TypeDef *pid, float error) { float d_term (error - pid-last_error); pid-output pid-kp * error pid-kd * d_term; pid-last_error error; // 輸出限幅防止舵機(jī)指令超出機(jī)械極限 if (pid-output pid-out_max) pid-output pid-out_max; if (pid-output -pid-out_max) pid-output -pid-out_max; }這段代碼有幾個(gè)需要說明的點(diǎn)第一誤差error的來源是前面計(jì)算的中線偏差加權(quán)平均法輸出的是“當(dāng)前位置與目標(biāo)位置的偏差”單位是探頭間距的倍數(shù)范圍通常在-3.5到3.5之間。第二微分項(xiàng)用的是誤差變化量而不是誤差對(duì)時(shí)間的導(dǎo)數(shù)原因是控制周期固定誤差變化量本身就是微分的離散形式。這里不需要除以控制周期因?yàn)樗刃в诎裬d放大了一個(gè)系數(shù)調(diào)參時(shí)直接通過kd來消化。第三輸出限幅out_max要跟舵機(jī)機(jī)械限位對(duì)應(yīng)。舵機(jī)中位1500微秒假設(shè)左右各允許轉(zhuǎn)動(dòng)30度對(duì)應(yīng)約200微秒的變化范圍那么輸出限幅就設(shè)為200防止PID在高速過彎時(shí)給出一個(gè)根本執(zhí)行不了的舵機(jī)角度。調(diào)參的順序也是一門學(xué)問。我的習(xí)慣是先調(diào)kp從小到大讓小車能在直線上穩(wěn)定循跡出現(xiàn)輕微擺動(dòng)即可然后加kd逐漸增大直到彎道出彎時(shí)不再有明顯的左右甩動(dòng)。如果發(fā)現(xiàn)小車在進(jìn)彎時(shí)反應(yīng)遲鈍多半是kp不夠如果發(fā)現(xiàn)小車在直線段高頻抖動(dòng)多半是kd過大放大了一丁點(diǎn)噪聲。4.3 電機(jī)速度環(huán)為什么競(jìng)速小車要加編碼器閉環(huán)驅(qū)動(dòng)電機(jī)的PWM輸出和舵機(jī)類似但頻率要高一些一般用20kHz。電機(jī)的速度控制有兩種思路開環(huán)給固定PWM占空比或者用編碼器做閉環(huán)PID。開環(huán)方案在電池電壓充足的時(shí)候很穩(wěn)定但跑著跑著電池電壓下降同樣占空比下電機(jī)轉(zhuǎn)速會(huì)下跌速度環(huán)就失效了。加編碼器閉環(huán)的好處有兩層一是穩(wěn)速。用TIM4編碼器模式讀取電機(jī)轉(zhuǎn)速STM32的定時(shí)器自帶正交解碼功能只需要把編碼器的A相和B相接在定時(shí)器的兩個(gè)通道上硬件會(huì)自動(dòng)計(jì)數(shù)不需要外部中斷。二是能實(shí)現(xiàn)“彎道降速”策略。競(jìng)速小車不追求全程跑最高速而是直線快速、彎道減速。通過編碼器把當(dāng)前車速反饋回來在檢測(cè)到大偏差時(shí)給定一個(gè)目標(biāo)速度曲線速度環(huán)自動(dòng)把車速壓下來出彎后再升上去。void Speed_Control(float target_rps) { float current_rps Encoder_GetSpeed(); // 編碼器換算為實(shí)際轉(zhuǎn)速 float error target_rps - current_rps; static float integral 0; integral error; integral CLAMP(integral, -50.0f, 50.0f); // 抗積分飽和 // 增量式PID輸出疊加到當(dāng)前PWM上 float delta speed_pid.kp * (error - speed_pid.last_error) speed_pid.ki * error; speed_pid.output delta; speed_pid.output CLAMP(speed_pid.output, -1000, 1000); speed_pid.last_error error; Motor_SetPWM((int)speed_pid.output); }這段代碼里用的實(shí)際上是增量式PID輸出的是PWM的增量而不是絕對(duì)值。這樣做的優(yōu)點(diǎn)是即使pid參數(shù)不合適也不會(huì)一下把PWM沖到滿值導(dǎo)致小車猛沖出去。調(diào)參時(shí)速度環(huán)的kp比轉(zhuǎn)向環(huán)小一個(gè)數(shù)量級(jí)因?yàn)楸豢貙?duì)象是電機(jī)機(jī)械系統(tǒng)響應(yīng)慢kp太大會(huì)出現(xiàn)電機(jī)嘯叫。4.4 轉(zhuǎn)向環(huán)與速度環(huán)的優(yōu)先級(jí)先穩(wěn)住舵機(jī)再談速度在實(shí)際跑車中兩個(gè)環(huán)同時(shí)工作如果速度環(huán)沒有收斂車速時(shí)快時(shí)慢轉(zhuǎn)向環(huán)的誤差變化率會(huì)被放大導(dǎo)致舵機(jī)輸出的微分項(xiàng)波動(dòng)劇烈。反過來如果轉(zhuǎn)向環(huán)不收斂小車蛇形走位速度環(huán)也會(huì)因?yàn)樨?fù)載波動(dòng)而振蕩。所以我的調(diào)車順序非常固定先把小車架起來讓后輪懸空速度環(huán)開到目標(biāo)速度閉環(huán)穩(wěn)定然后把小車放地上速度環(huán)關(guān)閉只調(diào)舵機(jī)PD讓車能循跡最后兩個(gè)環(huán)同時(shí)開啟從小目標(biāo)速度開始逐步加速。整個(gè)過程每調(diào)整一次速度目標(biāo)值都要同時(shí)看一下轉(zhuǎn)向環(huán)的微分項(xiàng)是否在合理范圍。5. 常見問題與避坑指南從傳感器失靈到PID震蕩的排查思路5.1 現(xiàn)象傳感器閾值固定導(dǎo)致全盤誤判小車在實(shí)驗(yàn)室白紙上跑得好好的拿到比賽場(chǎng)地之后上電一看8路傳感器全部輸出“壓線”或者全部輸出“白底”小車原地打轉(zhuǎn)或直接沖出賽道。原因灰度傳感器模塊的電位器調(diào)節(jié)的是比較器參考電壓不同場(chǎng)地的白底反射率差異很大。如果你用的是數(shù)字輸出模塊出場(chǎng)默認(rèn)電位器位置可能只適配某一種光照環(huán)境如果用的是模擬輸出模塊ADC讀到的白底電壓范圍可能整體偏移。解決在程序里加入初始化校準(zhǔn)流程啟動(dòng)后小車保持在賽道上程序自動(dòng)采集白底和黑線的電壓范圍并計(jì)算閾值。如果傳感器模塊是數(shù)字輸出不能讀模擬量就把電位器調(diào)到一個(gè)中性位置在比賽場(chǎng)地用螺絲刀微調(diào)直到地面白底時(shí)指示燈全滅、黑線時(shí)對(duì)應(yīng)指示燈亮起——這個(gè)方法比較原始但確實(shí)管用。5.2 現(xiàn)象舵機(jī)PWM配置正確但抖動(dòng)嚴(yán)重舵機(jī)上電后持續(xù)發(fā)出“嗡嗡”聲用手摸舵機(jī)外殼能感覺到高頻振動(dòng)轉(zhuǎn)動(dòng)角度在中位附近來回抖動(dòng)。原因最常見的是PWM頻率不對(duì)舵機(jī)標(biāo)準(zhǔn)頻率是50Hz如果你把定時(shí)器配成100Hz甚至更高舵機(jī)內(nèi)部的電路會(huì)不斷嘗試重新定位表現(xiàn)就是抖動(dòng)還有一種情況是PWM信號(hào)有毛刺可能是時(shí)鐘配置里PWM輸出引腳和另一個(gè)定時(shí)器共用了通道。解決用示波器看PWM波形確認(rèn)周期確實(shí)是20ms。沒有示波器的話寫一個(gè)最簡(jiǎn)單的測(cè)試程序固定輸出1500微秒脈寬如果舵機(jī)還在抖大概率是PWM配置問題。還有一個(gè)隱蔽坑電機(jī)的PWM地線沒有和舵機(jī)地線共地舵機(jī)信號(hào)參考電位浮空也會(huì)造成抖動(dòng)檢查電源地和信號(hào)地是否連在一起。5.3 現(xiàn)象PID參數(shù)調(diào)好之后高速過彎還是沖出賽道小車在低速時(shí)循跡很順?biāo)俣纫惶岣咧苯菑澲苯記_出去或者S彎連續(xù)擺動(dòng)失控。原因這通常不是PID參數(shù)本身的問題而是“轉(zhuǎn)向響應(yīng)速度跟不上車速”。舵機(jī)的物理響應(yīng)時(shí)間在50毫秒到100毫秒級(jí)別車速2米/秒時(shí)100毫秒車已經(jīng)前進(jìn)了20厘米而探頭排列覆蓋寬度只有12厘米傳感器已經(jīng)檢測(cè)不到線了。也就是說問題出在信息滯后而不是控制算法。解決一是降低進(jìn)彎速度在檢測(cè)到連續(xù)多路壓線時(shí)提前減速二是增加前瞻距離把傳感器支架往前延伸讓探頭比前輪更早探測(cè)到彎道三是使用補(bǔ)線策略——當(dāng)最外側(cè)探頭壓線時(shí)直接把偏差量設(shè)為一個(gè)預(yù)設(shè)的較大值而不是依賴PID逐步逼近。這個(gè)做法相當(dāng)于在算法里做了“彎道預(yù)判”實(shí)車效果提升非常明顯。5.4 現(xiàn)象電池電壓下降后電機(jī)明顯變慢小車剛開始速度正常跑了兩三分鐘后越跑越慢即使PWM輸出沒變。原因鋰電池放電過程中電壓會(huì)從12.6V降到10V左右電機(jī)轉(zhuǎn)速和供電電壓近似成正比。開環(huán)PWM控制補(bǔ)償不了這個(gè)電壓跌落。解決如果已經(jīng)加了編碼器速度環(huán)這個(gè)現(xiàn)象會(huì)自動(dòng)消失速度環(huán)會(huì)通過提高占空比來補(bǔ)償電壓下降如果用的是開環(huán)可以加一個(gè)電壓監(jiān)測(cè)ADC根據(jù)電池電壓修正PWM比例系數(shù)。這算不上真正的閉環(huán)但能緩解最明顯的掉速問題。注意編碼器速度環(huán)的PID輸出上限要留夠余量否則電壓低時(shí)PID飽和還是會(huì)出現(xiàn)掉速。5.5 現(xiàn)象藍(lán)牙調(diào)試數(shù)據(jù)正常但小車實(shí)際表現(xiàn)異常代碼里打印的傳感器狀態(tài)和解算出來的偏差數(shù)值看起來都對(duì)但小車跑起來和預(yù)期完全不符。原因大部分情況是“調(diào)試狀態(tài)”和“運(yùn)行狀態(tài)”的程序路徑不一致。比如調(diào)試時(shí)傳感器輸出通過串口打印運(yùn)行循環(huán)里又讀了一遍GPIO兩處讀取的GPIO引腳或去抖邏輯不同導(dǎo)致實(shí)際控制用的數(shù)據(jù)和你在串口上看到的不一樣。解決把數(shù)據(jù)源統(tǒng)一成一個(gè)函數(shù)無(wú)論調(diào)試還是運(yùn)行都調(diào)用同一個(gè)讀取接口。然后做一個(gè)關(guān)鍵驗(yàn)證——用串口把“PID輸入偏差”和“PID輸出指令”同時(shí)打出來讓小車固定在一個(gè)偏移位置檢查輸出的舵機(jī)角度是否符合直覺。這個(gè)操作能一次性驗(yàn)證傳感器、PID計(jì)算、PWM輸出三個(gè)環(huán)節(jié)的通路。6. 驗(yàn)收與答辯用可量化的測(cè)試賽道和調(diào)參記錄證明方案有效到了臨近交付或者答辯的時(shí)候很多同學(xué)會(huì)陷入一個(gè)誤區(qū)反復(fù)調(diào)車試圖跑出一個(gè)完美成績(jī)卻忘了準(zhǔn)備“如何證明這套設(shè)計(jì)是可行的”的呈現(xiàn)材料。課程設(shè)計(jì)和畢業(yè)設(shè)計(jì)的評(píng)審關(guān)注點(diǎn)是需求理解是否準(zhǔn)確、方案是否合理、實(shí)現(xiàn)是否完整、結(jié)果是否可靠。按照下面這套驗(yàn)收流程走下來基本能把所有可能被問到的問題前置堵住。第一步搭建標(biāo)準(zhǔn)測(cè)試賽道。用黑色電工膠帶在白色KT板上貼出包含直線段、90度直角彎、S彎、十字交叉、大回環(huán)五種元素的賽道直線段長(zhǎng)度不少于1米彎道曲率覆蓋高中低三檔。賽道寬度取30厘米到40厘米引導(dǎo)線寬度2.5厘米。這項(xiàng)工作的意義是讓測(cè)試可復(fù)現(xiàn)——換一個(gè)場(chǎng)地、換一個(gè)時(shí)間跑同一個(gè)賽道成績(jī)差異能說明系統(tǒng)的穩(wěn)定性。第二步定義量化指標(biāo)并逐項(xiàng)測(cè)試。建議至少記錄四個(gè)指標(biāo)完賽率連續(xù)跑10圈的成功圈數(shù)占比平均圈速完整一圈的時(shí)間最大舵機(jī)響應(yīng)延遲通過串口把傳感器變化時(shí)間戳和舵機(jī)指令時(shí)間戳打出來計(jì)算掉線次數(shù)8路傳感器全部無(wú)壓線的累計(jì)計(jì)數(shù)。每調(diào)整一組PID參數(shù)就完整測(cè)一遍這四項(xiàng)指標(biāo)記錄在一個(gè)表格里。最終的驗(yàn)收表可以這樣展示測(cè)試項(xiàng)驗(yàn)收標(biāo)準(zhǔn)實(shí)測(cè)結(jié)果直線循跡穩(wěn)定性直線段不發(fā)生S形擺動(dòng)偏差保持在±1路探頭內(nèi)通過直角彎通過率連續(xù)10次過彎不沖出賽道9次通過1次因入彎速度過高沖出S彎平均偏差全程加權(quán)平均偏差小于1.5路探頭平均值0.8路十字交叉處理直行通過不斷線通過平均圈速至少達(dá)到0.8米/秒0.9米/秒第三步梳理演示主線。答辯時(shí)不要從“什么是灰度傳感器”講起直接從問題切入為什么選擇舵機(jī)轉(zhuǎn)向方案、如何把8路傳感器信號(hào)變成PID輸入、PID參數(shù)是怎么調(diào)的、最終測(cè)出來的數(shù)據(jù)說明什么。每一個(gè)結(jié)論都能對(duì)應(yīng)到調(diào)試過程中的一張截圖或者一段代碼。我會(huì)建議把串口打印的偏差曲線用串口繪圖工具錄制一段視頻答辯現(xiàn)場(chǎng)放10秒就夠比講一百句話都有說服力。還有一個(gè)容易被問到的核心問題“你的PID參數(shù)為什么是這幾個(gè)值”如果回答“調(diào)出來的”顯得沒有深度正確的打開方式是展示調(diào)參過程初始kp0.5時(shí)小車振蕩增大到1.0后振蕩消失但過彎響應(yīng)慢加kd8后出彎穩(wěn)定kp和kd的比例大約在1比5到1比10之間。這套數(shù)據(jù)鏈能體現(xiàn)你對(duì)控制算法的理解是完整的而不只是會(huì)調(diào)用庫(kù)函數(shù)。最后說一個(gè)習(xí)慣從那以后我每次調(diào)完車都會(huì)把最終的PID參數(shù)、傳感器布局、機(jī)械連桿位置拍照存檔并且把每輪測(cè)試數(shù)據(jù)做成一個(gè)表格放在工程目錄里。答辯前翻一遍這些記錄幾乎能回答評(píng)審提出的所有細(xì)節(jié)問題。做項(xiàng)目最怕的不是沒解決問題而是解決了問題卻沒有記錄下是怎么解決的。希望這篇基于STM32F103ZET6的循跡小車設(shè)計(jì)方案能幫你把硬件、算法和驗(yàn)收邏輯串起來少走幾步彎路。本文還有配套的精品資源點(diǎn)擊獲取