Skip to content

SD Logging

The node logs the complete telemetry snapshot to an SD card as CSV, at 50 Hz, without ever blocking sampling on the (slow, bursty) card write. It does that with a classic ping-pong buffer. The app is sd_buffer_app.c; it drives the sd_buffer_driver, and the actual card I/O goes through FileX.

Ping-pong buffering

There are two equal buffers, A and B, each SD_BUFFER_BYTES_PER_CHUNK (8 lines × 2048 B = 16 KiB).

  • The SD Buffer thread appends CSV lines into whichever buffer is active.
  • When a buffer fills, the driver swaps to the other one and signals the SD Writer via s_sd_writer_sem.
  • The writer flushes the full buffer to the card while the buffer thread keeps filling the other. Neither waits on the other.
  • If the writer is still busy when the second buffer also fills, new lines are dropped, never blocked: a slow card costs samples, not real-time behavior.

Why two buffers

An SD write can stall for tens of milliseconds. If sampling and writing shared one buffer, every stall would drop samples. With ping-pong, the card latency is hidden behind the second buffer.

The sampling loop

The buffer thread runs at SD_BUFFER_HZ (50 Hz). Each tick it:

  1. Takes a snapshot (telemetry_snapshot). If it fails, it sleeps one tick and retries.
  2. Checks whether SD logging is enabled (see toggle below). If not, it idles at ~10 Hz.
  3. Serializes the snapshot to a line with telemetry_csv_line(..., MSG_TYPE_FULL): the full CSV row, both fast and slow blocks. See Data Format.
  4. Appends the line to the active buffer via sd_buffer_driver_append_line().

The writer side (FileX)

The SD Writer thread lives in app_filex.c and does the actual card I/O:

  1. Waits on an event flag until fx_media_open() succeeds (a separate FileX thread opens the media at boot; if the card is missing or unreadable, that's an Error_Handler() halt).
  2. Generates the log filename once per boot: <rtc-date>_<runid>.csv, where the run counter is persisted in a RUN.ID file on the card.
  3. Creates/opens the file, seeks to the end, and writes the CSV header row if the file is empty.
  4. Loops: wait for a chunk-ready signal → write the chunk → fx_media_flush().

The flush after every chunk bounds data loss on a power cut to roughly one chunk plus the active buffer: a few hundred milliseconds of data, not the whole session.

Dashboard toggle

Logging can be switched off from the dashboard so you're not filling the card in the pits. The thread reads two flags out of the snapshot:

  • dbg_buttons_valid: are the dashboard flags meaningful yet?
  • dbg_log_sd_toggle: the SD-logging enable.

Logic (in sd_buffer_app_dashboard_sd_enabled()): log by default. Only stop logging when the dashboard flags are valid and the SD toggle is explicitly off. So a missing or not-yet-initialized dashboard never accidentally silences the log.

Toggle currently disabled

The dashboard ingest was commented out in a hotfix (0x0D0 handling in telemetry.c), so dbg_buttons_valid never becomes 1 and SD logging is always on regardless of the dashboard button. Re-enable the ingest to get the toggle back.

Message type

SD always logs MSG_TYPE_FULL: the complete row, unprefixed. This is the opposite of the UDP path, which splits fast/slow to save bandwidth. On the card, bandwidth isn't the constraint, so completeness wins.

Filename column

Every line carries sd_filename. Keep the CSV line buffer sized to TELEMETRY_CSV_LINE_MAX (2048); the full row is long, and a truncated snprintf will silently drop trailing columns.

Released under the MIT License.