1.0
This commit is contained in:
806
App/est/corridor_ekf.c
Normal file
806
App/est/corridor_ekf.c
Normal file
@@ -0,0 +1,806 @@
|
||||
/**
|
||||
* @file corridor_ekf.c
|
||||
* @brief 鲁棒 EKF 走廊相对定位滤波器实现
|
||||
*
|
||||
* 完整算法流程:
|
||||
*
|
||||
* 【预测步】Predict(vx, wz, dt)
|
||||
* x_pred = f(x, u) -- 非线性状态转移
|
||||
* P_pred = F * P * F^T + Q -- 协方差预测
|
||||
*
|
||||
* 【更新步】Update(obs)
|
||||
* z = h(x_pred) -- 观测预测
|
||||
* y = z_meas - z -- 新息 (Innovation)
|
||||
* S = H * P_pred * H^T + R -- 新息协方差
|
||||
* d² = y^T * S^(-1) * y -- 马氏距离平方
|
||||
* if d² > χ²_threshold: 拒绝观测 (鲁棒)
|
||||
* K = P_pred * H^T * S^(-1) -- 卡尔曼增益
|
||||
* x = x_pred + K * y -- 状态更新
|
||||
* P = (I - K * H) * P_pred -- 协方差更新
|
||||
*/
|
||||
|
||||
#include "corridor_ekf.h"
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
/* =========================================================
|
||||
* 内部静态状态
|
||||
* ========================================================= */
|
||||
static CorridorEKFConfig_t s_cfg;
|
||||
static CorridorEKFState_t s_state;
|
||||
static bool s_initialized = false;
|
||||
static uint32_t s_last_update_ms = 0U;
|
||||
|
||||
/* 协方差上界保护阈值 */
|
||||
#define P_MAX_DIAG 100.0f
|
||||
|
||||
/* =========================================================
|
||||
* 内部辅助函数
|
||||
* ========================================================= */
|
||||
|
||||
/** 限幅 */
|
||||
static inline float clampf(float val, float lo, float hi)
|
||||
{
|
||||
if (val < lo) return lo;
|
||||
if (val > hi) return hi;
|
||||
return val;
|
||||
}
|
||||
|
||||
/** 对称矩阵拷贝 + 上三角 symmetrize */
|
||||
static void symmetrize(float M[3][3])
|
||||
{
|
||||
M[1][0] = M[0][1];
|
||||
M[2][0] = M[0][2];
|
||||
M[2][1] = M[1][2];
|
||||
}
|
||||
|
||||
/** P 上界保护 */
|
||||
static void protect_P(float P[3][3])
|
||||
{
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (P[i][i] > P_MAX_DIAG) P[i][i] = P_MAX_DIAG;
|
||||
if (P[i][i] < 0.0f) P[i][i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/** 计算 2x2 对称矩阵的逆 (原地) */
|
||||
static bool invert_2x2_sym(float S[2][2])
|
||||
{
|
||||
float det = S[0][0] * S[1][1] - S[0][1] * S[1][0];
|
||||
if (fabsf(det) < 1e-8f) {
|
||||
return false; // 奇异矩阵
|
||||
}
|
||||
float inv_det = 1.0f / det;
|
||||
float S00 = S[0][0];
|
||||
S[0][0] = inv_det * S[1][1];
|
||||
S[1][1] = inv_det * S00;
|
||||
S[0][1] = -inv_det * S[0][1];
|
||||
S[1][0] = S[0][1];
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 2x2 对称矩阵求逆 (原地) */
|
||||
static bool invert_3x3_cholesky(float S[3][3])
|
||||
{
|
||||
// 使用 Cholesky 分解求逆
|
||||
float L[3][3] = {0};
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j <= i; j++) {
|
||||
float sum = S[i][j];
|
||||
for (int k = 0; k < j; k++) {
|
||||
sum -= L[i][k] * L[j][k];
|
||||
}
|
||||
if (i == j) {
|
||||
if (sum <= 0.0f) return false;
|
||||
L[i][j] = sqrtf(sum);
|
||||
} else {
|
||||
L[i][j] = sum / L[j][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 求逆: S_inv = L^(-T) * L^(-1)
|
||||
float Linv[3][3] = {0};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Linv[i][i] = 1.0f / L[i][i];
|
||||
for (int j = i - 1; j >= 0; j--) {
|
||||
float sum = 0.0f;
|
||||
for (int k = j + 1; k <= i; k++) {
|
||||
sum += L[k][j] * Linv[k][i];
|
||||
}
|
||||
Linv[j][i] = -sum / L[j][j];
|
||||
}
|
||||
}
|
||||
|
||||
// S_inv = Linv^T * Linv
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
float sum = 0.0f;
|
||||
for (int k = 0; k < 3; k++) {
|
||||
sum += Linv[k][i] * Linv[k][j];
|
||||
}
|
||||
S[i][j] = sum;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 计算马氏距离平方 (新息向量 y, 新息协方差 S_inv) */
|
||||
static float mahalanobis_d2_3dof(const float y[3], const float S_inv[3][3])
|
||||
{
|
||||
// d² = y^T * S_inv * y
|
||||
float tmp[3] = {0};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
tmp[i] += S_inv[i][j] * y[j];
|
||||
}
|
||||
}
|
||||
float d2 = 0.0f;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
d2 += y[i] * tmp[i];
|
||||
}
|
||||
return d2;
|
||||
}
|
||||
|
||||
/** 计算马氏距离平方 (1DOF: 只用 e_y) */
|
||||
static float mahalanobis_d2_1dof(float y_ey, float S_inv_00)
|
||||
{
|
||||
return y_ey * y_ey * S_inv_00;
|
||||
}
|
||||
|
||||
/** 计算马氏距离平方 (2DOF: e_ey + e_th_avg) */
|
||||
static float mahalanobis_d2_2dof(const float y[2], const float S_inv[2][2])
|
||||
{
|
||||
float tmp[2] = {0};
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
tmp[i] += S_inv[i][j] * y[j];
|
||||
}
|
||||
}
|
||||
float d2 = 0.0f;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
d2 += y[i] * tmp[i];
|
||||
}
|
||||
return d2;
|
||||
}
|
||||
|
||||
/** 零矩阵 */
|
||||
static void zero_3x3(float M[3][3])
|
||||
{
|
||||
memset(M, 0, sizeof(float) * 9);
|
||||
}
|
||||
|
||||
/** 单位矩阵 */
|
||||
static void eye_3x3(float M[3][3])
|
||||
{
|
||||
zero_3x3(M);
|
||||
M[0][0] = M[1][1] = M[2][2] = 1.0f;
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
* API 实现
|
||||
* ========================================================= */
|
||||
|
||||
void CorridorEKF_Init(const CorridorEKFConfig_t *config)
|
||||
{
|
||||
s_cfg = *config;
|
||||
memset(&s_state, 0, sizeof(s_state));
|
||||
|
||||
/* 初始化状态 */
|
||||
s_state.x[0] = 0.0f; // e_y
|
||||
s_state.x[1] = 0.0f; // e_th
|
||||
s_state.x[2] = 0.0f; // s
|
||||
|
||||
/* 初始化协方差 */
|
||||
eye_3x3(s_state.P);
|
||||
s_state.P[0][0] = config->P0_diag[0];
|
||||
s_state.P[1][1] = config->P0_diag[1];
|
||||
s_state.P[2][2] = config->P0_diag[2];
|
||||
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
void CorridorEKF_Reset(void)
|
||||
{
|
||||
if (!s_initialized) return;
|
||||
|
||||
s_state.x[0] = 0.0f;
|
||||
s_state.x[1] = 0.0f;
|
||||
s_state.x[2] = 0.0f;
|
||||
|
||||
eye_3x3(s_state.P);
|
||||
s_state.P[0][0] = s_cfg.P0_diag[0];
|
||||
s_state.P[1][1] = s_cfg.P0_diag[1];
|
||||
s_state.P[2][2] = s_cfg.P0_diag[2];
|
||||
|
||||
s_last_update_ms = 0U;
|
||||
}
|
||||
|
||||
void CorridorEKF_SetProcessNoise(float q_ey, float q_eth, float q_s)
|
||||
{
|
||||
s_cfg.q_ey = q_ey;
|
||||
s_cfg.q_eth = q_eth;
|
||||
s_cfg.q_s = q_s;
|
||||
}
|
||||
|
||||
void CorridorEKF_SetMeasurementNoise(float r_ey, float r_eth)
|
||||
{
|
||||
s_cfg.r_ey = r_ey;
|
||||
s_cfg.r_eth = r_eth;
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
* 预测步 (Predict)
|
||||
* ========================================================= */
|
||||
void CorridorEKF_Predict(float odom_vx, float imu_wz, float dt)
|
||||
{
|
||||
if (!s_initialized || dt <= 0.0f) return;
|
||||
|
||||
float e_y = s_state.x[0];
|
||||
float e_th = s_state.x[1];
|
||||
float s = s_state.x[2];
|
||||
float vx = odom_vx;
|
||||
float wz = imu_wz;
|
||||
|
||||
/* 状态预测: x_pred = f(x, u) */
|
||||
float cos_th = cosf(e_th);
|
||||
float sin_th = sinf(e_th);
|
||||
|
||||
/* 安全检查: cos_th 不能太小 (防止数值爆炸) */
|
||||
if (fabsf(cos_th) < 0.01f) cos_th = (cos_th >= 0.0f) ? 0.01f : -0.01f;
|
||||
|
||||
float e_y_pred = e_y + vx * sin_th * dt;
|
||||
float e_th_pred = e_th + wz * dt;
|
||||
float s_pred = s + vx * cos_th * dt;
|
||||
|
||||
s_state.x[0] = e_y_pred;
|
||||
s_state.x[1] = e_th_pred;
|
||||
s_state.x[2] = s_pred;
|
||||
|
||||
/* 雅可比矩阵 F (状态转移的 Jacobian) */
|
||||
float F[3][3] = {0};
|
||||
F[0][0] = 1.0f;
|
||||
F[0][1] = vx * cos_th * dt; // de_y/de_th
|
||||
F[0][2] = 0.0f;
|
||||
|
||||
F[1][0] = 0.0f;
|
||||
F[1][1] = 1.0f;
|
||||
F[1][2] = 0.0f;
|
||||
|
||||
F[2][0] = 0.0f;
|
||||
F[2][1] = -vx * sin_th * dt; // ds/de_th
|
||||
F[2][2] = 1.0f;
|
||||
|
||||
/* 过程噪声协方差 Q (对角) */
|
||||
float Q[3][3] = {0};
|
||||
Q[0][0] = s_cfg.q_ey * dt * dt;
|
||||
Q[1][1] = s_cfg.q_eth * dt * dt;
|
||||
Q[2][2] = s_cfg.q_s * dt * dt;
|
||||
|
||||
/* 协方差预测: P_pred = F * P * F^T + Q */
|
||||
float F_P[3][3] = {0};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int k = 0; k < 3; k++) {
|
||||
F_P[i][j] += F[i][k] * s_state.P[k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float P_pred[3][3] = {0};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int k = 0; k < 3; k++) {
|
||||
P_pred[i][j] += F_P[i][k] * F[j][k]; // F^T: F[j][k] = F[k][j]
|
||||
}
|
||||
}
|
||||
P_pred[i][i] += Q[i][i]; // 加过程噪声
|
||||
}
|
||||
|
||||
symmetrize(P_pred);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
s_state.P[i][j] = P_pred[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
protect_P(s_state.P);
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
* 观测步 (Update) - 鲁棒 EKF
|
||||
* ========================================================= */
|
||||
int CorridorEKF_Update(const CorridorObs_t *obs, CorridorState_t *out_state)
|
||||
{
|
||||
if (!s_initialized) return 0;
|
||||
|
||||
int updated_obs_count = 0;
|
||||
|
||||
/* 清除新息和拒绝掩码 */
|
||||
memset(s_state.K, 0, sizeof(s_state.K));
|
||||
uint8_t reject_mask = 0U;
|
||||
float max_maha_d2 = 0.0f;
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* 提取有效观测
|
||||
* ---------------------------------------------------- */
|
||||
bool left_ok = ((obs->valid_mask & (1U << 0)) != 0U) &&
|
||||
((obs->valid_mask & (1U << 1)) != 0U);
|
||||
bool right_ok = ((obs->valid_mask & (1U << 2)) != 0U) &&
|
||||
((obs->valid_mask & (1U << 3)) != 0U);
|
||||
|
||||
/* 左右侧横向平均距离 */
|
||||
float d_lf = obs->d_lf, d_lr = obs->d_lr;
|
||||
float d_rf = obs->d_rf, d_rr = obs->d_rr;
|
||||
float Ls = s_cfg.sensor_base_length;
|
||||
float W = s_cfg.corridor_width;
|
||||
float yoff = s_cfg.y_offset;
|
||||
float inset = s_cfg.side_sensor_inset;
|
||||
float Rw = s_cfg.robot_width;
|
||||
|
||||
/* 传感器居中时的理论读数 (考虑车体宽度和传感器内缩)
|
||||
*
|
||||
* 推导 (以左侧为例):
|
||||
* 沟道宽 W,车体宽 Rw,传感器内缩 inset
|
||||
* 居中时:
|
||||
* 车体左边缘到左墙 = (W - Rw) / 2
|
||||
* 传感器到左墙 = (W - Rw) / 2 + inset (传感器比边缘更靠内)
|
||||
*
|
||||
* 所以 d_center = (W - Rw) / 2 + inset
|
||||
*
|
||||
* 验证:W=0.40, Rw=0.20, inset=0
|
||||
* d_center = (0.40-0.20)/2 + 0 = 0.10m ✓ (每边余量10cm)
|
||||
*
|
||||
* 单侧公式:e_y = d_center - d_left (左侧传感器越近墙,偏差越大)
|
||||
* 双侧公式:e_y = [(d_center - d_left) + (d_right - d_center)] / 2
|
||||
* = (d_right - d_left) / 2 (d_center 被消掉)
|
||||
*
|
||||
* ⚠ 当 inset = 0 且 Rw = 0 时,d_center = W/2,退化回原始行为
|
||||
*/
|
||||
float d_center = (W - Rw) / 2.0f + inset; /* 传感器居中时的理论读数 */
|
||||
|
||||
/* 观测值 (测量) */
|
||||
float z_ey = 0.0f;
|
||||
float z_eth_L = 0.0f;
|
||||
float z_eth_R = 0.0f;
|
||||
|
||||
int valid_sides = 0;
|
||||
|
||||
if (left_ok) {
|
||||
z_ey += d_center - ((d_lf + d_lr) / 2.0f) - yoff;
|
||||
z_eth_L = atan2f(d_lr - d_lf, Ls);
|
||||
valid_sides++;
|
||||
}
|
||||
|
||||
if (right_ok) {
|
||||
z_ey += ((d_rf + d_rr) / 2.0f) - d_center - yoff;
|
||||
z_eth_R = atan2f(d_rf - d_rr, Ls);
|
||||
valid_sides++;
|
||||
}
|
||||
|
||||
if (valid_sides == 0) {
|
||||
/* 两边都失效: 协方差持续膨胀,输出预测值 */
|
||||
out_state->t_ms = obs->t_ms;
|
||||
out_state->e_y = s_state.x[0];
|
||||
out_state->e_th = s_state.x[1];
|
||||
out_state->s = s_state.x[2];
|
||||
out_state->conf = clampf(1.0f - (s_state.P[0][0] + s_state.P[1][1]) * 0.1f, 0.0f, 1.0f);
|
||||
out_state->obs_reject_mask = 0xFF;
|
||||
out_state->mahalanobis_d2 = 0.0f;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
out_state->P[i][j] = s_state.P[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
/* 协方差膨胀 (无观测时的信任衰减) */
|
||||
s_state.P[0][0] += s_cfg.q_ey * 5.0f;
|
||||
s_state.P[1][1] += s_cfg.q_eth * 5.0f;
|
||||
protect_P(s_state.P);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 横向观测: 两侧平均 */
|
||||
if (valid_sides == 2) {
|
||||
z_ey /= 2.0f;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* 构建观测向量 z 和预测观测 h(x)
|
||||
* ---------------------------------------------------- */
|
||||
float e_y = s_state.x[0];
|
||||
float e_th = s_state.x[1];
|
||||
|
||||
/* 预测观测 h(x) */
|
||||
float h_ey = e_y; // 横向: z_ey ≈ e_y
|
||||
float h_eth_L = e_th; // 左侧航向: z_eth_L ≈ e_th
|
||||
float h_eth_R = e_th; // 右侧航向: z_eth_R ≈ e_th
|
||||
|
||||
/* 新息向量 y = z - h(x) */
|
||||
float y[3] = {0};
|
||||
int obs_idx = 0;
|
||||
|
||||
/* e_y 观测 */
|
||||
y[obs_idx++] = z_ey - h_ey;
|
||||
|
||||
/* e_th_L 观测 */
|
||||
if (left_ok) y[obs_idx++] = z_eth_L - h_eth_L;
|
||||
|
||||
/* e_th_R 观测 */
|
||||
if (right_ok) y[obs_idx++] = z_eth_R - h_eth_R;
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* 构建 H 矩阵 (Jacobian of h(x))
|
||||
* ---------------------------------------------------- */
|
||||
/* H 布局:
|
||||
* 航向角观测对应列是 1,0,0 (e_y 观测量)
|
||||
* 航向角观测对应列是 0,1,0 (e_th 观测量)
|
||||
* s 不被直接观测
|
||||
*/
|
||||
float H[3][3] = {0};
|
||||
int H_row = 0;
|
||||
|
||||
/* e_y 行: H = [1, 0, 0] */
|
||||
H[H_row][0] = 1.0f; H[H_row][1] = 0.0f; H[H_row][2] = 0.0f;
|
||||
H_row++;
|
||||
|
||||
/* e_th_L 行: H = [0, 1, 0] */
|
||||
if (left_ok) {
|
||||
H[H_row][0] = 0.0f; H[H_row][1] = 1.0f; H[H_row][2] = 0.0f;
|
||||
H_row++;
|
||||
}
|
||||
|
||||
/* e_th_R 行: H = [0, 1, 0] */
|
||||
if (right_ok) {
|
||||
H[H_row][0] = 0.0f; H[H_row][1] = 1.0f; H[H_row][2] = 0.0f;
|
||||
H_row++;
|
||||
}
|
||||
|
||||
int obs_count = H_row;
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* 构建观测噪声协方差 R (根据有效侧数量调整)
|
||||
* ---------------------------------------------------- */
|
||||
float R[3][3] = {0};
|
||||
R[0][0] = s_cfg.r_ey; // e_y 的噪声
|
||||
|
||||
if (left_ok && right_ok) {
|
||||
/* 双侧: 航向噪声更小 (两个独立观测平均) */
|
||||
R[1][1] = s_cfg.r_eth * 0.5f; // e_th_L
|
||||
R[2][2] = s_cfg.r_eth * 0.5f; // e_th_R
|
||||
} else {
|
||||
/* 单侧: 航向噪声较大 */
|
||||
if (left_ok) {
|
||||
R[1][1] = s_cfg.r_eth;
|
||||
}
|
||||
if (right_ok) {
|
||||
R[1][1] = s_cfg.r_eth;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* 计算新息协方差 S = H * P * H^T + R
|
||||
* ---------------------------------------------------- */
|
||||
float HP[3][3] = {0};
|
||||
for (int i = 0; i < obs_count; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int k = 0; k < 3; k++) {
|
||||
HP[i][j] += H[i][k] * s_state.P[k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float S[3][3] = {0};
|
||||
for (int i = 0; i < obs_count; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int k = 0; k < 3; k++) {
|
||||
S[i][j] += HP[i][k] * H[j][k]; // H^T: H[j][k] = H[k][j]
|
||||
}
|
||||
}
|
||||
S[i][i] += R[i][i]; // 加观测噪声
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* 计算 S 的逆
|
||||
* ---------------------------------------------------- */
|
||||
float S_inv[3][3] = {0};
|
||||
if (obs_count == 1) {
|
||||
/* 1 观测: 标量 */
|
||||
if (fabsf(S[0][0]) < 1e-8f) {
|
||||
goto output_result;
|
||||
}
|
||||
S_inv[0][0] = 1.0f / S[0][0];
|
||||
} else if (obs_count == 2) {
|
||||
/* 2 观测:2x2 - 拷贝到局部矩阵 */
|
||||
float S_2x2[2][2];
|
||||
S_2x2[0][0] = S[0][0]; S_2x2[0][1] = S[0][1];
|
||||
S_2x2[1][0] = S[1][0]; S_2x2[1][1] = S[1][1];
|
||||
|
||||
if (!invert_2x2_sym(S_2x2)) {
|
||||
goto output_result;
|
||||
}
|
||||
|
||||
/* 拷贝回 S_inv */
|
||||
S_inv[0][0] = S_2x2[0][0]; S_inv[0][1] = S_2x2[0][1];
|
||||
S_inv[1][0] = S_2x2[1][0]; S_inv[1][1] = S_2x2[1][1];
|
||||
} else {
|
||||
/* 3 观测: 3x3 */
|
||||
memcpy(S_inv, S, sizeof(float) * 9);
|
||||
if (!invert_3x3_cholesky(S_inv)) {
|
||||
goto output_result;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* χ² 马氏距离检验 (鲁棒拒绝)
|
||||
* ---------------------------------------------------- */
|
||||
float d2_total = 0.0f;
|
||||
if (obs_count == 1) {
|
||||
d2_total = mahalanobis_d2_1dof(y[0], S_inv[0][0]);
|
||||
} else if (obs_count == 2) {
|
||||
d2_total = mahalanobis_d2_2dof(y, (const float (*)[2])S_inv);
|
||||
} else {
|
||||
d2_total = mahalanobis_d2_3dof(y, S_inv);
|
||||
}
|
||||
max_maha_d2 = d2_total;
|
||||
|
||||
/* 单自由度检验: e_y 单独检验 */
|
||||
float d2_ey = mahalanobis_d2_1dof(y[0], S_inv[0][0]);
|
||||
if (d2_ey > s_cfg.chi2_1dof) {
|
||||
reject_mask |= (1U << 0); // 拒绝 e_y
|
||||
}
|
||||
|
||||
/* 1 DOF 门限约 3.84 (95%), 约 6.63 (99%) */
|
||||
/* 如果整体 d² 过大,拒绝最可疑的观测 */
|
||||
if (obs_count >= 2) {
|
||||
/* 检验 e_th_L */
|
||||
if (left_ok && !(reject_mask & (1U << 0))) {
|
||||
/* 需要重新计算不含 e_y 的马氏距离 */
|
||||
/* 简化: 用 y[1]^2 / S[1][1] 作为 1DOF 近似 */
|
||||
if (fabsf(S[1][1]) > 1e-8f) {
|
||||
float d2_eth_L = y[1] * y[1] / S[1][1];
|
||||
if (d2_eth_L > s_cfg.chi2_1dof) {
|
||||
reject_mask |= (1U << 1); // 拒绝 e_th_L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 检验 e_th_R */
|
||||
if (right_ok && !(reject_mask & (1U << 0)) && obs_count >= 3) {
|
||||
if (fabsf(S[2][2]) > 1e-8f) {
|
||||
float d2_eth_R = y[2] * y[2] / S[2][2];
|
||||
if (d2_eth_R > s_cfg.chi2_1dof) {
|
||||
reject_mask |= (1U << 2); // 拒绝 e_th_R
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* 计算卡尔曼增益 K = P * H^T * S^(-1)
|
||||
* P1 修复: 必须做完整矩阵乘法 (P*H^T) * S_inv,
|
||||
* 而不能只乘 S_inv 的对角项 S_inv[j][j]。
|
||||
* 后者会忽略 S 的非对角元素(观测间相关性),
|
||||
* 导致卡尔曼增益不正确,影响滤波收敛性。
|
||||
* ---------------------------------------------------- */
|
||||
float HT[3][3] = {0};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < obs_count; j++) {
|
||||
HT[i][j] = H[j][i]; // H^T
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 1: PH^T = P * H^T, 结果为 3×obs_count */
|
||||
float PHT[3][3] = {0};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < obs_count; j++) {
|
||||
for (int k = 0; k < 3; k++) {
|
||||
PHT[i][j] += s_state.P[i][k] * HT[k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 2: K = PHT * S_inv, 结果为 3×obs_count */
|
||||
float K[3][3] = {0};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < obs_count; j++) {
|
||||
for (int k = 0; k < obs_count; k++) {
|
||||
K[i][j] += PHT[i][k] * S_inv[k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* 跳过被拒绝的观测,更新剩余观测
|
||||
* ---------------------------------------------------- */
|
||||
int used_obs = 0;
|
||||
for (int i = 0; i < obs_count; i++) {
|
||||
uint8_t bit = (i == 0) ? (1U << 0) : ((i == 1) ? (1U << 1) : (1U << 2));
|
||||
if (reject_mask & bit) {
|
||||
/*
|
||||
* 关键:被拒绝的观测不仅不能更新状态,
|
||||
* 也不能参与后续 KH/P 更新,否则会错误收缩协方差。
|
||||
*/
|
||||
K[0][i] = 0.0f;
|
||||
K[1][i] = 0.0f;
|
||||
K[2][i] = 0.0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 状态更新: x += K[:, i] * y[i] */
|
||||
s_state.x[0] += K[0][i] * y[i];
|
||||
s_state.x[1] += K[1][i] * y[i];
|
||||
s_state.x[2] += K[2][i] * y[i];
|
||||
used_obs++;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
* 协方差更新: P = (I - K * H) * P_pred
|
||||
* 简化 Joseph 形式: P = (I - K*H) * P * (I - K*H)^T + K * R * K^T
|
||||
* 这里使用简化形式: P = (I - K*H) * P
|
||||
* ---------------------------------------------------- */
|
||||
float KH[3][3] = {0};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int k = 0; k < obs_count; k++) {
|
||||
KH[i][j] += K[i][k] * H[k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float I_KH[3][3];
|
||||
I_KH[0][0] = 1.0f - KH[0][0]; I_KH[0][1] = -KH[0][1]; I_KH[0][2] = -KH[0][2];
|
||||
I_KH[1][0] = -KH[1][0]; I_KH[1][1] = 1.0f - KH[1][1]; I_KH[1][2] = -KH[1][2];
|
||||
I_KH[2][0] = -KH[2][0]; I_KH[2][1] = -KH[2][1]; I_KH[2][2] = 1.0f - KH[2][2];
|
||||
|
||||
float P_tmp[3][3] = {0};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int k = 0; k < 3; k++) {
|
||||
P_tmp[i][j] += I_KH[i][k] * s_state.P[k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
s_state.P[i][j] = P_tmp[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
symmetrize(s_state.P);
|
||||
protect_P(s_state.P);
|
||||
|
||||
updated_obs_count = used_obs;
|
||||
|
||||
output_result:
|
||||
/* ----------------------------------------------------
|
||||
* 填充输出
|
||||
* ---------------------------------------------------- */
|
||||
out_state->t_ms = obs->t_ms;
|
||||
out_state->e_y = s_state.x[0];
|
||||
out_state->e_th = s_state.x[1];
|
||||
out_state->s = s_state.x[2];
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
out_state->P[i][j] = s_state.P[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
out_state->mahalanobis_d2 = max_maha_d2;
|
||||
out_state->obs_reject_mask = reject_mask;
|
||||
|
||||
/* 置信度: 基于协方差迹和拒绝比例 */
|
||||
float P_trace = s_state.P[0][0] + s_state.P[1][1] + s_state.P[2][2];
|
||||
float conf_from_P = clampf(1.0f - P_trace * 0.5f, 0.0f, 1.0f);
|
||||
|
||||
/* 根据有效侧数加权 */
|
||||
float side_factor = (valid_sides == 2) ? 1.0f : 0.7f;
|
||||
|
||||
/* 根据拒绝比例降低置信度 */
|
||||
float reject_ratio = 0.0f;
|
||||
if (obs_count > 0) {
|
||||
int rejected = 0;
|
||||
if (reject_mask & (1U << 0)) rejected++;
|
||||
if (reject_mask & (1U << 1)) rejected++;
|
||||
if (reject_mask & (1U << 2)) rejected++;
|
||||
reject_ratio = (float)rejected / (float)obs_count;
|
||||
}
|
||||
|
||||
out_state->conf = clampf(conf_from_P * side_factor * (1.0f - reject_ratio * 0.5f), 0.0f, 1.0f);
|
||||
|
||||
return updated_obs_count;
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
* 辅助 API
|
||||
* ========================================================= */
|
||||
|
||||
/* =========================================================
|
||||
* IMU 航向观测更新 (独立 1DOF 标量 EKF 更新)
|
||||
*
|
||||
* 观测方程: z_eth_imu = imu_yaw_rad - imu_yaw_ref_rad
|
||||
* 对应状态: e_th (x[1])
|
||||
* H = [0, 1, 0] (只观测 e_th)
|
||||
*
|
||||
* 设计意图:
|
||||
* - IMU 内部维护的 yaw 经过模块校准,比外部积分 wz 更稳定
|
||||
* - 在侧墙观测丢失 (转弯/单侧退化) 时提供航向约束
|
||||
* - 使用较大 R 值,让侧墙观测在有效时主导
|
||||
* ========================================================= */
|
||||
void CorridorEKF_UpdateIMUYaw(float imu_yaw_rad, float imu_yaw_ref_rad, bool valid)
|
||||
{
|
||||
if (!s_initialized || !valid) return;
|
||||
|
||||
/* 观测值: IMU 相对于走廊参考方向的航向偏差 */
|
||||
float z_eth_imu = imu_yaw_rad - imu_yaw_ref_rad;
|
||||
|
||||
/* 新息: y = z - h(x), h(x) = e_th = x[1] */
|
||||
float y_imu = z_eth_imu - s_state.x[1];
|
||||
|
||||
/* H = [0, 1, 0] → S = P[1][1] + R_imu */
|
||||
float R_imu = s_cfg.r_eth_imu;
|
||||
float S_imu = s_state.P[1][1] + R_imu;
|
||||
|
||||
if (fabsf(S_imu) < 1e-8f) return;
|
||||
|
||||
/* χ² 1DOF 检验: d² = y² / S */
|
||||
float d2_imu = y_imu * y_imu / S_imu;
|
||||
if (d2_imu > s_cfg.chi2_1dof) return; /* 拒绝异常观测 */
|
||||
|
||||
/* 卡尔曼增益: K = P * H^T / S = P[:][1] / S */
|
||||
float K[3];
|
||||
float S_inv = 1.0f / S_imu;
|
||||
K[0] = s_state.P[0][1] * S_inv;
|
||||
K[1] = s_state.P[1][1] * S_inv;
|
||||
K[2] = s_state.P[2][1] * S_inv;
|
||||
|
||||
/* 状态更新: x += K * y */
|
||||
s_state.x[0] += K[0] * y_imu;
|
||||
s_state.x[1] += K[1] * y_imu;
|
||||
s_state.x[2] += K[2] * y_imu;
|
||||
|
||||
/* 协方差更新: P = (I - K*H) * P
|
||||
* H = [0, 1, 0], 所以 KH[i][j] = K[i] * H[j] = K[i] * δ(j,1) */
|
||||
float P_new[3][3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
P_new[i][j] = s_state.P[i][j] - K[i] * s_state.P[1][j];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
s_state.P[i][j] = P_new[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
symmetrize(s_state.P);
|
||||
protect_P(s_state.P);
|
||||
}
|
||||
|
||||
void CorridorEKF_GetState(CorridorState_t *out)
|
||||
{
|
||||
if (!s_initialized || out == NULL) return;
|
||||
|
||||
out->t_ms = s_last_update_ms;
|
||||
out->e_y = s_state.x[0];
|
||||
out->e_th = s_state.x[1];
|
||||
out->s = s_state.x[2];
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
out->P[i][j] = s_state.P[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
out->mahalanobis_d2 = 0.0f;
|
||||
out->obs_reject_mask = 0U;
|
||||
|
||||
float P_trace = s_state.P[0][0] + s_state.P[1][1] + s_state.P[2][2];
|
||||
out->conf = clampf(1.0f - P_trace * 0.5f, 0.0f, 1.0f);
|
||||
}
|
||||
137
App/est/corridor_ekf.h
Normal file
137
App/est/corridor_ekf.h
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* @file corridor_ekf.h
|
||||
* @brief 鲁棒 EKF 走廊相对定位滤波器
|
||||
*
|
||||
* 状态向量:x = [e_y, e_th, s]^T
|
||||
* - e_y : 横向偏差 (m)
|
||||
* - e_th : 航向偏差 (rad)
|
||||
* - s : 沿走廊进度 (m)
|
||||
*
|
||||
* 观测向量:z = [z_ey, z_eth_L, z_eth_R]^T
|
||||
* - z_ey : 横向偏差观测 (由左右侧平均距离差计算)
|
||||
* - z_eth_L: 左侧航向观测 (atan2((d_lr-d_lf), L_s))
|
||||
* - z_eth_R: 右侧航向观测 (atan2((d_rf-d_rr), L_s))
|
||||
*
|
||||
* 鲁棒机制:
|
||||
* - χ² 马氏距离检验拒绝异常观测
|
||||
* - 自适应观测噪声 (根据传感器健康度调整 R 矩阵)
|
||||
* - 协方差上界保护 (防止发散)
|
||||
*/
|
||||
|
||||
#ifndef CORRIDOR_EKF_H
|
||||
#define CORRIDOR_EKF_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* 先引入消息定义 (含 EKF_STATE_DIM/EKF_OBS_DIM 宏) */
|
||||
#include "preproc/corridor_msgs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* =========================================================
|
||||
* EKF 配置参数
|
||||
* ========================================================= */
|
||||
typedef struct {
|
||||
/* 过程噪声协方差 Q */
|
||||
float q_ey; // e_y 过程噪声方差
|
||||
float q_eth; // e_th 过程噪声方差
|
||||
float q_s; // s 过程噪声方差
|
||||
|
||||
/* 观测噪声协方差 R */
|
||||
float r_ey; // 横向观测噪声方差 (侧墙)
|
||||
float r_eth; // 航向观测噪声方差 (侧墙)
|
||||
float r_eth_imu; // 航向观测噪声方差 (IMU yaw),建议远大于 r_eth
|
||||
|
||||
/* 初始协方差 */
|
||||
float P0_diag[3]; // 初始 P 对角线
|
||||
|
||||
/* χ² 检验门限 */
|
||||
float chi2_1dof; // 1 自由度门限 (默认 3.84)
|
||||
float chi2_2dof; // 2 自由度门限 (默认 5.99)
|
||||
|
||||
/* 走廊几何参数 */
|
||||
float sensor_base_length; // 同侧前后雷达间距 L_s
|
||||
float corridor_width; // 走廊标准宽度
|
||||
float y_offset; // 期望偏置
|
||||
float side_sensor_inset; // VL53L0X 侧向传感器内缩距离 (传感器面到车体外边缘)
|
||||
float robot_width; // 车体外轮廓宽度 (用于单侧退化时的精确定位)
|
||||
} CorridorEKFConfig_t;
|
||||
|
||||
/* =========================================================
|
||||
* EKF 内部状态
|
||||
* ========================================================= */
|
||||
typedef struct {
|
||||
float x[EKF_STATE_DIM]; // 状态向量
|
||||
float P[EKF_STATE_DIM][EKF_STATE_DIM]; // 状态协方差
|
||||
float K[EKF_STATE_DIM][EKF_OBS_DIM]; // 卡尔曼增益
|
||||
float S[EKF_OBS_DIM][EKF_OBS_DIM]; // 新息协方差
|
||||
float S_inv[EKF_OBS_DIM][EKF_OBS_DIM]; // S 的逆
|
||||
} CorridorEKFState_t;
|
||||
|
||||
/* =========================================================
|
||||
* API 接口
|
||||
* ========================================================= */
|
||||
|
||||
/**
|
||||
* @brief 初始化 EKF 滤波器
|
||||
* @param config 配置参数
|
||||
*/
|
||||
void CorridorEKF_Init(const CorridorEKFConfig_t *config);
|
||||
|
||||
/**
|
||||
* @brief EKF 预测步 (时间更新)
|
||||
* @param odom_vx 里程计线速度 (m/s)
|
||||
* @param imu_wz IMU 角速度 (rad/s)
|
||||
* @param dt 时间间隔 (s)
|
||||
*/
|
||||
void CorridorEKF_Predict(float odom_vx, float imu_wz, float dt);
|
||||
|
||||
/**
|
||||
* @brief EKF 观测步 (测量更新) - 鲁棒版本
|
||||
* @param obs 预处理后的观测快照
|
||||
* @param out_state 输出状态 (含马氏距离、拒绝掩码等)
|
||||
* @return 成功更新的观测数
|
||||
*/
|
||||
int CorridorEKF_Update(const CorridorObs_t *obs, CorridorState_t *out_state);
|
||||
|
||||
/**
|
||||
* @brief EKF IMU 航向观测更新 (独立于侧墙观测,在 Update 后调用)
|
||||
*
|
||||
* 将 IMU 连续 yaw 的变化量作为 e_th 的额外标量观测,执行 1DOF EKF 更新。
|
||||
* 在侧墙观测丢失时 (转弯/单侧退化) 提供航向约束,防止 e_th 漂移。
|
||||
*
|
||||
* @param imu_yaw_rad IMU 连续 yaw 当前值 (rad)
|
||||
* @param imu_yaw_ref_rad 进入走廊时锁定的 IMU yaw 参考值 (rad)
|
||||
* z_eth_imu = imu_yaw_rad - imu_yaw_ref_rad
|
||||
* @param valid IMU 数据是否有效
|
||||
*/
|
||||
void CorridorEKF_UpdateIMUYaw(float imu_yaw_rad, float imu_yaw_ref_rad, bool valid);
|
||||
|
||||
/**
|
||||
* @brief 获取当前状态估计
|
||||
*/
|
||||
void CorridorEKF_GetState(CorridorState_t *out);
|
||||
|
||||
/**
|
||||
* @brief 重置 EKF 状态
|
||||
*/
|
||||
void CorridorEKF_Reset(void);
|
||||
|
||||
/**
|
||||
* @brief 设置过程噪声 (运行时可调)
|
||||
*/
|
||||
void CorridorEKF_SetProcessNoise(float q_ey, float q_eth, float q_s);
|
||||
|
||||
/**
|
||||
* @brief 设置观测噪声 (运行时可调,用于自适应)
|
||||
*/
|
||||
void CorridorEKF_SetMeasurementNoise(float r_ey, float r_eth);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CORRIDOR_EKF_H */
|
||||
108
App/est/corridor_filter.c
Normal file
108
App/est/corridor_filter.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @file corridor_filter.c
|
||||
* @brief 走廊相对定位滤波器 - 鲁棒 EKF 实现
|
||||
*
|
||||
* 本文件是对外统一接口层,内部调用 corridor_ekf 模块
|
||||
* 保持与原有互补滤波接口兼容,方便无缝替换
|
||||
*/
|
||||
|
||||
#include "corridor_filter.h"
|
||||
#include "corridor_ekf.h"
|
||||
#include "preproc/corridor_msgs.h" /* 引入 CHI2_THRESHOLD 宏 */
|
||||
#include "robot_params.h" /* 引入 PARAM_EKF_* 宏 */
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
/* =========================================================
|
||||
* 兼容层配置
|
||||
* ========================================================= */
|
||||
static CorridorFilterConfig_t s_cfg;
|
||||
static bool s_initialized = false;
|
||||
|
||||
/* IMU yaw 参考值管理:进入走廊时锁定,用于计算相对走廊方向的航向偏差 */
|
||||
static float s_imu_yaw_ref_rad = 0.0f; // 走廊参考 yaw (rad)
|
||||
static bool s_imu_yaw_ref_set = false; // 参考值是否已锁定
|
||||
|
||||
/* =========================================================
|
||||
* 初始化
|
||||
* ========================================================= */
|
||||
void CorridorFilter_Init(const CorridorFilterConfig_t *config)
|
||||
{
|
||||
s_cfg = *config;
|
||||
|
||||
/* 转换为 EKF 配置 */
|
||||
CorridorEKFConfig_t ekf_cfg;
|
||||
memset(&ekf_cfg, 0, sizeof(ekf_cfg));
|
||||
|
||||
/* 走廊几何参数 */
|
||||
ekf_cfg.sensor_base_length = config->sensor_base_length;
|
||||
ekf_cfg.corridor_width = config->corridor_width;
|
||||
ekf_cfg.y_offset = config->y_offset;
|
||||
ekf_cfg.side_sensor_inset = config->side_sensor_inset;
|
||||
ekf_cfg.robot_width = config->robot_width;
|
||||
|
||||
/* 过程噪声 Q —— 统一从 robot_params.h 读取,改参数只改那一个文件 */
|
||||
ekf_cfg.q_ey = PARAM_EKF_Q_EY; /* 横向过程噪声 */
|
||||
ekf_cfg.q_eth = PARAM_EKF_Q_ETH; /* 航向过程噪声 */
|
||||
ekf_cfg.q_s = PARAM_EKF_Q_S; /* 里程过程噪声 */
|
||||
|
||||
/* 观测噪声 R */
|
||||
ekf_cfg.r_ey = PARAM_EKF_R_EY; /* 横向观测噪声 (侧墙) */
|
||||
ekf_cfg.r_eth = PARAM_EKF_R_ETH; /* 航向观测噪声 (侧墙) */
|
||||
ekf_cfg.r_eth_imu = PARAM_EKF_R_ETH_IMU; /* 航向观测噪声 (IMU yaw),较大 */
|
||||
|
||||
/* 初始协方差 */
|
||||
ekf_cfg.P0_diag[0] = PARAM_EKF_P0_EY; /* e_y 初始不确定度 */
|
||||
ekf_cfg.P0_diag[1] = PARAM_EKF_P0_ETH; /* e_th 初始不确定度 */
|
||||
ekf_cfg.P0_diag[2] = 0.0f; /* s 初始确定(已知从 0 出发) */
|
||||
|
||||
/* χ² 门限 */
|
||||
ekf_cfg.chi2_1dof = PARAM_CHI2_1DOF;
|
||||
ekf_cfg.chi2_2dof = PARAM_CHI2_2DOF;
|
||||
|
||||
CorridorEKF_Init(&ekf_cfg);
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
* 核心更新函数
|
||||
* ========================================================= */
|
||||
void CorridorFilter_Update(const CorridorObs_t *obs, float imu_wz, float odom_vx,
|
||||
float dt_s, float imu_yaw_continuous_rad, bool imu_yaw_valid,
|
||||
CorridorState_t *out_state)
|
||||
{
|
||||
if (!s_initialized || dt_s <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 预测步 */
|
||||
CorridorEKF_Predict(odom_vx, imu_wz, dt_s);
|
||||
|
||||
/* 侧墙观测更新步 (含马氏距离异常检测) */
|
||||
CorridorEKF_Update(obs, out_state);
|
||||
|
||||
/* ---- IMU yaw 航向观测更新 ---- *
|
||||
* 在侧墙观测之后独立执行 1DOF 标量更新。
|
||||
*
|
||||
* 参考值管理策略:
|
||||
* 首次收到有效 IMU yaw 且 EKF 置信度足够时锁定参考值。
|
||||
* 此时 e_th ≈ 0 (刚被侧墙观测修正过),
|
||||
* 所以 yaw_ref = imu_yaw_current → z_eth_imu = 0,
|
||||
* 后续 yaw 的变化量就等于 e_th 的变化量。
|
||||
*/
|
||||
if (imu_yaw_valid) {
|
||||
if (!s_imu_yaw_ref_set && out_state->conf >= 0.5f) {
|
||||
/* 首次锁定:此刻 e_th 已被侧墙修正到接近 0 */
|
||||
s_imu_yaw_ref_rad = imu_yaw_continuous_rad - out_state->e_th;
|
||||
s_imu_yaw_ref_set = true;
|
||||
}
|
||||
|
||||
if (s_imu_yaw_ref_set) {
|
||||
CorridorEKF_UpdateIMUYaw(imu_yaw_continuous_rad, s_imu_yaw_ref_rad, true);
|
||||
|
||||
/* 更新输出 (IMU 观测可能微调了 e_th) */
|
||||
CorridorEKF_GetState(out_state);
|
||||
out_state->t_ms = obs->t_ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
App/est/corridor_filter.h
Normal file
46
App/est/corridor_filter.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef CORRIDOR_FILTER_H
|
||||
#define CORRIDOR_FILTER_H
|
||||
#include "preproc/corridor_preproc.h"
|
||||
#include "preproc/corridor_msgs.h"
|
||||
|
||||
/* 滤波器配置参数结构体 */
|
||||
typedef struct {
|
||||
float sensor_base_length; // 同侧前后雷达的纵向安装间距 L_s (m)
|
||||
float corridor_width; // 走廊标准宽度 (m),比赛规则为 0.4m
|
||||
float y_offset; // 期望的偏置行走量 (m),0 表示绝对居中
|
||||
float side_sensor_inset; // VL53L0X 传感器内缩距离 (传感器面到车体外边缘) (m)
|
||||
float robot_width; // 车体外轮廓宽度 (m)
|
||||
float alpha_theta; // 航向互补滤波系数 (0~1)
|
||||
// 【注】因为您的IMU极好,此值建议设为 0.98~0.995
|
||||
float alpha_y; // 横向低通滤波系数 (0~1),建议设为 0.6~0.8 防止地毯颠簸
|
||||
} CorridorFilterConfig_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 初始化走廊滤波器
|
||||
* @param config 滤波器调参配置
|
||||
*/
|
||||
void CorridorFilter_Init(const CorridorFilterConfig_t *config);
|
||||
|
||||
/**
|
||||
* @brief 核心函数:执行一次走廊状态估计 (建议与 CAN 发送同频,如 50Hz/20ms运行)
|
||||
* @param obs 预处理模块输出的、清洗干净的雷达观测快照
|
||||
* @param imu_wz 当前的 IMU Z轴角速度 (rad/s),左转为正
|
||||
* @param odom_vx 底盘反馈的当前里程计线速度 (m/s)
|
||||
* @param dt_s 距离上一次调用的时间间隔 (s)
|
||||
* @param imu_yaw_continuous_rad IMU unwrap 后的连续偏航角 (rad)
|
||||
* @param imu_yaw_valid IMU 数据是否有效
|
||||
* @param out_state 输出平滑后的 e_y (横向误差), e_th (航向误差) 和健康度
|
||||
*/
|
||||
void CorridorFilter_Update(const CorridorObs_t *obs, float imu_wz, float odom_vx,
|
||||
float dt_s, float imu_yaw_continuous_rad, bool imu_yaw_valid,
|
||||
CorridorState_t *out_state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CORRIDOR_FILTER_H
|
||||
Reference in New Issue
Block a user