-
Notifications
You must be signed in to change notification settings - Fork 18
Software Loadout
If you find something confusing, please let @nateinaction know OR contribute clarification directly to the Wiki.
Need help with:
3.3: need a step-by-step guide to determine coil parameters
5.3: need a step-by-step guide on how to remove existing telemetry (is this only valid with the tlm archive component?)
5.5: need an expert review of OTA procedure AND explanation why you load into a slot but don't boot from it?
How to take a stock proves-core-reference and turn it into the flight build for a specific
PROVES Kit satellite, then get that build and its supporting files onto the flight controller
before delivery.
You need, per the reference README:
- The mission secrets bundle (see §3.1) — signing key + HMAC key
Everything else (fprime-venv, Zephyr workspace, SDK) is set up by make.
Each satellite gets its own fork. The fork keeps main tracking upstream and holds all
mission-specific edits on a long-lived configuration branch, so upstream fixes can be merged in
repeatedly without rebasing the mission config away.
-
Fork
Open-Source-Space-Foundation/proves-core-referenceinto the org, named after the satellite (e.g.david-cubesat). -
Clone it and wire up the upstream remote:
git clone git@github.com:Open-Source-Space-Foundation/<your-cubesat>.git cd <your-cubesat> git remote add upstream git@github.com:Open-Source-Space-Foundation/proves-core-reference.git
-
Create the configuration branch:
git checkout -b configuration git push -u origin configuration
-
Build the toolchain and the stock image once, to confirm the fork builds before you change anything:
make
To pick up upstream changes later:
git fetch upstream
git checkout configuration
git merge upstream/mainDo this before configuring, so you have a working GDS to verify each change against.
The GDS must be able to sign uplinked commands, so build the framing plugin first (once per checkout):
make framer-plugin
make gdsTo point the GDS at a specific serial device:
make gds UART_DEVICE=/dev/ttyACM0 # Linux
make gds UART_DEVICE=/dev/cu.usbmodem1201 # macOSThe GDS reads the HMAC key from
PROVESFlightControllerReference/Components/TcSecurityDeframer/AuthDefaultKey.hin your checkout. If that file does not match the key compiled into the board's image, every authenticated command will be rejected. See §3.1.
After connecting to a board, sync the anti-replay sequence number so the GDS and flight software agree:
make sync-sequence-numberEverything in this section is a code edit committed to the configuration branch. Work through
it in order; the last two items (§3.10, §3.11) turn the radio and antenna live and should be done
only when the satellite is otherwise flight-ready.
Uplinked commands are authenticated per CCSDS 355.0-B-2 by
TcSecurityDeframer.
The key lives in a generated, git-ignored header:
PROVESFlightControllerReference/Components/TcSecurityDeframer/AuthDefaultKey.h
Two ways to populate it:
# Development: generate a random 16-byte key on this machine
make generate-auth-key
# Flight: install the mission key from the secrets bundle
make copy-secrets SECRETS_DIR=/path/to/mission-secretscopy-secrets installs three files — keys/proves.pem and keys/proves.pub.pem (the MCUBoot
image-signing keypair) and AuthDefaultKey.h (the command HMAC key).
Rules for flight:
- Generate the mission HMAC key once, store it in the mission secrets store, and use
copy-secretseverywhere after that. Never letmake generate-auth-keysilently mint a different key on an operator's laptop. - The same header must be present on every machine that will command the satellite, because the GDS framing plugin reads it.
-
keys/proves.pemmust match the key the flashed MCUBoot bootloader was built with, or signed images will not boot.
The IMU is soldered to the board in a fixed orientation, but the board's orientation inside the
chassis varies per build. Correct for it with the AXIS_ORIENTATION parameter in
PROVESFlightControllerReference/Components/ImuManager/ImuManager.fpp:
param AXIS_ORIENTATION: AxisOrientation default AxisOrientation.STANDARD id 3
Allowed values: STANDARD, ROTATED_90_DEG_CW, ROTATED_90_DEG_CCW, ROTATED_180_DEG.
How to determine it: with the satellite assembled and the GDS connected, place the satellite so that
it is sitting on its side (usually on a solar board) and the text of the top cap is right-side-up. Watch
the IMU telemetry and pick the orientation that makes the acceleration of gravity read -9.8 on the Y axis.
Another way to visualize this is to consider the satellite to be on an X/Y coordinate plane where X is the
surface of the table and Y flows from the ceiling to the floor. In this scenario, gravity is a vector flowing
through the top face of the satellite through the bottom face of the satellite.
This step is important if you do not have 4 equivalent solar boards to control the craft.
TODO: A short video should be added here.
Which magnetorquer drives which body axis depends on how the face boards are populated and cabled. Setup your satellite the same way as in the previous step: sitting on its side (usually on a solar board) and the text of the top cap is right-side-up. In the GDS, turn on each face. If you're looking at the top cap and the right face lights turns on, it is the X+, left is X-, top is Y+ making the final face is Y-.
This step ensures that detumble is torquing the satellite in the correct direction; without it, detumble may introduce spin to your satellite.
These settings are found in the connections DetumbleManager block of
PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp:
detumbleManager.xPlusStart -> drv2605Face0Manager.start
detumbleManager.xMinusStart -> drv2605Face1Manager.start
detumbleManager.yPlusStart -> drv2605Face2Manager.start
detumbleManager.yMinusStart -> drv2605Face3Manager.start
detumbleManager.zMinusStart -> drv2605Face5Manager.start
…and the matching ...Stop block immediately below it. Both blocks must be remapped
identically — a mismatch leaves a coil energized.
detumbleManager.xMinusStart -> drv2605Face3Manager.start
detumbleManager.yPlusStart -> drv2605Face1Manager.start
detumbleManager.yMinusStart -> drv2605Face2Manager.start
Also set the coil electrical parameter defaults in
PROVESFlightControllerReference/Components/DetumbleManager/DetumbleManager.fpp to match the
coils actually flown — X_PLUS_RESISTANCE, X_MINUS_RESISTANCE, Y_PLUS_RESISTANCE,
Y_MINUS_RESISTANCE (and voltage / length / width / shape if the coil geometry differs).
If you do not know these numbers, detumbling may take longer or may not work at all.
# --- X+ Coil ---
param X_PLUS_VOLTAGE: F64 default 3.3 id 9
param X_PLUS_RESISTANCE: F64 default 57.2 id 10
param X_PLUS_LENGTH: F64 default 0.053 id 12
param X_PLUS_WIDTH: F64 default 0.045 id 13
param X_PLUS_SHAPE: CoilShape default CoilShape.RECTANGULAR id 33
TODO: Add a step-by-step guide to determine coil parameters.
When ModeManager exits safe mode it turns on the load switches wired to loadSwitchTurnOn.
The reference ships with all of those connections commented out, marked
TODO(ALLTEAMS) — meaning a stock build never powers a face back on after safe mode. You must
wire the faces this satellite actually flies:
This step helps ensure your satellite conserves power when it's low on battery.
# In connections ModeManager, topology.fpp
# TODO(ALLTEAMS): Configure the faces you want to automatically turn on
# modeManager.loadSwitchTurnOn[0] -> face4LoadSwitch.turnOn
# ...
modeManager.loadSwitchTurnOn[0] -> face0LoadSwitch.turnOn
modeManager.loadSwitchTurnOn[1] -> face1LoadSwitch.turnOn
modeManager.loadSwitchTurnOn[2] -> face2LoadSwitch.turnOn
modeManager.loadSwitchTurnOn[3] -> face3LoadSwitch.turnOn
modeManager.loadSwitchTurnOn[4] -> face5LoadSwitch.turnOn
modeManager.loadSwitchTurnOff[0] -> face0LoadSwitch.turnOff
... (same five)
| Item | Value |
|---|---|
| Spacecraft ID (See instructions below) | 4 |
Default RTC to proc time (RtcManager.fpp) |
Set default to TB_PROC_TIME
|
| Item | Value |
|---|---|
| Spacecraft ID (See instructions below) | 5 |
| Extra | Merge the Mosaic component PR into the configuration branch |
Set the assigned SCID for this satellite so ground stations can identify your satellite from other PROVES Kit-based satellites.
PROVESFlightControllerReference/project/config/ComCfg.fpp:
module ComCfg {
@ Spacecraft ID (10 bits) for CCSDS Data Link layer
dictionary constant SpacecraftId = 0x0044
}
Remember to update the ground side to match the YAMCS adapter's
SPACECRAFT_ID (decimal) and yamcs/yamcs-data/etc/yamcs.fprime-project.yaml.
ProvesRouter accepts a small set of opcodes without authentication (so an unsynced ground
station can still no-op the satellite and read back its sequence number). That allowlist is a
hard-coded array in
PROVESFlightControllerReference/Components/ProvesRouter/Bypasser.cpp:
static constexpr uint32_t kBypassOpCodes[] = {
0x01000000, //!< CdhCore.cmdDisp.CMD_NO_OP
0x2100B000, //!< ComCcsdsUart.tcSecurityDeframer.GET_SEQ_NUM
0x2200B000, //!< ComCcsdsLora.tcSecurityDeframer.GET_SEQ_NUM
0x2300B000, //!< ComCcsdsSband.tcSecurityDeframer.GET_SEQ_NUM
0x10065000, //!< ReferenceDeployment.amateurRadio.TELL_JOKE
};Opcodes may shift whenever component base IDs or the topology change, so after make build verify
every entry still resolves to the command its comment claims. Run this from the repo root:
python3 - <<'PY'
import json, re
dict_path = "build-artifacts/zephyr/fprime-zephyr-deployment/dict/ReferenceDeploymentTopologyDictionary.json"
src_path = "PROVESFlightControllerReference/Components/ProvesRouter/Bypasser.cpp"
d = json.load(open(dict_path))
want = {int(m, 16) for m in re.findall(r"0x([0-9A-Fa-f]{8}),", open(src_path).read())}
byop = {c["opcode"]: c["name"] for c in d["commands"] if c["opcode"] in want}
for op in sorted(want):
print(f"0x{op:08X} {byop.get(op, '*** NOT IN DICTIONARY ***')}")
PYAny *** NOT IN DICTIONARY *** line is either a stale opcode (fix Bypasser.cpp) or a component
that is not in this build (e.g. S-band is not compiled into every deployment — confirm which
before deciding it is harmless). A stale entry is a security-relevant defect: it either silently
disables a needed bypass, or leaves an unauthenticated hole pointing at whatever command inherited
that opcode.
When multiple PROVES satellites deploy into the same orbit on the same frequency
allocation, set ComDelay so they do not transmit on top of each other.
How it works. ComDelay sits between the radio's flow control and the framer.
It counts rate-group ticks and releases a "ready to send" token only once every
DIVIDER + 1 ticks, holding the downlink off in between:
period = (DIVIDER + 1) x tick interval
There are two instances, on different rate groups:
| Instance | Rate group | Divider | Period |
|---|---|---|---|
downlinkDelay |
10 Hz | 299 | 30 s |
telemetryDelay |
1 Hz | 29 | 30 s |
Why it separates satellites. Nothing synchronizes the constellation — each board counts ticks from its own boot on its own crystal. If two satellites run the same cadence, then once they drift into alignment they stay aligned for hours, because only crystal error pulls them apart again. Give them cadences that are not simple multiples of one another and any overlap drifts back out within a cycle or two instead of beating in lockstep.
Because the period is DIVIDER + 1 ticks, the value that must be distinct and
co-prime across the pod is DIVIDER + 1, not the divider itself. So pick a
prime and subtract one. Near 30 s on the 10 Hz group:
DIVIDER |
Period ticks (prime) | Period |
|---|---|---|
| 292 | 293 | 29.3 s |
| 306 | 307 | 30.7 s |
| 310 | 311 | 31.1 s |
| 312 | 313 | 31.3 s |
| 316 | 317 | 31.7 s |
This spreads collisions out and shares them fairly; it does not make them rare. Collision frequency is driven by how much airtime each satellite uses, so keep relying on retransmission.
The ComDelay divides the 1 Hz rate group to set the beacon/downlink cadence.
PROVESFlightControllerReference/Components/ComDelay/ComDelay.fpp:
constant DEFAULT_DIVIDER = 299 # On a 1Hz input, outputs every ~30s
There are two dividers in play, and the startup sequence sets both at runtime:
R00:00:00 ReferenceDeployment.downlinkDelay.DIVIDER_PRM_SET, 299
R00:00:00 ReferenceDeployment.telemetryDelay.DIVIDER_PRM_SET, 299
Set the flight value in both places — the FPP default (what runs if the startup sequence never
executes) and sequences/startup.seq — or the two will disagree.
Anything else this satellite needs: payload components merged in, camera configuration, additional
sequences. Keep these as separate commits on configuration so upstream merges stay readable.
PROVESFlightControllerReference/Components/StartupManager/HardCodedStartup.h:
// 0 means no HardCodedStartup, 1 means HardCodedStartup is enabled
#define DEFAULT_STARTUP_VALUE 0Set to 1 for flight. With it enabled, StartupManager counts down TRANSMIT_ENABLE_TICKS
(default 2800 ticks at 1 Hz — the 45-minute quiescence plus 100 s margin), then asserts
enableTransmit and logs HardcodedRadioEnable, turning the LoRa transmitter on independently
of the startup sequence. That is the backstop that gets the satellite talking even if
startup.bin is missing or fails.
Leave it at 0 for all ground testing so the radio cannot key up in the lab. This is the last
software change before delivery.
make buildThis produces, at the repo root:
| Artifact | Use |
|---|---|
bootable.uf2 |
Copy to the board in UF2 bootloader mode |
bootable.signed.hex |
Flash over SWD (make debug-install bootable.signed.hex) |
Flash by putting the board into UF2 bootloader mode and copying:
cp bootable.uf2 /Volumes/RP2350 # macOS; use findmnt on LinuxThen reconnect the GDS and confirm the version telemetry matches what you just built.
With the flight image running and the GDS connected.
Clears every file left over from integration and testing — boot count, mode state, quiescence start, sequence number, old telemetry, stale antenna state:
ReferenceDeployment.fsFormat.FORMAT
⚠️ Destructive, the satellite will not be happy about this and will crash. The hardware watchdog will reboot the satellite after some time.
Sequences are text in sequences/, compiled to .bin and uplinked as files.
make sequence SEQ=startup # -> sequences/startup.bin
./tools/bin/make-sequences # or: compile every sequences/*.seq at onceThen uplink with the GDS file uplink, with the destination path the flight software expects. The
startup sequence must land at /startup.bin (the STARTUP_SEQUENCE_FILE parameter of
StartupManager); other sequences go where the operator will cmdSeq them from.
For uplink over the radio, slow the GDS down so packets do not overrun the transmitter:
fprime-gds --file-uplink-cooldown 0.8See Ops Constraint #2 for the LoRa cooldown values.
Sequences shipped in the reference:
| File | Purpose |
|---|---|
startup.seq |
Post-deploy startup: event filters, antenna deploy at T+45 min, LoRa config, exit safe mode, detumble AUTO for 3 h |
enter_safe.seq / radio_enter_safe.seq
|
Force safe mode |
radio-fast.seq |
Switch LoRa to the fast link settings |
throttle_amateurs.seq |
Reduce amateur-radio activity |
lose_time.seq, your-face.seq, not-your-face.seq, camera_handler_1.seq
|
Test / operations sequences |
Review startup.seq against this satellite's mission before compiling it — the deploy delay,
downlink divider (§3.8), LoRa data rate, and detumble duration are all
mission decisions baked into that file.
To disable the startup sequence entirely, remove the file:
FileHandling.fileManager.RemoveFile, /startup.bin
Remove telemetry and data files accumulated during ground testing so the first downlink after deployment contains only flight data.
TODO Needs step-by-step guide
Delete the deployer's state file and directory so the satellite deploys on orbit:
FileHandling.fileManager.RemoveFile, /antenna/antenna_deployer.bin
FileHandling.fileManager.RemoveDirectory, /antenna
FORMAT in §5.1 already removes these; do this explicitly if you formatted earlier and have since
run a deploy test. Verify with ListDirectory / that no antenna directory remains — this is
the single check that decides whether the antenna deploys in orbit.
MCUBoot is built with two image slots so the satellite can be updated on orbit and roll back if the new image misbehaves.
TODO Review guide below.
Uplink build-artifacts/zephyr.signed.bin via the GDS file uplink, slowed down so packets do not
overrun the transmitter:
fprime-gds --file-uplink-cooldown 0.8UPDATE_IMAGE_FROM takes the expected CRC32 as an argument and verifies the file against it, so
you need this value before running the command. Use the repo's calculator, not an online one —
F´ uses a specific CRC variant (Ops Recommendation #1):
./tools/bin/calculate-crc.py build-artifacts/zephyr.signed.binCommand names below are taken from the built dictionary
(ReferenceDeploymentTopologyDictionary.json), not from the Update::Updater SDD — the SDD in
lib/fprime-extras still documents the older PREPARE_IMAGE / UPDATE_IMAGE_FROM_FILE spellings
and is out of date.
| Order | Command | Arguments |
|---|---|---|
| 1 | Update.updater.PREPARE_UPDATE |
none |
| 2 | Update.updater.UPDATE_IMAGE_FROM |
file, crc32
|
| 3 | Update.updater.CONFIGURE_NEXT_BOOT |
next = TEST
|
| — | Update.updater.CONFIRM_UPDATE |
none — on-orbit only, not at delivery |
PREPARE_UPDATE must come first: it erases the target slot, and the FPP annotation states
"Users are expected to have run the PREPARE_UPDATE command prior to running [UPDATE_IMAGE_FROM]".
Note Ops Constraint #1: FSW freezes for ~5 s during
PREPARE_UPDATE— the watchdog LED and event stream stop, then resume. Expected. Do not run it during anything delicate.
CONFIGURE_NEXT_BOOT, TEST boots the new image exactly once, with automatic reversion to the
previous image on the following boot. CONFIRM_UPDATE is what makes it permanent — run it only
after the new image has booted and been verified on orbit. Do not run it as part of the
delivery loadout.
NEEDS CONFIRMATION: the checklist says "preload but do not use", yet also lists
CONFIGURE_NEXT_BOOT, TESTas part of the preload. Those conflict — armingTESTmeans the satellite boots the preloaded image on its next power cycle. Confirm whether the delivered state should haveCONFIGURE_NEXT_BOOTalready set toTEST, or whether the image should sit in slot 1 untouched withCONFIGURE_NEXT_BOOTdeferred to on-orbit operations. This document assumes the latter is safer.
NEEDS CONFIRMATION: the reference README's OTA notes mention trying
regionnumber = 1instead of2. Confirm which slot the delivered satellite should have loaded.
Capture these from GDS telemetry once the satellite is in its final flight state, and file them with the delivery paperwork. They are the baseline every on-orbit anomaly gets compared against.
| Value | Where to read it |
|---|---|
| Boot count |
StartupManager BootCount telemetry, or the GET_BOOT_COUNT command → CurrentBootCount event |
| Sequence number |
tcSecurityDeframer CurrentSequenceNumber telemetry, or GET_SEQ_NUM → SequenceNumberGet event (per link: UART / LoRa / S-band) |
| Full file listing |
FileHandling.fileManager.ListDirectory, / (check the resulting events); also record FsSpace FreeSpace / TotalSpace
|
| Idle power consumption |
ina219SysManager Voltage / Current / Power telemetry, with the satellite in its delivery quiescent state |
Also record, for the record:
- Git commit SHA of the
configurationbranch that was built - Spacecraft ID
- Downlink divider value
- Whether
DEFAULT_STARTUP_VALUEis1 - Confirmation that no
/antennadirectory exists
- Fork created,
configurationbranch pushed, upstream merged to a known-good commit - Mission HMAC key and signing key installed via
make copy-secrets -
AXIS_ORIENTATIONset and verified against the assembled spacecraft - Detumble start and stop wiring remapped; coil resistances match flown coils
-
ModeManagerload-switch turn-on connections uncommented for the flown faces - Spacecraft ID set in
ComCfg.fpp(and matched on the ground side) - Bypass opcode allowlist verified against the built dictionary
- Downlink divider set to a pod-unique prime, in both
ComDelay.fppandstartup.seq - Amateur radio name resolved (§3.5)
-
DEFAULT_STARTUP_VALUEset to1 -
make buildclean;make check-console-disabledpasses - Flight image flashed; version telemetry confirms it
- Filesystem formatted
-
startup.binuplinked to/startup.bin; other sequences loaded - No
/antennadirectory present - Update image (USP) preloaded into the second bootloader slot, CRC verified,
CONFIRM_UPDATEnot run - Delivery values recorded (§6)
- PreFlight Testing complete
This page was written against:
-
proves-core-referencemain@451c5bcb("Timebase paramter (#455)") -
david-cubesatconfiguration@0d1ae5c, mission commitsceb925f,87b9d1f,6265d71,d553cfd - The built topology dictionary
build-artifacts/zephyr/fprime-zephyr-deployment/dict/ReferenceDeploymentTopologyDictionary.json
Command names, parameter defaults, and file paths were taken from those sources rather than from
component SDDs, several of which are stale (notably the Update::Updater SDD in
lib/fprime-extras, which still documents PREPARE_IMAGE / UPDATE_IMAGE_FROM_FILE).
When main moves, re-check at minimum: the bypass opcode allowlist
(§3.7), the Update.updater command names
(§5.5), and the TODO(ALLTEAMS)
markers in topology.fpp.