ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

ESP32+PMW3901MB光流传感器实现运动检测与测速实战

ESP32+PMW3901MB光流传感器实现运动检测与测速实战 最近在做一台小型的室内循迹底盘常规轮式编码器一到瓷砖地面就疯狂打滑超声波方案又太靠不住。折腾了几天后我重新把手边的 PMW3901MB 光流传感器接到 ESP32 上用 SPI 直接读回位移增量运动检测这个问题立刻变得清爽了很多。这篇文章我就把从硬件连接到运动检测的完整踩坑过程、驱动代码和几个容易被忽视的细节一次讲清楚给准备做无人机悬停、小车测速或者低成本表面运动检测的朋友一份可以直接抄作业的参考。PMW3901MB 并不是一块摄像头模块它本质上是把光学成像单元和运动算法做在一个小封装里的专用芯片。它不输出画面而是直接告诉你“物体表面相对于传感器在 X 方向移动了多少、Y 方向移动了多少”这个特性让它在很多嵌入式运动检测场景里特别好用。但光有传感器还不够想要实时、多任务、甚至把数据无线传出去ESP32 是个非常合适的搭档。下面是整个方案从设计到实测的完整记录。1. 为什么是“ESP32 PMW3901MB”这个组合1.1 光流传感器到底在测什么很多第一次接触光流传感器的人会以为它和摄像头一样输出一帧一帧的图像然后在单片机里跑光流算法。实际上 PMW3901MB 这类器件内部已经集成了专用的运动引擎它通过内部的一小块低分辨率图像传感器连续拍摄表面纹理再在芯片内部完成特征跟踪和位移计算最后把当前帧相对上一帧的位移增量也就是 Delta X 和 Delta Y通过 SPI 或 I2C 接口输出给外部单片机。这个设计思路和办公室里最常见的“光电鼠标”很接近区别在于 PMW3901MB 的工作距离、帧率和算法调校更偏向机器人、无人机这类需要低空测速的场景。它给你的不是图像而是“相对位移量”所以单片机端不需要任何 OpenCV 级别的运算几个寄存器就能拿到当前时刻的相对运动状态。另外它还有一个非常重要的字段叫 SQUAL全称是 Surface Quality也就是当前捕获到的表面纹理质量。这个值可以用来判断传感器是不是“看得到”有效纹理数值太低时读到的位移基本没有参考价值。1.2 为什么用 ESP32 而不是 STM32 或 Arduino UNO选 ESP32 不是因为它比 STM32“高级”而是因为这颗芯片在开发效率、外设资源、无线通信集成三个维度上非常均衡。ESP32 自带双核 240MHz CPU硬件 SPI 接口可以直接跑 PMW3901MB同时它还有 BLE 和 Wi-Fi运动数据读回来后可以直接推给手机或者上位机不需要外加蓝牙串口模块。如果拿 STM32 来对比STM32 当然也能读 SPI而且实时性可能更稳定但大部分场景下开发者还得自己处理无线传输、协议栈、固件升级这些事。Arduino UNO 的问题则主要是主频太低PMW3901MB 那种以毫秒为周期的连续采样虽然不算重负载但后续要叠加滤波、PID 或者无线发送UNO 就会显得很吃力。ESP32 在 Arduino 生态和 PlatformIO 生态下都有非常成熟的库支持编译下载极其顺手这在我实际项目里省下的时间远比想象中要多。1.3 这个组合适合哪些场景最常见的应用就是室内无人机悬停。无人机在 GPS 信号不好的地方需要靠光流传感器测量对地速度再配合加速度计和气压计做位置估计。PMW3901MB 因为体积小、重量轻、功耗低是很多开源飞控的常见选型。第二个典型场景是低成本机器人测速尤其是那种在瓷砖、水泥地面跑的轮式机器人轮子打滑会让编码器数据失真光流传感器可以当作一个不依赖轮子转动的“视觉里程计”来用。第三个场景是手势识别、人机交互或者传送带测速之类的工业小项目只要表面纹理清晰这个组合都能胜任。我实际做的项目是给一台双轮差速小车补一个测速通道用来和编码器数据做交叉验证。一开始我用的是普通鼠标传感器改装后来换成 PMW3901MB 模块后体积、接口和驱动难度都改善了不少这个方案很快就成了我测速部分的主力。2. 硬件连接把模块和 ESP32 正确接起来2.1 PMW3901MB 引脚与供电注意事项市面上的 PMW3901MB 模块通常会引出 6 个甚至更多引脚最常见的是 VCC、GND、MOSI、MISO、SCLK、CS。有些模块还带中断引脚或者辅助接口但核心的部分就是这六根线。它的工作电压是 3.3V绝对不能直接接到 5V 电源上否则芯片很容易烧坏。ESP32 开发板的 3V3 引脚给这个小模块供电没有问题因为它的工作电流只有几十毫安但如果你准备把传感器安装在无人机或者小车上建议给光流模块单独配一个低噪声 LDO不要让电机驱动的大电流波动直接砸到传感器电源上。接线时还要留意模块的引脚顺序不是统一的。不同卖家做的 PCB 板可能把 MOSI 和 MISO 交换位置或者把 CS 放在另一边。我吃过这个亏拿到模块后第一件事应该是去翻商家给的原理图或者模块丝印千万别默认所有模块引脚排列都一样。我也建议你在焊接或插线之前用万用表量一下 VCC 和 GND确认没有反接的风险。2.2 ESP32 接线表与一种可靠的引脚分配我使用的是 ESP32 DevKitC 开发板上面引出的 GPIO 很多光流模块我直接接到了硬件 SPIVSPI对应的默认引脚上。这里的接线方式如下PMW3901MB 模块 ESP32 DevKitC VCC ----------- 3V3 GND ----------- GND MOSI ----------- GPIO23 MISO ----------- GPIO19 SCLK ----------- GPIO18 CS ----------- GPIO5如果你用的是 NodeMCU-32S 这类板子引脚名称可能略有差异但 GPIO 编号是一致的。CS 引脚可以选择任意空闲 GPIO只要在代码里初始化指定即可。我选 GPIO5 是因为它离 SPI 引脚比较近飞线方便而且和当时测试用的其他外设不冲突。SPI 模式必须用 Mode 3也就是 CPOL 1、CPHA 1这一点在代码初始化时会再次强调。2.3 供电滤波和飞线避坑硬件连接看起来很简单但影响稳定性的往往是细节。PMW3901MB 内部的红外 LED 在启动和运动检测过程中会产生一定的电流波动如果供电线上长了、细了或者电源纹波很大SPI 通信就会出现偶发误码典型表现是读出来的位移值突然跳一个很大的数或者 ID 都读不对。我实际测试时在模块的 VCC 和 GND 之间加了一颗 10uF 的电解电容和一颗 0.1uF 的陶瓷电容异常跳数的情况明显减少。飞线长度也需要注意。杜邦线在桌面测试阶段问题不大一旦装到小车上电机启停带来的震动和电磁干扰就会让杜邦线成为小天线。我的建议是原型阶段用杜邦线控制在 10cm 以内正式装配时尽量使用短而粗的导线直接焊接或者做一块小的转接板。还有一点非常容易被忽略就是 PMW3901MB 模块的镜头和传感器表面非常娇贵手指碰到就容易留下指纹甚至划伤。我有一块模块在测试时不小心被螺丝刀蹭到了镜头边缘结果 SQUAL 值整整掉了一半位移数据也变得断断续续。3. SPI 通信与数据寄存器深度解读3.1 SPI 读写格式和时序PMW3901MB 是一个 SPI 从设备工作在 SPI Mode 3。读寄存器的过程是主机先把片选 CS 拉低然后发送一个 8 位的寄存器地址地址最高位要置 1 表示读操作。随后主机再发送一个任意字节的时钟同时从 MISO 引脚上读回该寄存器的数据。写寄存器则是先把片选拉低发送最高位为 0 的寄存器地址然后紧跟着发送要写入的字节最后拉高片选。这套逻辑和很多普通 SPI 传感器芯片是一致的唯一的坑在于“读一个寄存器必须连续 transfer 两个字节”。我第一次写驱动时只发了一个地址字节就立刻读 MISO结果读回来的永远是 0xFF浪费了不少时间。另外SPI 时钟频率建议先保守一点我用的 1MHz 在杜邦线连接下非常稳定后面尝试 10MHz 也能跑但没有必要冒这个风险。3.2 关键寄存器一览根据我手上这颗 PMW3901MB 的资料和参考驱动主要需要关注的寄存器如下地址名称说明0x00Product_ID芯片型号标识读到 0x49 表示通信正常0x01Revision_ID版本信息不同批次可能略有差异0x02Motion运动状态标志位bit7 为 1 表示当前帧检测到运动0x03Delta_X_LX 方向位移增量低字节0x04Delta_X_HX 方向位移增量高字节0x05Delta_Y_LY 方向位移增量低字节0x06Delta_Y_HY 方向位移增量高字节0x07SQUAL表面纹理质量数值越低说明越看不清楚0x08Raw_Data_Sum内部像素帧总能量调试光照时有用0x09Raw_Data_Max内部像素最大值0x0ARaw_Data_Min内部像素最小值0x0BShutter_Upper曝光时间高字节0x0CShutter_Lower曝光时间低字节有一点要注意网上能找到的 PMW3901MB 寄存器表有多个版本有的把 Motion 放在 0x03Delta X 从 0x04 开始SQUAL 在 0x08。不同批次或者不同封装可能在寄存器定义上有细微差别所以最好以你买到模块附带资料为准。我下面代码里的寄存器定义是按 0x02 Motion、0x03 到 0x06 为位移、0x07 为 SQUAL 这个版本写的这也是我实测可用的一组。Delta X 和 Delta Y 是带符号的 16 位整数数据格式是小端也就是低字节在前。每次读取时应该把高字节左移 8 位再和低字节按位或起来然后根据最高位判断是正数还是负数。正数代表沿对应坐标轴正方向移动负数代表反方向。3.3 上电初始化和 ID 校验很多驱动资料里都提到 PMW3901MB 上电后可以自动从内部 Flash 加载配置所以理论上直接读数据也能工作。但为了保险我在代码里加上了一段唤醒操作往 0x00 寄存器写 0x5A延时几毫秒后再写回 0x00。这个操作在某些省电版本或者从睡眠模式唤醒时非常关键不加的话传感器可能一直停在低功耗状态。初始化完成后读一次 Product_ID判断是否等于 0x49能读到这个值基本就说明 SPI 接线和时序没大问题。4. 运动检测实战与完整代码实现4.1 Arduino 环境下的 SPI 初始化要点在 Arduino 环境中使用 ESP32 的硬件 SPI需要调用 SPI.begin(SCLK, MISO, MOSI, CS)。注意这里不是传统 Arduino 的 begin() 无参数版本而是显式传入引脚编号的版本因为 ESP32 的 SPI 引脚可以映射到多个 GPIO。初始化时还要设置 SPI 模式为 SPI_MODE3时钟频率先降到 1MHz等确认通信稳定后再慢慢往上提。有一个隐藏比较深的坑如果你同时在项目里挂了 SD 卡、LCD 或者其他 SPI 外设多个设备的 CS 必须分开管理并在每次读写前用自己的 CS 引脚。如果 CS 没有正确拉高拉低另一个 SPI 设备可能会响应原本发给 PMW3901MB 的指令导致数据错乱。我在调试期间就遇到过这个问题后来把所有 SPI 设备的片选引脚分别定义才彻底解决。4.2 完整可编译代码下面的代码可以直接在 Arduino IDE 或者 PlatformIO 中编译目标是 ESP32 DevKitC。代码结构分成三部分SPI 读写寄存器封装、PMW3901 驱动类和运动检测主循环。我保留了足够的注释方便你根据自己的引脚修改。#include SPI.h // 自定义引脚根据自己的接线修改 #define PMW_CS 5 #define SPI_MISO 19 #define SPI_MOSI 23 #define SPI_SCLK 18 // PMW3901MB 关键寄存器地址 #define REG_PRODUCT_ID 0x00 #define REG_MOTION 0x02 #define REG_DELTA_X_LOW 0x03 #define REG_DELTA_X_HIGH 0x04 #define REG_DELTA_Y_LOW 0x05 #define REG_DELTA_Y_HIGH 0x06 #define REG_SQUAL 0x07 struct MotionData { bool motion; // 当前帧是否有运动 int16_t deltaX; // X 方向位移增量 int16_t deltaY; // Y 方向位移增量 uint8_t squal; // 表面质量 }; class PMW3901Driver { public: bool begin() { pinMode(PMW_CS, OUTPUT); digitalWrite(PMW_CS, HIGH); SPI.begin(SPI_SCLK, SPI_MISO, SPI_MOSI, PMW_CS); SPI.setDataMode(SPI_MODE3); SPI.setClockDivider(SPI_CLOCK_DIV64); // 约1MHz delay(50); // 唤醒芯片防止处于低功耗状态 writeRegister(0x00, 0x5A); delay(5); writeRegister(0x00, 0x00); delay(5); uint8_t pid readRegister(REG_PRODUCT_ID); Serial.printf(Product ID: 0x%02X\n, pid); if (pid ! 0x49) { Serial.println(PMW3901 ID 校验失败请检查接线和SPI时序); return false; } return true; } uint8_t readRegister(uint8_t reg) { uint8_t value 0; SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE3)); digitalWrite(PMW_CS, LOW); SPI.transfer((reg 0x7F) | 0x80); // 读地址最高位置1 value SPI.transfer(0x00); // 产生时钟并读回数据 digitalWrite(PMW_CS, HIGH); SPI.endTransaction(); return value; } void writeRegister(uint8_t reg, uint8_t data) { SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE3)); digitalWrite(PMW_CS, LOW); SPI.transfer(reg 0x7F); // 写地址最高位为0 SPI.transfer(data); digitalWrite(PMW_CS, HIGH); SPI.endTransaction(); } MotionData readMotion() { MotionData data; uint8_t motion readRegister(REG_MOTION); data.motion (motion 0x80) ! 0; uint8_t xLow readRegister(REG_DELTA_X_LOW); uint8_t xHigh readRegister(REG_DELTA_X_HIGH); uint8_t yLow readRegister(REG_DELTA_Y_LOW); uint8_t yHigh readRegister(REG_DELTA_Y_HIGH); data.deltaX (int16_t)((xHigh 8) | xLow); data.deltaY (int16_t)((yHigh 8) | yLow); data.squal readRegister(REG_SQUAL); return data; } }; PMW3901Driver sensor; // 累计位移用于验证 int32_t accumX 0; int32_t accumY 0; uint32_t lastPrintTime 0; void setup() { Serial.begin(115200); delay(500); if (!sensor.begin()) { Serial.println(传感器初始化失败进入死循环); while (1) delay(100); } Serial.println(PMW3901MB 初始化成功); } void loop() { MotionData data sensor.readMotion(); // 只要检测到表面质量足够就累计位移增量 if (data.squal 15) { accumX data.deltaX; accumY data.deltaY; } // 每 50ms 打印一次数据 if (millis() - lastPrintTime 50) { lastPrintTime millis(); Serial.print(Motion:); Serial.print(data.motion ? Y : N); Serial.print( DeltaX:); Serial.print(data.deltaX); Serial.print( DeltaY:); Serial.print(data.deltaY); Serial.print( SQUAL:); Serial.print(data.squal); Serial.print( AccumX:); Serial.print(accumX); Serial.print( AccumY:); Serial.println(accumY); } }这段代码的核心逻辑是在 loop 里不断读取 Motion、DeltaX、DeltaY 和 SQUAL然后累计位移。打印频率控制在 50ms 一次避免串口输出拖慢主循环。实际测试时把模块放在桌面上慢慢向正前方移动能看到 DeltaX 产生正负变化SQUAL 在正常表面通常在 30 到 120 之间。4.3 方向判断和位移积分的处理思路拿到裸的 Delta X 和 Delta Y 后下一步是把它变成有实际意义的方向和距离。方向判断可以这样理解如果 |DeltaX| 明显大于 |DeltaY|说明当前运动主要发生在 X 轴DeltaX 为正就是沿 X 轴正向移动为负就是反向。如果两个方向的差值不大说明传感器在走斜线此时可以用 atan2 函数计算运动角度。位移积分则要注意累积误差。光流传感器的位移增量不是绝对位置它更像是每一帧的“变化量”所以积分时间越长、误差累积越多。我的做法是维护两个 32 位整数 accumX 和 accumY每次读到有效数据就累加需要测一段距离时先清零测完再算差值。另外如果 SQUAL 低于阈值我会直接跳过本次累加因为不明智的数据比没有数据更危险。很多资料里会提到“每 count 对应多少毫米”这个数值并不是固定的它跟 PMW3901MB 芯片到被测表面的高度、表面纹理、甚至环境光都有关系。比较靠谱的办法是做一个简单的标定让小车或模组直线移动已知距离比如 20 厘米读取这段时间的累加位移值然后算出 counts 到厘米的换算系数。我在桌面上标定的系数是 1 count 大约对应 0.045 毫米但换到地毯上这个值就变了所以正式使用时一定要针对自己实际场景重新标定。4.4 如何判断“当前数据是否可信”PMW3901MB 不会主动告诉你“我现在读数很准”但你可以通过几个寄存器组合起来判断。首先看 Motion 标志位如果为 0说明芯片内部算法认为目标在当前帧没有明显移动此时 DeltaX 和 DeltaY 即使不是 0 也不需要太在意。其次看 SQUAL我实际测试的经验是 SQUAL 低于 15 时数据几乎不可用低于 30 时只能用于粗略方向判断大于 50 才适合做积分测距。最后看快门值也就是 Shutter 寄存器如果曝光时间过长说明环境太暗过短说明环境太亮这些都是传感器在努力适应光照的表现可以作为环境判断的辅助信号。在运动检测的工程实现里我只会在 SQUAL 15 且 Motion 标志位为 1 时才更新累积位移否则当前帧直接丢弃。这个策略虽然会让有效数据出现一点点断续但换来的稳定性是巨大的。毕竟光流传感器本身就很容易受表面纹理影响与其让脏数据污染积分结果不如宁可少取一点也不要取错。5. 常见问题与排查技巧实录5.1 现象对照速查表底下列出我实际使用和帮助朋友排查时遇到频率最高的问题用表格整理出来方便快速定位。现象可能原因解决思路串口打印 Product ID 为 0xFF 或乱值接线错误、SPI Mode 不对、模块供电异常检查 MOSI/MISO/SCLK/CS确认使用 SPI_MODE3Product ID 为 0x00模块没有正常上电、电源纹波太大、芯片损坏测量 VCC 电压给模块加滤波电容SQUAL 一直为 0镜头保护膜未撕掉、表面无纹理、距离过近或过远清洁镜头换到有纹理表面调整高度到 8~30cmDeltaX/DeltaY 数值跳变特别大电源干扰、SPI 时序不稳、CS 与其他外设冲突加滤波电容降低 SPI 时钟频率检查 CS 引脚静止时累积位移仍然一直增加表面纹理太弱、震动影响、SQUAL 过低时错误累计提高 SQUAL 阈值增加静止判断逻辑移动时读数正常但方向反了X/Y 轴定义与机械方向不一致交换接线或对 deltaX/deltaY 取反5.2 读不到 ID 时的详细排查步骤如果你烧录代码后串口打印的 Product ID 不对不要急着怀疑芯片坏了。我的排查顺序是先确认模块供电用万用表量 VCC 对 GND 的电压必须稳定在 3.3V变化幅度不超过 0.1V。然后检查 MOSI 和 MISO 这两根线很多人在这上面栽跟头。模块的 MOSI 要接到 ESP32 的 GPIO23也就是主机 SPI 的 MOSI 引脚MISO 接到 GPIO19如果你把这两根线接反了读出来的数据就会全是 0xFF。排除了接线之后还需要确认代码里的 SPI 初始化参数。PMW3901MB 必须工作在 SPI Mode 3如果误写成 Mode 0芯片不会正常响应。我把SPI.setDataMode(SPI_MODE3)放在SPI.begin之后同时在每次传输时用SPISettings再强制指定一次避免被其他库影响。还有一种情况是模块本身已经进入低功耗模式。上电后如果一直没有人去“唤醒”它部分模块会保持睡眠状态此时任何寄存器读取都返回异常。我的代码里在初始化时写了 0x5A 到 0x00 寄存器这个操作对省电版本是必需的。如果你用的是比较老的 PMW3901 模块可能还需要额外的初始化序列建议找商家要出厂配置表。5.3 数据漂移和 SQUAL 不稳定的实战避坑我在测试中遇到过这么一件事模块静止在桌面上串口里的累积位移却在十几秒内慢慢漂了 20 多个 count。最开始我以为是算法问题后来排查发现是模块底部垫的一块双面胶在微微变形加上周围有人走动引起桌面轻微震动传感器把这些低频震动当成真实运动了。解决方法是加一个简单的“静止检测”如果连续 N 帧的 DeltaX 和 DeltaY 都小于某个阈值就强制认为当前处于静止状态把累积位移清零或者冻结输出。SQUAL 不稳定最常见的原因是表面纹理不够清晰。纯色桌面、玻璃、镜面这些表面会让芯片内部的光学算法抓不住特征点SQUAL 自然上不去。还有一个容易被忽略的情况是使用环境里的强红外干扰。阳光直射、白炽灯近距离照射都会影响内部传感器灵敏度。我后来给传感器加了一个简单的遮光罩效果非常明显户外测试时 SQUAL 从不到 20 提升到了 60 以上。如果你发现模块离地面太近时数据反而不准那很正常。PMW3901MB 有一个最小工作高度大概是 80 毫米左右太近会导致视场太小、纹理无法对焦。太远也不行超过一两米后表面纹理在传感器上已经小到无法跟踪。我实际使用下来比较舒服的高度范围是 10 到 30 厘米。5.4 和 ESP32 一起使用时的特殊坑ESP32 本身是颗高度集成的芯片Wi-Fi 和 BLE 同时启用时占用的资源不少。虽然 SPI 外设和无线外设底层互不冲突但在极端情况下无线协议栈的中断优先级和 CPU 调度会影响你读取光流数据的实时性。我的做法是把光流读取放在 loop 里高频执行数据不做复杂处理直接缓存无线发送单独在一个任务里消费缓存。这样即使无线在忙于重传也不会让运动数据采集停顿太久。另外要留意 ESP32 DevKitC 板载的 3V3 稳压器输出能力有限。如果你同时接了 PMW3901、LED 灯、蜂鸣器、SD 卡等外设3V3 电压可能会被拉低到 3.1V 甚至更低。PMW3901 对电压波动比较敏感我建议在供电吃紧时把光流模块的供电直接走单独 LDO或者至少用万用表量一下实际电压再决定。6. 扩展玩法与个人调试体会6.1 把数据变成遥控指令或上位机曲线PMW3901MB 输出的是相对位移增量拿来当手势控制传感器非常顺手。你可以把 DeltaX 和 DeltaY 映射成鼠标的相对移动再用 ESP32 模拟 USB 鼠标或者通过 BLE 发送给手机 App。如果你只需要调试观测最好的方式是通过 WebSocket 把位移数据实时传到浏览器画一条曲线看起来直观很多。我在调试阶段就是这么做的ESP32 作为 WebSocket 服务端PC 浏览器打开一个简单页面实时显示 DeltaX、DeltaY 和 SQUAL 的曲线。ESP32 自带 Wi-Fi省去了一根串口线调试小车在屋里跑来跑去时非常方便。如果你纠结蓝牙和 Wi-Fi 能不能一起用我的经验是两者可以同时开启但要注意合理分配任务和中断优先级。简易的运动数据广播用 BLE 更省电需要高频率大数据量传输时用 Wi-Fi 更合适。6.2 融合其他传感器才是完整方案光流传感器不是万能的它只解决“相对位移”这一个问题。要做完整的测速或定位通常还需要 IMU 做加速度计融合、高度传感器做距离修正。因为光流的位移增量受高度影响很大如果你知道了相对地面的高度就能把 counts 更准确地换算成实际距离。我的小车后来加了一个超声波模块测高配合 PMW3901MB 的位移数据测速精度比单独用编码器提升明显。在融合思路上最常用的是卡尔曼滤波或互补滤波。把光流输出的位移当作速度观测值把 IMU 的加速度当作运动模型输入融合后既能避免加速度计的漂移又能滤掉光流的跳变。我在 ESP32 上用 Arduino 环境实现了一个简单的互补滤波其实核心就几行代码但效果提升是肉眼可见的。6.3 我给后来者的三点调试建议第一先把寄存器 ID 读出来再谈算法。PMW3901MB 只要能稳定读到 0x49说明硬件通路已经打通后面的一切工作才有意义。不要一上来就急着看运动数据那样出了问题你根本分不清是硬件还是软件。第二务必把 SQUAL 打印到串口并且养成随时观察它的习惯。SQUAL 是传感器在告诉你“我到底看不看得清表面”这个值在医院上经常能帮你快速判断问题。很多看似是算法或者时序问题最后都出在表面纹理不清或者高度不对上。第三做一个可重复的移动测试流程。把传感器固定在一个滑台上以固定速度移动固定距离然后看累积位移是否线性增长。我最早做标定时用手推着小车走数据散得没法看后来改用缓慢匀速的滑台后标定系数才稳定下来。这个流程看起来简单但能省下后面大量排查时间。把这些细节都处理好之后ESP32 加 PMW3901MB 这套组合会变得非常可靠。我在小车项目里把光流数据作为编码器数据的交叉验证通道连续跑了几个小时位移趋势始终对得上。说实话光流传感器并不算一个特别复杂的外设真正的难点是搞清楚它的数据语义、工作边界和影响精度的外部因素。只要你愿意花一点时间把底层的寄存器行为弄明白后续不管是做悬停、做测速还是做手势控制都会轻松很多。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表