Firmware for the MECSEM controller stack. Each board is an independent RP2350 (Pico-SDK 2.2.0) target sharing a common library (mecsem-std/) and a protobuf-based message protocol (ext/mecsem_protobuf).
Safety notice. This firmware drives kV-level electronics and a vacuum system (turbo + roughing pump + isolation valve). Do not flash it onto hardware without the corresponding hardware and interlock review.
| Directory | Target board | Role in the stack | USB |
|---|---|---|---|
lv-controller/ |
LV controller | Roughing/turbo pump state machine, isolation-valve sequencing, Pirani gauge, HYVFD turbo control | Dual CDC (mscf+console) |
hv-controller/ |
HV controller | ±5 kV inverter enables, six 5 kV amp setpoints, scintillator bias, raster scan, PMT readout, MSCF bridge to cathode controller | Dual CDC + UART link to cathode controller |
hv-inverter/ |
HV inverter (×2 variants) | Buck control + arc detection for the negative 5 kV inverter and the positive scintillator bias | Dual CDC |
cathode-controller/ |
Cathode controller | Battery-powered cathode/filament current loop and battery monitor | UART MSCF only (USB disabled) |
beam-align/ |
Beam alignment (×2 stages) | Two independent ±0.5 A current sources for primary/secondary beam alignment magnetics | Dual CDC |
The cathode controller is battery-powered and isolated; it speaks MSCF (MECSEM communications format, see below) over UART to the HV controller, which bridges those messages to the host over USB.
firmware/
├── lv-controller/ # Per-board firmware distros (each has main.cpp,
├── hv-controller/ # Pinout.hpp, StateVariables.cpp, lib_config.hpp,
├── hv-inverter/ # CMakeLists.txt, and a board header)
├── cathode-controller/
├── beam-align/
├── mecsem-std/ # Shared library (scheduler, drivers, MSCF, state vars)
├── ext/ # Submodules: pico-sdk, tinyusb, nanopb, fmt,
│ # mecsem_protobuf, openocd
│ # plus mecsem-tinyusb.patch (see ext/tinyusb)
├── flash.sh # ./flash.sh path/to/firmware.elf (CMSIS-DAP via openocd)
├── start-openocd.sh # Launch openocd for gdb attach
├── mecsem-openocd # Wrapper that invokes ext/openocd/src/openocd
├── format.sh # clang-format the tree (skips ext/ and build/)
└── cloc.sh # SCC line-count report
- arm-none-eabi-gcc capable of C++23 (the CMake config sets
CMAKE_CXX_STANDARD 23). - CMake >= 3.13 (per the per-board
cmake_minimum_required). - Python 3 (Pico-SDK build scripts).
- picotool — pulled in transitively by the Pico-SDK build.
- clang-format if you intend to run
format.sh. - openocd — a Raspberry Pi fork is vendored at
ext/openocdand used bymecsem-openocd. Build it once:cd ext/openocd && ./bootstrap && ./configure && make.
The build pulls in submodules, initialize with: git submodule update --init --recursive
Pinned submodule versions:
| Submodule | Version |
|---|---|
ext/pico-sdk |
2.2.0 |
ext/tinyusb |
0.20.0 (patched, see below) |
ext/nanopb |
0.4.9.1 |
ext/fmt |
12.1.0 |
ext/mecsem_protobuf |
tracks main |
ext/openocd |
Raspberry Pi fork, sdk-2.2.0 |
ext/mecsem-tinyusb.patch factors tud_task_ext's inner switch into a new tud_task_iter(timeout_ms) so USBTask can timeslice it inside the cooperative scheduler. The patch is not auto-applied by CMake; apply it once after git submodule update:
cd ext/tinyusb
git apply ../mecsem-tinyusb.patchSee mecsem-std/Tasks/USB/README.md for details on what the patch changes and why.
Each board lives in its own CMake project. To build, e.g., the LV controller:
cd lv-controller
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -jThe output ELF is lv-controller/build/lv-controller-firmware.elf (and .uf2/.bin/.hex/.map). Other boards follow the same pattern; the produced binary is named <board>-firmware.<ext>.
Flash via CMSIS-DAP (e.g., a Picoprobe):
./flash.sh lv-controller/build/lv-controller-firmware.elfFor interactive debugging:
./start-openocd.sh # leaves openocd listening on :3333 / :4444
arm-none-eabi-gdb lv-controller/build/lv-controller-firmware.elf -ex 'target extended-remote :3333'Debug builds (-DCMAKE_BUILD_TYPE=Debug) enable MECSEM_DEBUG_ENABLED, which turns on MECSEM_ASSERT and disables LTO/whole-program optimization.
Two boards support per-build variants via a CMake-level define:
hv-inverter: defineMECSEM_HV_INVERTER_SCINTto build the positive scintillator-bias variant (PID0xABD0, producthv-scint-inverter); otherwise it builds the negative 5 kV inverter (PID0xABCF, producthv-inverter).beam-align: defineMECSEM_BEAM_ALIGN_STAGE2for the secondary stage (PID0xABD2,beam-align-s2); otherwise it builds the primary stage (PID0xABD1,beam-align-s1).
Pass the define via -DCMAKE_CXX_FLAGS=-DMECSEM_HV_INVERTER_SCINT or set it in the board's lib_config.hpp before configuring.
USB-enabled boards expose two CDC ACM interfaces under VID 0xFEFE, manufacturer string "MECSEM":
| Interface | Index | Descriptor name | Purpose |
|---|---|---|---|
| CDC 0 | 0 | mscf |
Binary protocol (nanopb + COBS framing + CRC32C); see mecsem-std/Interface/MSCFInterface.hpp |
| CDC 1 | 1 | console |
Human-readable command shell (SerialTTY) |
Per-board PIDs and product strings are in each board's lib_config.hpp (see the build-variants table above).
See .clang-format. Conventions:
- Globals:
g_camelCase - Constants:
kPascalCase - State variables:
SCREAMING_SNAKE_CASE(registered viaMECSEM_DECL_STATE_VAR/MECSEM_IMPL_STATE_VAR— seemecsem-std/State/StateVariable.hpp)
Run ./format.sh from the repo root to format all in-tree C/C++ sources.