Note: This repository contains only one module of a larger rocket project developed by the student group RocKIT at the Karlsruhe Institute of Technology (KIT). The overall project focuses on the development of a small vertical takeoff and landing (VTVL) rocket powered by an ethanol–nitrous oxide (N₂O) propulsion system.
The estimator is based on the PX4 estimator, which is a robust professional estimator. The implementation has the following features:
- Sensor fusion for 3IMUs, 2Mag, 2GPS and 3Lidars
- Multi EKF Selector for robustness (multi IMUs)
- Two levels of output fidelity:
- Output predictor — runs at IMU sample rate. On each new IMU sample, it cheaply integrates the last corrected state forward (no full EKF math), giving the low-latency, high-rate estimate the controller needs for stability.
- Full EKF correction — runs at a lower rate, applying the complete Kalman filter (prediction + measurement correction) to produce the corrected delayed state that the output predictor integrates from.
The DropperOS/src/GNC/Estimators/EKF/EKFcore folder is a faithful copy of the PX4 estimator.
Then, the DropperOS/src/GNC/Estimators/EKF folder is our OS-specific wrapper. The main actions of this folder are:
- Conversion from DropperOS data structs to EKFcore structs that the estimator needs
- Parameter override with our Dropper rocket specifics
- Multi EKF selector for robustness (makes use of our multi-IMUs). This logic is also drawn from PX4.
This is a basic overall explanation of the estimator. More details can be found at PX4 EKF Tuning Guide.
flowchart TB
subgraph SensorTasks["Sensor tasks (FreeRTOS)"]
IMU["IMUTask @ IMU_HZ (200 Hz)"]
GPS["GPSTask"]
MAG["MagTask"]
LIDAR["LidarTask"]
end
subgraph Wrapper["StateEstimatorWrapper"]
STAGE["Stage sensor data\n(imuDirty, gpsDirty, ...)"]
EST["estimateState()"]
end
subgraph DropperLayer["Dropper EKF layer (./EKF)"]
EE["EkfEstimator"]
CONV["SensorConversion\n(convertIMU, convertGPS, ...)"]
SEL["EkfInstanceSelector\n(3 instances, pick best)"]
OUT["convertOutput()\n→ StateEstimationData"]
end
subgraph EKFcore["PX4 EKF copy (./EKF/EKFcore)"]
SET["Ekf::setIMUData()"]
OP["OutputPredictor\ncalculateOutputStates()"]
DS["ImuDownSampler\n→ IMU ring buffer"]
UPD["Ekf::update()\npredict + fuse"]
end
subgraph Consumers["Consumers"]
CTRL["ControlTask @ CONTROL_HZ (100 Hz)\n(reads getStateEstimation)"]
TEL["Telemetry / FSM / ..."]
end
IMU --> STAGE
GPS --> STAGE
MAG --> STAGE
LIDAR --> STAGE
SETask["StateEstimationTask @ STATE_ESTIMATION_HZ (200 Hz)"] --> EST
EST --> EE
EE --> CONV
CONV --> SET
SET --> OP
SET --> DS
DS --> UPD
UPD --> SEL
SEL --> OUT
OUT --> EST
EST --> CTRL
EST --> TEL
Note: this diagram represents the target architecture and is still a work in progress — several pieces are already implemented, but the design isn't fully locked down yet.
The flow reads roughly as follows:
sensor data (IMU, GPS, magnetometer, LIDAR) is staged by StateEstimatorWrapper, which calls estimateState(). Sensor conversion translates the data from DropperOS structs into the structs expected by EKFcore. From there, setIMUData() immediately calls the OutputPredictor (calculateOutputStates()), producing the fast prediction at the same rate as the IMU. The full Ekf::update() — which does the complete Kalman prediction and sensor fusion — is triggered separately, via a downsampler mechanism (in estimator_interface.cpp) that accumulates a certain number of IMU samples before running the whole Kalman update. How many samples get accumulated before that happens is configurable via a parameter, and is something we'll need to tune. Once a result is produced, it's converted back into the DropperOS output struct via convertOutput(), making it available to consumers such as the control task or telemetry.
The input/output interface is designed to match the OS data struct contract. It is documented in detail in another file.
Please see Dropper/Avionics/DropperOS/docs/markdown_docs/estimator/interface.md The filter parameters are also documented there.
Build and test the EKFcore:
# From the EKF directory
cd Dropper/Avionics/DropperOS/src/GNC/Estimators/EKF
# First-time setup (only if build/ doesn't exist)
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
# Build
cmake --build . -j$(nproc)
# Run all tests
ctest --output-on-failure -j$(nproc)The implementation in DropperOS/src/GNC/Estimators/EKF can be tested on Simulink.
Find the ready-to-use Simulink Subsystem here:
DropperModel/models/Dropper_Subsystem_StateEstimation.slx
And a minimal testbench for the estimator here:
DropperModel/tests/EstimatorGroundTruthData/test/dropper_excitation_testbench.slx
For this, we compile our C++ estimator with the MATLAB MEX compiler. Then we can use an S-function block in Simulink. See the files in DropperModel/scripts/estimator to do the compilation:
build_sfun.m— does the buildsfun_dropper_ekf.cpp— the minimal Simulink interface which makes API calls to our estimator
To build, simply:
# Set env var for non-interactive batch mode
export DROPPER_OS_PATH="/path/to/Dropper/Avionics/DropperOS"
# Run MATLAB batch compile
matlab -batch "cd('DropperModel/scripts/estimator'); build_sfun"The output file is platform dependent:
DropperModel/sfun_dropper_ekf.mexa64will run on Linux.- Compile again from a Windows machine to get the proper Windows format:
.mexw64
The following items are still open and need to be addressed:
- Fully understand all of the parameters in
Dropper/Avionics/DropperOS/src/GNC/Estimators/EKF/EkfConfig.cppand define them correctly. - Update OS data structs of sensors and the estimator output.
- Update the sensor model to be rich enough to simulate the estimator. An example can be found in:
openExample("px4/RunPX4SITLWithQuadcopterPlantExample")- The in ./EKF files which does the orchestration between the ./EKF/EKFcore and the DropperOS is not completely define.
- Decide: normal LIDAR measurement models in the Kalman filter (as already implemented in the Kalman filter) or triangulation dynamics for the measurement model to help predict height and quaternion.
- Test and validation, first in Simulink and then on the Teensy — both are missing.