Principles of Extended Kalman Filter and C Implementation for Embedded Systems

A friend and I discussed the Extended Kalman Filter and its implementation in C, so I organized this for future reference.The Extended Kalman Filter (EKF) is an extension of the Kalman filter for nonlinear systems, which approximates nonlinear models through Taylor series first-order linearization. It is suitable for systems where the state or observation equations are nonlinear (such as rotor position estimation in sensorless Field Oriented Control (FOC) of motors). Below are the core formulas, derivations, and C implementation of EKF:

1. Core Formulas of EKF

1. System Model

Nonlinear state equations:

Principles of Extended Kalman Filter and C Implementation for Embedded SystemsPrinciples of Extended Kalman Filter and C Implementation for Embedded SystemsPrinciples of Extended Kalman Filter and C Implementation for Embedded SystemsPrinciples of Extended Kalman Filter and C Implementation for Embedded Systems

3. C Implementation of EKF

Below is a general EKF framework code (using motor position/speed estimation as an example):

#include <math.h>#include <string.h>// Matrix dimension definitions#define STATE_DIM 2   // State dimension: [theta, omega]#define OBSERVE_DIM 2 // Observation dimension: [I_d, I_q]#define INPUT_DIM 2   // Input dimension: [U_d, U_q]// EKF structuretypedef struct {    float x[STATE_DIM];          // State estimate x^+    float P[STATE_DIM][STATE_DIM]; // Covariance matrix P^+    float Q[STATE_DIM][STATE_DIM]; // Process noise covariance    float R[OBSERVE_DIM][OBSERVE_DIM]; // Observation noise covariance    float F[STATE_DIM][STATE_DIM]; // State Jacobian matrix F    float H[OBSERVE_DIM][STATE_DIM]; // Observation Jacobian matrix H    float K[STATE_DIM][OBSERVE_DIM]; // Kalman gain K    float x_pred[STATE_DIM];     // Predicted state x^-    float P_pred[STATE_DIM][STATE_DIM]; // Predicted covariance P^-} EKF_TypeDef;

2. Matrix Operation Utility Functions

// Matrix multiplication: C = A*B (m×n)*(n×p) → m×pvoid matrix_mult(float A[][STATE_DIM], float B[][STATE_DIM],                  float C[][STATE_DIM], int m, int n, int p) {    memset(C, 0, sizeof(float)*m*p);    for(int i=0; i<m; i++) {        for(int j=0; j<p; j++) {            for(int k=0; k<n; k++) {                C[i][j] += A[i][k] * B[k][j];            }        }    }}// Matrix transpose: B = A^T (m×n) → n×mvoid matrix_transpose(float A[][STATE_DIM], float B[][STATE_DIM], int m, int n) {    for(int i=0; i<m; i++) {        for(int j=0; j<n; j++) {            B[j][i] = A[i][j];        }    }}// 2×2 matrix inversion: B = A^-1void matrix_inv_2x2(float A[2][2], float B[2][2]) {    float det = A[0][0]*A[1][1] - A[0][1]*A[1][0];    if(fabs(det) < 1e-6) return; // Singular matrix    float inv_det = 1.0f / det;    B[0][0] =  A[1][1] * inv_det;    B[0][1] = -A[0][1] * inv_det;    B[1][0] = -A[1][0] * inv_det;    B[1][1] =  A[0][0] * inv_det;}

3. EKF Initialization

void EKF_Init(EKF_TypeDef *ekf) {    // Initial state: position 0, speed 0    ekf->x[0] = 0.0f; // theta    ekf->x[1] = 0.0f; // omega    // Initial covariance: high uncertainty    ekf->P[0][0] = 1.0f; ekf->P[0][1] = 0.0f;    ekf->P[1][0] = 0.0f; ekf->P[1][1] = 1.0f;    // Process noise covariance: adjust based on system characteristics    ekf->Q[0][0] = 1e-4f; ekf->Q[0][1] = 0.0f;    ekf->Q[1][0] = 0.0f; ekf->Q[1][1] = 1e-3f;    // Observation noise covariance: adjust based on current sampling accuracy    ekf->R[0][0] = 1e-3f; ekf->R[0][1] = 0.0f;    ekf->R[1][0] = 0.0f; ekf->R[1][1] = 1e-3f;}

4. Core EKF Computation (Prediction + Update)

// State equation f(x, u): predict next statevoid EKF_State_Model(float x_prev[], float u[], float x_pred[], float Ts) {    // theta_pred = theta_prev + omega_prev * Ts    x_pred[0] = x_prev[0] + x_prev[1] * Ts;    // omega_pred = omega_prev + (Te - TL)/J * Ts (simplified: assume Te is known, TL=0)    float Te = 1.0f; // Example: electromagnetic torque (actual calculation needed)    float J = 0.01f; // Moment of inertia    x_pred[1] = x_prev[1] + (Te / J) * Ts;}// Calculate state Jacobian matrix Fvoid EKF_Calc_F(float x[], float Ts, float F[][STATE_DIM]) {    // F = df/dx = [d(theta)/d(theta), d(theta)/d(omega);    //              d(omega)/d(theta), d(omega)/d(omega)]    F[0][0] = 1.0f; F[0][1] = Ts;    F[1][0] = 0.0f; F[1][1] = 1.0f; // Simplified model, omega has no partial derivative with respect to theta}// Observation equation h(x, u): predict observation valuesvoid EKF_Observe_Model(float x_pred[], float u[], float z_pred[]) {    // Example: PMSM current observation model (simplified)    float theta = x_pred[0];    float U_d = u[0], U_q = u[1];    float R = 0.5f; // Stator resistance    float L_d = 0.001f, L_q = 0.001f; // Inductance    float Ke = 0.1f; // Back EMF coefficient    // I_d = (U_d - R*I_d - omega*L_q*I_q)/L_d (actual calculation needs iteration, simplified here)    z_pred[0] = (U_d) / (R + L_d / Ts);     // I_q = (U_q - R*I_q + omega*L_d*I_d - Ke*omega)/L_q    z_pred[1] = (U_q - Ke*x_pred[1]) / (R + L_q / Ts);}// Calculate observation Jacobian matrix Hvoid EKF_Calc_H(float x_pred[], float H[][STATE_DIM]) {    // H = dh/dx = [d(I_d)/d(theta), d(I_d)/d(omega);    //              d(I_q)/d(theta), d(I_q)/d(omega)]    H[0][0] = 0.0f; H[0][1] = 0.0f; // I_d has no partial derivative with respect to theta/omega (simplified)    H[1][0] = 0.0f; H[1][1] = -0.1f / (0.5f + 0.001f / 1e-4f); // I_q partial derivative with respect to omega}// EKF main function: input u (control), z (observation), Ts (sampling period)void EKF_Update(EKF_TypeDef *ekf, float u[], float z[], float Ts) {    // -------------------------- 1. State Prediction --------------------------    EKF_State_Model(ekf->x, u, ekf->x_pred, Ts);    // -------------------------- 2. Covariance Prediction -----------------------    EKF_Calc_F(ekf->x, Ts, ekf->F); // Calculate F matrix    float F_T[STATE_DIM][STATE_DIM];    matrix_transpose(ekf->F, F_T, STATE_DIM, STATE_DIM); // F^T    float F_P[STATE_DIM][STATE_DIM];    matrix_mult(ekf->F, ekf->P, F_P, STATE_DIM, STATE_DIM, STATE_DIM); // F*P    matrix_mult(F_P, F_T, ekf->P_pred, STATE_DIM, STATE_DIM, STATE_DIM); // F*P*F^T    // Add process noise Q    for(int i=0; i<STATE_DIM; i++) {        for(int j=0; j<STATE_DIM; j++) {            ekf->P_pred[i][j] += ekf->Q[i][j];        }    }    // -------------------------- 3. Calculate Kalman Gain K --------------------    EKF_Calc_H(ekf->x_pred, ekf->H); // Calculate H matrix    float H_T[OBSERVE_DIM][STATE_DIM];    matrix_transpose(ekf->H, H_T, OBSERVE_DIM, STATE_DIM); // H^T    float H_P[OBSERVE_DIM][STATE_DIM];    matrix_mult(ekf->H, ekf->P_pred, H_P, OBSERVE_DIM, STATE_DIM, STATE_DIM); // H*P^-    float H_P_Ht[OBSERVE_DIM][OBSERVE_DIM];    matrix_mult(H_P, H_T, H_P_Ht, OBSERVE_DIM, STATE_DIM, OBSERVE_DIM); // H*P^-*H^T    // Add observation noise R    for(int i=0; i<OBSERVE_DIM; i++) {        for(int j=0; j<OBSERVE_DIM; j++) {            H_P_Ht[i][j] += ekf->R[i][j];        }    }    // Inversion: (H*P^-*H^T + R)^-1    float H_P_Ht_inv[OBSERVE_DIM][OBSERVE_DIM];    matrix_inv_2x2(H_P_Ht, H_P_Ht_inv);    // K = P^- * H^T * (H*P^-*H^T + R)^-1    float P_Ht[STATE_DIM][OBSERVE_DIM];    matrix_mult(ekf->P_pred, H_T, P_Ht, STATE_DIM, STATE_DIM, OBSERVE_DIM);    matrix_mult(P_Ht, H_P_Ht_inv, ekf->K, STATE_DIM, OBSERVE_DIM, OBSERVE_DIM);    // -------------------------- 4. State Update --------------------------    float z_pred[OBSERVE_DIM];    EKF_Observe_Model(ekf->x_pred, u, z_pred); // Predict observation values    float z_error[OBSERVE_DIM];    for(int i=0; i<OBSERVE_DIM; i++) {        z_error[i] = z[i] - z_pred[i]; // Observation residual    }    // x^+ = x^- + K*(z - h(x^-))    for(int i=0; i<STATE_DIM; i++) {        ekf->x[i] = ekf->x_pred[i];        for(int j=0; j<OBSERVE_DIM; j++) {            ekf->x[i] += ekf->K[i][j] * z_error[j];        }    }    // -------------------------- 5. Covariance Update ------------------------    float I_KH[STATE_DIM][STATE_DIM];    float K_H[STATE_DIM][STATE_DIM];    matrix_mult(ekf->K, ekf->H, K_H, STATE_DIM, OBSERVE_DIM, STATE_DIM);    // I_KH = I - K*H    for(int i=0; i<STATE_DIM; i++) {        for(int j=0; j<STATE_DIM; j++) {            I_KH[i][j] = (i==j ? 1.0f : 0.0f) - K_H[i][j];        }    }    // P^+ = (I - K*H)*P^-    matrix_mult(I_KH, ekf->P_pred, ekf->P, STATE_DIM, STATE_DIM, STATE_DIM);}

5. Example of EKF Usage

int main() {    EKF_TypeDef ekf;    EKF_Init(&ekf);    float Ts = 1e-4f; // 100us sampling period    float u[INPUT_DIM] = {0.0f, 1.0f}; // U_d=0, U_q=1V    float z[OBSERVE_DIM] = {0.1f, 2.0f}; // Measured I_d, I_q    while(1) {        EKF_Update(&ekf, u, z, Ts); // EKF iteration        float theta_est = ekf->x[0]; // Estimated position        float omega_est = ekf->x[1]; // Estimated speed    }}

6. Key Considerations

  1. Accuracy of Jacobian Matrix: EKF relies on first-order linearization, and the accuracy of the Jacobian matrix directly affects the filtering performance, requiring strict derivation.
  2. Tuning of Noise Covariance Q/R
  • Increasing Q → faster tracking, but weaker noise suppression;
  • Increasing R → trust predictions, distrust observations; conversely, trust observations.
  • Numerical Stability: Avoid matrix singularity (e.g., check determinant during inversion), and ensure the covariance matrix remains positive definite.
  • Model Simplification: In practical applications, optimize state and observation equations based on system characteristics (e.g., PMSM should combine accurate motor parameters).
  • 7. Extended Applications

    EKF is widely used in:

    • Sensorless FOC of motors (position/speed estimation);
    • Drone attitude estimation (IMU+GPS fusion);
    • Target tracking (radar/vision fusion).

    With the above code framework, EKF can be quickly implemented and adapted to specific nonlinear systems, with the core being to modify the state equations, observation equations, and Jacobian matrices according to the actual scenario.

    Leave a Comment