ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

IMU硬件同步实战:FSYNC引脚如何根治VINS-Fusion时间戳抖动

IMU硬件同步实战:FSYNC引脚如何根治VINS-Fusion时间戳抖动 很多做VINS-Fusion跑视觉惯性里程计或者正准备给激光雷达做lidar-imu标定的朋友第一次撞上“IMU数据时间戳总差那么一点”这个问题时第一反应都是写个程序求个固定时间差然后当常数减掉。我一开始也这么干后来发现D435相机和IMU之间的时间偏移会随曝光时间、USB负载、甚至板卡温度来回飘纯软件补偿根本补不干净。真正把这个问题解决掉靠的是IMU的FSYNC脚——把外部传感器的同步信号直接接到ICM20602上让IMU在硬件层面记录外部事件发生时刻再做传感器时间同步。这篇把我调FSYNC的完整过程整理出来包括原理、接线、寄存器配置代码、FIFO解析、实测验证最后聊一下怎么接进VINS-Fusion这类融合框架。1. 为什么融合系统里软件时间戳靠不住FSYNC到底解决了什么1.1 软件时间戳的每一层都在增加抖动脉宽传感器的“时间戳”听起来是个软件概念实际上它背后是两套时钟在打架。主控用系统时钟给每一帧IMU数据盖时间戳但主控什么时候知道“IMU采样完成”通常是SPI中断、DMA搬运、驱动解析、应用层回调这一整条链路。任何一个环节被操作系统调度、中断抢占、总线竞争打断时间戳就会被推移几毫秒甚至几十毫秒。我在调试一个视觉惯性融合项目时遇到过很典型的现象IMU数据在驱动层打印出来的接收间隔理论上是5ms一个200Hz实际打印却是3ms、7ms、4ms、6ms这样跳。主控单独跑一个任务时还挺稳定一旦开始同时跑RGB-D点云处理和回环检测抖动立刻变得夸张。这种抖动对IMU预积分是致命的尤其VINS-Fusion里视觉和惯性做联合初始化那一下时间轴不对齐尺度估计和重力估计直接崩。最初我用了一种“看起来聪明”的笨办法离线统计相机和IMU时间戳的固定差值在代码里减掉。结果是今天能用明天换个环境又不稳了。因为曝光时间、USB带宽、CPU频率都会影响那条“固定差值”。软件时间戳本质上是主控视角的“到达时间”不是传感器视角的“采样时间”只要这条链路里存在排队就永远差一个不确定量。1.2 FSYNC把盖时间戳的动作从主控搬进了传感器FSYNC的思路和软件补偿完全不同它让外部传感器比如相机把“我这一帧曝光开始了”的电平信号直接送到IMU的FSYNC引脚。IMU内部检测到这个边沿后会把当前样本的一个特征值锁存到该样本的FIFO扩展字段里。也就是说IMU在硬件层面就把“外部事件发生在哪一帧IMU数据附近”这个对应关系记录了下来。主控的任务只剩下一件记录FSYNC引脚对应GPIO中断的系统时间。由于IMU内部采样节拍是晶振稳定的我们只要知道“哪一帧FIFO样本对应这次FSYNC中断”就能用固定的采样周期推算出前后每一帧IMU数据的真实时间。传感器时间同步精度从软件层的毫秒级抖动压到了微秒级中断延迟加上最多一个IMU采样周期的误差。注意FSYNC解决的不是“晶振漂移”而是“事件与样本的对应关系”。它把“外部事件的时刻”和“IMU样本序列”在硬件里做了一个绑定剩下的才是软件层面的时间戳换算。这也是它和PTP、NTP这类时钟同步方案的互补关系——FSYNC负责传感器事件对齐时钟同步负责多设备时钟基准一致。1.3 哪些项目最需要FSYNC这种硬件同步以我实际接触的场景来看下面这几类项目的需求最迫切视觉惯性SLAM和VIO。VINS-Fusion这类算法对IMU和图像时间戳的偏差极其敏感初始化阶段的视觉惯性对齐失败往往不是外参标定问题而是时间轴没对齐。带上FSYNC后相机帧同步信号直接触发IMU打标图像曝光中段时间与IMU锚点帧对齐初始化成功率明显提高。lidar-imu标定。激光雷达现在普遍提供PPS或扫描同步脉冲把脉冲接FSYNC后IMU和雷达扫描起始时刻在硬件上就绑定了。之后用标定工具解算时间延迟外参得到的值会接近零而不是一个需要反复猜的未知数。里程计和IMU融合定位。轮式里程计、IMU、相机三者融合时如果轮速计只在主控软件里打时间戳驱动刷新延迟就会变成融合误差。给里程计的同步脉冲接FSYNC等于给轮速数据一个硬件锚点融合效果稳定很多。我自己最早是在D435IMU组合上踩的坑。D435自带的IMU时间戳由固件打标硬件同步做得好所以用它跑VINS-Fusion比较省心。换成外接ICM20602之后如果不用FSYNC同样的VINS配置会出现奇怪的尺度漂移接上FSYNC基本恢复D435内置IMU那种顺滑表现。2. 从FSYNC引脚到FIFO两个字节ICM20602的同步链条2.1 引脚连接和内部触发逻辑ICM20602的FSYNC是一个专用输入引脚一般支持边沿触发常见用法是上升沿有效。外部传感器把同步脉冲接到这个引脚IMU检测到有效边沿后就会把当前“最新一帧样本”的某个通道数据锁存起来。关键点是FSYNC边沿不一定正好落在IMU采样的瞬间。多数情况下它落在相邻两次采样的间隙里此时IMU锁存的是“边沿到来之前最近一次采样”的数据。这个细节决定了后面的时间戳必然包含一个小于等于一个采样周期的误差我们只能减小它不能完全消除它。实际工程里提高IMU输出频率是降低这个误差最直接的手段。我实验时的典型接线是MCU的定时器输出引脚接ICM20602的FSYNC同时接MCU另一个带中断的GPIO。外部事件到来时MCU中断记录系统时间IMU在硬件里给对应的样本打标。两边各自工作的好处是如果后面发现时间戳对不上可以先分离问题——是MCU中断延迟大了还是IMU的FSYNC配置没生效。2.2 EXT_SYNC_SET选择的是“哪一个特征值”ICM20602的CONFIG寄存器0x1A高三位是EXT_SYNC_SET字段它决定FSYNC触发时锁存哪个内部通道的低字节。可选值包括温度、陀螺X/Y/Z、加速度X/Y/Z的最近一次采样低字节。这个字段有三比特取值和通道的对应关系不同批次的手册略有出入我用的版本里是001对应TEMP_OUT_L010对应GYRO_XOUT_L101对应ACCEL_XOUT_L其他依次类推。这里有个容易被忽略的工程点这个“特征值”并不是一个计数器而是某个物理量最近一次的采样低字节。它能不能反映FSYNC事件取决于这个物理量是否有变化。如果外部事件到来时你选的那个通道数据恰好长期不变比如静止状态下温度低字节几小时都不变那FSYNC来了也看不出来。所以“选哪个通道”不是随便填的后面第三节我会专门讲这个坑。2.3 FIFO样本的16字节布局配置FSYNC并开启FIFO后ICM20602的FIFO从原来一个样本若干字节变成固定16字节一个包。以我开启加速度、温度、陀螺仪三组数据为例每个包的布局是字节偏移内容说明0-1ACCEL_XOUT加速度计X轴16位有符号2-3ACCEL_YOUT加速度计Y轴4-5ACCEL_ZOUT加速度计Z轴6-7TEMP_OUT温度16位有符号8-9GYRO_XOUT陀螺仪X轴10-11GYRO_YOUT陀螺仪Y轴12-13GYRO_ZOUT陀螺仪Z轴14-15EXT_SENS_DATAFSYNC锁存的扩展数据如果初始化时没有使能温度采集包长会变成14字节解析时偏移量要跟着改。我一般建议把温度也一起开因为16字节正好对齐而且温度低字节虽然在静止时变化慢但作为辅助判断通道仍有价值。2.4 软件端判断锚点扩展数据“变化”的那一帧从FIFO里读出16字节包后真正判断“哪一帧对应FSYNC事件”的依据是第14-15字节的扩展数据值是否和上一帧不同。想象你以200Hz采样外部相机以50Hz产生同步脉冲。FSYNC没来的时候扩展数据保持上一次锁存的值不变。每来一次脉冲扩展数据更新成新值和你上一帧读到的旧值不同。于是你就能定位扩展数据发生变化的这一帧就是FSYNC事件之后的第一个可用锚点帧。这个“变化检测”逻辑是我踩过最多坑的地方。很多人一开始用“是否非零”来判断但ICM20602锁存的特征值本来就可能是0尤其在加速度计静止时X轴读数为0附近低字节为0很正常。所以必须用“相邻帧变化”而非“非零判断”。3. 接线与寄存器配置一套可复制的ICM20602同步配置代码3.1 接线清单与电平注意事项配代码之前先检查硬件接线。这是整个同步方案里最容易出问题的地方也是排查时最容易被忽略的地方。推荐的接线方式是ICM20602通过SPI接主控SCLK、MOSI、MISO、CS四根线FSYNC脚接外部传感器的同步输出。外部信号如果是相机曝光信号注意确认是“曝光中高电平”还是“帧起始脉冲”。有些相机的曝光信号是一个高电平持续整个曝光时间直接接到FSYNC可能被当成一次较宽的脉冲最好用一个单稳态电路或MCU整形把曝光中段变成一个干净的上升沿。电平上ICM20602供电3.3VFSYNC输入高电平阈值大约是0.7倍VDD也就是2.3V左右。如果外部给的是5V CMOS信号需要分压或加电平转换直接怼上去长时间工作容易损坏引脚。如果外部信号是集电极开路输出FSYNC引脚需要接上拉电阻我习惯用10k欧姆。我在调试时踩过一次把相机触发输出直接接到了FSYNC信号本身是3.3V但相机内部驱动能力弱再加上线缆长上升沿抖动严重导致IMU频繁误锁存FIFO里扩展数据一直在跳根本无法定位锚点。后来在FSYNC脚和GND之间加了一个100nF电容上升沿变缓但抖动消失问题才解决。所以信号整形和滤波不是可选项。3.2 初始化顺序和关键寄存器说明初始化顺序上我习惯按“复位-唤醒-配置-校验”四个阶段走。先软复位芯片等20ms解除休眠并选择内部PLL时钟等10ms然后按顺序配置DLPF、采样率、量程、FIFO使能、FSYNC最后把关键寄存器读回来确认写入是否成功。需要关注的寄存器有这几组CONFIG0x1A的低三位是DLPF_CFG控制内部低通滤波档位高三位是EXT_SYNC_SET是FSYNC功能的核心开关必须确保写成功。SMPLRT_DIV0x19控制采样率分频在1kHz内部采样率的前提下ODR等于1000除以(1SMPLRT_DIV)。GYRO_CONFIG0x1B和ACCEL_CONFIG0x1C分别控制陀螺和加速度量程。FIFO_EN0x23控制哪些数据组进入FIFO我需要加速度、温度、陀螺三组全开。USER_CTRL0x6A里的bit6是FIFO使能bit4是I2C接口禁用SPI模式下建议把I2C禁用掉避免引脚电平干扰误触发。DLPF档位和ODR的关系有个容易忽略的点并非所有档位都对应1kHz内部采样率有些档位内部采样率会变成4kHz或8kHz这时候SMPLRT_DIV的分频基数就变了ODR计算要按对应关系重新算。我常用DLPF_CFG0x02这一档陀螺仪约98Hz带宽、加速度约94Hz带宽内部采样率还是1kHz再配SMPLRT_DIV4就能得到200Hz ODR。Fsync首批我选择GYRO_XOUT_L是因为陀螺仪在静止时仍有噪声低字节变化相对活跃作为mark不易漏检。具体怎么取舍3.4节详细说。3.3 配置代码与FIFO解析代码下面的代码基于SPI接口假设已经有spi_read_reg、spi_write_reg、spi_read_burst这些底层函数。寄存器地址宏和注释里的说明我是按手头版本整理的不同批次芯片有可能调整使用前对照一下数据手册。// icm20602_fsync.h #define ICM20602_REG_SMPLRT_DIV 0x19 #define ICM20602_REG_CONFIG 0x1A #define ICM20602_REG_GYRO_CONFIG 0x1B #define ICM20602_REG_ACCEL_CONFIG 0x1C #define ICM20602_REG_ACCEL_CONFIG2 0x1D #define ICM20602_REG_FIFO_EN 0x23 #define ICM20602_REG_USER_CTRL 0x6A #define ICM20602_REG_PWR_MGMT_1 0x6B #define ICM20602_REG_FIFO_COUNTH 0x72 #define ICM20602_REG_FIFO_COUNTL 0x73 #define ICM20602_REG_FIFO_R_W 0x74 #define ICM20602_REG_WHO_AM_I 0x75 // EXT_SYNC_SET: 010 - GYRO_XOUT_L #define EXT_SYNC_SET_GYRO_X (0x02 3) // DLPF_CFG: 0x02 - 陀螺约98Hz带宽 #define DLPF_CFG_98HZ 0x02 #define IMU_PACKET_SIZE 16 typedef struct { int16_t acc_x; int16_t acc_y; int16_t acc_z; int16_t temp; int16_t gyro_x; int16_t gyro_y; int16_t gyro_z; uint16_t fsync_mark; } imu_sample_t;// icm20602_fsync.c #include stdint.h #include stdbool.h static void spi_write_reg(uint8_t reg, uint8_t val) { // 底层SPI写函数CS拉低写寄存器地址(最高位置1表示写)再写数据 } static uint8_t spi_read_reg(uint8_t reg) { // 底层SPI读函数CS拉低发寄存器地址读一字节 return 0; } static void spi_read_burst(uint8_t reg, uint8_t *buf, uint16_t len) { // 底层SPI突发读发寄存器地址后连续读len字节 } uint8_t icm20602_init(void) { uint8_t tmp; // 1. 软复位等芯片稳定 spi_write_reg(ICM20602_REG_PWR_MGMT_1, 0x80); delay_ms(20); // 2. 解除休眠选择内部PLL时钟源 spi_write_reg(ICM20602_REG_PWR_MGMT_1, 0x01); delay_ms(10); // 3. 配置DLPF和FSYNC扩展数据源 // EXT_SYNC_SET010选陀螺X低字节作为锚点特征值 spi_write_reg(ICM20602_REG_CONFIG, EXT_SYNC_SET_GYRO_X | DLPF_CFG_98HZ); // 4. 采样率分频1kHz / (14) 200Hz spi_write_reg(ICM20602_REG_SMPLRT_DIV, 0x04); // 5. 量程陀螺±1000dps加速度±8g spi_write_reg(ICM20602_REG_GYRO_CONFIG, 0x08); spi_write_reg(ICM20602_REG_ACCEL_CONFIG, 0x08); // 6. 加速度计低通暂时不额外设置保持默认直通 spi_write_reg(ICM20602_REG_ACCEL_CONFIG2, 0x00); // 7. FIFO使能加加速度、温度、陀螺X/Y/Z // bit6 XG | bit5 YG | bit4 ZG | bit3 ACCEL | bit2 TEMP 0x7C spi_write_reg(ICM20602_REG_FIFO_EN, 0x7C); // 8. USER_CTRLSPI下禁用I2C(0x10) FIFO使能(0x40) spi_write_reg(ICM20602_REG_USER_CTRL, 0x50); // 9. 读回关键寄存器确认写入 tmp spi_read_reg(ICM20602_REG_CONFIG); if ((tmp 0x38) ! EXT_SYNC_SET_GYRO_X) { return 1; // EXT_SYNC_SET配置失败 } tmp spi_read_reg(ICM20602_REG_PWR_MGMT_1); if ((tmp 0x40) ! 0) { return 2; // 复位未解除 } return 0; }FIFO读取和解析函数static int16_t to_i16(uint8_t h, uint8_t l) { return (int16_t)((h 8) | l); } int imu_read_fifo(imu_sample_t *out, int max_count) { uint16_t fifo_cnt; uint8_t h, l; int n; // 读FIFO剩余字节数 h spi_read_reg(ICM20602_REG_FIFO_COUNTH); l spi_read_reg(ICM20602_REG_FIFO_COUNTL); fifo_cnt (uint16_t)((h 8) | l); n fifo_cnt / IMU_PACKET_SIZE; if (n max_count) { n max_count; } if (n 0) { return 0; } for (int i 0; i n; i) { uint8_t raw[IMU_PACKET_SIZE]; spi_read_burst(ICM20602_REG_FIFO_R_W, raw, IMU_PACKET_SIZE); out[i].acc_x to_i16(raw[0], raw[1]); out[i].acc_y to_i16(raw[2], raw[3]); out[i].acc_z to_i16(raw[4], raw[5]); out[i].temp to_i16(raw[6], raw[7]); out[i].gyro_x to_i16(raw[8], raw[9]); out[i].gyro_y to_i16(raw[10], raw[11]); out[i].gyro_z to_i16(raw[12], raw[13]); out[i].fsync_mark (uint16_t)((raw[14] 8) | raw[15]); } return n; }FSYNC锚点检测和时间戳重映射volatile uint64_t g_fsync_time_ns 0; // 外部中断回调只记录系统时间保持轻量 void fsync_gpio_isr(void) { g_fsync_time_ns get_system_time_ns(); } #define IMU_ODR_HZ 200 #define IMU_PERIOD_NS (1000000000UL / IMU_ODR_HZ) int imu_process_fifo(imu_sample_t *samples, int n) { static uint16_t last_mark 0; int sync_index -1; for (int i 0; i n; i) { if (samples[i].fsync_mark ! last_mark) { // 找到FSYNC事件后的锚点帧 sync_index i; last_mark samples[i].fsync_mark; } } if (sync_index 0) { // 本批没有新FSYNC事件只延长已有锚点的时间戳 // 时间戳由上一批末尾累加得到实现在上层 return -1; } // 用FSYNC中断时间重排本批样本时间戳 // 这里简单示意从sync_index开始按周期递增 for (int i 0; i n; i) { int64_t offset (int64_t)(i - sync_index) * IMU_PERIOD_NS; samples[i].timestamp_ns (int64_t)g_fsync_time_ns offset; } return sync_index; }这套代码的思路是每批最多只处理一个FSYNC事件。如果外部触发频率和ODR差距太大一个批次里可能出现多个mark变化处理逻辑要改成分段式。第四节我会讲怎么判断这个频率关系是否合理。3.4 通道选择的一个坑温度低字节作mark会漏检我最早做FSYNC验证时EXT_SYNC_SET选的是TEMP_OUT_L。结果示波器上FSYNC脉冲非常干净可FIFO里扩展数据纹丝不动读了几千帧都没有变化。当时一度以为是芯片坏了甚至怀疑FSYNC引脚定义不对。排查到最后才发现温度低字节在常温且传感器几乎不发热的环境下可能很长时间保持同一个值。外部触发来了锁存的是温度低字节和上一次一模一样软件端自然检测不到mark变化。这就像喊了半天“谁动了我的奶酪”结果奶酪压根没变过。后来把EXT_SYNC_SET改成GYRO_XOUT_L问题立刻消失。陀螺X在静止时也会有一些偏置噪声低字节基本每帧都在跳FSYNC一来锁存的新值几乎不可能和上一帧相同。从这个角度看选一个“动态活跃”的通道比选“理论上稳定”的通道更合理。实际项目里如果你发现mark偶发漏检先不用怀疑硬件把EXT_SYNC_SET换一个通道试试经常能解决。4. 验证你的同步准不准方法与实测数据4.1 周期方波实验mark间隔应该稳定在采样比附近验证FSYNC是否生效我习惯用一个纯软件可复现的实验让MCU定时器产生一个精确的50Hz方波接到FSYNC引脚ICM20602 ODR设200Hz然后连续解析FIFO里的mark变化。理论上外部50Hz对应IMU每4个样本出现一次FSYNC事件。所以mark发生变化的索引间隔应该集中在4帧附近。由于FSYNC边沿落在采样间歇的哪个位置是随机的实际统计会出现3、4、5这几个值抖动分布越集中说明ODR和外部帧率的关系越稳定。我连续读了两万个样本统计得到的mark间隔分布是间隔3帧约占12%间隔4帧约占76%间隔5帧约占12%。这个分布说明FSYNC事件和IMU采样之间的相位关系在正常抖动范围内没有出现莫名其妙的间隔跳变。如果间隔出现10帧、20帧这种值基本可以断定有漏检。mark间隔帧出现次数占比361212.2%4380476.1%558411.7%其他00%4.2 示波器法FSYNC脉冲和中断响应的延迟软件统计只能证明“mark能变”还不能证明“mark时间和外部事件时间真的对得上”。这一步我用示波器双通道同时看两条线一条是FSYNC输入脉冲一条是在FSYNC外部中断ISR里翻转的GPIO。示波器能直接看到从FSYNC上升沿到GPIO翻转之间的时间差这个时间差就是主控中断响应的延迟。我实测在STM32F4上这个延迟通常在1到3微秒左右远小于200Hz ODR的5ms周期所以中断侧误差可以忽略。但示波器看不到IMU内部锁存延迟。这部分误差受IMU内部采样时刻限制最多一个采样周期在200Hz ODR下是5ms。如果对精度要求更高就把ODR提到500Hz或者1kHz锁存不确定性降到1到2ms。这也是为什么我建议做视觉惯性融合时IMU频率不要只卡在100Hz尽量给到200Hz以上。4.3 连续统计mark间隔判断时间轴稳定性除了单次间隔分布我还会看连续mark间隔的累加漂移。做法是记录每个FSYNC事件对应的系统时间戳差分后和外部真实周期比。如果系统时间戳每隔一段时间就多出或减少一个采样周期说明主控时钟和IMU内部时钟存在明显频差长时间运行后会对不齐。这种漂移可以通过FSYNC持续校正每来一个mark变化就把该帧时间戳重新对齐到FSYNC中断时间后面的帧再按IMU周期推算。相当于系统每隔几十毫秒被重新校准一次即使晶振有偏差也会被限制在一个外部触发周期内。我在5.4节会再说明这个做法。4.4 对不齐时的排查方向如果FSYNC配置完成后mark完全没有变化或者变化无规律按下面这几个方向排查基本上能覆盖90%的问题确认EXT_SYNC_SET写进去了没有。很多SPI时序问题导致寄存器写失败尤其是CONFIG寄存器读回来还是0。用SPI读回校验是必须的别省这一步。确认FSYNC引脚真有脉冲。示波器直接量FSYNC脚看电压幅度和上升沿质量。很多时候是线缆太长信号到了IMU端已经软成一团。确认外部触发频率和IMU ODR是否匹配。外部触发频率高于ODR的一半时会出现多个FSYNC事件夹在同一个IMU采样间隔里mark变化无法可靠对应。最好外部频率不要超过ODR的四分之一。确认FIFO读取是否有丢包。FIFO溢出或者突发读长度不对会导致样本错位mark看起来乱跳。检查FIFO_COUNT是否异常大或者解析出来陀螺仪数值是否在相邻样本间突然跳变。5. 接进VINS-Fusion/LIO驱动把同步结果变成IMU话题时间戳5.1 驱动层时间戳重映射锚点加周期推算从FIFO解析出N个样本后不能直接把当前ROS时间复制给所有样本。正确做法是找到mark发生变化的锚点帧把该帧时间戳设置为FSYNC中断对应的系统时间锚点前后的样本用固定的IMU采样周期向前后推算。这样做的前提是FSYNC中断记录时间戳这个动作必须非常轻。不能在ISR里做FIFO读取、消息打入队、日志打印这类操作否则主控中断延迟会从微秒级恶化到几十微秒甚至毫秒级直接破坏整个同步精度。我的习惯是ISR里只更新一个全局时间戳变量主体逻辑全部放到任务上下文中处理。5.2 一个ROS发布器的参考结构在ROS里IMU消息是sensor_msgs/Imuheader.stamp需要填一个与相机图像时间戳同基准的时间。参考结构大致是这样void imu_publisher_loop() { imu_sample_t samples[32]; while (ros::ok()) { int n imu_read_fifo(samples, 32); for (int i 0; i n; i) { sensor_msgs::Imu msg; msg.header.stamp ros::Time().fromNSec(samples[i].timestamp_ns); msg.angular_velocity.x samples[i].gyro_x * gyro_scale; // ... imu_pub.publish(msg); } // 控制读取周期例如2ms sleep loop_rate.sleep(); } }实际项目中我还会在samples[i].timestamp_ns 计算完后检查一下时间戳是否单调递增。如果发现某批样本时间戳回退说明锚点定位出错或者外部触发和ODR关系异常宁可丢弃这批数据也不要发给融合算法回退时间戳会让预积分直接报废。5.3 和相机曝光、雷达扫描时刻对齐时要注意什么相机接FSYNC时要确认外部信号代表的是“曝光起始”还是“曝光中段”。理想的取法是曝光中段时间因为VINS-Fusion处理图像时通常认为特征点对应的是曝光中段时刻。如果FSYNC接的是帧起始脉冲图像时间戳还要额外加上曝光时间的一半。有些相机驱动直接输出frame timestamp但需要注意它是否已经包含曝光补偿。激光雷达接FSYNC时每个扫描周期给一个同步脉冲即可。IMU的ODR最好高于雷达扫描频率四倍以上才能保证每个扫描周期内都有足够多的IMU样本参与插值。对于10Hz的雷达IMU 200Hz是比较理想的配置。里程计和IMU融合定位场景轮速计同步脉冲如果频率远低于IMU ODR比如1Hz那FSYNC锚点只能校准到秒级其他时间还是靠IMU周期推算。这种情况建议提升轮速计同步脉冲频率或者用MCU内部定时器把低频脉冲扩展成高频触发。5.4 几个让系统更稳的小细节把FSYNC中断优先级设成系统中最高但不在中断里做任何耗时的操作。如果主控有多核可以考虑把IMU读取和解析任务绑在一个独立核心上减少调度扰动。批次内多FSYNC事件的处理。如果FIFO批次跨越多个外部触发周期简单地找第一个mark变化会丢掉后面的锚点。建议每次读取的FIFO长度控制在“小于一个外部触发周期对应的样本数”这样每批最多一个mark逻辑最简单。ODR 200Hz、外部50Hz时一批最多读4到5帧32个样本的缓冲足够用。用FSYNC持续校正IMU周期误差。长时间运行后IMU晶振和主控晶振的频差会积累时间偏差。通过记录连续两次mark之间的系统时间和样本数可以动态修正IMU_PERIOD_NS让每帧时间戳都贴合真实采样间隔。这个修正不需要很复杂一个滑动平均滤波器就够了。另外ICM20602的FIFO有溢出风险特别在读取不及时的时候。代码里要检测FIFO_COUNT超过缓冲上限的情况发现异常时先复位FIFO再继续宁可丢一小段数据也不能让错位样本污染融合结果。最后再分享一个很实际的感受加了FSYNC之后再去标定外参收敛速度明显加快激光雷达的IMU时间延迟标定值也稳定在一个接近零的小范围内。以前那些靠调参数硬撑的场景现在从硬件层面就对齐了省下来的调试时间够你多吃好几顿火锅。如果你正被多传感器融合时间不同步折磨别急着写补偿算法先看看板子上有没有一根FSYNC脚能帮上忙。
返回列表