控制實(shí)戰(zhàn))
簡(jiǎn)介這是一套面向電子愛(ài)好者與嵌入式初學(xué)者的二輪平衡車DIY完整資料包涵蓋從硬件搭建到軟件調(diào)試的全鏈路內(nèi)容。資源定位為STM32平臺(tái)平衡車的開(kāi)源學(xué)習(xí)套件包含互補(bǔ)濾波、卡爾曼濾波、DMP三種姿態(tài)解算方案的源碼三套工程均編譯通過(guò)、可直接燒錄驗(yàn)證適合希望動(dòng)手制作平衡小車或深入理解濾波算法差異的讀者。壓縮包大小148.18MB內(nèi)置全部平衡小車相關(guān)資料類型上以Keil工程源碼、電路原理圖、PCB文件、連線說(shuō)明文檔為主硬件與軟件配套呈現(xiàn)可以大幅降低DIY門檻。已有3129人學(xué)習(xí)瀏覽整體經(jīng)過(guò)本科階段完整調(diào)試驗(yàn)證是一份少有的“從零到跑通”的參考資源。讀者可以獲得實(shí)物接線指引、電路設(shè)計(jì)源文件以及三種濾波方案的對(duì)比實(shí)現(xiàn)既能直接復(fù)刻樣車也能基于源碼二次開(kāi)發(fā)用于課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)或興趣項(xiàng)目。1. 平衡車全套源碼從零開(kāi)始搭一輛能站穩(wěn)的兩輪車第一次接觸二輪平衡車的人往往以為難點(diǎn)在機(jī)械組裝等真把套件拼好、電機(jī)轉(zhuǎn)起來(lái)才發(fā)現(xiàn)真正讓人頭疼的是讓它站穩(wěn)。一個(gè)常見(jiàn)現(xiàn)象是車能啟動(dòng)但兩三秒內(nèi)就朝一個(gè)方向沖出去或者原地高頻抖動(dòng)這就是典型的角度環(huán)與速度環(huán)沒(méi)配合好。市面上能跑的平衡車源碼不少但大多數(shù)要么只給姿態(tài)解算片段要么把 PID 調(diào)參寫成玄學(xué)讓新手連入口都找不到。這套二輪平衡車全套源碼的價(jià)值在于它把從傳感器讀取、姿態(tài)解算到雙環(huán)/三環(huán)控制、PWM 輸出的完整鏈路都串起來(lái)了適合正在做課設(shè)、電賽備賽或想入門嵌入式控制的人直接照抄再改。2. 姿態(tài)解算選型卡爾曼濾波、DMP 與互補(bǔ)濾波的取舍和落地代碼2.1 三種解算方案的適用邊界為什么不是越復(fù)雜越好平衡車要解決的核心問(wèn)題是MPU6050 輸出的加速度計(jì)和陀螺儀數(shù)據(jù)各自有硬傷。加速度計(jì)在靜止時(shí)能準(zhǔn)確給出重力方向但車體一震動(dòng)高頻噪聲會(huì)直接把角度信號(hào)淹沒(méi)陀螺儀積分得到的角度在短時(shí)間內(nèi)很平滑但積分漂移會(huì)隨時(shí)間累積幾秒后角度就開(kāi)始“飄”。姿態(tài)解算的全部工作就是把這兩個(gè)都不完美的信號(hào)融合成一個(gè)相對(duì)可信的角度。卡爾曼濾波的思路是把角度和陀螺儀零偏作為狀態(tài)量每次拿加速度計(jì)的角度做測(cè)量更新拿陀螺儀的角速度做預(yù)測(cè)更新。它收斂快、動(dòng)態(tài)響應(yīng)好但需要反復(fù)調(diào)協(xié)方差矩陣的過(guò)程噪聲和測(cè)量噪聲調(diào)不好就會(huì)出現(xiàn)角度滯后或者過(guò)沖。DMP 是 MPU6050 內(nèi)部的硬件解算引擎DMP 庫(kù)初始化后直接讀四元數(shù)再轉(zhuǎn)歐拉角CPU 占用極小但它把融合過(guò)程當(dāng)黑匣子輸出是相對(duì)姿態(tài)想改濾波特性基本無(wú)從下手。互補(bǔ)濾波則用一個(gè)系數(shù) alpha 把兩個(gè)信號(hào)按頻率高低切開(kāi)陀螺儀負(fù)責(zé)高頻段加速度計(jì)負(fù)責(zé)低頻段。代碼只有幾行在平衡車這種垂直安裝、運(yùn)行范圍不到 30 度的場(chǎng)景里完全夠用而且調(diào)試只要調(diào)一個(gè) alpha。我的建議很簡(jiǎn)單做課設(shè)或功能演示優(yōu)先互補(bǔ)濾波代碼短、好解釋、調(diào)參直觀做競(jìng)賽或追求極限動(dòng)態(tài)響應(yīng)用卡爾曼濾波DMP 庫(kù)適合不想碰矩陣運(yùn)算、把精力全放在 PID 上的人。這套源碼把三種方案都放了切換時(shí)只需要改融合函數(shù)入口不用動(dòng)上層控制邏輯。2.2 卡爾曼濾波實(shí)現(xiàn)一維模型與核心代碼卡爾曼濾波在平衡車場(chǎng)景里可以簡(jiǎn)化為一維模型——只對(duì)俯仰角pitch做融合橫滾角同理但角度變化幅度遠(yuǎn)小于俯仰可以先不處理。狀態(tài)量有兩個(gè)角度 angle 和陀螺儀零偏 bias。預(yù)測(cè)方程用的是陀螺儀角速度減掉零偏后的值測(cè)量方程用的是加速度計(jì)算出的角度。下面是一份可直接嵌入單片機(jī)的卡爾曼濾波 C 代碼typedef struct { float angle; // 融合后的角度輸出 float bias; // 陀螺儀零偏 float P[2][2]; // 協(xié)方差矩陣 float Q_angle; // 角度過(guò)程噪聲 float Q_bias; // 零偏過(guò)程噪聲 float R_measure; // 測(cè)量噪聲 } Kalman_t; void Kalman_Init(Kalman_t *kf) { kf-angle 0.0f; kf-bias 0.0f; kf-P[0][0] 0.0f; kf-P[0][1] 0.0f; kf-P[1][0] 0.0f; kf-P[1][1] 0.0f; kf-Q_angle 0.001f; kf-Q_bias 0.003f; kf-R_measure 0.03f; } float Kalman_Update(Kalman_t *kf, float new_angle, float new_rate, float dt) { float rate new_rate - kf-bias; kf-angle dt * rate; kf-P[0][0] dt * (dt * kf-P[1][1] - kf-P[0][1] - kf-P[1][0] kf-Q_angle); kf-P[0][1] - dt * kf-P[1][1]; kf-P[1][0] - dt * kf-P[1][1]; kf-P[1][1] kf-Q_bias * dt; float S kf-P[0][0] kf-R_measure; float K[2]; K[0] kf-P[0][0] / S; K[1] kf-P[1][0] / S; float y new_angle - kf-angle; kf-angle K[0] * y; kf-bias K[1] * y; float P00_temp kf-P[0][0]; float P01_temp kf-P[0][1]; kf-P[0][0] - K[0] * P00_temp; kf-P[0][1] - K[0] * P01_temp; kf-P[1][0] - K[1] * P00_temp; kf-P[1][1] - K[1] * P01_temp; return kf-angle; }這套代碼的邏輯是每毫秒調(diào)用一次 Update傳入加速度計(jì)解算出的角度 new_angle 和陀螺儀的原始角速度 new_rate。注意代碼里 P 矩陣的更新方式和網(wǎng)絡(luò)上流傳的簡(jiǎn)化版不太一樣這里是完整保留了協(xié)方差矩陣的遞推關(guān)系在車體低轉(zhuǎn)速時(shí)能明顯減少角度輸出的抖動(dòng)。參數(shù)調(diào)整的經(jīng)驗(yàn)是如果車體反應(yīng)遲鈍、倒下前角度還沒(méi)跟上就把 Q_angle 調(diào)大一點(diǎn)如果角度噪聲大、車體原地微抖就把 R_measure 調(diào)大。 dt 必須和實(shí)際調(diào)用周期一致如果主循環(huán)是 2ms 調(diào)用一次dt 就傳 0.002寫死 0.001 會(huì)讓濾波輸出失真這也是很多人移植濾波代碼后發(fā)現(xiàn)角度“慢半拍”的直接原因。2.3 互補(bǔ)濾波與 DMP更輕量的替代方案互補(bǔ)濾波的實(shí)現(xiàn)思路非常直觀陀螺儀積分后的角度在短時(shí)間內(nèi)可信加速度計(jì)直接解算的角度在長(zhǎng)時(shí)間內(nèi)可信所以每次取 0.98 倍的陀螺儀角度加上 0.02 倍的加速度計(jì)角度一秒內(nèi)完成約 50 次這樣的遞推后陀螺儀漂移就被不斷“拉回”到加速度計(jì)參考軸上。典型代碼如下#define ALPHA 0.98f float Complementary_Filter(float acc_angle, float gyro_rate, float dt) { static float angle 0.0f; angle ALPHA * (angle gyro_rate * dt) (1.0f - ALPHA) * acc_angle; return angle; }ALPHA 越大濾波結(jié)果越信任陀螺儀角度平滑但滯后明顯ALPHA 越小角度越容易受振動(dòng)影響但跟隨加速度計(jì)的速度更快。平衡車上一般從 0.95 起步試車體前后傾斜時(shí)角度響應(yīng)遲鈍就減小到 0.90高頻抖動(dòng)就回調(diào)到 0.98。它雖然沒(méi)有卡爾曼濾波那種嚴(yán)格的統(tǒng)計(jì)最優(yōu)性但在平衡車這種控制周期只有 5ms、角度變化率有限的場(chǎng)景里性能差異幾乎感知不到。DMP 的接入路徑是官方庫(kù)配合 I2C 讀取。初始化時(shí)調(diào)用 dmp_init 并配置采樣率主循環(huán)里通過(guò) dmp_read_fifo 獲取四元數(shù)再用四元數(shù)轉(zhuǎn)歐拉角的公式得到 pitch。需要注意三點(diǎn)一是 DMP 初始化后必須先調(diào)用 dmp_set_fifo_rate 把輸出頻率設(shè)到 100Hz 左右太高的頻率會(huì)導(dǎo)致 FIFO 頻繁溢出二是讀取時(shí)要用 dmp_get_pitch 這類接口而不是直接拿原始四元數(shù)算避免單位轉(zhuǎn)換錯(cuò)誤三是 DMP 的 yaw 會(huì)隨機(jī)漂移但平衡車控制根本不依賴 yaw只取 pitch 和 roll 就夠了。3. 源碼架構(gòu)拆解數(shù)據(jù)流、工程結(jié)構(gòu)與啟動(dòng)順序3.1 從傳感器到 PWM 輸出的完整數(shù)據(jù)鏈路這套源碼的工程結(jié)構(gòu)并不復(fù)雜核心可以分成四層傳感器層負(fù)責(zé)初始化 MPU6050 并讀取原始數(shù)據(jù)解算層負(fù)責(zé)把原始數(shù)據(jù)變成角度和角速度控制層跑 PID 三環(huán)輸出層把控制量換算成 PWM 占空比。數(shù)據(jù)從傳感器到電機(jī)輸出完整鏈路是MPU6050 原始數(shù)據(jù) → 加速度計(jì)/陀螺儀分量提取 → 姿態(tài)解算 → 機(jī)械角度與角速度 → 直立環(huán) PD → 速度環(huán) PI → 轉(zhuǎn)向環(huán) PD → 左右電機(jī) PWM。這條鏈路里最容易斷的位置在解算層和控制層之間。解算層輸出的是“物理角度”單位是度控制層期望的輸入是“相對(duì)于機(jī)械中位”的角度偏差。如果車體安裝時(shí)傳感器不是絕對(duì)水平解算出的角度就帶了固定偏置這時(shí)必須做機(jī)械零點(diǎn)校準(zhǔn)把這組偏置值記錄在結(jié)構(gòu)體里否則直立環(huán)會(huì)一直朝著一個(gè)方向推電機(jī)車一上電就沖出去。源碼里用了 Angle_Offset 結(jié)構(gòu)體來(lái)存三軸零偏這個(gè)設(shè)計(jì)很實(shí)用換車架時(shí)只需要重新校準(zhǔn)不需要改控制代碼。3.2 主循環(huán)與控制周期設(shè)計(jì)整個(gè)控制實(shí)時(shí)性依賴定時(shí)器中斷而不是主循環(huán) while(1) 里裸跑。源碼用定時(shí)器產(chǎn)生 5ms 中斷中斷里依次執(zhí)行讀取 MPU6050 原始數(shù)據(jù)、姿態(tài)解算更新、直立環(huán)計(jì)算、速度環(huán)計(jì)算、轉(zhuǎn)向環(huán)計(jì)算、最終 PWM 輸出。這段代碼是核心中的核心按順序?qū)懬宄?duì)后期調(diào)參非常關(guān)鍵。主循環(huán)部分通常長(zhǎng)這樣int main(void) { System_Init(); // 時(shí)鐘、GPIO、定時(shí)器初始化 MPU6050_Init(); // 傳感器初始化含自檢 Kalman_Init(kf); // 選擇卡爾曼濾波時(shí)啟用 PID_Init(); // 三環(huán) PID 參數(shù)初始化 Encoder_Init(); // 編碼器捕獲初始化 Motor_Init(); // PWM 定時(shí)器初始化 MPU6050_Calibrate(); // 靜止校準(zhǔn)獲取零點(diǎn)偏置 while (1) { // 主循環(huán)只做低優(yōu)先級(jí)任務(wù) // 串口打印調(diào)試數(shù)據(jù)、按鍵檢測(cè)、OLED 顯示 Debug_Print_Angle(); Delay(10); } }這里有一個(gè)值得注意的設(shè)計(jì)控制部分全部塞進(jìn)中斷回調(diào)主循環(huán)只做調(diào)試信息輸出和按鍵掃描這樣 CPU 不會(huì)被耗時(shí)的浮點(diǎn)運(yùn)算卡住控制節(jié)拍穩(wěn)定。代碼里涉及到一個(gè)關(guān)鍵的結(jié)構(gòu)體 Balance_Control_t里面保存了角度目標(biāo)值、速度累加值、上一次誤差等狀態(tài)每次中斷更新它主循環(huán)讀它避免數(shù)據(jù)競(jìng)爭(zhēng)。3.3 模塊劃分與關(guān)鍵參數(shù)速查表源碼把功能拆成了獨(dú)立的模塊文件命名和職責(zé)對(duì)應(yīng)得非常清楚。下表列出主要文件、核心接口和修改參數(shù)時(shí)的關(guān)注點(diǎn)方便拿到源碼后快速定位文件模塊核心接口參數(shù)修改點(diǎn)mpu6050.cMPU6050_Init / MPU6050_Read低通濾波帶寬、采樣率分頻kalman.cKalman_Init / Kalman_UpdateQ_angle / Q_bias / R_measurepid.cPID_Calc / PID_Reset三環(huán) Kp / Ki / Kd、積分限幅motor.cMotor_SetPWM / Motor_InitPWM 頻率、死區(qū)補(bǔ)償、方向翻轉(zhuǎn)encoder.cEncoder_Read / Encoder_Init編碼器線數(shù)倍頻、方向判定control.cBalance_Control_Run目標(biāo)角度、速度環(huán)輸出限幅這套源碼里一個(gè)值得學(xué)習(xí)的點(diǎn)電機(jī)和編碼器的方向判定用宏定義統(tǒng)一管理更換電機(jī)或把編碼器裝到另一側(cè)時(shí)只需要改一個(gè)宏不用在 PID 輸出里到處改正負(fù)號(hào)。初次移植時(shí)最容易翻車的就是方向問(wèn)題電機(jī)轉(zhuǎn)了但編碼器計(jì)數(shù)方向反了直立環(huán)直接變成正反饋車越倒越加速。4. 三環(huán) PID 控制參數(shù)整定流程與輸出限幅實(shí)戰(zhàn)4.1 直立環(huán)、速度環(huán)、轉(zhuǎn)向環(huán)的分工與串聯(lián)方式平衡車控制系統(tǒng)的框架是三個(gè)控制環(huán)疊加在同一個(gè)輸出通道上。最內(nèi)層是直立環(huán)輸入是當(dāng)前角度與目標(biāo)角度的偏差輸出是電機(jī)力矩目標(biāo)是讓車體保持垂直外層是速度環(huán)輸入是目標(biāo)速度與當(dāng)前速度的偏差輸出不是直接疊加到 PWM 上而是改變直立環(huán)的目標(biāo)角度最外層是轉(zhuǎn)向環(huán)輸入是轉(zhuǎn)向目標(biāo)與當(dāng)前偏航角速度的偏差輸出疊加到左右電機(jī)的差模分量上。這種串聯(lián)方式的核心設(shè)計(jì)意圖是直立環(huán)必須最靈敏響應(yīng)速度要快所以用 PD 控制Kp 負(fù)責(zé)把角度偏差轉(zhuǎn)化成恢復(fù)力矩Kd 提供阻尼防止過(guò)沖速度環(huán)用 PI 控制因?yàn)樗俣日`差的消除需要積分項(xiàng)來(lái)克服靜摩擦力轉(zhuǎn)向環(huán)和直立環(huán)一樣用 PD因?yàn)檗D(zhuǎn)向動(dòng)作本身是瞬態(tài)的不需要積分。三個(gè)環(huán)的輸出疊加順序是先算直立環(huán)得到基礎(chǔ) PWM再疊加速度環(huán)修正量最后疊加轉(zhuǎn)向環(huán)差模量等效于左電機(jī) 直立環(huán) 速度環(huán) 轉(zhuǎn)向環(huán)右電機(jī) 直立環(huán) 速度環(huán) - 轉(zhuǎn)向環(huán)。4.2 參數(shù)整定流程從靜止到運(yùn)動(dòng)的三個(gè)階段調(diào)參順序強(qiáng)烈建議按下面的步驟走跳過(guò)任何一步都會(huì)讓后面排查變得痛苦。第一步先把車體用繩子吊起來(lái)或者用手扶著讓車輪懸空第二步調(diào)直立環(huán) Kp從小到大逐步加直到用手把車體掰到一邊時(shí)能感覺(jué)到明顯的回復(fù)力矩并且車體不再一松手就軟塌塌地倒下第三步加 Kd抑制車體在目標(biāo)角度附近的振蕩標(biāo)準(zhǔn)是車體被瞬時(shí)撥動(dòng)后能在 1 到 2 個(gè)周期內(nèi)收斂而不是持續(xù)高頻抖動(dòng)第四步把車放到地面調(diào)速度環(huán) Kp讓它能抵抗輕輕的推拉而不倒第五步加速度環(huán) Ki消除長(zhǎng)時(shí)間勻速漂移最后調(diào)轉(zhuǎn)向環(huán)讓車體能按照遙控指令平滑轉(zhuǎn)向而不打轉(zhuǎn)。調(diào)參過(guò)程中一個(gè)可行的中間狀態(tài)判斷方法代碼里加一個(gè)串口調(diào)試輸出每 10ms 輸出一組數(shù)據(jù)格式為當(dāng)前角度、目標(biāo)角度、直立環(huán)輸出、速度環(huán)輸出、最終 PWM。調(diào)參時(shí)盯著這幾個(gè)數(shù)值的關(guān)系看如果車體靜止時(shí)直立環(huán)輸出不為零說(shuō)明目標(biāo)角度和實(shí)際零點(diǎn)不一致如果速度環(huán)輸出持續(xù)朝一個(gè)方向增長(zhǎng)說(shuō)明積分項(xiàng)在飽和需要檢查限幅。源碼里提供了這種調(diào)試宏開(kāi)關(guān)默認(rèn)注釋掉打開(kāi)后編譯即可。直立環(huán)和速度環(huán)的代碼通常是這樣串聯(lián)的int16_t Balance_Control_Run(float angle, float gyro, int16_t speed_left, int16_t speed_right) { float angle_err g_balance.angle_target - angle; float angle_out g_pid_balance.Kp * angle_err g_pid_balance.Kd * (-gyro); float speed_avg (speed_left speed_right) / 2.0f; float speed_err g_balance.speed_target - speed_avg; float speed_out g_pid_speed.Kp * speed_err g_pid_speed.Ki * g_pid_speed.integral; g_pid_speed.integral speed_err * 0.005f; if (g_pid_speed.integral 1000.0f) g_pid_speed.integral 1000.0f; if (g_pid_speed.integral -1000.0f) g_pid_speed.integral -1000.0f; float pwm_base angle_out speed_out; return (int16_t)pwm_base; }這段代碼里有幾個(gè)關(guān)鍵細(xì)節(jié)直立環(huán)的微分項(xiàng)用的是陀螺儀的角速度 gyro而不是角度誤差的差分這樣能避免對(duì)加速度計(jì)噪聲做微分放大的問(wèn)題速度環(huán)的積分項(xiàng)手動(dòng)做了限幅防止車被持續(xù)外力推著時(shí)積分無(wú)限增大導(dǎo)致外力消失后車瘋狂朝反方向沖出去速度環(huán)輸出是疊加到 angle_out 上的這正好實(shí)現(xiàn)了“速度環(huán)通過(guò)改變目標(biāo)角度來(lái)影響直立環(huán)”的設(shè)計(jì)意圖。限幅參數(shù)的經(jīng)驗(yàn)范圍可以這樣記直立環(huán)輸出在 -700 到 700 之間速度環(huán)輸出在 -300 到 300 之間轉(zhuǎn)向環(huán)輸出在 -200 到 200 之間最終 PWM 輸出限幅在 -900 到 900。這些數(shù)值不是固定的具體要看電源電壓和電機(jī)額定參數(shù)。如果 PWM 滿偏值本身設(shè)定是 1000就不要讓最終輸出長(zhǎng)期貼著 1000 跑否則電機(jī)處于飽和狀態(tài)速度環(huán)和轉(zhuǎn)向環(huán)都會(huì)失效車體會(huì)呈現(xiàn)明顯的“失控感”。5. 避坑排查五種高頻故障現(xiàn)象原因與解決記錄5.1 電機(jī)高頻“嗞嗞”聲且車體劇烈抖動(dòng)現(xiàn)象上電后電機(jī)沒(méi)有正常轉(zhuǎn)動(dòng)而是發(fā)出尖銳的高頻嘯叫同時(shí)車體以肉眼可見(jiàn)的頻率劇烈抖動(dòng)用手按住車體時(shí)能感覺(jué)到明顯的振動(dòng)力度但一松手就倒向一側(cè)。原因PWM 頻率設(shè)置過(guò)低常見(jiàn)值是幾百赫茲。電機(jī)驅(qū)動(dòng)電路和電機(jī)的電感在這種頻率下產(chǎn)生明顯的電流波動(dòng)導(dǎo)致力矩輸出斷續(xù)而直立環(huán)又把這種斷續(xù)當(dāng)成角度誤差來(lái)響應(yīng)形成正反饋振蕩。解決把 PWM 定時(shí)器的頻率配置成 10kHz 到 20kHz 之間。在源碼的 motor.c 里修改定時(shí)器分頻和重載值讓 PWM 頻率進(jìn)入人耳聽(tīng)不到的超聲范圍。修改后電機(jī)聲音會(huì)變得很干凈車體的高頻抖動(dòng)會(huì)立刻消失。檢查手段是用示波器看 PWM 波形頻率或者聽(tīng)聲音變化明顯可感知。5.2 MPU6050 初始化失敗I2C 通信卡死現(xiàn)象程序運(yùn)行到 MPU6050_Init 時(shí)卡住串口沒(méi)有打印任何信息或者打印了初始化失敗提示復(fù)位后依舊如此。原因排除了接線錯(cuò)誤后最常見(jiàn)的是 I2C 總線上沒(méi)有上拉電阻。部分開(kāi)發(fā)板內(nèi)部已經(jīng)集成上拉但外接 MPU6050 模塊時(shí)如果連接線過(guò)長(zhǎng)、接觸不良總線 SCL/SDA 處于高阻態(tài)單片機(jī)檢測(cè)不到從設(shè)備應(yīng)答。解決給 SCL 和 SDA 各加一個(gè) 4.7k 歐姆上拉電阻到 3.3V另外 I2C 速率不要太高源碼里默認(rèn)配置為 400kHz把分頻系數(shù)改大降速到 100kHz 試試能提高抗干擾能力。檢查手段是單獨(dú)寫一個(gè)只掃描 I2C 設(shè)備地址的測(cè)試函數(shù)看能否返回 0x68 或 0x69如果返回值不對(duì)優(yōu)先懷疑上拉和接線。5.3 靜止時(shí)角度輸出緩慢漂移現(xiàn)象車體靜止放在桌面上串口打印的姿態(tài)角度數(shù)值每隔幾秒就變化零點(diǎn)幾度時(shí)間越長(zhǎng)漂移越明顯導(dǎo)致直立環(huán)誤以為車體在傾斜。原因最直接的原因是陀螺儀零偏未校準(zhǔn)。MPU6050 出廠時(shí)存在溫漂和個(gè)體差異的零偏如果不做靜止校準(zhǔn)陀螺儀積分會(huì)把這個(gè)固定偏差不斷累積導(dǎo)致角度隨時(shí)間漂移。另外一個(gè)常見(jiàn)原因是加速度計(jì)未做低通濾波高頻振動(dòng)噪聲被直接引入角度計(jì)算。解決第一步在初始化后讓車體完全靜止 1 秒以上采樣陀螺儀輸出求平均值作為零偏補(bǔ)償值第二步確認(rèn)濾波系數(shù)是否正確把加速度計(jì)原始數(shù)據(jù)經(jīng)過(guò)低通濾波后再算角度。這套源碼里已經(jīng)實(shí)現(xiàn)了 mpu6050_offset_cal 函數(shù)調(diào)用位置在主循環(huán)之前使用時(shí)要確認(rèn)車體確實(shí)靜止否則會(huì)把運(yùn)動(dòng)狀態(tài)當(dāng)成零偏記錄下來(lái)。5.4 電池電壓下降后車子突然變軟或朝一個(gè)方向加速現(xiàn)象滿電時(shí)車體直立穩(wěn)定運(yùn)行幾分鐘后電池電壓降低車體開(kāi)始慢慢前傾直立環(huán)輸出不足以把它拉回來(lái)最后朝前加速倒下。原因這是典型的 PWM 輸出未做電源電壓補(bǔ)償。當(dāng)電池電壓下降時(shí)同樣占空比下電機(jī)實(shí)際電壓降低、力矩變小但控制環(huán)的 PID 計(jì)算依然按滿電壓的等效關(guān)系輸出相當(dāng)于控制器對(duì)執(zhí)行機(jī)構(gòu)的控制能力打了折扣系統(tǒng)增益隨電壓變化。解決在讀取電池電壓的 AD 值后按電壓比例對(duì)最終 PWM 輸出做修正常見(jiàn)做法是維持一個(gè)基準(zhǔn)電壓比如 12V 時(shí)輸出 1000 對(duì)應(yīng)滿占空比當(dāng)電壓降到 10V 時(shí)輸出限幅值同步按 10/12 縮放保持電機(jī)的實(shí)際出力不變。這樣電壓變化時(shí)控制參數(shù)基本不受影響。源碼里預(yù)留了 voltage_compensate 接口沒(méi)有實(shí)現(xiàn)完整邏輯建議自行填充 AD 采集代碼。5.5 裝好后轉(zhuǎn)向環(huán)正常一加速度環(huán)就左右甩尾現(xiàn)象單獨(dú)調(diào)直立環(huán)時(shí)車體很穩(wěn)手輕輕推也能恢復(fù)直立。但開(kāi)啟速度環(huán)后車體開(kāi)始前后往復(fù)擺動(dòng)幅度越來(lái)越大最后變成左右甩尾式的失穩(wěn)。原因速度環(huán)積分系數(shù)設(shè)置過(guò)大或者速度環(huán)輸出限幅值設(shè)得太高。速度環(huán)輸出改變的是直立環(huán)的目標(biāo)角度如果目標(biāo)角度變化幅度過(guò)大、變化速度過(guò)快直立環(huán)會(huì)認(rèn)為車體嚴(yán)重偏離目標(biāo)產(chǎn)生一個(gè)很大的輸出導(dǎo)致電機(jī)猛烈加速反過(guò)來(lái)又讓速度誤差更大形成惡性循環(huán)。解決把速度環(huán)的 Ki 減半再觀察同時(shí)把速度環(huán)輸出限幅從 300 收窄到 150讓速度環(huán)對(duì)目標(biāo)角度的改變更溫和。另外確認(rèn)速度環(huán)的反饋方向是否正確——車體向前運(yùn)動(dòng)時(shí)編碼器值應(yīng)為負(fù)速度誤差為負(fù)輸出應(yīng)為正如果方向反了速度環(huán)會(huì)持續(xù)加大車速導(dǎo)致失控。判斷方法用手推動(dòng)車體觀察串口打印的速度值符號(hào)和方向是否一致不對(duì)的話修改編碼器方向宏定義。6. 進(jìn)階用法上電自檢、數(shù)據(jù)觀測(cè)與站立自鎖調(diào)試技巧最后分享三個(gè)在這套源碼基礎(chǔ)上常用的進(jìn)階技巧能顯著減少調(diào)試時(shí)間和維護(hù)成本。第一個(gè)是上電自檢流程在進(jìn)入主循環(huán)之前依次執(zhí)行傳感器初始化、零偏校準(zhǔn)、電機(jī) PWM 輸出置零、編碼器讀數(shù)清零然后延時(shí) 500ms 等待電源穩(wěn)定再解除電機(jī)剎車。這個(gè)流程看起來(lái)簡(jiǎn)單但能避免一個(gè)問(wèn)題——很多車一上電就突然加速是因?yàn)殡姍C(jī)在初始化瞬間收到了一個(gè)殘留在 PWM 寄存器里的舊值。正確做法是初始化最后把 PWM 輸出強(qiáng)制寫到 0再做一次編碼器清零確保第一幀控制輸出時(shí)所有反饋都是干凈的。第二個(gè)是數(shù)據(jù)觀測(cè)方法。調(diào)平衡車必須能看到角度和輸出不然全靠手感是玄學(xué)。建議用串口以 100Hz 頻率輸出一段格式化數(shù)據(jù)終端里按列顯示角度、目標(biāo)角度、直立環(huán)輸出、速度環(huán)輸出、最終 PWM。調(diào)試時(shí)重點(diǎn)看兩個(gè)指標(biāo)一是靜止時(shí)角度和目標(biāo)角度的差長(zhǎng)期是否為零不為零就要重新做機(jī)械零點(diǎn)校準(zhǔn)二是撥動(dòng)車體一次后角度能否在兩個(gè)來(lái)回內(nèi)收斂到目標(biāo)值附近如果連續(xù)振蕩四次以上就說(shuō)明 Kd 不夠或者濾波環(huán)節(jié)的相位滯后太大。更快的方式是先用仿真工具對(duì)卡爾曼濾波和 PID 參數(shù)做一次離線驗(yàn)證很多數(shù)學(xué)工具都能直接導(dǎo)入傳感器采集的數(shù)據(jù)調(diào)好參數(shù)再燒進(jìn)單片機(jī)能明顯減少在實(shí)物上反復(fù)燒錄的次數(shù)。第三個(gè)是站立自鎖功能。平衡車有兩種常見(jiàn)工作模式上電即平衡、上電視為待機(jī)模式。待機(jī)模式更安全——上電后車子不轉(zhuǎn)按下按鍵后才啟動(dòng)平衡算法。實(shí)現(xiàn)方式不復(fù)雜主循環(huán)檢測(cè)按鍵按下置位 enable 標(biāo)志控制中斷里檢查該標(biāo)志沒(méi)有置位時(shí)直接把 PWM 輸出置零且不執(zhí)行 PID 計(jì)算。這個(gè)習(xí)慣值得保留特別是調(diào)試階段車子放在桌面時(shí)不會(huì)被意外啟動(dòng)的電機(jī)帶跑。初始化時(shí)我一般會(huì)強(qiáng)制走一遍整體檢查先看傳感器數(shù)據(jù)是否穩(wěn)定再看角度能否跟隨手動(dòng)傾斜再確認(rèn)電機(jī)方向與編碼器方向一致最后才松開(kāi)手讓它進(jìn)入平衡狀態(tài)。從那以后我每次移植新源碼或者換新硬件都強(qiáng)制走這一遍節(jié)省過(guò)太多排查時(shí)間希望幫到你。整套源碼的完整工程文件、依賴庫(kù)和燒錄說(shuō)明都在壓縮包里按 README 里的引腳映射接好線燒錄后先調(diào)直立環(huán)就能站起來(lái)。本文還有配套的精品資源點(diǎn)擊獲取