Skip to content

Firmware development environment

This page describes the controller firmware repository (SmartManipulator-X-Controller). The Robot PC repository has its own development page. The firmware repository uses separate environments:

Environment Runs where Contains
Firmware Docker image (Dev Container, Compose, or CI) Zephyr, west, Zephyr SDK with arm-zephyr-eabi, CMake, Ninja, ccache, and the DynamixelSDK host library
Host tools Repository .venv on Windows or Linux Python dependencies of tools/dxl_test_gui (requirements.txt)
FPGA / hardware Host only Microchip Libero SoC and licenses: MSS, clocks, fabric, pins, programming

Libero and Microchip licenses are never installed in the image or committed to the repository.

Repository layout

.
├── apps/SmartManipulator_Controller/   Zephyr application (product firmware)
│   ├── boards/                         target overlay, _sim overlay, board .conf
│   ├── include/{dynamixel,motor,servo}/ public module headers
│   ├── src/                            main, transport, codec, XW430 and servo models,
│   │                                   motor workers and backends, servo output
│   ├── tests/protocol2_test.c          host unit tests for the codec
│   ├── prj.conf, prj_sim.conf, Kconfig, CMakeLists.txt
├── tools/dxl_test_gui/                 Protocol 2.0 motor tester (Python/Tkinter)
├── .devcontainer/devcontainer.json     Dev Container (uses ../Dockerfile)
├── .vscode/                            tasks, workspace menu scripts, settings
├── Dockerfile, compose.yaml            firmware build image
├── west.yml                            Zephyr manifest (pinned revision)
├── requirements.txt                    host Python dependencies (.venv) of the GUI
└── .gitattributes                      line-ending rules (LF; CRLF for .bat/.ps1)

Generated directories (build/, .west/, .ccache/, .venv/, zephyr/ checkouts) are ignored and must not be committed.

Pinned tool versions

Component Version
Zephyr (west.yml) v4.5.0-rc1 (first release with m2s_hello_fpga_kit/m2s010)
Base image python:3.12.15-slim-bookworm, digest-pinned, linux/amd64
west 1.4.0
Zephyr SDK 1.0.0 (ARM toolchain in /opt/zephyr-sdk/gnu/arm-zephyr-eabi)
CMake 3.30.5
ccache 4.12.3
jsonschema / pyelftools 4.26.0 / 0.33
DynamixelSDK (host library only) 4.1.0, commit f838bc90f72fcf5b9c279432d6e12fc24969daa8
pyserial (.venv) 3.5
Image tag (compose.yaml) zephyr-sf2:1.0.0

Versions are ARGs in the Dockerfile. The SDK, ARM toolchain, and ccache archives are verified by SHA-256. Do not use latest or a moving branch for build-critical dependencies.

Dev Container

  1. Install Docker (with BuildKit) and the VS Code Dev Containers extension.
  2. Open the repository and run F1 → Dev Containers: Reopen in Container.

.devcontainer/devcontainer.json builds the repository Dockerfile (the single source of truth for tool versions). It mounts the repository at /workspace and .ccache/ at /ccache, sets BOARD=m2s_hello_fpga_kit/m2s010, and installs the Zephyr IDE, C/C++, CMake, Cortex-Debug, and Serial Monitor extensions. After you change the Dockerfile or build arguments, run Dev Containers: Rebuild Container. Reopening the container does not update the tools.

Check the toolchain inside the container:

west --version
arm-zephyr-eabi-gcc --version
cmake --version && ninja --version

VS Code tasks

Ctrl+Shift+B opens Zephyr: Workspace Menu. Its options depend on the operating system:

OS Action Implementation
Linux (Dev Container) Initialize + update: west init -l . if needed, then west update .vscode/workspace-menu.sh
Update dependencies: initializes if needed, then west update
Build firmware: checks the workspace (ensure-west-workspace.sh), then west build
Clean: removes build/, build-sim/, build-native/
Clean and Build: pristine build (-p always)
Build simulation firmware / Clean and Build simulation firmware: -d build-sim -- -DFILE_SUFFIX=sim
Run simulation on PC (native_sim): builds the simulation for native_sim/native/64 and runs it; the UART is a /dev/pts/N
Windows host Create .venv: creates .venv, upgrades pip, installs requirements.txt, checks Tkinter .vscode/host-menu.ps1
Run motor tester GUI / (simulator): creates .venv if needed, then starts tools/dxl_test_gui

Individual tasks are available under Tasks: Run Task: Zephyr: Build, Zephyr: Pristine Build, Zephyr: Build (simulation) (all depend on Zephyr: Ensure workspace), Zephyr: Clean, Zephyr: Ccache Stats, Zephyr: Ccache Clear. Keep shell logic in the scripts under .vscode/, not inline in tasks.json.

Building

# target firmware (TMC5160 over SPI)
west build -b m2s_hello_fpga_kit/m2s010 -s apps/SmartManipulator_Controller -d build
# simulation firmware (no motor hardware; prj_sim.conf + _sim overlay)
west build -b m2s_hello_fpga_kit/m2s010 -s apps/SmartManipulator_Controller -d build-sim -- -DFILE_SUFFIX=sim
# simulation firmware as a Linux executable (UART on a pseudo-terminal)
west build -b native_sim/native/64 -s apps/SmartManipulator_Controller -d build-native -- -DFILE_SUFFIX=sim

Use a separate build directory per variant. The variants are described in Firmware variants. Run these commands from the workspace root. The root is a west workspace, and -s selects the application. Make a pristine build after changes to prj.conf, Kconfig, DeviceTree overlays, CMake files, the Zephyr version, or the toolchain:

west build -p always -b m2s_hello_fpga_kit/m2s010 -s apps/SmartManipulator_Controller -d build

Zephyr picks the board overlay from boards/ automatically: m2s_hello_fpga_kit_m2s010.overlay for the target, and m2s_hello_fpga_kit_m2s010_sim.overlay with FILE_SUFFIX=sim. Passing -DDTC_OVERLAY_FILE=... replaces it; use -DEXTRA_DTC_OVERLAY_FILE=... to add one.

Build artifacts

File Use
build/zephyr/zephyr.elf Debugging (symbols)
build/zephyr/zephyr.hex / zephyr.bin Programming tools. Check the eNVM and hardware configuration first.
build/compile_commands.json IntelliSense (C_Cpp.default.compileCommands)

ccache

The cache lives in .ccache/ on the host and is mounted at /ccache, so it survives container rebuilds. Use ccache --show-stats and ccache --clear, or the matching VS Code tasks. The cache is only an optimization. If a build behaves strangely, make a pristine build first.

West and Zephyr

west.yml pins Zephyr v4.5.0-rc1 and imports its modules. Run west update after changing the manifest. Keep the revision pinned; never use main. A change of the Zephyr version must be verified with a build, a pristine build, the host tests, and a test on the kit.

Host Python environment (.venv)

requirements.txt in the repository root lists the host dependencies: pyserial==3.5 for the GUI. Tkinter comes with Python and cannot be installed with pip.

# Windows (PowerShell, repository root)
py -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
# Linux (repository root); first: sudo apt install python3-tk python3-venv
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt

On Windows, the Create .venv menu action does the same. tools/dxl_test_gui/run_gui.bat creates the environment on first use.

Tests

Test Command Where
Firmware codec (C) gcc -std=c11 -Wall -Wextra -Werror -I include src/protocol2.c tests/protocol2_test.c -o /tmp/protocol2_test && /tmp/protocol2_test in apps/SmartManipulator_Controller Container or any gcc host
GUI codec, bus, simulator, and firmware cross-check python -m unittest discover -s tests -v in tools/dxl_test_gui .venv or the container
Protocol conformance of the real firmware python tools/dxl_test_gui/firmware_check.py --port <COMx or /dev/pts/N> Board with the simulation (or target) firmware; or native_sim in the container

The cross-check compiles src/protocol2.c into a shared library. It verifies that the firmware and the Python codec produce identical packets and that the firmware parser accepts the Python packets. It is skipped when gcc is not available.

firmware_check.py sends a fixed sequence of transactions and reports PASS or FAIL per check: - PING and broadcast PING, identity registers, and Realtime Tick; - every error code; - SYNC_READ, SYNC_WRITE, REG_WRITE, and ACTION; - CRC and stuffing robustness; - the gripper servo device, if the controller reports one (protocol, limits, ramp, SYNC_WRITE); - on the simulation firmware only: position and velocity motion and telemetry.

It never moves motors on the target firmware unless --allow-motion is given. To test the complete firmware stack without a board, inside the container:

west build -b native_sim/native/64 -s apps/SmartManipulator_Controller -d build-native -- -DFILE_SUFFIX=sim
build-native/zephyr/zephyr.exe &          # note the /dev/pts/N it prints
python3 tools/dxl_test_gui/firmware_check.py --port /dev/pts/N

This needs pyserial in the container Python; the image does not include it (pip install pyserial==3.5). The motor backends have no unit tests; motor_tmc5160.c must be tested on hardware.

Documentation

The system documentation, including these firmware pages, is maintained in the Robot PC repository. See Documentation there.

Code style

clang-format.txt holds the formatting configuration (LLVM-based, with embedded-systems customizations). clang-format looks for a file named .clang-format, so either pass the file explicitly (clang-format --style=file:clang-format.txt) or copy it to .clang-format.

Line endings

.gitattributes makes every text file LF in the working tree, including on Windows. The exceptions are *.bat, *.cmd, and *.ps1, which use CRLF. Bash in the container fails on CRLF scripts ($'\r': command not found). If an old clone still has CRLF files, commit your work and then run:

git rm -r --cached -q . && git reset --hard

Docker without VS Code

docker build -t zephyr-sf2:1.0.0 .
docker run --rm -it -v "$PWD:/workspace" -v "$PWD/.ccache:/ccache" -w /workspace zephyr-sf2:1.0.0
# or, using compose.yaml (runs the default build):
docker compose run --rm zephyr

Debugging

Debug with zephyr.elf through VS Code, Cortex-Debug, GDB, and a SmartFusion2-compatible debug probe. The probe configuration depends on the board setup and is not yet defined here. The host UART carries binary Protocol 2.0 traffic, so there is no text console. Use a debugger, or route diagnostics to the spare uart1 with an explicit DeviceTree and Kconfig change.

Updating tool versions

  1. Change the ARG in the Dockerfile (and its SHA-256 for downloaded archives).
  2. Rebuild the image (docker build --no-cache ... or Rebuild Container).
  3. Build, make a pristine build, and run the host tests.
  4. Test on the M2S Hello FPGA Kit.
  5. Bump the image tag and update the version tables (this page and the zephyr-smartfusion2 skill).

CI

CI should use the same Dockerfile: build the image, run west update, build both variants (build and build-sim), and archive zephyr.elf/.hex/.bin. Run the codec tests as well. Run the native_sim build together with firmware_check.py as an end-to-end protocol test. Persist /ccache through the CI cache. A firmware-only pipeline needs no Libero and no Microchip licenses.

Licenses and secrets

Never commit or bake into images: Microchip license files, license server credentials, tokens, keys, or proprietary binaries. Keep Libero on the host.

Troubleshooting

Symptom Fix
$'\r': command not found in a .vscode/*.sh task CRLF line endings; see Line endings
west: command not found Not inside the container; which west should print /opt/venv/bin/west
source directory "." does not contain a CMakeLists.txt Add -s apps/SmartManipulator_Controller, or use the menu
arm-zephyr-eabi-gcc: command not found PATH must contain /opt/zephyr-sdk/gnu/arm-zephyr-eabi/bin; rebuild the container
Strange build after prj.conf / overlay changes Pristine build (-p always)
IntelliSense errors Build once so build/compile_commands.json exists
ccache shows no hits echo $CCACHE_DIR should print /ccache; check that .ccache/ exists on the host
GUI: No module named serial Use the .venv interpreter, or run Create .venv
GUI: No module named tkinter Reinstall Python from python.org with Tcl/Tk, or apt install python3-tk
GUI: timeouts on every ID Wrong COM port or baud rate (kit USB port: 460800; choose Auto to detect it), PIC32 not switched to UART (&, checkbox PIC32 bridge), board not powered, firmware not running, or the UART still has a text console