This project implements an Inertial Navigation System (INS) using a 9-DoF IMU (ICM-20948) and an ESP32.
The system performs sensor calibration, fusion for attitude estimation, linear acceleration computation, and position estimation using ZVU.
The core of this code relies on the Fusion AHRS algorithm.
Before data enters the fusion filter, raw sensor readings are converted into standard physical units.
| Sensor | Raw Input | Conversion Formula |
|---|---|---|
| Gyroscope | myICM.gyrX() (Raw Counts) |
Gyroscope (DPS) = Raw - Bias |
| Accelerometer | data.Raw_Accel.Data.X (Raw Counts) |
a_calib = (Sensitivity * a_raw - b_accel) ⊙ S * G |
Where:
a_calib: calibrated acceleration (m/s²)a_raw: raw acceleration vector (Raw Counts)b_accel: bias vectorS: scale factor vectorSensitivity = (FSR * g) / 32767.5⊙: Hadamard (element-wise) product
- Gyroscope: Measures angular rates. Integration over time updates sensor orientation
q:
- Accelerometer: Measures gravity for drift correction (Roll & Pitch)
The accelerometer measures the direction of gravity and we get the predicted direction of gravity using the predicted quaternion and compare it with the accelerometer and get an error Gravity vector in the sensor frame :
Why the outer product?
1 ) e is perpendicular to both a and g(q) This is the axis we rotate around to fit a onto g(q)
2) Since the magnitude of a and g(q) is 1,
The larger θ, the more error we produce
The accelerometer measures sensor-frame acceleration. To remove gravity:
R: Rotation matrix obtained from quaternions.
Reduces high-frequency noise:
This section is the core of the INS (Inertial Navigation System) method based on PDR (Pedestrian Dead Reckoning) which works using the ZVU technique to deal with drift.
To update the position and velocity, the trapezoidal integration method is used (which is more accurate than the simple Euler integration):
The position drift in the INS is caused by the accumulation of small errors in a and Δt. ZVU assumes that during a short stop, the velocity and acceleration must be zero and uses this assumption to reset the accumulated error.
For each axis (X, Y, Z), the filtered linear acceleration in Earth Frame is compared to a threshold:
- X, Y: 0.5 m/s²
- Z: 0.8 m/s²
If the ZVU_{counter}counter crosses the threshold for a certain number of samples, a complete stop is assumed and the speed of that axis is explicitly set to zero:
Of course, it should be noted that using the IMU alone will not provide very good positioning accuracy because this method depends on creating acceleration from a threshold, and as a result, positioning cannot be done with the IMU alone for very slow movements because when stationary, the IMU accelerometer also has a value for acceleration in each axis, and with slow movements, it becomes difficult to detect and differentiate between no movement and slow movement, and this is a big challenge.
This video is to demonstrate repeatability, with a small error of 2 cm.
![]()
The next video is about the movement in the z-axis direction, which can be seen moving in other axes. Of course, in subsequent updates, the drift in other axes was reduced, but unfortunately I don't have a video of it.