動PMW3901光流傳感器:從SPI時序到位移累計完整指南)
熟悉無人機或者機器人底盤項目的朋友應該對“光流”這個概念不會陌生。室內(nèi)沒有GPS飛機又不想靠超聲波那么粗糙的高度與速度反饋這時候光流傳感器就派上了用場。而PMW3901MB就是這類傳感器里非常典型的一款它本質(zhì)上是一顆帶光學透鏡的低分辨率攝像頭通過計算連續(xù)圖像幀之間的像素移動來輸出x/y兩個方向的運動增量。我以前一直把它跟STM32搭配用最近換了塊ESP32開發(fā)板順手把整個流程重新梳理了一遍硬件連接、SPI底層時序、寄存器讀寫、再到最后的運動位移累計全鏈路都跑通了。這篇文章就把我踩過的坑和驗證過的代碼完整記錄下來給正在用ESP32做小車、機器人底盤、室內(nèi)定位或者課程設計的同學一個可以直接抄作業(yè)的參考。1. 方案選型為什么選PMW3901MB搭配ESP321.1 光流法測運動的基本原理光流法聽起來高大上其實原理很樸素。你看窗外風景往后跑就知道車在往前開光流傳感器做的也是這件事。傳感器內(nèi)部有一顆30x30分辨率的低分辨率灰度圖像傳感器以大約121fps的幀率連續(xù)抓拍地面然后硬件直接完成前后兩幀圖像的互相關(guān)計算輸出的就是這段時間內(nèi)圖像在x和y方向上偏移了多少個像素。這些像素偏移量被封裝成8位有符號整數(shù)通過SPI接口供主控讀取。PMW3901MB區(qū)別于普通光學鼠標傳感器的關(guān)鍵點在于它使用了專門定制的透鏡和更大的視場角支持從80mm高度到無窮遠的工作范圍而且對低紋理表面比如普通木地板、水泥地的識別能力比鼠標傳感器強很多。所以它不需要像鼠標那樣緊貼桌面懸空安裝就能感知相對地面的運動這就天然適合無人機懸停和小車里程計補充。1.2 PMW3901MB的關(guān)鍵參數(shù)解讀選型的時候最應該關(guān)注的是這組參數(shù)輸出分辨率、最大測量速度、工作高度范圍、接口方式。PMW3901MB的幀率是121fps輸出數(shù)據(jù)通過SPI讀取片選單獨控制。它的x/y增量寄存器是8位帶符號數(shù)也就是單次讀取范圍在-128到127之間如果運動太快導致增量超過這個范圍就會出現(xiàn)“丟幀”表現(xiàn)為累計位移比實際值偏小。實測下來在80到120mm的安裝高度下水平移動速度不超過1.5m/s時數(shù)據(jù)都還算可靠這已經(jīng)覆蓋大多數(shù)桌面小車和室內(nèi)無人機場景。另外要注意PMW3901有個孿生版本PMW3901MB-A和PMW3901MB-BB版本改進了鏡頭和LED驅(qū)動新買模塊基本都是B版。兩者寄存器定義基本一致代碼可以通用。模塊自帶一個紅光LED和鏡頭LED是給傳感器補光用的不需要主控額外控制模塊上電后會自動點亮。1.3 為什么主控選ESP32而不是STM32不是STM32不行而是ESP32在這個場景下太方便了。一是有成熟的Arduino生態(tài)SPI庫和串口調(diào)試開箱即用十分鐘就能把數(shù)據(jù)打出來二是自帶WiFi和藍牙光流數(shù)據(jù)可以直接通過WiFi UDP或者藍牙發(fā)給上位機后續(xù)做地面站調(diào)試省掉一根USB線三是ESP32性能對這類低速傳感器來說嚴重過剩同時跑光流讀取、姿態(tài)解算、電機控制都沒問題。唯一的注意點是ESP32的ADC精度存在一些缺陷但那跟SPI數(shù)字接口沒關(guān)系完全不影響光流傳感器使用。1.4 這個組合能用在哪些項目里最直接的應用是給小車加一個“地面速度計”和輪子編碼器互相校正。編碼器會受打滑影響而光流傳感器測的是相對地面的真實光學運動兩者融合能得到更可靠的里程數(shù)據(jù)。再進階一點就是無人機室內(nèi)懸停用光流數(shù)據(jù)做水平位置環(huán)的反饋配合氣壓計或TOF激光測距做高度反饋這已經(jīng)是很成熟的低成本方案。如果只是做畢設或者課程設計用這個傳感器讀取數(shù)據(jù)、繪制運動軌跡、實現(xiàn)簡單的方向判斷也足夠撐起一個完整性不錯的項目。2. 硬件連接引腳定義與接線全流程2.1 模塊引腳與ESP32的對應關(guān)系PMW3901模塊的封裝有好幾種常見的Arducam風格的模塊引出6到8個引腳VCC、GND、SCK、MOSI/SDI、MISO/SDO、CS/NCS部分模塊還多一個LED_EN。核心引腳就六個和ESP32的VSPI接口直接對插即可。我用的默認引腳接線如下。PMW3901模塊引腳ESP32引腳說明VCC3V3電源3.3V輸入GNDGND共地SCKGPIO18SPI時鐘MOSI/SDIGPIO23主機輸出接傳感器輸入MISO/SDOGPIO19主機輸入接傳感器輸出CS/NCSGPIO5片選低電平有效需要注意模塊上的引腳名稱可能標注為SDI和SDO對應關(guān)系不要接反。SDI是數(shù)據(jù)輸入接到ESP32的MOSI主機輸出SDO是數(shù)據(jù)輸出接到ESP32的MISO主機輸入。接反的后果是讀不到ID或者讀到全0xFF。2.2 電平匹配和供電細節(jié)PMW3901的工作電壓是2.7V到3.6VI/O端口也是3.3V電平所以和ESP32之間不需要電平轉(zhuǎn)換直接連接是安全的。這里最容易被忽略的是千萬不要拿5V給模塊供電模塊上如果帶板載穩(wěn)壓芯片還好如果是裸芯片模塊5V上去很可能直接燒毀。供電質(zhì)量對光流數(shù)據(jù)的影響很大很多人裝好后發(fā)現(xiàn)SQUAL值忽高忽低或者靜止時數(shù)據(jù)亂跳最后排查發(fā)現(xiàn)是電源紋波太大。傳感器的LED補光燈在高頻開關(guān)如果電源帶載能力不足LED亮度波動會直接影響成像質(zhì)量。我的做法是在模塊的VCC和GND之間就近加一顆10uF電解電容和一顆0.1uF陶瓷電容去耦效果立竿見影。2.3 布局和走線上的經(jīng)驗傳感器安裝高度是硬件布局里最重要的一項。PMW3901的工作范圍下限是80mm低于這個距離鏡頭無法合焦輸出SQUAL會掉到10以下數(shù)據(jù)基本不可用。建議安裝高度控制在80到150mm之間最理想的是100mm左右。如果裝在小車上傳感器盡量靠近車體中心減少旋轉(zhuǎn)時帶來的額外光流分量。另外模塊鏡頭要朝下安裝時注意方向別裝反否則會導致x和y軸的符號與預期相反這不是代碼問題是坐標系沒對齊。SPI線材方面我測試過10厘米以內(nèi)的杜邦線在2MHz時鐘下工作穩(wěn)定超過15厘米就需要考慮降低SPI時鐘或者使用雙絞線。如果項目中同時有電機和電調(diào)傳感器連接線要盡量遠離動力線避免大電流切換時的電磁干擾耦合進SPI信號。3. SPI通信原理寄存器地圖與關(guān)鍵時序3.1 PMW3901MB的寄存器速查表SPI設備無非就是一堆寄存器理解這些寄存器就等于掌握了一半的驅(qū)動代碼。PMW3901MB核心寄存器不算多我把項目里實際用到的整理成了速查表。寄存器地址名稱讀/寫說明0x00Product_ID1只讀固定值0x42用于握手0x01Product_ID2只讀固定值0x010x02Motion只讀bit7表示是否有新運動數(shù)據(jù)0x03Delta_X只讀x方向運動增量帶符號0x04Delta_Y只讀y方向運動增量帶符號0x05SQUAL只讀表面質(zhì)量越大代表地面紋理越清晰0x0EResolution只讀分辨率相關(guān)參數(shù)0x3FInverse_Product_ID只讀反相ID固定值0xBD0x7FPower_Up_Reset等讀寫配置寄存器00x61寫進入配置模式寫0xAD0x63寫退出配置模式每次讀取運動數(shù)據(jù)的過程是先讀0x02 Motion寄存器如果bit7為1說明這一幀有新的運動數(shù)據(jù)然后再讀0x03和0x04拿x和y的增量同時讀0x05拿SQUAL評估地面質(zhì)量。Motion寄存器被讀取之后傳感器內(nèi)部會自動把Motion位清零所以讀取順序不能亂先讀Motion再讀Delta否則下一幀的數(shù)據(jù)狀態(tài)會無法判斷。3.2 SPI讀寫時序到底長什么樣PMW3901MB使用的SPI模式是Mode 3也就是CPOL1、CPHA1。這個必須注意如果用了默認的SPI Mode 0去讀大概率讀回來全是垃圾數(shù)據(jù)。SPI時鐘建議設置在1到2MHz之間足夠快也足夠穩(wěn)定太高的時鐘反而容易出現(xiàn)讀錯字節(jié)的問題。讀單個寄存器的時序分成兩段先片選拉低發(fā)送一個地址字節(jié)地址字節(jié)的bit7置1表示讀操作然后發(fā)送一個任意字節(jié)通常發(fā)0x00用來產(chǎn)生時鐘同時從MISO上讀回數(shù)據(jù)最后片選拉高。寫寄存器則更簡單片選拉低后發(fā)送地址字節(jié)bit7為0緊接著發(fā)送要寫入的數(shù)據(jù)然后片選拉高。代碼層面就是SPI庫的簡單封裝uint8_t readRegister(uint8_t addr) { _spi.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE3)); digitalWrite(_cs, LOW); _spi.transfer(addr | 0x80); // bit71 表示讀 uint8_t val _spi.transfer(0x00); // 產(chǎn)生時鐘并讀數(shù)據(jù) digitalWrite(_cs, HIGH); _spi.endTransaction(); return val; } void writeRegister(uint8_t addr, uint8_t val) { _spi.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE3)); digitalWrite(_cs, LOW); _spi.transfer(addr 0x7F); // bit70 表示寫 _spi.transfer(val); digitalWrite(_cs, HIGH); _spi.endTransaction(); }3.3 芯片上電握手與初始化序列傳感器上電后需要先讀ID確認SPI通信鏈路正常。讀0x00寄存器應該得到0x42讀0x3F寄存器應該得到0xBD如果這兩個值對不上說明接線、電壓或者SPI模式有問題不要急著往下寫代碼。握手通過之后芯片還需要一段初始化序列才能真正開始輸出運動數(shù)據(jù)。這個序列在不同的模塊之間不是百分之百相同但Arducam版本和市面上大多數(shù)國產(chǎn)模塊都支持下面這套我實測可以正常出數(shù)據(jù)writeRegister(0x7F, 0x00); writeRegister(0x61, 0xAD); writeRegister(0x7F, 0x03); writeRegister(0x40, 0x00); writeRegister(0x7F, 0x05); writeRegister(0x4E, 0x20); writeRegister(0x7F, 0x00); writeRegister(0x63, 0x00);這段序列的意思大致是先重置寄存器頁然后寫入特定密鑰進入配置模式配置LED控制寄存器使能板載補光燈最后退出配置模式。如果初始化之后SQUAL一直為0首先懷疑這個序列沒執(zhí)行到位。4. 完整代碼實現(xiàn)從底層驅(qū)動到運動檢測4.1 代碼整體架構(gòu)代碼基于Arduino框架開發(fā)適用于ESP32全系列開發(fā)板。整個工程拆成兩個部分PMW3901驅(qū)動類負責SPI通信和寄存器讀寫主程序負責周期讀取數(shù)據(jù)、做簡單濾波、累計位移并輸出結(jié)果。這樣做的好處是傳感器邏輯和業(yè)務邏輯解耦以后想移植到STM32或者樹莓派Pico只需要改動驅(qū)動類內(nèi)部實現(xiàn)上層邏輯不用動。驅(qū)動類的核心成員函數(shù)就是begin、readMotion。begin里完成ID握手和初始化序列readMotion一次性讀出dx、dy和squal三個關(guān)鍵值。對外調(diào)用方不需要關(guān)心寄存器細節(jié)這讓代碼的復用性提高很多。4.2 PMW3901驅(qū)動類實現(xiàn)下面是完整的驅(qū)動類頭文件和實現(xiàn)我直接在單個.ino文件里寫完方便復制。默認使用ESP32的VSPI接口引腳分別是SCK18、MISO19、MOSI23CS用GPIO5。#include SPI.h #define SCK_PIN 18 #define MISO_PIN 19 #define MOSI_PIN 23 #define CS_PIN 5 class PMW3901 { public: PMW3901(uint8_t cs, SPIClass spi) : _cs(cs), _spi(spi) {} bool begin() { pinMode(_cs, OUTPUT); digitalWrite(_cs, HIGH); _spi.begin(SCK_PIN, MISO_PIN, MOSI_PIN, -1); uint8_t id1 readRegister(0x00); uint8_t id2 readRegister(0x3F); if (id1 ! 0x42 || id2 ! 0xBD) { return false; } writeRegister(0x7F, 0x00); writeRegister(0x61, 0xAD); writeRegister(0x7F, 0x03); writeRegister(0x40, 0x00); writeRegister(0x7F, 0x05); writeRegister(0x4E, 0x20); writeRegister(0x7F, 0x00); writeRegister(0x63, 0x00); return true; } void readMotion(int16_t dx, int16_t dy, uint8_t squal) { uint8_t motion readRegister(0x02); dx 0; dy 0; squal 0; if (motion 0x80) { dx (int8_t)readRegister(0x03); dy (int8_t)readRegister(0x04); } squal readRegister(0x05); } private: void writeRegister(uint8_t addr, uint8_t val) { _spi.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE3)); digitalWrite(_cs, LOW); _spi.transfer(addr 0x7F); _spi.transfer(val); digitalWrite(_cs, HIGH); _spi.endTransaction(); } uint8_t readRegister(uint8_t addr) { _spi.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE3)); digitalWrite(_cs, LOW); _spi.transfer(addr | 0x80); uint8_t val _spi.transfer(0x00); digitalWrite(_cs, HIGH); _spi.endTransaction(); return val; } uint8_t _cs; SPIClass _spi; }; PMW3901 flow(CS_PIN, SPI);讀寄存器函數(shù)里面的_spi.transfer(0x00)不是真的在發(fā)數(shù)據(jù)而是為了產(chǎn)生SCLK時鐘同時把MISO上的數(shù)據(jù)讀回來。SPI是全雙工協(xié)議主機發(fā)一個字節(jié)的同時會收到從機返回的一個字節(jié)這個機制是理解SPI讀操作的關(guān)鍵。4.3 主程序數(shù)據(jù)讀取、濾波與位移累計主程序的邏輯很直接10毫秒一個周期調(diào)用readMotion把增量做一次死區(qū)濾波再累加到全局變量里。死區(qū)濾波的意思是當增量絕對值小于2時直接看成0用來消除靜止狀態(tài)下傳感器內(nèi)部噪聲引起的漂移。這個閾值不是死的如果發(fā)現(xiàn)數(shù)據(jù)太靈敏就調(diào)大如果發(fā)現(xiàn)微小移動檢測不到就調(diào)小。long x_acc 0; long y_acc 0; void setup() { Serial.begin(115200); if (!flow.begin()) { Serial.println(PMW3901 init failed, check SPI wiring!); while (1) { delay(100); } } Serial.println(PMW3901 ready); } void loop() { int16_t dx 0, dy 0; uint8_t squal 0; flow.readMotion(dx, dy, squal); // 死區(qū)濾波消除靜止噪聲 if (dx -2 dx 2) dx 0; if (dy -2 dy 2) dy 0; x_acc dx; y_acc dy; // 串口發(fā)送字符 r 可以清零累計位移 if (Serial.available() 0) { char c Serial.read(); if (c r) { x_acc 0; y_acc 0; Serial.println(accumulate reset); } } Serial.printf(dx%4d dy%4d squal%3d x_acc%6ld y_acc%6ld\n, dx, dy, squal, x_acc, y_acc); delay(10); }運行之后打開Arduino IDE的串口監(jiān)視器把波特率調(diào)到115200就能看到一行行實時數(shù)據(jù)。模塊靜止時dx和dy應該在0附近小幅波動x_acc和y_acc基本保持不變手拿著模塊在桌面上方平移增量方向?qū)苿臃较蚶塾嬛禃S移動持續(xù)變化。這個反應速度很跟手基本沒有延遲感。4.4 簡單運動方向判斷純累計位移已經(jīng)足夠看出運動趨勢但為了驗證數(shù)據(jù)可用性我加了一段方向判斷邏輯當x方向的累計位移超過100時判定為一次明顯的左右移動并清零重計。閾值100是counts單位在100mm高度大概對應幾厘米的實際位移具體換算關(guān)系見下一節(jié)。if (abs(x_acc) 100) { Serial.println(x_acc 0 ? - RIGHT : - LEFT); x_acc 0; } if (abs(y_acc) 100) { Serial.println(y_acc 0 ? ^ FORWARD : v BACKWARD); y_acc 0; }這個判斷邏輯放在loop里readMotion之后即可。實測在桌面上平移模塊到左右兩側(cè)串口能穩(wěn)定輸出LEFT和RIGHT方向達到預期效果。方向的正負號取決于模塊安裝方向如果符號反了把模塊轉(zhuǎn)180度安裝或者交換代碼里的判斷邏輯都可以。4.5 從counts換算成實際距離很多人在這一步卡住光流輸出的dx/dy到底是什么單位答案是counts也就是像素偏移計數(shù)。要想換算成毫米必須知道傳感器在當前安裝高度下的分辨率系數(shù)。PMW3901沒有固定輸出的毫米值因為同一個像素偏移量在不同高度對應的地面位移是不一樣的這是幾何透視的自然結(jié)果。經(jīng)驗值是在80到120mm安裝高度下1 count大約對應1.0到1.5mm的地面位移。但這個值受表面紋理、光照、鏡頭個體差異影響最可靠的辦法是現(xiàn)場標定。把模塊固定在小車上讓車走一段已知距離比如1米記錄累計counts然后用實際距離除以累計counts得到這個安裝高度下的標定系數(shù)。標定系數(shù)k的公式是k 實際位移(mm) / 累計counts。以后每次移動的距離估算就是dx乘以k。這個標定過程看似簡單但實際做的時候要注意小車走的速度要均勻不能急加速急減速否則傳感器瞬時增量可能超出寄存器范圍導致丟數(shù)據(jù)標定結(jié)果就會偏大或偏小。我自己的經(jīng)驗是來回跑三趟取平均這樣標出來的系數(shù)誤差能控制在5%以內(nèi)。4.6 實測效果展示在普通木紋桌面上我的模塊高度約100mmSQUAL值穩(wěn)定在45到55之間靜止狀態(tài)dx/dy輸出基本為0。手拿著模塊以正常速度平移10厘米累計值大約在80到120 counts之間和標定系數(shù)估算的范圍吻合。在白色A4紙表面上SQUAL會掉到25左右但依然能檢測到運動只是偶發(fā)一兩個像素的跳動在純色光滑瓷磚上SQUAL低于15數(shù)據(jù)明顯變差會出現(xiàn)漏檢和方向不穩(wěn)。這個結(jié)果符合預期光流傳感器依賴地面紋理特征紋理越豐富數(shù)據(jù)越可靠。純色光滑表面是光流傳感器的天敵這不是模塊質(zhì)量問題是物理限制。5. 常見問題與避坑指南5.1 讀不到芯片ID怎么辦這是第一個也是最常見的坑。程序卡在init failed讀0x00寄存器不是0x42。排查順序我建議從硬件到軟件先用萬用表確認模塊VCC引腳確實是3.3VGND和ESP32共地然后核對SCK、MOSI、MISO、CS四根線是否一一對應MISO和MOSI接反的概率最高確認無誤后看SPI模式是否設置為Mode 3默認的Mode 0會讀不到正確ID。如果硬件和模式都正確還是讀不到檢查SPI時鐘頻率。有些模塊對時序要求苛刻2MHz不行就降成1MHz試。還有一個小細節(jié)CS引腳在初始化之前必須處于高電平因為PMW3901片選是低電平有效如果上電時CS被拉低芯片可能進入異常狀態(tài)。我的代碼里在begin函數(shù)第一件事就是把CS拉高這個順序很重要。5.2 SQUAL值一直很低或者為0SQUAL是表面質(zhì)量參數(shù)數(shù)值越大代表地面紋理越清晰。我實測的參考標準是大于30數(shù)據(jù)可靠20到30勉強能用低于15基本沒法用。如果SQUAL長期個位數(shù)先檢查安裝高度模塊距離地面低于80mm會離焦這是最常見原因。把模塊抬高到100mm左右再試通常會有明顯改善。排除高度問題后看地面材質(zhì)純白紙、亞克力板、拋光瓷磚都屬于弱紋理表面光流效果會很差。這不是傳感器壞了換一塊木紋紙或者地毯就能驗證。還要檢查鏡頭表面是不是沾了灰模塊上的小鏡頭很容易積灰用棉簽蘸酒精輕輕擦拭即可。最后確認板載LED有沒有亮如果LED不亮可能是供電不足或者LED控制寄存器被錯誤配置。5.3 數(shù)據(jù)跳動很大靜止時累計值亂飄靜止時dx/dy理論應該為0但實際上會有±1或者±2的噪聲這是正常的。如果噪聲幅度超過了±5就要排查電源。PMW3901內(nèi)部有LED補光電源紋波大會導致成像亮度波動直接影響光流計算。處理辦法是在模塊供電腳加去耦電容10uF和0.1uF并聯(lián)效果最好。另外SPI線太長也會導致讀回來的數(shù)據(jù)偶發(fā)錯誤表現(xiàn)就是某一次dx突然跳到一個很大的值。解決方法是縮短杜邦線或者把SPI時鐘從2MHz降到1MHz。如果項目中同時有電機在轉(zhuǎn)傳感器線要遠離電機線我之前把光流線跟電機線綁在一起走結(jié)果電機一轉(zhuǎn)數(shù)據(jù)就狂跳分開走線后問題立刻消失。5.4 為什么移動方向和實際方向相反這通常不是傳感器故障而是坐標系問題。PMW3901的x/y正方向是相對于芯片封裝絲印定義的如果模塊安裝方向和你的車體坐標系不一致輸出符號就會反。解決辦法要么旋轉(zhuǎn)模塊安裝方向要么在程序里對dx/dy取反。還有一點容易被忽視模塊平移和旋轉(zhuǎn)時光流數(shù)據(jù)是平移和旋轉(zhuǎn)分量的疊加如果模塊沒有安裝在車體旋轉(zhuǎn)中心小車的原地旋轉(zhuǎn)也會產(chǎn)生平移動量。這個在做精確里程計的時候必須注意。5.5 傳感器和WiFi同時使用會不會互相干擾ESP32開WiFi時射頻工作頻段是2.4GHzPMW3901的SPI時鐘只有2MHz兩者頻率差得很遠正常情況下WiFi射頻不會直接影響SPI通信。真正的問題在電源WiFi發(fā)射瞬間電流可以達到幾百毫安如果ESP32和傳感器共用一組穩(wěn)壓電源這個瞬時跌落會傳導到傳感器的LED驅(qū)動導致幀間亮度波動。所以如果項目同時開啟WiFi和光流電源設計要更扎實傳感器供電最好單獨用一路LDO并且加足夠的去耦電容。實測在ESP32開WiFi發(fā)送UDP數(shù)據(jù)的同時讀取光流數(shù)據(jù)依然穩(wěn)定沒有出現(xiàn)丟包或者誤碼。6. 后續(xù)可以怎么擴展如果跑通了這個基礎(chǔ)版后面有很多有價值的擴展方向。最簡單的是給模塊增加MPU6050慣性測量單元用光流數(shù)據(jù)校準加速度計積分漂移可以做更平滑的速度估計。稍微復雜一點的是用ESP32的WiFi把光流數(shù)據(jù)通過UDP廣播到電腦在上位機里實時繪制運動軌跡這個非常適合作為課程設計或者畢業(yè)設計的展示內(nèi)容。再進階就是用光流做小車的閉環(huán)走直線控制通過讀取光流側(cè)向位移量修正電機的PWM輸出。按我的經(jīng)驗最值得先做的是把標定系數(shù)測準。很多人在這一步嫌麻煩直接用經(jīng)驗值結(jié)果小車跑一米測出來一米三整個系統(tǒng)都跟著不準。花十分鐘現(xiàn)場標定一次后面所有數(shù)據(jù)才談得上可信。光流傳感器是一個“看著簡單細節(jié)很多”的器件接線、時序、安裝高度、供電質(zhì)量任何一環(huán)掉鏈子都會讓數(shù)據(jù)變得不可用但也正因為如此完整跑通一遍之后對SPI協(xié)議和傳感器特性的理解會非常扎實。