type: readme status: current summary: Orientation to the embedded tree: boards, proto-c, bases, tags and loaders, and how CMake ties firmware targets to them.
Embedded Source Layout
The embedded tree is organized around five major sections:
bases: firmware images for programmer/base boards.boards: generated ChibiOS board descriptions for physical hardware.proto-c: nanopb C bindings and default configuration data for tag protocols.tags: firmware images that run on the tags themselves.loaders: SRAM-resident external-flash loaders for reading a tag's external flash over SWD without touching its internal flash.
At a high level, boards describes hardware pins and signals, proto-c
describes the compact protocol structures used by tag firmware, and bases and
tags consume those descriptions to build firmware images.
Directory Relationship
embedded/
boards/ Hardware signal and pin definitions
| Generates board.h, board.c, and board.mk
|
bases/ Programmer/base-board firmware targets
|
tags/ Tag firmware targets
|
loaders/ External flash loaders (per board + flash part)
|
proto-c/ nanopb protocol bindings and default tag configuration
|
+-----> various tags/ Tag firmware communication structures
The CMake build wires these relationships together. A firmware target usually depends on one generated board target and, for tag firmware, one protocol target. For example:
add_embedded_target(BitTag bittag_proto)
add_dependencies(BitTag board-bittag-v6)
This means the BitTag firmware uses the bittag_proto nanopb target from
proto-c and the board-bittag-v6 generated board files from boards.
boards
boards contains the physical board descriptions used by both tag and base
firmware. The current preferred workflow is generated from a ChibiOS processor
template plus a small project JSON file:
embedded/boards/<board>/
CMakeLists.txt
cfg/board-customizations.json
The board CMakeLists.txt calls generate_configured_board_files() from
embedded/boards/CMakeLists.txt:
generate_configured_board_files(board-bitprestag
PROCESSOR stm32l4xx
BOARD_TYPE BitPresTagv1
CUSTOMIZATIONS cfg/board-customizations.json)
The arguments define the board contract:
PROCESSOR: ChibiOS processor-template family. For example,stm32l4xxuses the ChibiOSstm32l4board.xmltemplate and the matching STM32L4 FreeMarker board templates.BOARD_TYPE: output board directory name in the CMake build tree and the name firmware makefiles include as$(BOARDDIR)/<BOARD_TYPE>/board.mk. The JSONboard_idshould normally match this value because ChibiOS' stockboard.mk.ftlemitsBOARDSRCandBOARDINCfromboard_id.CUSTOMIZATIONS: the board-specific JSON file relative to the board source directory.
The generated-board build has two steps:
embedded/boards/tools/generate_board_chcfg.pystarts from the ChibiOS processor XML template and appliesboard-customizations.jsonto board metadata, clocks, optional XML elements, and GPIO pin attributes.- CMake runs
fmppwith the generatedboard.chcfgand ChibiOS' processor-specificboard.c.ftl,board.h.ftl, andboard.mk.ftltemplates.
The outputs are board.h, board.c, board.mk and board_standby.h. For a
board used by a distributed tag they are committed in
embedded/boards/<board>/generated/ and regenerated only when their recorded
inputs change. Either way, firmware reads them through BOARDDIR from the build
tree: a board that does not commit its files generates them there, and a
committed board's files are copied there:
<build>/embedded/boards/<BOARD_TYPE>/board.h
<build>/embedded/boards/<BOARD_TYPE>/board.c
<build>/embedded/boards/<BOARD_TYPE>/board.mk
<build>/embedded/boards/<BOARD_TYPE>/board_standby.h
See Tag Firmware Build Reproducibility for which boards commit their files and how staleness is detected.
board.h defines the board-level LINE_xxx names consumed by tag and base
code. Keep physical signal names in the board customizations whenever possible;
avoid creating tag-level aliases in custom.h just to compensate for a board
name mismatch. For example, BitPresTag uses the board-generated LINE_LPS_CS
and LINE_WKUP1 names directly in its family code.
board.c contains the low-level board initialization code, and board.mk is
included by firmware project.mk files:
include $(BOARDDIR)/BitPresTagv1/board.mk
The firmware CMake target also declares an explicit dependency on the generated board target:
add_embedded_target(BitPresTag bitprestag_proto)
add_dependencies(BitPresTag board-bitprestag)
This dependency ensures the board files exist before the ChibiOS make-based
firmware build starts. common/make.mk receives BOARDDIR from CMake and uses
the board make fragment selected by project.mk.
Some older board directories still use the static-board helper
generate_board_files(). Those boards keep cfg/board.chcfg, cfg/board.fmpp,
and local FreeMarker templates in the source tree:
generate_board_files(board-older-board)
That path still renders board.h, board.c, and board.mk into the build
tree, but new boards should prefer generate_configured_board_files() so the
repository stores only project-specific customizations while ChibiOS owns the
processor XML and template defaults.
Board directories are shared resources. They do not normally define a complete application on their own; instead, a tag or base firmware target declares which board target it depends on.
proto-c
proto-c builds the nanopb C view of the shared protocol definitions. It uses
the top-level .proto files from proto, combines shared nanopb options with
tag-specific overrides, generates .pb.c and .pb.h files, and converts
tag-specific default configuration JSON into C data. For the protocol variants
of distributed tags the generated files are committed in
<variant>-proto-c/generated/; the others are generated in the build tree.
Each protocol subdirectory provides:
default-config.json: the default protobuf configuration message for that tag family, written as JSON.tag.override.options: nanopb overrides fortag.proto.tagdata.override.options: nanopb overrides fortagdata.proto.CMakeLists.txt: a call toadd_nanopb_target(<name>).
The resulting protocol target is an interface library such as bittag_proto or
prestag_proto. Tag firmware links to one of these targets so it can encode and
decode compact messages for host communication and stored tag configuration.
default-config.json is intentionally tag-specific. The shared protobuf
configuration message can describe features used by many tag families, but a
particular firmware image should only initialize the portions that apply to that
tag. embedded/proto-c/config-gen.py converts this JSON file into
default_config.c, which is compiled into the firmware through the generated
protocol target. It runs in a virtual environment the build creates from
embedded/proto-c/requirements.txt
(decision 0013).
Nanopb options also have two layers:
| File | Purpose |
|---|---|
proto-c/default-options/tag.options |
Shared defaults for tag.proto |
proto-c/default-options/tagdata.options |
Shared defaults for tagdata.proto |
proto-c/<tag>-proto-c/tag.override.options |
Tag-specific overrides for tag.proto |
proto-c/<tag>-proto-c/tagdata.override.options |
Tag-specific overrides for tagdata.proto |
At build time, CombineFiles.cmake combines the shared default and the
tag-specific override into the conventional nanopb file names in the build tree,
<build>/embedded/proto-c/<tag>-proto-c/tag.options and tagdata.options,
which nanopb_generator reads. This lets most protobuf fields share one
memory/layout policy while still allowing a tag family to tighten array sizes,
string sizes, field allocation, or other nanopb details where the default would
be too broad.
To add a protocol target:
- Create
embedded/proto-c/<tag>-proto-c/CMakeLists.txtcallingadd_nanopb_target(<tag>). - Add
tag.override.options,tagdata.override.options, anddefault-config.json. - Add the subdirectory from
embedded/proto-c/CMakeLists.txt. - Reference the generated interface target as
<tag>_protofrom the firmware target.
If the tag will be distributed, create generated/ in the new directory and
regenerate; see
Promoting a tag to distributed.
tags
tags contains the firmware applications that run on tag hardware. A tag target
combines:
- tag-specific application code, usually under
inc,src, andcfg; - shared tag support code from
tags/common; - one board target from
boards; - one nanopb protocol target from
proto-c; - ChibiOS sources from the repository
ChibiOSsubmodule.
The target's CMakeLists.txt declares both sides of that relationship. A
typical tag uses:
add_embedded_target(PresTag prestag_proto DISTRIBUTE)
add_dependencies(PresTag board-prestag)
DISTRIBUTE marks a tag that ships: it installs its firmware, is built by
distributed_firmware, and is held to the reproducibility checks. Prototypes
omit it.
Generated build products are placed in the CMake build tree, not in the source
tree. The important final artifacts are the .elf, .hex, .bin, .dmp, and
.list files under the tag target's build directory. When installed, those
same artifacts are copied to
share/UltralightTags/firmware/<target>/.
Firmware targets also get STM32CubeProgrammer helper targets such as
BitTag-download and BitTag-erase when STM32_Programmer_CLI is available.
These targets select an ST-LINK probe before invoking CubeProgrammer. By
default, CMake sets STM32_PROGRAMMER_PROBE=pid:0483:3748, which prefers the
tag programmer base. If that PID is not present, the selector falls back to the
available ST-LINK probes, using the only attached probe automatically or
prompting when multiple probes are attached. Override this at configure time or
in the build environment when you need a specific probe such as an STLINK-V3PWR
(0483:3757):
cmake -S . -B build -DSTM32_PROGRAMMER_PROBE=serial:<stlink-serial>
cmake -S . -B build -DSTM32_PROGRAMMER_PROBE=index:<cube-index>
cmake -S . -B build -DSTM32_PROGRAMMER_PROBE=pid:0483:3757
cmake -S . -B build -DSTM32_PROGRAMMER_PROBE=prompt
The selector uses USB VID:PID discovery on Linux, macOS, and Windows when
available, then passes sn=<serial> to CubeProgrammer. index:<n> is still
available for setups where the OS does not expose a usable serial number.
bases
bases contains firmware applications for base or programmer boards. These
targets use the same board-generation mechanism as tags, but they generally do
not use a nanopb protocol target because they are not tag data-log firmware.
A base target typically looks like:
add_embedded_target(bittag-base-v7 0)
add_dependencies(bittag-base-v7 board-bittag-base-v7)
The 0 protocol argument means the firmware target does not consume generated
nanopb sources.
Adding or Updating Firmware
When adding a new embedded target, decide which layer is changing:
- Add or update a
boardsentry when the physical pinout, signal names, or ChibiOS board configuration changes. For new boards, add acfg/board-customizations.jsonfile and callgenerate_configured_board_files()from the boardCMakeLists.txt. - Add or update a
proto-centry when the tag family needs a new nanopb option set or default configuration. - Add or update a
tagsentry when creating tag firmware for a tag family or sensor combination. - Add or update a
basesentry when creating firmware for a programmer or base board.
Most bugs should be fixed at the lowest layer that owns the behavior. Pin and
signal mistakes belong in boards; protocol encoding and default configuration
belong in proto-c; sensor behavior and runtime state belong in tags; base
board USB/SWD/programmer behavior belongs in bases.
loaders
loaders contains STM32CubeProgrammer-compatible external loaders (.stldr),
one per board and flash part, and RV3028 RTC register probes (RV3028_*), which
use the same framework but are not flash loaders. A loader is laid out like a
tag target -- a Makefile including ../common/make.mk (../common/make-u375.mk
for STM32U375 targets), a project.mk, and local overrides of
../common -- but it has no startup code, no RTOS kernel and no protocol
target: it uses the committed board.h, ChibiOS PAL and the os-less OSAL, and
shares the flash part's command set with the firmware driver through a
dependency-free <part>_commands.h. Each AT25XE source directory builds two
images, read-only and read-write; GD5F2GM7RE_IMUTagNandv2 and the probes
build one:
add_embedded_loader(AT25XE_PresTagv3 DISTRIBUTE)
add_embedded_loader(AT25XE_PresTagv3-RW
DIRECTORY AT25XE_PresTagv3 ALLOW_WRITE DISTRIBUTE)
Distributed loaders are built by distributed_firmware and installed to
share/<package>/loaders/<image>/. See External Flash
Loaders.