實(shí)戰(zhàn):視覺反饋與PID控制的工程實(shí)現(xiàn))
簡介2017年全國大學(xué)生電子設(shè)計(jì)競賽B題滾球控制系統(tǒng)的完整工程代碼包面向參加電賽或?qū)W習(xí)嵌入式視覺控制的學(xué)生與開發(fā)者。系統(tǒng)以O(shè)penMV攝像頭追蹤小球位置通過藍(lán)牙將坐標(biāo)發(fā)送至STM32控制端結(jié)合PID算法驅(qū)動(dòng)電機(jī)實(shí)現(xiàn)滾球控制覆蓋圖像處理、無線通信與閉環(huán)控制等關(guān)鍵環(huán)節(jié)。壓縮包共254個(gè)文件約8.76MB主要包括Keil工程文件uvproj/uvopt、C語言源碼與頭文件c/h、編譯生成的目標(biāo)文件與匯編o/axf/s、燒錄固件hex以及腳本和備份文件bat/bak目錄結(jié)構(gòu)便于直接打開工程查看或重新編譯。目前已有1810人學(xué)習(xí)下載適合希望快速上手完整賽題方案、參考硬件驅(qū)動(dòng)與圖像識(shí)別代碼、梳理PID控制流程的參賽者和嵌入式愛好者。1. 題面回顧與整體方案選型1.1 這一題到底在考什么2017年電賽B題“滾球控制系統(tǒng)”題面核心一句話概括讓一個(gè)金屬小球在一個(gè)可雙向傾斜的平板上穩(wěn)定到指定位置并能沿給定軌跡運(yùn)動(dòng)。聽起來像玩具實(shí)際做起來是集機(jī)械、視覺、控制、嵌入式于一身的綜合題。先說清楚系統(tǒng)工作邏輯平板繞X軸和Y軸兩個(gè)方向傾斜小球受重力分量驅(qū)動(dòng)在平板上滾動(dòng)。控制系統(tǒng)需要實(shí)時(shí)獲取小球的位置坐標(biāo)和期望位置求偏差通過控制算法計(jì)算出平板應(yīng)該傾斜的角度再由舵機(jī)或電機(jī)驅(qū)動(dòng)平板動(dòng)作形成閉環(huán)。整個(gè)過程對(duì)實(shí)時(shí)性要求很高典型的視覺反饋閉環(huán)系統(tǒng)。從當(dāng)年參賽隊(duì)伍的方案分布來看大致分三類方案類型視覺方案主控方案優(yōu)缺點(diǎn)A類OpenMV攝像頭模塊STM32F4系列開發(fā)快視覺端獨(dú)立處理串口通信適合趕時(shí)間B類普通攝像頭上位機(jī)圖像處理上位機(jī)STM32圖像處理靈活但需要多設(shè)備聯(lián)調(diào)現(xiàn)場穩(wěn)定性考驗(yàn)大C類攝像頭直連高性能MCU樹莓派/全志等嵌入式Linux算力強(qiáng)可跑復(fù)雜算法但功耗和實(shí)時(shí)性要平衡我當(dāng)年采用的是A類方案OpenMV做圖像識(shí)別輸出小球坐標(biāo)STM32做主控跑PID控制算法驅(qū)動(dòng)舵機(jī)改變平板角度。這套方案最大的好處是視覺和控制解耦調(diào)試時(shí)可以分別驗(yàn)證不用一上來就面對(duì)“全鏈路不工作”的噩夢(mèng)。1.2 系統(tǒng)構(gòu)成與機(jī)械結(jié)構(gòu)要點(diǎn)整個(gè)系統(tǒng)拆開看核心模塊就四個(gè)視覺檢測(cè)模塊、主控計(jì)算模塊、執(zhí)行機(jī)構(gòu)模塊、機(jī)械平臺(tái)結(jié)構(gòu)。機(jī)械結(jié)構(gòu)是我第一個(gè)踩坑的地方。平板和底座之間必須用萬向節(jié)或者類似結(jié)構(gòu)連接確保平板能以中心為支點(diǎn)做兩個(gè)自由度的旋轉(zhuǎn)。很多隊(duì)伍用兩個(gè)舵機(jī)疊加的云臺(tái)結(jié)構(gòu)一個(gè)舵機(jī)控制俯仰一個(gè)控制橫滾下面一個(gè)舵機(jī)要馱著另一個(gè)舵機(jī)結(jié)構(gòu)重量大容易產(chǎn)生機(jī)械抖動(dòng)。我當(dāng)時(shí)的做法是底座固定一個(gè)數(shù)字舵機(jī)通過連桿結(jié)構(gòu)驅(qū)動(dòng)平板繞X軸旋轉(zhuǎn)另一個(gè)舵機(jī)垂直安裝驅(qū)動(dòng)平板繞Y軸旋轉(zhuǎn)。這樣兩個(gè)舵機(jī)都固定在底座上平板上的負(fù)載很小轉(zhuǎn)動(dòng)慣量低響應(yīng)快得多。連桿結(jié)構(gòu)需要自己用3D打印或者鋁合金件加工精度要求挺高如果連桿虛位大了控制死區(qū)會(huì)讓你懷疑人生。機(jī)械部分的經(jīng)驗(yàn)總結(jié)第一舵機(jī)選型至少用20kg級(jí)別的數(shù)字舵機(jī)金屬齒輪不然平板帶球運(yùn)行時(shí)根本穩(wěn)不住第二所有連接件盡量用軸承減少摩擦阻力第三平板的平面度非常關(guān)鍵如果平板本身是翹的PID參數(shù)調(diào)到天邊也救不回來。2. 代碼工程結(jié)構(gòu)與數(shù)據(jù)流設(shè)計(jì)2.1 代碼模塊劃分的底層邏輯寫這套代碼最大的體會(huì)是競賽代碼和平時(shí)練手的例程完全是兩碼事。平時(shí)寫代碼追求“能跑”競賽代碼追求“在規(guī)定的幾十秒內(nèi)穩(wěn)定跑完整個(gè)賽程”。所以代碼結(jié)構(gòu)設(shè)計(jì)的第一步不是急著寫邏輯而是先把數(shù)據(jù)流理清楚。我的代碼整體分三大模塊視覺模塊OpenMV端、通信協(xié)議層、控制執(zhí)行模塊STM32端。視覺模塊負(fù)責(zé)的事情比較純粹通過攝像頭采集圖像識(shí)別小球位置把坐標(biāo)算出來。OpenMV上的Micropython代碼簡單直接就是初始化傳感器、設(shè)置閾值、找色塊、計(jì)算重心。通信協(xié)議層是最容易被低估的部分。因?yàn)镺penMV和STM32通過串口通信數(shù)據(jù)幀格式必須自己定。我定義的數(shù)據(jù)幀是幀頭0xAA 數(shù)據(jù)長度 數(shù)據(jù)位X坐標(biāo)、Y坐標(biāo)、標(biāo)志位 校驗(yàn)位。X和Y坐標(biāo)各占兩個(gè)字節(jié)先高后低這樣可以在一次傳輸里同時(shí)傳遞坐標(biāo)和當(dāng)前狀態(tài)??刂茍?zhí)行模塊是STM32端的核心包含三個(gè)子模塊串口解析、期望軌跡生成、PID控制器。數(shù)據(jù)流向 攝像頭采集 - OpenMV圖像處理 - 小球坐標(biāo)(x,y) - 串口協(xié)議幀 - STM32串口中斷接收 - 數(shù)據(jù)校驗(yàn)解析 - 與期望坐標(biāo)(x_ref, y_ref)求偏差 - PID計(jì)算 - 舵機(jī)PWM輸出 - 平板傾斜 - 小球滾動(dòng) - 攝像頭再次采集閉環(huán)理清數(shù)據(jù)流之后寫代碼就變成填空了。每個(gè)模塊的輸入輸出都明確測(cè)試的時(shí)候可以單獨(dú)喂數(shù)據(jù)驗(yàn)證不用每次都要整個(gè)系統(tǒng)跑起來。2.2 控制周期與任務(wù)調(diào)度設(shè)計(jì)控制系統(tǒng)的實(shí)時(shí)性是所有代碼設(shè)計(jì)中優(yōu)先級(jí)最高的一件事。我的系統(tǒng)采用的是固定控制周期設(shè)計(jì)核心控制環(huán)運(yùn)行在5ms定時(shí)器中斷中也就是200Hz的控制頻率。這個(gè)頻率怎么定的主要考慮因素有三個(gè)舵機(jī)響應(yīng)速度數(shù)字舵機(jī)典型響應(yīng)時(shí)間在50ms到200ms級(jí)別控制頻率太高意義不大、OpenMV的幀率實(shí)際穩(wěn)定跑30fps左右即大約33ms一幀新數(shù)據(jù)、PID微分項(xiàng)的噪聲敏感性頻率太高會(huì)放大噪聲。這里有個(gè)重要的實(shí)際經(jīng)驗(yàn)控制周期和視覺數(shù)據(jù)的到達(dá)周期是不匹配的200Hz的控制率但視覺數(shù)據(jù)只有30Hz左右。如果直接把上一幀坐標(biāo)用在控制里小球可能已經(jīng)跑出去很遠(yuǎn)了控制必然滯后。我的做法是在主控里做坐標(biāo)緩存和外推預(yù)測(cè)當(dāng)收到最新一幀坐標(biāo)時(shí)記錄該幀的時(shí)間戳控制環(huán)計(jì)算時(shí)讀取最近一幀坐標(biāo)并乘以一個(gè)根據(jù)實(shí)際調(diào)試得出的速度修正系數(shù)相當(dāng)于簡單預(yù)測(cè)了小球在當(dāng)前時(shí)刻的位置。這個(gè)優(yōu)化看似不起眼實(shí)測(cè)對(duì)系統(tǒng)穩(wěn)定性的提升非常大。整個(gè)任務(wù)的調(diào)度結(jié)構(gòu)主循環(huán)非阻塞 while(1) { 處理串口接收數(shù)據(jù)幀中斷環(huán)形緩沖區(qū) 更新期望軌跡點(diǎn)根據(jù)任務(wù)狀態(tài)機(jī) 非實(shí)時(shí)狀態(tài)刷新OLED顯示、按鍵掃描 } 定時(shí)器中斷5ms 讀取最近狀態(tài)緩存 計(jì)算PID輸出 更新舵機(jī)PWM占空比狀態(tài)機(jī)也是很關(guān)鍵的設(shè)計(jì)。賽題有多個(gè)任務(wù)定點(diǎn)停留、直線運(yùn)動(dòng)、圓周運(yùn)動(dòng)等。我把這些做成一個(gè)簡單的枚舉狀態(tài)機(jī)主循環(huán)根據(jù)狀態(tài)決定當(dāng)前執(zhí)行的軌跡類型定時(shí)器中斷只負(fù)責(zé)執(zhí)行“位置偏差-PID-輸出”這條固定流水線不關(guān)心現(xiàn)在跑什么軌跡。這樣軌跡切換的時(shí)候控制環(huán)完全不需要改動(dòng)。3. 核心算法模塊拆解與代碼實(shí)現(xiàn)3.1 圖像處理從像素到坐標(biāo)OpenMV端識(shí)別小球最常用的是色塊識(shí)別函數(shù)find_blobs()。但直接調(diào)用API只能拿到一個(gè)色塊的矩形框我們需要的是小球在圖像中的具體中心坐標(biāo)。小球一般選擇橙色或者紅色和白色平板背景對(duì)比度高閾值設(shè)置也相對(duì)容易。圖像處理的幾個(gè)關(guān)鍵點(diǎn)第一ROI區(qū)域裁剪。平板在攝像頭視野內(nèi)是固定區(qū)域我直接在初始化時(shí)獲取平板四個(gè)角的圖像坐標(biāo)設(shè)為ROI。只在這個(gè)區(qū)域內(nèi)做色塊查找能大幅減少誤識(shí)別也能提高幀率。第二用色塊的中心作為小球位置。blob.cx()和blob.cy()返回的是色塊中心對(duì)小圓球來說這和球心位置偏差很小可以接受。但如果小球快速運(yùn)動(dòng)圖像會(huì)有運(yùn)動(dòng)模糊色塊區(qū)域會(huì)拉長這時(shí)候取中心點(diǎn)會(huì)有幾個(gè)像素的誤差在控制上體現(xiàn)為幾毫米的偏差。解決方法是適當(dāng)增大曝光時(shí)間換取更亮更清晰的圖像或者改用邊緣檢測(cè)加灰度重心法。第三多目標(biāo)干擾處理。如果賽場環(huán)境有其他橙紅色物體混進(jìn)ROIfind_blobs會(huì)返回多個(gè)結(jié)果。我加了面積和圓度雙重過濾條件只保留面積在合理區(qū)間且圓度接近1的目標(biāo)。核心代碼片段import sensor, image, time from pyb import UART sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time2000) sensor.set_auto_gain(False) # 關(guān)閉自動(dòng)增益防止亮度變化導(dǎo)致閾值漂移 sensor.set_auto_whitebal(False) uart UART(3, 115200, timeout_char1000) THRESHOLD (30, 80, 20, 80, 30, 80) # 橙色小球LAB閾值 ROI (60, 40, 200, 120) # 平板區(qū)域需要根據(jù)實(shí)際標(biāo)定 while True: img sensor.snapshot() blobs img.find_blobs([THRESHOLD], roiROI, pixels_threshold20, area_threshold20) if blobs: # 面積過濾 圓度過濾 best_blob max(blobs, keylambda b: b.area()) if best_blob.area() 100 and best_blob.roundness() 0.5: x best_blob.cx() y best_blob.cy() # 組幀發(fā)送幀頭 0xAAX高字節(jié)X低字節(jié)Y高字節(jié)Y低字節(jié)校驗(yàn) data bytearray([0xAA, x 8, x 0xFF, y 8, y 0xFF, (x ^ y) 0xFF]) uart.write(data)這里特別提一下自動(dòng)增益和自動(dòng)白平衡這是新手最容易忽略的坑。如果不關(guān)閉當(dāng)環(huán)境亮度或者小球位置變化時(shí)攝像頭的增益會(huì)自己調(diào)整同樣的顏色在不同時(shí)刻測(cè)出的閾值完全不一致導(dǎo)致時(shí)而識(shí)別不到、時(shí)而誤識(shí)別。調(diào)好一次閾值后固定參數(shù)是最穩(wěn)妥的做法。3.2 坐標(biāo)映射圖像坐標(biāo)到物理坐標(biāo)的標(biāo)定攝像頭拿到的是圖像像素坐標(biāo)幾百x幾百但PID控制需要的是平板上的物理坐標(biāo)或者歸一化坐標(biāo)。如果直接用像素坐標(biāo)做控制鏡頭畸變?cè)谶吘墔^(qū)域會(huì)造成很大的控制誤差小球在平板中間穩(wěn)了到邊緣就明顯跑偏。我采用的方案是四點(diǎn)標(biāo)定法在平板的四個(gè)角各做一個(gè)標(biāo)記記錄它們?cè)趫D像中的像素坐標(biāo)同時(shí)知道它們的物理坐標(biāo)比如定義平板中心為原點(diǎn)長寬方向上歸一化到-100到100。用透視變換把圖像坐標(biāo)轉(zhuǎn)換到物理坐標(biāo)。數(shù)學(xué)上這是一個(gè)單應(yīng)性變換矩陣求解問題。四個(gè)點(diǎn)對(duì)可以求解8個(gè)自由度的單應(yīng)矩陣。OpenMV的庫函數(shù)里沒有直接封裝透視變換的API我是在STM32端實(shí)現(xiàn)的坐標(biāo)解算。事先在電腦上用Python離線算出變換矩陣系數(shù)固化在代碼常量里。// STM32端簡化實(shí)現(xiàn)假設(shè)已通過標(biāo)定得到系數(shù) a0~a7 // 物理坐標(biāo)X_phys (a0*x_img a1*y_img a2) / (a6*x_img a7*y_img 1) // Y_phys (a3*x_img a4*y_img a5) / (a6*x_img a7*y_img 1) float x_phys (a0*x a1*y a2) / (a6*x a7*y 1); float y_phys (a3*x a4*y a5) / (a6*x a7*y 1);標(biāo)定的操作要注意幾點(diǎn)四個(gè)標(biāo)定點(diǎn)盡量覆蓋整個(gè)平板區(qū)域不要集中在中間否則外推邊緣誤差很大標(biāo)定時(shí)平板必須處于水平狀態(tài)每次挪動(dòng)攝像頭或者平板位置后需要重新標(biāo)定。這個(gè)矩陣的關(guān)鍵作用不只是矯正畸變還能把圖像坐標(biāo)系的原點(diǎn)對(duì)齊到平板中心讓控制變量的物理意義更清晰。3.3 PID控制位置環(huán)加前饋控制算法的核心是一個(gè)雙通道PIDX軸和Y軸獨(dú)立控制。最樸素的想法就是兩個(gè)獨(dú)立的PID分別把小球拉向目標(biāo)坐標(biāo)。但實(shí)際跑起來你會(huì)發(fā)現(xiàn)純位置PID在小球接近目標(biāo)時(shí)會(huì)反復(fù)震蕩難以穩(wěn)定。原因在于小球是無自平衡能力的對(duì)象沒有阻尼平板一傾斜就加速它相當(dāng)于一個(gè)二階積分環(huán)節(jié)位置PID要穩(wěn)住這種對(duì)象本質(zhì)上需要靠微分項(xiàng)的阻尼作用。單純調(diào)大比例增益會(huì)出現(xiàn)極限環(huán)震蕩調(diào)大微分項(xiàng)會(huì)放大噪聲。我的解決方案是位置式PID加微分先行并加入速度前饋typedef struct { float kp, ki, kd; float integral; float last_err; float target; } PID_t; float PID_Update(PID_t *pid, float measured, float dt) { float err pid-target - measured; pid-integral err * dt; // 積分限幅 if (pid-integral 300) pid-integral 300; if (pid-integral -300) pid-integral -300; float derivative (measured - pid-last_measured) / dt; // 微分先行的關(guān)鍵 float output pid-kp * err pid-ki * pid-integral - pid-kd * derivative; pid-last_err err; pid-last_measured measured; return output; }微分先行和標(biāo)準(zhǔn)PID的區(qū)別在于微分項(xiàng)作用于被控量小球位置的變化率而不是偏差的變化率。這樣目標(biāo)值突變比如從點(diǎn)A切換到點(diǎn)B時(shí)微分項(xiàng)不會(huì)產(chǎn)生巨大的沖擊系統(tǒng)不會(huì)猛地抖一下。速度前饋是這樣加的當(dāng)執(zhí)行圓軌跡或直線軌跡時(shí)目標(biāo)點(diǎn)本身在運(yùn)動(dòng)小球的期望速度不為零。單純的位置PID輸出的是把小球拉向目標(biāo)點(diǎn)的力但如果目標(biāo)點(diǎn)本身在移動(dòng)就相當(dāng)于一直在追一個(gè)動(dòng)點(diǎn)。我的做法是根據(jù)期望軌跡計(jì)算出目標(biāo)點(diǎn)的運(yùn)動(dòng)速度乘以一個(gè)前饋系數(shù)加到輸出上。這個(gè)前饋系數(shù)只有純位置PID輸出的一半左右但對(duì)軌跡跟蹤精度的提升很明顯跟蹤圓形軌跡的半徑誤差能縮小到一個(gè)很小的范圍。4. 調(diào)試實(shí)戰(zhàn)從能跑到跑穩(wěn)的完整鏈條4.1 分步調(diào)試法先開環(huán)后閉環(huán)拿到代碼的第一件事我強(qiáng)烈建議先做開環(huán)測(cè)試不要一上來就跑閉環(huán)。第一步測(cè)試視覺鏈路。把OpenMV單獨(dú)上電在OLED或者串口輸出里看小球坐標(biāo)是否連續(xù)、平滑坐標(biāo)在平板不同位置是否和預(yù)想一致。這個(gè)環(huán)節(jié)最容易發(fā)現(xiàn)的是圖像閾值問題、標(biāo)定矩陣用錯(cuò)的問題、串口通信丟幀的問題。第二步測(cè)試舵機(jī)執(zhí)行鏈路。用STM32直接發(fā)不同脈寬的PWM信號(hào)觀察平板傾斜方向是否正確、響應(yīng)是否靈敏。這里注意舵機(jī)的脈寬范圍和旋轉(zhuǎn)方向同一型號(hào)的舵機(jī)不同批次可能反著轉(zhuǎn)直接寫死很容易翻車。第三步純P控制測(cè)試。把KI和KD設(shè)為0只保留比例項(xiàng)從很小的KP開始加。觀察小球在平板上是否被“推”向目標(biāo)如果有來回震蕩就降低KP如果響應(yīng)太慢就增大一點(diǎn)。純P能讓小球穩(wěn)定在一個(gè)范圍內(nèi)就算是第一步成功。第四步逐步加D和I。先加微分項(xiàng)壓制震蕩再加積分項(xiàng)消除靜差。積分項(xiàng)一定要限幅這一點(diǎn)前面代碼里已經(jīng)體現(xiàn)了。4.2 現(xiàn)場調(diào)試的實(shí)戰(zhàn)經(jīng)驗(yàn)比賽現(xiàn)場和實(shí)驗(yàn)室環(huán)境差別很大燈光的位置、亮度完全不一樣攝像頭看到的畫面可能和調(diào)參時(shí)完全不同。這是每年電賽翻車最多的地方。我當(dāng)時(shí)的做法是在OpenMV初始化時(shí)加入一個(gè)閾值自動(dòng)適配邏輯。上電后先讀取當(dāng)前環(huán)境光下的圖像取ROI區(qū)域內(nèi)最亮和最暗的像素值范圍結(jié)合預(yù)設(shè)的色差偏移動(dòng)態(tài)生成閾值。這個(gè)邏輯只在上電時(shí)執(zhí)行一次運(yùn)行期間閾值不變化避免圖像處理在循環(huán)中自適應(yīng)帶來的不確定抖動(dòng)。還有現(xiàn)場電磁干擾問題。舵機(jī)是強(qiáng)干擾源大電流變化會(huì)通過電源線影響主控和攝像頭。我的做法是每個(gè)模塊獨(dú)立供電舵機(jī)用單獨(dú)的7.4V電池主控和攝像頭用5V電源通過光耦隔離或者是至少用共地加粗導(dǎo)線來緩解。實(shí)測(cè)下來如果共用一個(gè)電源舵機(jī)啟動(dòng)瞬間電壓跌落非常明顯攝像頭畫面會(huì)閃甚至直接重啟。4.3 PID參數(shù)整定的實(shí)用技巧PID參數(shù)整定很多人喜歡用試湊法一頓亂調(diào)最后調(diào)出來的系統(tǒng)可能剛好能過測(cè)試但稍微換個(gè)環(huán)境就垮了。我建議采用臨界比例度法的思路來做系統(tǒng)性的參數(shù)整定。做法先只保留比例項(xiàng)逐步增大KP直到系統(tǒng)出現(xiàn)等幅震蕩記錄此時(shí)的KP值為Ku震蕩周期為Tu。然后根據(jù)經(jīng)驗(yàn)公式計(jì)算初始參數(shù)再在這組參數(shù)附近微調(diào)。當(dāng)年調(diào)試出來的參數(shù)大概在一個(gè)范圍內(nèi)但不同機(jī)械結(jié)構(gòu)的系統(tǒng)差異很大——比如我后期換了一組舵機(jī)參數(shù)就必須重新調(diào)。給一個(gè)參考方向比例系數(shù)一般在2到8之間微分系數(shù)在比例系數(shù)的5到10倍之間積分系數(shù)很小主要用來消除靜態(tài)誤差調(diào)大了很容易引發(fā)低頻震蕩。在整定過程中有兩個(gè)細(xì)節(jié)值得特別留意。一個(gè)是在線修改參數(shù)的功能調(diào)試時(shí)用按鍵或者串口命令實(shí)時(shí)調(diào)整參數(shù)比重新燒錄程序快太多了。另一個(gè)是用OLED實(shí)時(shí)顯示PID的各個(gè)環(huán)節(jié)的值定位問題快比如看到輸出一直頂?shù)缴舷薜∏蚣y絲不動(dòng)基本可以判斷是機(jī)械卡死而不是PID參數(shù)問題。5. 高頻故障與排查手冊(cè)5.1 常見故障速查表現(xiàn)象可能原因優(yōu)先級(jí)排查項(xiàng)小球靜止時(shí)來回震蕩微分項(xiàng)過小阻尼不足增大KD檢查微分項(xiàng)是否有效小球偏向一側(cè)無法居中積分項(xiàng)過小時(shí)有靜差或機(jī)械零點(diǎn)偏了檢查平板水平度增加KI小球跑飛完全不受控坐標(biāo)映射錯(cuò)誤或舵機(jī)方向反了先檢查跟蹤的坐標(biāo)是否正確再看PWM方向攝像頭找不到小球閾值不對(duì)、光照變化、小球顏色被反光顯示二值化圖像確認(rèn)關(guān)閉自動(dòng)增益控制反應(yīng)明顯遲鈍控制周期太長或串口丟幀嚴(yán)重檢查串口波特率確認(rèn)幀校驗(yàn)機(jī)制是否有效舵機(jī)嗡嗡響且發(fā)燙控制頻率過高或輸出抖動(dòng)頻繁降低控制頻率輸出加死區(qū)5.2 獨(dú)家避坑經(jīng)驗(yàn)寫代碼時(shí)最大的一個(gè)教訓(xùn)串口中斷接收里千萬不要做耗時(shí)操作。我早期在串口中斷處理里直接解析數(shù)據(jù)并更新PID目標(biāo)值結(jié)果導(dǎo)致中斷函數(shù)執(zhí)行時(shí)間太長主循環(huán)被阻塞得厲害。正確做法是中斷里只把數(shù)據(jù)扔進(jìn)環(huán)形緩沖區(qū)在主循環(huán)里做解析。還有一個(gè)我花了一整天才解決的詭異問題小球在某一個(gè)特定位置附近總是出現(xiàn)規(guī)律性偏轉(zhuǎn)。排查到最后發(fā)現(xiàn)是攝像頭支架的共振造成的。攝像頭固定在一個(gè)較長的金屬支架上舵機(jī)工作時(shí)支架產(chǎn)生高頻微振動(dòng)導(dǎo)致圖像坐標(biāo)周期性漂移。后來把攝像頭支架加粗加固后問題徹底消失。調(diào)試時(shí)建議把重要數(shù)據(jù)通過串口打印或者無線模塊發(fā)到電腦上做成實(shí)時(shí)曲線。很多隊(duì)伍在實(shí)驗(yàn)室里看OLED上的數(shù)字變化完全看不出趨勢(shì)。我在筆記本上用匿名上位機(jī)或者直接用Python的matplotlib做實(shí)時(shí)繪圖把小球的實(shí)際軌跡畫出來調(diào)試效率提升非常明顯。5.3 功能擴(kuò)展思路如果這套系統(tǒng)想繼續(xù)深入做有兩個(gè)方向很值得擴(kuò)展。一個(gè)是把控制算法升級(jí)為串級(jí)PID結(jié)構(gòu)內(nèi)環(huán)控制平板角速度外環(huán)控制小球位置響應(yīng)速度會(huì)有質(zhì)的提升。另一個(gè)是在軌跡規(guī)劃上下功夫賽題要求畫圓或走直線時(shí)目前的簡單逐點(diǎn)逼近方案在高速下精度有限可以考慮前瞻加軌跡插值讓運(yùn)動(dòng)更順滑。比賽的代碼就是這樣一套東西從原理到落地每一步都有坑每一步也都有收獲。希望這篇內(nèi)容能幫到準(zhǔn)備電賽的朋友們特別是那些正在為B題代碼頭疼的隊(duì)友們。調(diào)試的時(shí)候保持耐心分模塊去驗(yàn)證不要指望一次點(diǎn)亮剩下來的事情就交給時(shí)間和手感了。本文還有配套的精品資源點(diǎn)擊獲取