
Nicola Destro · Flight software and applied AI
Ask the
onboard computer
Aerospace engineering student, software lead of the J2050 PocketQube, planned to launch with ESA. Building flight software and AI.
The background shows the Earth as seen from a satellite in a simulated orbit at 550 kilometres. The view depends on the time of your visit.
Anatomy
Take it apart
The parts of the RedPill satellite, from top to bottom.Scroll to open the satellite. Select a part to highlight it.
An illustrative, stylised model built for this site: it is not the flight design, and it omits the real internal detail.

- CRYSTALS payloadThe CRYSTALS payload sits at the top behind a glass window.
- CameraA camera module mounted below the payload.
- Micro-switchesTwo micro-switches on the frame.
- Top bridgeA structural plate closing the top of the stack.
- EPS boardThe electrical power system board. It runs on its own microcontroller and its interface to the on-board computer is still being defined.
- BatteriesFour battery cells stacked in the middle of the satellite.
- Magnetic rodsTwo magnetic rods and their cover, part of the attitude hardware.
- AOCS boardsThe attitude determination and control boards. They run on their own microcontroller and their interface to the on-board computer is still being defined.
- Telecom boardThe telecommunications board with the LoRa radio used for beacons, downlink and the authenticated uplink.
- Front plateThe structural plate at the bottom of the satellite.
- Antenna deployerThe antenna is held stowed at the base until it is deployed.
- Solar panel +XA solar panel on the +X face.
- Solar panel -XA solar panel on the -X face.
- Solar panel +YA solar panel on the +Y face.
- Side panel -YThe -Y side panel of the frame.
Mission
RedPill flight software
JOS is the on-board software of the RedPill PocketQube, part of the J2050 project. Nicola leads its development. It is written for flight, so every change is gated by verification. It is under active development.
Operational state machine: select a state
Inside JOS
The flight software
JOS is written by Nicola Destro for the J2050 team of the Università degli Studi di Padova (UNIPD). It runs on an STM32L496 under FreeRTOS and is under active development. Pick a topic; each one has a short version and a "Go deeper" block for the details.
The decisions behind it
Nicola wrote the flight software from start to end. These are the choices that shaped it: the problem, what was chosen, what it costs, and where to look in the code.
- 01Repair memory upsets instead of only detecting them
- Problem
- Hardware parity in the protected RAM only fires when a corrupted byte is read, and it cannot repair anything: its answer is always a reboot. A flipped bit in a critical structure could sit unnoticed until the worst moment.
- Choice
- A scrubber reads the critical structures on a timer and votes over three legs: the live object, a shadow copy at another address and a reference CRC-32, with a non-volatile record in FRAM. A single upset is repaired; two different ones are recorded and contained, never guessed.
- Trade-off
- It costs discipline: the scrubber assumes a region only changes when its owner says so, so every legitimate write must be followed by a commit, atomically with the write. Forgetting it would make the scrubber undo real updates.
Go deeper
Every non-healthy outcome is written to the forensic log with the region, the number of flipped bits and running counters, so the radiation environment can be trended from the ground.
In the code:
Core/Src/seu_mitigation.c - 02Log every state change, but never let a bad flash page freeze the satellite
- Problem
- A state transition is written to the forensic log before it is committed, so the trail exists even if the change fails. In an early version a failed write vetoed the transition, and a sick flash page left the software stuck in the initial state, with no way to enter the safe state on low battery.
- Choice
- The log stays before the commit, but a failed record no longer blocks the transition: the loss is counted instead. If logging fails while entering the safe state, the software still forces it.
- Trade-off
- Containment is preferred over evidence: the safe state can be entered without a persisted record. The flash write also happens inside the state lock, with a documented worst-case hold (nominally about 160 ms) and a fixed lock order.
Go deeper
The number of transitions committed without a record is exposed by a counter, so the gap is visible instead of silent.
In the code:
App/obsw/state_machine.c - 03Use an interrupt mask, not the RTOS critical section, in the radiation code
- Problem
- The log can also be written from the parity fault interrupt. Inside that handler the RTOS critical-section check would spin forever.
- Choice
- The scrubber's lock masks interrupts directly. Commit is SRAM-only and safe under the lock and in the fault path; the blocking FRAM write-through runs later, in task context, outside the lock.
- Trade-off
- Interrupts are masked for a short window on every commit, and the log bookkeeping deliberately stays in the main RAM so that the block that reported a parity error is not on the path that records its own failure.
Go deeper
The split between commit and write-through is part of the documented ownership contract of the module.
In the code:
Core/Src/seu_mitigation.c, App/memory/memory.c - 04A stalled task means a reset, not a silent suspension
- Problem
- The first watchdog suspended a stalled task while the hardware watchdog kept being refreshed. There was no recovery path at all: a suspended receiver task meant a deaf satellite.
- Choice
- The software monitor flags any task silent for more than three times its declared period, records it and resets the computer. Two hardware watchdogs sit behind it, and the refresh period is kept well inside the external chip's minimum timeout.
- Trade-off
- If the stalled task holds the log mutex, recording would wedge the monitor, so the escalation is deferred for at most 60 scans (about 30 seconds) and then the computer resets without the record. Recovery takes priority over the record: once the deferrals run out, the computer resets even if the record is lost.
Go deeper
Each scan samples every task's stack high-water mark for telemetry, and the deferral count is exposed.
In the code:
App/obsw/watchdog.c - 05Check the image in plain software and degrade to beacon-only
- Problem
- A corrupted firmware image must not run as if it were trusted, but there is no golden image available in flight to fall back to.
- Choice
- The CRC-32 of the image is computed in pure software before any RTOS object exists, so a mis-configured peripheral cannot defeat it. The policy can reset; after its retry budget the software continues in an untrusted, constrained mode that only beacons.
- Trade-off
- There is no safe halt: the usual error handler would only let the hardware watchdog reset the part in a loop, so it is deliberately not used. The image is stamped at build time, so an unstamped build is a known case handled by policy.
Go deeper
The expected value is always the last four bytes of the image, so the check can be reproduced on the ground.
In the code:
App/obsw/boot_crc.c - 06Fail closed on authentication, and say what is still weak
- Problem
- A telecommand must not be obeyed just because its structure and CRC are valid: a CRC is integrity, not authentication.
- Choice
- Without a provisioned key the image rejects every authenticated frame. The key is provided at build time through a generated file that never appears on a command line, and a bench profile is the only one that accepts the public default.
- Trade-off
- The current design has a short key and a short tag, no replay protection and no encryption. These are open items to settle before flight, and the site lists them as such.
Go deeper
The dispatcher reports a verdict per frame, and every verdict is counted.
In the code:
App/comms/comms.c
How it starts
- 01Reset
The processor comes out of reset with nothing trusted yet.
Go deeper
Every boot starts from the initial state with an unknown battery: only the ground-commanded beacon override survives a reset.
- 02MPU and hardware watchdog
Memory protection and the independent watchdog are switched on before anything else runs.
Go deeper
The IWDG (about 31 s) is started first, so a hang in the earliest code still ends in a reset. Long work before the scheduler calls a boot kick.
- 03HAL and SRAM2 parity
The hardware layer starts and the 64 KB parity-protected RAM block is initialised.
Go deeper
SRAM2 parity must be initialised before any protected data is used. A corrupted read raises an NMI, is recorded, and resets the OBC.
- 04Clocks and peripherals
Clocks and the buses to the radio, memory and sensors come up.
Go deeper
The radio sits on SPI1 with DMA, the FRAM bank on I2C1 and the camera bus on I2C2.
- 05Fault handlers and image CRC
Crash handlers are installed and the firmware image is checked against its stored CRC-32.
Go deeper
The check runs in pure software, so a mis-configured peripheral cannot defeat it. The expected value is the last four bytes of the image, stamped at build time.
- 06Dual-bank policy check
The bank-switch mechanism is examined.
Go deeper
The golden-image fallback is implemented but inhibited in this build: its slot collides with the LastStates log, so it can never arm and is not an available flight fallback.
- 07Memory and forensic log
The FRAM bank and the LastStates log, a ring in internal Flash, are initialised.
Go deeper
The log is 64 slots of 128 bytes at 0x08080000. Every state transition is recorded there before it is committed.
- 08Boot faults persisted
Anything that went wrong early in the boot is written to the log now that it can be.
Go deeper
The fault has to be persistable, which is why the CRC policy runs only after the log is up.
- 09Boot CRC policy
The result of the image check decides how the software carries on.
Go deeper
On failure it can reset. After its retry budget it continues in an untrusted, constrained mode and can only beacon, because there is no golden image to fall back to.
- 10Radio, state machine, watchdog, SEU
The remaining subsystems are prepared: temperature sensing, the radio, the state machine, the watchdog and radiation protection.
Go deeper
The return value of the radio initialisation is not checked at boot in the current snapshot. It is listed as an open item.
- 11Tasks created
The FreeRTOS tasks are created.
Go deeper
Created in main(): default, state machine, watchdog, LoRa beacon, LoRa receive and SEU scrub. The AOCS, CLOUD and CLEAR task factories are not wired in main() yet.
- 12Scheduler starts
FreeRTOS takes over.
Go deeper
The watchdog monitor declares the boot good only after the software has stayed up for a stability window.
What runs on the computer
Each task promises how often it will report in. The watchdog resets the computer if one stays silent for more than three times its period.
- stateMachinecreated in main()
Operational state machine · period 100 ms
Go deeper
10 Hz loop. Runs the boot sequence, polls the EPS at 1 Hz, checks the battery on its own and feeds the watchdog.
- loraRXcreated in main()
Uplink receiver · period 100 ms
Go deeper
Interrupt-driven. Validates and authenticates each frame, then dispatches the command.
- loraBeaconcreated in main()
Downlink beacon · period 1 to 16 min
Go deeper
A 128-byte beacon sent in three chunks of at most 64 bytes, at an interval that depends on the state. One transmission can hold the task for about 6 s.
- watchdogcreated in main()
Task liveness monitor · period 500 ms
Go deeper
Flags any task silent for more than three times its declared period, records it and resets the OBC. It also refreshes the hardware and the external watchdog.
- seuScrubcreated in main()
Radiation scrubber · period periodic
Go deeper
Reads the critical structures, votes over redundant copies and repairs them.
- defaultTaskcreated in main()
Housekeeping · period 1 s
Go deeper
Created by the code generator. Registered with the watchdog like every other task.
- AOCSnot created yet
Attitude control · period 20 ms
Go deeper
The task factory exists, but it is not created in main() in this snapshot. The OBC does not run attitude control: the AOCS board is a separate microcontroller.
- CLOUDnot created yet
Payload · period 90 min
Go deeper
The task factory exists, but it is not created in main() in this snapshot.
- CLEARnot created yet
Payload · period 1 s
Go deeper
The task factory exists, but it is not created in main() in this snapshot.
Surviving radiation
Charged particles flip bits in memory. JOS answers in layers, so that a single flip is repaired and a second one is contained instead of being trusted.
- Hardware parity
A parity bit per byte in SRAM2. A corrupted read raises an interrupt.
Go deeper
It only fires when the bad byte is read and it cannot repair anything: the answer is always a reboot.
- Scrubbing and voting
Every few moments the critical data is read and compared with a redundant copy.
Go deeper
Three legs vote: the live object, a shadow copy at another address and a reference CRC-32, plus a non-volatile record in FRAM. A single upset is repaired; two different ones are recorded and contained.
- Watchdogs
A stalled task is detected and the computer restarts.
Go deeper
A software monitor at 500 ms, the internal watchdog and a separate external watchdog chip on the board.
- Forensic log
Every event is written to a ring in flash that survives a reset.
Go deeper
Each scrub outcome is stored with the region, the flipped bits and the running counters, so the ground can trend the radiation environment.
Try it (simulated)
One protected byte with three legs. Flip a bit in any leg, then let the scrubber vote. Flip two different legs to see a fault that cannot be repaired.
The byte is healthy: live and shadow both match the reference.
Go deeper: the decision table
| Live | Shadow | Conclusion | Action |
|---|---|---|---|
| live matches | shadow matches | Healthy | nothing to do |
| live matches | shadow differs | Shadow was hit | shadow rebuilt from live |
| live differs | shadow matches | Live was hit | live rewritten from the shadow |
| live differs | shadow differs, both equal | Reference was hit | reference recomputed |
| live differs | shadow differs, not equal | Unrecoverable | recorded and contained, never guessed |
The demo uses one byte and a CRC-8 to show the logic. The firmware votes over whole regions with CRC-32.
How it is verified
This software is meant for flight, so nothing is merged without passing the same gates every time.
- Firmware build
The image is built with the pinned toolchain in CI, which is the authoritative build.
- Static analysis
cppcheck runs as a gate, and a canary proves the gate can still fail.
- Unit tests with a coverage gate
Host tests with Ceedling, Unity and CMock. The gate requires at least 90% of lines and 75% of branches. It is a threshold that must be met, not a measured result quoted here.
- CodeQL
Security analysis runs on every pull request and every push to the main branch.
- Workflow linting
The CI workflows themselves are checked.
Go deeper
Hardware-in-the-loop: a two-ESP32 harness (a development aid) simulates the neighbouring boards. The authoritative specification is a document set maintained by the team outside the repository; where code and documents disagree, the documents win.
Talk to the satellite
A simulated ground terminal using the real command names. Type help. Nothing is transmitted and there are no keys here.
What is real today
Implemented in the current snapshot
- Five-state operational state machine with a transition log written before each commit.
- Boot-time firmware image CRC-32, stamped at build time.
- Software task-liveness monitor plus internal and external hardware watchdogs.
- SRAM2 parity and a three-way vote and repair scrubber.
- Crash and stack-overflow handlers that write to the forensic log.
- LoRa link with chunked downlink and an authenticated uplink.
- Fail-closed authentication: without a provisioned key every authenticated frame is rejected.
Still open
- The dual-bank golden-image fallback is implemented but inhibited in this build, so it is not a flight fallback.
- The EPS and AOCS frame formats are not yet defined by the project documentation, so those interfaces are still being defined.
- The AOCS, CLOUD and CLEAR tasks are not created in main() yet.
- The uplink has no replay protection and the authentication key and tag are short: strength must be confirmed before flight.
- The radio initialisation result is not checked at boot.
- SET_CONFIG and SEND_DATA are validated but not implemented, and there is no downlink path for the forensic log yet.
Source: the public repository documentation. Where the code and the project documents disagree, the documents prevail.
Artificial intelligence
AI, close to the metal
- Local LLM
redditsenty
Sentiment analysis of Reddit posts done entirely on a local model: Qwen 3 4B (Q8) served by llama.cpp, a FastAPI backend, SQLite storage and a Chart.js dashboard.
EvidencePicks NVIDIA CUDA, AMD ROCm or Apple Metal automatically, exposes a REST API for posts, sentiment distribution and timelines, and refreshes the dashboard every 5 minutes.
View on GitHub ↗ - Neural networks
tternary
A ternary neural network in PyTorch, with its tokenizer written in Rust and exposed to Python through PyO3.
EvidenceReproducible from a clean checkout with uv and maturin, and trainable with a single command (a tiny configuration for 100 steps).
View on GitHub ↗ - Low-power systems
limitless-labs
A dated log of measurements from a Raspberry Pi 3 with 906 MB of RAM: memory budget, software-rendered WebGL, build pipelines and what breaks silently on weak hardware.
EvidenceMeasured on the host: 906 MB of RAM with about 193 MB free under load, a 334 MB headless browser and a 137 MB Python environment. The notes are written by an AI agent, as the repository says.
View on GitHub ↗ - Embedded QA
fieldnote-firmware-qa
A release-quality starter kit for teams shipping hardware, firmware or software: checklists, templates, CI scaffolding and a deterministic bundle builder. It is a starter kit, not a certification.
EvidenceShips with artifact and privacy tests, builds its ZIP bundle deterministically, and includes a public validation landing page.
View on GitHub ↗
About and contact
Let us talk
Nicola Destro studies aerospace engineering with a specialisation in Space and leads the software of the J2050 PocketQube project. Computer science, and AI in particular, is the other half of the work. Open to internships, theses and projects in flight software and applied AI.
Earth imagery: NASA Visible Earth (Blue Marble and Earth at Night). Star positions: Yale Bright Star Catalogue (Hoffleit and Warren), via CDS Strasbourg. The orbit shown in the background is a simulation of a generic sun-synchronous orbit, not the real trajectory of the satellite.