top of page

SDL Firmware Architecture

  • Justin Michaud
  • May 2
  • 8 min read

SDL started as a rocket data logger, but even a basic flight data logger quickly becomes more complicated than just reading a sensor and printing values.

The board has multiple IMUs, barometers, GPS, magnetometer, power monitoring, onboard flash, SD card storage, USB output, LoRa telemetry, status LEDs, and a buzzer. Each device has its own setup process, communication bus, timing requirements, and failure modes. If all of that code lived directly in the main loop, the firmware would quickly become difficult to debug, expand, or trust.

Because of that, SDL’s firmware is built around a layered structure. Each hardware device is isolated behind its own driver, subsystem code handles shared behavior like logging and packet generation, and the main application controls the overall state of the board.

The goal is simple: the high-level firmware should not need to know the low-level details of every sensor register. It should be able to ask for accelerometer data, pressure data, GPS data, or battery data in known units, then pass that data into the logging and telemetry system.

Why SDL needed a firmware hierarchy

Early embedded projects can often get away with putting most of the code in main.c. That works when the system only has one or two devices. SDL is not that kind of project.

The board includes several different communication interfaces:

SPI    - IMUs, high-G accelerometer, SD card, LoRa radio
I2C    - barometers, magnetometer, power monitor, temperature sensor
UART   - GPS
QSPI   - onboard flash
USB    - serial output and development/debug interface
GPIO   - chip selects, status inputs, arming input, card detect
Timers - buzzer, LEDs, timing, and future scheduling support

On top of that, SDL does not only need to read sensors. It also needs to format data, log it, transmit it, store it safely, report status, and eventually make decisions based on flight state.

That means the firmware has to answer several different questions at once:

Has each sensor initialized correctly?
Is the board armed or disarmed?
Which sensors need to be sampled right now?
Where should each packet be sent?
Is flash logging active?
Is there data waiting to be copied to the SD card?
Is USB connected?
Is LoRa telemetry enabled?
Are any sensors reporting suspicious values?

Trying to handle all of that from one large main loop would make every change risky. Adding a new sensor would affect the logging code. Changing the packet format could affect radio output. Debugging a storage issue could involve unrelated sensor code.

Instead, SDL separates the firmware into layers.

Firmware layer overview

At a high level, SDL’s firmware can be thought of like this:

Application Layer
   Main state machine
   Arm/disarm logic
   Flight mode behavior
   Timing and scheduling
   Error/status handling

Subsystem Layer
   Packet builder
   Logging manager
   Storage manager
   Telemetry manager
   Power/battery manager
   Sensor health checks

Device Driver Layer
   IMU drivers
   Barometer drivers
   GPS parser
   Magnetometer driver
   Power monitor driver
   Flash driver
   SD card interface
   LoRa driver
   USB output

Hardware / HAL Layer
   SPI
   I2C
   UART
   QSPI
   GPIO
   Timers
   DMA / interrupts

The lower layers know about hardware details. The upper layers know about system behavior.

For example, an IMU driver knows which register contains gyroscope data and how to convert the raw counts into useful units. The packet builder does not need to know that. It only needs to receive a clean data structure and format it into a log packet.

That separation is what keeps the project manageable as it grows.

The hardware layer

The lowest layer is the hardware interface layer. For SDL, this is mostly built around STM32 HAL and CubeMX-generated initialization code.

This layer is responsible for setting up the STM32 peripherals:

SPI buses
I2C buses
UART for GPS
QSPI for flash
USB CDC
GPIO pins
Timers
DMA and interrupts where needed

This layer does not represent a “sensor” or a “flight event.” It only provides the communication tools that the rest of the firmware uses.

For example, SPI by itself does not know whether it is talking to an IMU, an SD card, or a LoRa radio. It only moves bytes. The device driver above it gives those bytes meaning.

The device driver layer

The next layer is the device driver layer.

This is where each sensor or peripheral gets its own small library. In SDL, the goal is for each major device to have its own .h and .c files that contain the details specific to that part.

A typical driver is responsible for:

Initializing the device
Checking device ID registers
Configuring measurement ranges and output rates
Reading raw registers
Converting raw values into useful units
Returning the result through a struct
Reporting basic error/status information

For example, an IMU driver might handle:

WHO_AM_I check
Accelerometer range setup
Gyroscope range setup
Raw accel register reads
Raw gyro register reads
Temperature readout
Conversion to mg and mdps

The main application should not need to know which register contains gyro X, or which scale factor applies to a specific accelerometer range. That information belongs inside the driver.

A simplified data path looks like this:

ICM42670 raw SPI registers
\/
ICM42670 driver
\/
accel_mg + gyro_mdps
\/
IMU packet / logging / future filtering

This makes the code much easier to debug. If the ICM42670 has an intermittent startup issue, the relevant logic is in the ICM42670 driver. If the GPS parser fails to decode a fix, that problem stays inside the GPS code. If the barometer conversion needs to change, it does not require rewriting the packet system.

It also makes the project more flexible. If a sensor changes in a future board revision, the rest of the system should not need to be rewritten. Ideally, only the driver changes, while the higher-level code still receives the same kind of standardized data.

Standardized sensor data

One of the most important goals of SDL’s firmware is to avoid spreading raw sensor values throughout the project.

Raw values are useful inside a driver, but they are not ideal as the common language between subsystems. A raw accelerometer count only makes sense if you know the sensor model, selected range, resolution, axis mapping, and scale factor. That information should not be required everywhere else in the firmware.

Instead, SDL tries to convert data into known units as early as practical.

Examples include:

Acceleration  - mg
Gyroscope     - mdps
Voltage       - mV
Current       - mA
Power         - mW
Temperature   - centi-degrees C
Pressure      - Pa
GPS position  - scaled latitude/longitude values

This keeps the rest of the code cleaner.

The packet builder should not need to know how the BMI088, ICM42670, or high-G accelerometer scale their raw readings. It should just receive acceleration in a known unit. Future filtering code should not need to care which pressure sensor produced a value. It should receive pressure in a consistent format.

This is especially important because SDL uses multiple sensors that can measure similar things. There are multiple IMUs and multiple pressure sensors. If every device returned data in its own raw format, comparing sensors would become messy. Standardized units make it much easier to check whether sensors agree, detect suspicious values, and eventually combine them into a state estimate.

Why SDL uses scaled integers

For logging and telemetry, SDL generally favors scaled integer values instead of formatted floating-point strings.

For example:

12.345 V   → 12345 mV
23.75 °C   → 2375 centi-degrees C
1.250 g    → 1250 mg
90.000 °/s → 90000 mdps

This has a few advantages.

First, the value is unambiguous. A logged value of 12345 in a voltage field means 12345 mV, or 12.345 V. There is no question about decimal formatting or how many digits were printed.

Second, it keeps packets smaller and easier to parse. This matters for onboard logs, but it matters even more for radio telemetry where bandwidth is limited.

Third, it avoids doing unnecessary floating-point formatting in the embedded system. The microcontroller can store and transmit clean integer values, while the ground station or analysis script can convert them back into human-readable units later.

This approach fits the overall SDL philosophy: keep the onboard firmware consistent, predictable, and easy to parse.

The subsystem layer

Above the device drivers is the subsystem layer.

This is where individual sensor readings become part of larger system behavior. The subsystem layer includes things like:

Packet building
Logging management
Storage management
Telemetry handling
Power/battery management
Sensor health checks

The packet builder is a good example. It does not directly talk to the sensors. Instead, it receives already-converted data and turns it into SDL’s log/telemetry format.

The logging manager then decides where that packet should go. It may be written to onboard flash, sent over USB, transmitted over LoRa, or eventually copied to the SD card.

This separation is important because the packet system should not care how the data was collected, and the sensor drivers should not care where the data is going.

A simplified flow looks like this:

Sensor read
\/
Device driver
\/
Standardized data struct
\/
Packet builder
\/
Output system
   Flash log
   USB serial
   LoRa telemetry
   SD export

That means SDL can build one packet and send it to multiple destinations.

During development, USB serial is useful because it gives immediate feedback. During flight, onboard flash is the most important output because it stores the high-rate data. After flight, SD card export makes the data easy to retrieve. LoRa telemetry can provide live status without replacing the onboard log.

The same firmware structure supports all of those outputs.

The application layer

The top layer is the application layer.

This is where SDL’s overall behavior is controlled. It handles the board’s main state, arming/disarming behavior, timing, and high-level decisions.

For the current version of SDL, this layer is still relatively simple. The board initializes sensors, reads them, builds packets, logs data, and handles status outputs. As the project develops, this layer will become more important.

Future application-layer behavior can include:

Higher-rate sensor scheduling
Flight state detection
Launch detection
Apogee detection
Sensor fault handling
Filter/state-estimation updates
Radio command handling
Replay of old flight data through updated filters

This is one of the main reasons the lower layers need to stay clean. If the sensor code, logging code, radio code, and state logic are all mixed together, adding flight-state behavior becomes much harder.

A proper hierarchy gives the application layer a cleaner job. It can focus on system behavior instead of low-level sensor details.

Why this matters

This firmware structure is not just about making the code look clean. It directly affects what SDL can become.

The first versions of SDL are focused on data logging. The board needs to collect useful flight data and survive real launch conditions. But the long-term goal is to build toward more capable avionics features: better filtering, higher-rate logging, event detection, live telemetry, and eventually flight-computer-style behavior.

That requires firmware that can grow.

A layered driver-based structure makes it easier to:

Add new sensors
Replace old sensors
Compare overlapping sensors
Increase logging rates
Add radio telemetry
Add sensor health checks
Improve storage handling
Build a scheduler
Run state estimation
Replay old data through new algorithms

It also makes testing easier. Individual drivers can be brought up one at a time. Packet output can be checked before flight. Storage behavior can be tested separately from sensor reads. If something fails, the structure gives a better starting point for debugging.

For a flight data logger, that matters. The system is expected to run in a harsh, short-duration environment where missed data is hard to recreate. A clean architecture does not guarantee success, but it makes the system much easier to improve after every test.

Current state and future direction

At this stage, the firmware architecture is still evolving. Some systems are already separated into dedicated drivers and packet outputs, while others are still being refined as the board moves from early test flights toward higher-rate logging and more advanced processing.

The next major firmware improvements are mostly about timing and coordination. SDL needs to read fast sensors more often, slower sensors less often, and avoid letting slow operations block the rest of the system. That is where the future scheduler fits in.

The same hierarchy also supports future state estimation. Once the sensors produce standardized data, that data can feed filters that estimate orientation, velocity, altitude, and flight events. The packet system can then log both the raw measurements and the processed results.

That is the main reason this structure matters. SDL is not only being written for the board as it exists today. It is being written so the project can continue growing.

Closing

SDL’s firmware is built around a simple idea: isolate the hardware details, standardize the data, and keep the higher-level system logic clean.

Each sensor driver handles the details of its own chip. Subsystem code turns those readings into packets, logs, and telemetry. The application layer controls the overall behavior of the board.

That hierarchy turns SDL from a collection of sensors into a scalable flight data system. It makes the current logger easier to debug, and it provides a foundation for the future features that will move SDL closer to a full avionics platform.

Comments


bottom of page