F Prime flight software runs on a Raspberry Pi 5. Standalone FreeRTOS firmware runs on a Raspberry Pi Pico 2. They talk over a dedicated 115200 8N1 UART. F Prime GDS uses a separate TCP connection.
The Pico samples an analog IR sensor on GP28 (ADC2) and reports millivolts
against the 3.3 V ADC reference. IrMilliVolts is voltage, not distance. A
mock millivolt source remains a CMake option (-DPICO_LINK_MOCK_SENSOR=ON).
GDS (browser)
TCP :50000
Raspberry Pi 5 — PicoLinkDeployment (F Prime)
UART 115200 8N1
Raspberry Pi Pico 2 — pico_link_firmware (FreeRTOS)
| Path | Role |
|---|---|
common/protocol/ |
Shared C++ line parser and formatter used by F Prime and firmware |
docs/protocol.md |
Exact UART frame grammar, fields, units, and error codes |
Components/PicoLink/ |
F Prime component: commands, events, telemetry, timeouts |
PicoLinkDeployment/ |
Linux F Prime app and topology (GDS TCP + Pico UART) |
firmware/ |
Pico 2 FreeRTOS firmware |
tools/pico_smoke.py |
pySerial hardware or PTY smoke test |
tools/mock_pico_peer.py |
Host-only fake Pico over a pseudo-terminal |
tests/ |
Unit tests for the smoke tool |
requirements.txt |
Python pins: F Prime tools 4.4.0 and pyserial |
settings.ini |
F Prime project settings |
third-party/ |
Local clones of F Prime, Pico SDK, and FreeRTOS (not committed) |
Firmware internals (firmware/):
src/main.cppstarts UART0 on GP0/GP1, then two FreeRTOS tasks.protocol(priority 3) owns UART RX/TX and answers PING/ECHO.sensor(priority 2) handles READ/STREAM and GP28 ADC samples.- Two static queues pass commands and outgoing frames between the tasks.
src/IrAdcSensor.*reads ADC2;src/MockSensor.*is the optional mock.
F Prime internals (Components/PicoLink/):
- Commands:
PING,ECHO,READ_SENSOR,START_STREAM,STOP_STREAM. - One request may be outstanding. A successful UART write does not complete the command. Completion waits for a matching Pico ACK or DATA.
- A second command returns
BUSY. A missing reply times out after 3 seconds.
Pinned versions: F Prime v4.4.0, Pico SDK 2.3.1, Raspberry Pi
FreeRTOS-Kernel commit 4f7299d6ea746b27a9dd19e87af568e34bd65b15.
On the Mac (firmware) or Pi 5 (F Prime):
cd /path/to/RP2350code
git clone --branch v4.4.0 --depth 1 https://github.com/nasa/fprime.git third-party/fprime
git -C third-party/fprime submodule update --init --recursive
git clone --branch 2.3.1 --depth 1 --recurse-submodules \
https://github.com/raspberrypi/pico-sdk.git third-party/pico-sdk
git clone https://github.com/raspberrypi/FreeRTOS-Kernel.git third-party/FreeRTOS-Kernel
git -C third-party/FreeRTOS-Kernel checkout 4f7299d6ea746b27a9dd19e87af568e34bd65b15
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txtFirmware also needs arm-none-eabi-gcc and Ninja. The official Pico VS Code
extension can install them on macOS.
3.3 V logic only. Cross TX to RX. Share ground. Power the Pico from USB.
| Pico 2 | Pi 5 header |
|---|---|
| GP0 TX / physical pin 1 | GPIO15 RX / physical pin 10 |
| GP1 RX / physical pin 2 | GPIO14 TX / physical pin 8 |
| GND / physical pin 3 | GND / physical pin 6 |
A 3.3 V USB-to-UART adapter is easier on Yocto: adapter TX → Pico GP1, adapter
RX → Pico GP0, grounds together. Discover the device; do not assume
/dev/serial0.
3.3 V analog output only. Do not feed a 5 V signal into GP28.
| IR module | Pico 2 |
|---|---|
| VCC | 3V3 / physical pin 36 |
| GND | GND / physical pin 33 |
| analog OUT | GP28 / ADC2 / physical pin 34 |
Firmware converts 12-bit ADC counts with millivolts = counts * 3300 / 4095.
Treat IrMilliVolts as voltage. Do not interpret it as range unless you add a
calibration for a specific module.
ls -l /dev/serial/by-id/ /dev/ttyAMA* /dev/ttyUSB* /dev/ttyACM* 2>/dev/nullThese run on the Mac and check framing, recovery, and the smoke tool.
cd /path/to/RP2350code
. .venv/bin/activate
cmake -S common/protocol -B build/protocol
cmake --build build/protocol --parallel
ctest --test-dir build/protocol --output-on-failure
python3 -m unittest discover -s tests -vExpected: PicoProtocol tests passed and four Python tests ok.
Terminal 1:
. .venv/bin/activate
python tools/mock_pico_peer.pyIt prints a path such as /dev/ttys012. Terminal 2:
. .venv/bin/activate
python tools/pico_smoke.py --port /dev/ttys012 --baud 115200Expected:
PASS PING: PONG
PASS ECHO: smoke-test
PASS READ: ... source=MOCK ...
PASS STREAM sample: ... source=MOCK ...
PASS STOP_STREAM
All PicoLink smoke checks passed
This proves the host protocol, not Pico hardware.
export PICO_SDK_PATH="$PWD/third-party/pico-sdk"
export FREERTOS_KERNEL_PATH="$PWD/third-party/FreeRTOS-Kernel"
cmake -S firmware -B firmware/build -G Ninja -DPICO_BOARD=pico2
cmake --build firmware/build --parallelAdd -DPICO_LINK_MOCK_SENSOR=ON only if you want a sawtooth 0..3300 mV source
instead of GP28.
Flash firmware/build/pico_link_firmware.uf2: hold BOOTSEL, plug in a USB
data cable, copy the UF2, wait for the drive to disappear.
USB logs should show:
PicoLink FreeRTOS starting: UART0 GP0/GP1 115200 8N1
Sensor task: source=REAL ready=1
Then, with only the smoke tool using the UART:
python tools/pico_smoke.py --port /dev/serial/by-id/YOUR_ADAPTER --baud 115200Close the smoke tool before starting F Prime.
cd /path/to/RP2350code
. .venv/bin/activate
fprime-util generate
fprime-util build -j4Outputs:
build-artifacts/Linux/PicoLinkDeployment/bin/PicoLinkDeployment
build-artifacts/Linux/PicoLinkDeployment/dict/PicoLinkDeploymentTopologyDictionary.json
Start flight software:
build-artifacts/Linux/PicoLinkDeployment/bin/PicoLinkDeployment \
-a 0.0.0.0 -p 50000 \
-d /dev/serial/by-id/YOUR_ADAPTER \
-b 115200In another terminal:
. .venv/bin/activate
fprime-gds --no-app \
--dictionary build-artifacts/Linux/PicoLinkDeployment/dict/PicoLinkDeploymentTopologyDictionary.json \
--ip-address 127.0.0.1 --ip-port 50000 --ip-clientOpen the GDS URL (usually http://127.0.0.1:5000) and send:
PicoLinkDeployment.picoLink.PINGPicoLinkDeployment.picoLink.ECHO(valuehas no comma, CR, or LF)PicoLinkDeployment.picoLink.READ_SENSORPicoLinkDeployment.picoLink.START_STREAM(period_ms10..60000)PicoLinkDeployment.picoLink.STOP_STREAM
A command is OK only after a matching Pico reply. Watch
DriverReady, RequestSent, ReplyReceived. Telemetry lives under
PicoLinkDeployment.picoLink. Use a sample only when SampleValid=1.
SampleSource=2 is the real GP28 ADC. IrMilliVolts is analog voltage.
CMD,<id>,PING
CMD,<id>,ECHO,<text>
CMD,<id>,READ
CMD,<id>,STREAM,<period_ms> # 0 = stop
ACK,<id>,OK[,payload]
ERR,<id>,<code>
DATA,<id>,<seq>,<uptime_us>,<valid>,<MOCK|REAL>,<ir_millivolts>,<age_ms>,<status>
IDs 1–65535 identify requests. ID 0 is unsolicited stream data. There is no
CRC; this is a bench protocol. Full grammar: docs/protocol.md.
- Build Pico 2 with
-DPICO_BOARD=pico2, notpico. - Use a USB data cable for BOOTSEL flashing.
- Cross TX/RX, share ground, stay at 3.3 V and 115200 8N1.
- Only one process may own the UART.
- Pico USB CDC is logs only; protocol is GP0/GP1.
/dev/serial0on Pi 5 is often the debug connector, not header pins 8/10.- Invalid or zero readings are not healthy unless
valid=1and source/age match. - Keep the IR analog pin at or below 3.3 V. Zero millivolts can be a real dark
reading; check
SampleValidand wiring before treating it as a fault.