1. What You Are Building
A custom board has two software sides:
- Firmware side on Pico/Pico2 — file pattern:
firmware/modules/breakoutboard_<ID>.c. Handles GPIO, I2C expanders, DACs, and safety state. Maps raw board state into packet fields. - LinuxCNC HAL side — file pattern:
hal-driver/modules/breakoutboard_hal_<ID>.c. Exports HAL pins and maps packet fields to those pins. Packs HAL outputs back into packet fields sent to firmware.
Both sides are selected using breakout_board in firmware/inc/config.h and hal-driver/config.h.
2. Prerequisites
- Working stepper-ninja build environment.
- Pico SDK installed (or already usable by
firmware/CMakeLists.txt). - LinuxCNC dev headers/tools installed for HAL module build.
- Reference implementation to copy:
firmware/modules/breakoutboard_1.candhal-driver/modules/breakoutboard_hal_1.c.
3. Hardware Planning Checklist
Lock down these items before writing any code:
- Power rails & logic levels — 3.3 V logic compatibility with Pico GPIO; level shifting/opto isolation where needed.
- Safety behaviour — define exactly what outputs must do on communication timeout. Enable lines must fail safe (motor enable off, spindle disable, analog outputs zero).
- I/O budget — digital input/output count; analog output count and DAC type; encoder channels and index behaviour.
- Bus / peripheral allocation — I2C bus/pins for expanders/DACs; SPI/WIZnet wiring and reset/interrupt lines.
- Signal integrity — keep step/dir traces clean and short; add pull-ups/pull-downs where boot-time state matters.
4. Choose a New Board ID
Pick an unused integer ID — for example 42. You will use this same ID in:
firmware/modules/breakoutboard_42.chal-driver/modules/breakoutboard_hal_42.c#define breakout_board 42in both config headers- Selection branches in firmware and HAL build paths
5. Firmware Side Integration
5.1 Create the firmware module
cp firmware/modules/breakoutboard_user.c firmware/modules/breakoutboard_42.c
Change the guard:
#if breakout_board == 42 // was 255
Implement the required callbacks:
breakout_board_setup()— init GPIO/I2C/SPI devices, probe and configure expanders and DACs.breakout_board_disconnected_update()— force all outputs to safe state; set DAC outputs to zero or safe bias.breakout_board_connected_update()— apply outputs from received packet; refresh input snapshots from hardware.breakout_board_handle_data()— fill outbound packet fields (tx_buffer->inputs[]etc.); consume inbound fields (rx_buffer->outputs[], analog payloads).
5.2 Add your module to the firmware build
Edit firmware/CMakeLists.txt and add your source file inside add_executable(...):
modules/breakoutboard_42.c
5.3 Define board macros in firmware config footer
Edit firmware/inc/footer.h and add a new block:
#if breakout_board == 42
// #define I2C_SDA 26
// #define I2C_SCK 27
// #define I2C_PORT i2c1
// #define MCP23017_ADDR 0x20
// #define ANALOG_CH 2
// #define MCP4725_BASE 0x60
// override stepgen/encoder/in/out/pwm macros as needed
#endif
5.4 Select board in firmware config
#define breakout_board 42
6. HAL Driver Side Integration
6.1 Create HAL board module
cp hal-driver/modules/breakoutboard_hal_user.c hal-driver/modules/breakoutboard_hal_42.c
Change the guard:
#if breakout_board == 42 // was 255
Implement:
bb_hal_setup_pins(...)— export all required pins (hal_pin_bit_newf,hal_pin_float_newf, etc.); initialize default values.bb_hal_process_recv(...)— unpackrx_buffer->inputs[...]and other fields into HAL output pins.bb_hal_process_send(...)— pack HAL command pins intotx_buffer->outputs[...]and analog fields.
6.2 Register your HAL board include
Edit hal-driver/stepper-ninja.c in the board-selection include section:
#elif breakout_board == 42
#include "modules/breakoutboard_hal_42.c"
6.3 Select board ID in HAL config
#define breakout_board 42 // must match firmware
7. Packet Mapping Rules
Any mismatch between firmware and HAL packet mapping causes swapped pins or dead I/O. Keep a single mapping table in comments while developing.
- Firmware → host: writes
tx_buffer->inputs[0..3], encoder feedback/jitter fields. - HAL reads these in
bb_hal_process_recv(). - HAL → firmware: writes
tx_buffer->outputs[0..1], analog and optional control fields. - Firmware reads those commands via
rx_bufferand applies them inbreakout_board_connected_update().
8. Build and Flash Firmware
cd firmware
cmake -S . -B build -DBOARD=pico2 -DWIZCHIP_TYPE=W5500
cmake --build build -j"$(nproc)"
ls build/*.uf2
Use -DBOARD=pico for Pico v1. Flash by holding BOOTSEL, plugging USB, and copying the .uf2 to the RPI-RP2 drive. Open serial output to verify init logs.
9. Build and Install the LinuxCNC HAL Module
cd hal-driver
cmake -S . -B build-cmake
cmake --build build-cmake --target stepper-ninja
sudo cmake --install build-cmake --component stepper-ninja
This installs stepper-ninja.so into your LinuxCNC module directory.
10. LinuxCNC HAL Bring-Up (Minimal)
loadrt stepper-ninja ip_address="192.168.0.177:8888"
addf stepgen-ninja.0.watchdog-process servo-thread
addf stepgen-ninja.0.process-send servo-thread
addf stepgen-ninja.0.process-recv servo-thread
# board I/O nets (names depend on bb_hal_setup_pins implementation)
# net estop-in stepgen-ninja.0.inputs.0 => some-signal
# net coolant-out some-command => stepgen-ninja.0.outputs.0
Verify with halshow or:
halcmd show pin stepgen-ninja.0.*
11. Commissioning Procedure (Recommended Order)
- Power-only test — validate rails, no overheating, no excessive idle current.
- Bus discovery test — confirm expected I2C addresses are detected by firmware prints.
- Disconnect safety test — stop LinuxCNC; confirm outputs and analog channels go to safe state.
- Input polarity test — toggle each physical input and confirm matching HAL pin state.
- Output test — toggle each HAL output pin and verify physical output.
- Encoder test — rotate slowly and quickly, verify count direction and index handling.
- Motion test (motor disconnected) — validate step/dir polarity and pulse timing.
- Motion test (connected) — start with conservative velocity/acceleration.
12. Troubleshooting
No I/O response
- Check that
breakout_boardID matches in both firmware and HAL configs. - Check your new files are included in build paths.
Build succeeds but board callbacks not active
- Confirm
#if breakout_board == 42guard is correct. - Confirm footer/config branch exists and compiles.
Inputs shifted / wrong bit order
Recheck bit packing/unpacking between breakoutboard_42.c and breakoutboard_hal_42.c.
Random disconnect behaviour
- Verify timeout-safe code in
breakout_board_disconnected_update(). - Check network stability and packet watchdog wiring.
Analog output clipping / overflow
Clamp HAL values before packing DAC words. Confirm scaling against DAC resolution.
14. Quick File Checklist
All files below must be touched for a new board ID. If IDs are consistent, integration is usually straightforward.
firmware/modules/breakoutboard_42.cfirmware/CMakeLists.txtfirmware/inc/footer.hfirmware/inc/config.hhal-driver/modules/breakoutboard_hal_42.chal-driver/stepper-ninja.chal-driver/config.h- (optional)
docs/your-board-name.md