Development environment¶
The repository uses two 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 and of the documentation build (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)
├── docs/ this documentation (Markdown, built with MkDocs)
├── tools/docs/ PDF build script, cover page, MkDocs hooks
├── mkdocs.yml documentation site and PDF configuration
├── .agents/skills/ engineering rules for coding agents
├── .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): GUI and documentation build
└── .gitattributes line-ending rules (LF; CRLF for .bat/.ps1)
Generated directories (build/, .west/, .ccache/, .venv/, site/, 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¶
- Install Docker (with BuildKit) and the VS Code Dev Containers extension.
- 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:
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 |
||
| Linux and Windows | Build PDF documentation: sets up the docs tools if needed, then runs tools/docs/build_pdf.py (on Linux in a separate .venv-linux when .venv is a Windows environment) |
both scripts |
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, and
Docs: Build PDF (also under Tasks: Run Build Task). 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:
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, and MkDocs, mkdocs-material,
mkdocs-print-site-plugin, Playwright and pypdf for the documentation. 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 and PDF manual¶
The documentation is the Markdown in docs/. mkdocs.yml turns it into a
website and into a printable A4 manual with a cover page, a table of contents
with page numbers, numbered chapters, headings and figures, a running header,
and page numbers. The print-site plugin combines all pages into
site/print_page/, and tools/docs/build_pdf.py prints that page with
Chromium (through Playwright) and adds PDF bookmarks.
The docs tools are in requirements.txt, so the repository .venv of the
GUI has them. The Build PDF documentation menu action (or the
Docs: Build PDF task) does the steps below. By hand:
# Windows (PowerShell, repository root)
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe tools\docs\build_pdf.py
# Linux (repository root)
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python -m playwright install chromium # once; Windows uses Edge instead
.venv/bin/python tools/docs/build_pdf.py
The result is site/SmartManipulator-X-Controller.pdf. The cover shows the
git describe version and the build date.
| Command | Result |
|---|---|
python -m mkdocs serve |
Live HTML preview at http://127.0.0.1:8000; the combined print view is at /print_page/ |
python -m mkdocs build |
Static website in site/ (strict mode: a broken link or missing file fails the build) |
python tools/docs/build_pdf.py |
Runs mkdocs build, then writes the PDF |
... build_pdf.py --output manual.pdf |
Writes the PDF elsewhere |
... build_pdf.py --browser msedge |
Uses a specific browser: chromium (Playwright), msedge, or chrome. The default auto tries them in that order. |
... build_pdf.py --mermaid-js mermaid.min.js |
Offline build: serves a local Mermaid 11 script instead of loading it from unpkg.com |
Writing rules that keep the PDF correct:
- Start every page with exactly one
# Title. Chapter and heading numbers are added automatically, so do not number headings by hand. - A new page must also be added to
navinmkdocs.yml, otherwise it is missing from the site and the PDF. - An image on a line of its own,
, becomes a numbered figure, and its alt text becomes the caption (tools/docs/hooks.py). - Link to other pages with relative
.mdpaths (../gui/index.md#starting); strict mode then checks them. - Mermaid diagrams (
```mermaid) are rendered in the browser. Tall diagrams are scaled down to fit on one page. - Print styling lives in
docs/assets/stylesheets/print.css, and the cover page intools/docs/cover_page.tpl.
MkDocs is pinned below 2.0, because MkDocs 2.0 removes the plugin system that the theme and the PDF build depend on.
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:
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¶
- Change the
ARGin theDockerfile(and its SHA-256 for downloaded archives). - Rebuild the image (
docker build --no-cache ...or Rebuild Container). - Build, make a pristine build, and run the host tests.
- Test on the M2S Hello FPGA Kit.
- Bump the image tag and update the version tables (this page and the
zephyr-smartfusion2skill).
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 |