简介:这份资源是一套用C语言实现的惯性导航与卫星导航组合导航算法程序,面向导航制导、自动驾驶及测绘等方向的学习者与工程人员,用于解决纯惯性解算、组合导航解算及初始对准等核心问题。压缩包共23个文件,约625KB,以14个c源文件与2个h头文件为主体,涵盖惯导更新、Kalman滤波、矩阵运算、数据读取与坐标转换等模块,另含3个dll运行库、2个txt说明文档、1个exe可执行程序及1张算法流程图,便于直接运行与二次开发。程序设有传统Kalman滤波、自适应滤波与抗差Kalman滤波,支持间接粗对准和滤波精对准,可输出载体的姿态角、速度与位置信息。目前已有908人学习下载,适合希望深入理解组合导航算法实现细节、对照源码梳理滤波与对准流程的读者参考。
1. 组合导航算法程序(C语言实现):从传感器融合到姿态解算的落地路径
拿到一份用 C 语言写的组合导航算法程序,很多人第一反应是「这玩意儿跟网上那些卡尔曼滤波 demo 有什么区别」。区别在于,组合导航不是单一滤波器的演示,它要把陀螺仪、加速度计、磁力计甚至 GNSS 的数据按不同速率、不同噪声特性揉进一个状态估计框架里,输出姿态、速度、位置。这份资源的核心价值在于:它用纯 C 实现了一套可编译、可移植的惯性/组合导航解算流程,不依赖 MATLAB 工具箱,也不绑定某个特定硬件平台。适合做嵌入式导航、无人机飞控、车载定位的工程师拿来当参考实现或二次开发底座。如果你正在找一份能直接读、能改、能跑的组合导航 C 代码,而不是只能看公式的论文附录,这份程序值得花时间拆一遍。
2. 组合导航的状态量与坐标系:先搞清楚代码在算什么
2.1 状态向量与坐标系定义
组合导航算法的本质是一个状态估计问题。代码里首先要明确状态向量包含哪些量。常见做法是 15 维状态:三维姿态误差、三维速度误差、三维位置误差、三维陀螺零偏、三维加计零偏。有些简化版本会砍到 9 维或 12 维,取决于是否在线估计零偏。
坐标系方面,代码里通常涉及三个:载体坐标系(b 系)、导航坐标系(n 系,一般取东北天 ENU 或北东地 NED)、地心地固坐标系(e 系)。C 语言实现里不会像 MATLAB 那样有现成的旋转矩阵函数,所有方向余弦矩阵和四元数运算都得手写。
/* 四元数结构体定义,常见做法是用 float 节省嵌入式端内存 */ typedef struct { float q0; /* 标量部分 */ float q1; /* 矢量部分 x */ float q2; /* 矢量部分 y */ float q3; /* 矢量部分 z */ } Quaternion; /* 四元数归一化,防止数值漂移导致姿态矩阵非正交 */ void quat_normalize(Quaternion *q) { float norm = sqrtf(q->q0*q->q0 + q->q1*q->q1 + q->q2*q->q2 + q->q3*q->q3); if (norm < 1e-8f) return; /* 防止除零,血泪经验 */ q->q0 /= norm; q->q1 /= norm; q->q2 /= norm; q->q3 /= norm; }这段代码的逻辑很直白:四元数在迭代更新后会因为浮点误差逐渐偏离单位长度,归一化是每次更新后必须做的收尾动作。参数上,1e-8f这个阈值是防止零范数导致除零崩溃,实际工程里如果传感器完全静止且初始化异常,确实可能碰到。用float还是double取决于目标平台,STM32 这类 MCU 上float够用,PC 端做离线验证可以换double提高精度。
2.2 姿态更新与圆锥误差补偿
姿态更新的核心是用陀螺输出的角增量更新四元数。最朴素的做法是一阶龙格库塔,但在高动态场景下误差会累积得很快。代码里如果只做了一阶更新,那基本只能用于低速、低精度的场景。
/* 一阶四元数更新:输入为角增量(弧度),输出更新后的四元数 */ void quat_update(Quaternion *q, float wx, float wy, float wz, float dt) { float dq0 = 1.0f; float dq1 = 0.5f * wx * dt; float dq2 = 0.5f * wy * dt; float dq3 = 0.5f * wz * dt; float tmp0 = q->q0; float tmp1 = q->q1; float tmp2 = q->q2; float tmp3 = q->q3; q->q0 = tmp0*dq0 - tmp1*dq1 - tmp2*dq2 - tmp3*dq3; q->q1 = tmp0*dq1 + tmp1*dq0 + tmp2*dq3 - tmp3*dq2; q->q2 = tmp0*dq2 - tmp1*dq3 + tmp2*dq0 + tmp3*dq1; q->q3 = tmp0*dq3 + tmp1*dq2 - tmp2*dq1 + tmp3*dq0; quat_normalize(q); }这里wx, wy, wz是陀螺在载体坐标系下的角速率,dt是采样间隔。一阶更新的截断误差在角速率较大时会明显体现,典型表现是姿态角在高动态机动后出现不可恢复的偏移。如果代码里没有圆锥误差补偿或多子样算法,那这份程序更适合做教学参考而非高精度应用。判断方法很简单:看代码里有没有对陀螺输出做多子样采样和补偿项计算。没有的话,动态精度就别期望太高。
3. 卡尔曼滤波在 C 里的实现:矩阵运算与数值稳定性
3.1 状态转移矩阵的构建
组合导航用的卡尔曼滤波通常是误差状态卡尔曼滤波。状态转移矩阵 F 的构建是整个算法里最容易写错的部分。F 矩阵的元素来自惯性导航的误差传播方程,涉及比力、地球自转角速率、位置相关的量。
/* 构建 15 维误差状态转移矩阵 F(简化示意,省略部分非零元素) */ void build_F_matrix(float F[15][15], float fn[3], float omega_ie, float lat, float h, float dt) { /* 先清零 */ for (int i = 0; i < 15; i++) for (int j = 0; j < 15; j++) F[i][j] = 0.0f; /* 姿态误差对速度误差的耦合项,fn 为导航系下的比力 */ F[3][6] = -fn[2]; /* 东向速度误差受天向比力影响 */ F[4][6] = fn[0]; F[5][7] = fn[1]; /* 速度误差对姿态误差的耦合 */ F[6][3] = fn[2]; F[7][4] = -fn[0]; /* 位置误差对速度误差的耦合 */ F[3][3] = 1.0f; F[4][4] = 1.0f; F[5][5] = 1.0f; /* 离散化:F_d = I + F*dt,一阶近似 */ for (int i = 0; i < 15; i++) F[i][i] += 1.0f; }这段代码展示的是 F 矩阵的骨架。实际工程里,F 矩阵的非零元素远不止这些,还涉及地球曲率半径、纬度相关的三角函数等。参数fn是导航系下的比力,由加速度计输出经姿态矩阵旋转得到。omega_ie是地球自转角速率,约 7.292115e-5 rad/s。lat和h分别是纬度和高度,用于计算子午圈和卯酉圈曲率半径。
常见错误是把 F 矩阵的耦合项符号写反,导致滤波器发散。验证方法:静态条件下跑几分钟,看姿态误差是否收敛。如果姿态角缓慢漂移且协方差矩阵对角线元素持续增大,大概率是 F 矩阵符号或量纲出了问题。
3.2 卡尔曼增益计算与数值稳定性
卡尔曼滤波的增益计算涉及矩阵求逆。在 C 语言里,矩阵求逆通常用高斯-约当消元或 Cholesky 分解。15 维矩阵求逆在嵌入式端是计算瓶颈,常见做法是利用 H 矩阵的稀疏性做降维处理。
/* 标量卡尔曼滤波更新示例:用于单轴姿态融合 */ typedef struct { float x; /* 状态估计 */ float P; /* 估计协方差 */ float Q; /* 过程噪声 */ float R; /* 量测噪声 */ float K; /* 卡尔曼增益 */ } ScalarKF; void scalar_kf_update(ScalarKF *kf, float z) { /* 预测 */ kf->P = kf->P + kf->Q; /* 更新 */ kf->K = kf->P / (kf->P + kf->R); kf->x = kf->x + kf->K * (z - kf->x); kf->P = (1.0f - kf->K) * kf->P; }标量卡尔曼滤波虽然简单,但它是理解组合导航里各传感器融合逻辑的基础。Q是过程噪声协方差,反映你对状态转移模型的不信任程度;R是量测噪声协方差,反映传感器噪声水平。Q设大了滤波器响应快但噪声抑制差,R设大了滤波器平滑但滞后严重。实际调参时,我一般先用传感器数据手册里的噪声密度初始化R,然后根据静态和动态测试结果微调Q。
提示:15 维矩阵求逆在 MCU 上耗时可能超过控制周期,常见做法是把量测更新拆成序贯更新,每次只处理一维量测,把矩阵求逆降为标量除法。
4. 传感器数据接口与时间同步:代码之外的硬骨头
4.1 IMU 数据读取与量纲统一
组合导航算法的输入是 IMU 和 GNSS 数据。C 语言程序里通常预留了数据接口函数,但具体读取方式取决于硬件。常见做法是 IMU 通过 SPI 或 I2C 读取,GNSS 通过 UART 读取。
/* IMU 数据结构体,包含原始数据和转换后的物理量 */ typedef struct { int16_t accel_raw[3]; /* 加速度计原始值 */ int16_t gyro_raw[3]; /* 陀螺原始值 */ float accel[3]; /* 转换后,单位 m/s^2 */ float gyro[3]; /* 转换后,单位 rad/s */ float temperature; /* 温度,用于零偏补偿 */ } ImuData; /* 量纲转换:根据传感器灵敏度和量程计算 */ void imu_convert(ImuData *imu, float accel_scale, float gyro_scale) { for (int i = 0; i < 3; i++) { imu->accel[i] = imu->accel_raw[i] * accel_scale; imu->gyro[i] = imu->gyro_raw[i] * gyro_scale; } }accel_scale和gyro_scale的计算方式是:量程乘以 2 再除以 2 的位数次方。比如加速度计量程 ±16g,16 位 ADC,那accel_scale = 16 * 2 / 65536 * 9.8。这个换算如果搞错,滤波器会直接发散,而且现象很隐蔽——姿态角看起来在动,但数值完全不对。
4.2 时间同步与延迟补偿
IMU 和 GNSS 的更新频率通常差一个数量级。IMU 可能 1000Hz,GNSS 只有 1Hz 到 10Hz。卡尔曼滤波的量测更新只在 GNSS 数据到达时触发,但状态预测必须按 IMU 速率执行。
/* 主循环:按 IMU 速率做预测,GNSS 到达时做量测更新 */ void navigation_loop(void) { ImuData imu; GnssData gnss; float dt = 0.001f; /* 1kHz IMU */ while (1) { if (imu_data_ready()) { imu_read(&imu); imu_convert(&imu, ACCEL_SCALE, GYRO_SCALE); /* 状态预测 */ ins_predict(&imu, dt); } if (gnss_data_ready()) { gnss_read(&gnss); /* 量测更新 */ kf_measurement_update(&gnss); } } }这段代码的逻辑是:预测步骤跟着 IMU 走,量测更新跟着 GNSS 走。坑在于 GNSS 数据的时间戳和 IMU 时间戳必须对齐到同一时间基准。如果 GNSS 数据有延迟而代码里没做补偿,位置估计会出现系统性偏差。常见做法是给 GNSS 数据打上接收时刻的时间戳,在量测更新时把状态预测到同一时刻。
注意:如果代码里没有时间同步机制,只靠
gnss_data_ready()标志位触发更新,那在动态场景下位置误差可能达到米级甚至更大。
5. 避坑与排查:组合导航 C 代码里最容易翻车的地方
5.1 滤波器发散:现象、原因与解决
现象:姿态角或位置估计在运行一段时间后逐渐偏离真值,协方差矩阵对角线元素持续增大,最终输出完全不可用。
原因:最常见的是 F 矩阵符号错误或量纲不一致。比如把地球自转角速率的方向搞反,或者加速度计量纲没统一到 m/s²。另一个常见原因是 Q 矩阵设得过小,滤波器过度信任模型,导致量测信息无法有效修正状态。
解决:先做静态测试,把 IMU 静止放置,看姿态角是否稳定。如果静态就漂,检查陀螺零偏是否被正确估计和补偿。如果静态稳定但动态发散,检查 F 矩阵中与比力相关的耦合项符号。用printf把协方差矩阵对角线打出来,观察哪个状态的方差在增长,定位到具体方程。
5.2 四元数归一化遗漏
现象:姿态矩阵逐渐失去正交性,姿态角出现缓慢漂移,横滚角和俯仰角在长时间运行后偏离真值。
原因:四元数更新后没有做归一化,浮点误差累积导致四元数范数偏离 1。这个坑在代码里很隐蔽,因为短时间内看不出问题,跑几十分钟后才显现。
解决:每次四元数更新后强制调用quat_normalize()。如果代码里没有这个函数,自己补一个。归一化频率至少和姿态更新频率一致,不能只在量测更新时做。
5.3 矩阵求逆的数值不稳定
现象:卡尔曼增益出现异常大值或 NaN,滤波器输出跳变。
原因:协方差矩阵 P 在迭代中失去正定性,导致求逆时出现数值问题。常见于量测噪声 R 设得过小,或者量测更新频率过高。
解决:在求逆前对 P 矩阵做对称化处理,即P = (P + P^T) / 2。另外,R 矩阵不能设得比传感器实际噪声还小,否则滤波器会过度信任量测。如果用的是序贯更新,确保每次更新后 P 矩阵仍然对称正定。
5.4 传感器坐标系与载体坐标系不重合
现象:姿态角在特定方向上出现耦合误差,比如横滚机动时航向角也跟着变。
原因:IMU 的安装坐标系和载体坐标系没有对齐,代码里没有做安装矩阵补偿。
解决:在代码里增加一个安装矩阵参数,把 IMU 输出旋转到载体坐标系。安装矩阵可以通过静态标定得到:把载体放在水平面上,记录 IMU 输出,计算旋转矩阵。
5.5 GNSS 失锁时的处理缺失
现象:GNSS 信号丢失后,位置估计迅速发散,重新捕获后需要很长时间才能收敛。
原因:代码里没有 GNSS 失锁检测和纯惯性递推逻辑。GNSS 失锁后量测更新停止,但预测步骤还在跑,误差随时间累积。
解决:增加 GNSS 有效性判断,失锁时只做预测不做量测更新,同时适当增大 P 矩阵以反映不确定性增加。重新捕获后,先做几次量测更新让滤波器收敛,再恢复正常融合。
6. 进阶用法:从能跑到好用,几个实操技巧
6.1 用静态数据标定陀螺零偏和加计零偏
组合导航算法跑之前,先做零偏标定。把 IMU 静止放置至少 5 分钟,采集陀螺和加计输出,取均值作为零偏初值。
/* 静态零偏标定:采集 N 组数据取均值 */ void calibrate_bias(ImuData *samples, int n, float *gyro_bias, float *accel_bias) { float gyro_sum[3] = {0}; float accel_sum[3] = {0}; for (int i = 0; i < n; i++) { for (int j = 0; j < 3; j++) { gyro_sum[j] += samples[i].gyro[j]; accel_sum[j] += samples[i].accel[j]; } } for (int j = 0; j < 3; j++) { gyro_bias[j] = gyro_sum[j] / n; accel_bias[j] = accel_sum[j] / n; } /* 加计零偏需要减去重力分量,这里假设 z 轴朝上 */ accel_bias[2] -= 9.80665f; }这段代码的逻辑是:静止时陀螺输出应该为零,加计输出应该只有重力分量。采集多组数据取均值可以降低噪声影响。n一般取几千到几万,取决于采样率和允许的标定时间。加计零偏的 z 轴要减去重力加速度,前提是知道 IMU 的安装方向。如果安装方向不确定,需要先做姿态对齐。
6.2 用 Allan 方差分析确定 Q 和 R
卡尔曼滤波的 Q 和 R 不能随便拍。常见做法是用 Allan 方差分析静态 IMU 数据,得到角度随机游走和速度随机游走系数,再换算成 Q 矩阵的元素。
| 噪声类型 | Allan 方差斜率 | 对应参数 | 用途 |
|---|---|---|---|
| 角度随机游走 | -1/2 | ARW | 陀螺 Q 矩阵 |
| 速度随机游走 | -1/2 | VRW | 加计 Q 矩阵 |
| 零偏不稳定性 | 0 | BI | 零偏 Q 矩阵 |
| 速率随机游走 | +1/2 | RRW | 长期漂移 |
实际操作时,采集至少 2 小时静态数据,用 Allan 方差工具计算各噪声系数。ARW 的单位是 °/√h,换算到离散 Q 矩阵需要除以采样频率的平方根。这一步如果跳过,Q 矩阵只能靠试凑,调参时间会成倍增加。
6.3 用序贯更新降低计算量
15 维卡尔曼滤波的矩阵求逆在 MCU 上很吃力。如果量测矩阵 H 是稀疏的,可以把量测更新拆成多次标量更新。
/* 序贯标量更新:每次只处理一维量测 */ void sequential_update(float *x, float P[15][15], float *H, float R, float z) { float S = R; float K[15]; for (int i = 0; i < 15; i++) { for (int j = 0; j < 15; j++) { S += H[i] * P[i][j] * H[j]; } } for (int i = 0; i < 15; i++) { K[i] = 0; for (int j = 0; j < 15; j++) { K[i] += P[i][j] * H[j]; } K[i] /= S; } float innovation = z; for (int i = 0; i < 15; i++) { innovation -= H[i] * x[i]; } for (int i = 0; i < 15; i++) { x[i] += K[i] * innovation; } /* 更新协方差矩阵,此处省略展开 */ }序贯更新的核心思想是把一个多维量测拆成多个标量量测,每次只计算一个标量增益,避免矩阵求逆。代价是更新顺序会影响结果,但实际工程里差异可以忽略。H是量测矩阵的行向量,R是标量量测噪声,z是量测值。这段代码里协方差矩阵的更新没有展开,实际实现时需要补全P = (I - K*H) * P的运算。
6.4 验证方法:用转台或已知轨迹做闭环测试
代码跑通不代表算法正确。验证分两步:静态测试和动态测试。静态测试把 IMU 静止放置,看姿态角是否稳定、位置是否漂移。动态测试如果有转台最好,没有的话用手持缓慢转动,对比姿态角变化是否平滑、有无跳变。
我一般会先跑 10 分钟静态,确认姿态漂移小于 0.1°/min,再跑动态。动态测试时重点看航向角,因为航向角没有绝对参考,最容易出问题。如果航向角在静止时缓慢旋转,说明陀螺 z 轴零偏没标定好。从那以后我每次拿到新的 IMU 数据,都强制先跑一遍 Allan 方差和零偏标定,再进滤波器。希望帮到你。
本文还有配套的精品资源,点击获取