Embedded Tag Firmware
Tag firmware targets share a ChibiOS makefile scaffold from common/make.mk.
Each target's project.mk now separates two concerns:
TAG_MODULES: shared source groups fromcommon/modules.- Optional tag-family manifests from
families/, when multiple targets share application code. ALLCSRC: tag-local application files from that target'ssrcdirectory.
Example:
TAG_MODULES += \
protocol_nanopb \
tag_core \
rtc_rv3028 \
flash_at25xe
include ../common/modules/modules.mk
include ../families/SomeTagFamily/family.mk
ALLCSRC += \
datalog.c \
state_run.c
The module fragments are a first step toward making the shared tag runtime more
understandable. Common runtime code is being moved out of the flat common/src
and common/inc directories into subsystem directories. The first split-out
areas are common/core, for the tag execution framework and persistent
STM32-flash state, common/rtc, for RTC drivers, common/storage, for
external flash drivers, and common/sensors, for reusable sensor drivers
grouped by sensor family.
When selecting a module is the same as enabling a firmware capability, the
module fragment defines that capability through UDEFS. For example,
rtc_rv3028 defines TAG_RTC_RV3028, while sensor_accel_adxl362 defines
TAG_SENSOR_ACCEL_ADXL362. Keep those module-selection switches out of
inc/custom.h; reserve custom.h for tag identity, constants, bus choices,
pin aliases, and behavior that is not equivalent to adding a module.
Optional firmware diagnostics follow the same rule. Select the debug_log
module to enable the in-firmware debug-message buffer and monitor Req_debug
support; do not enable debug logging by defining DEBUG_MESSAGES in
custom.h.
Diagnostic Architecture
The tag diagnostic code has two separate pieces:
debug_log: an optional in-firmware text buffer that the monitor can drain.tag_core: the shared self-test command entry point used by active tags. Thetag_testmodule is a no-op compatibility marker for existing manifests.
Debug Logging
debug_log is intentionally a build module, not a custom.h define. A tag that
wants monitor-readable diagnostic text lists the module in TAG_MODULES:
TAG_MODULES += \
tag_core \
debug_log
The module compiles common/core/src/debug_log.c, adds the ChibiOS stream
helpers, and defines TAG_DEBUG_LOG plus the legacy compatibility name
DEBUG_MESSAGES. New code should test TAG_DEBUG_LOG or, preferably, include
debug_log.h and call the public helpers without adding local preprocessor
guards.
The runtime flow is:
common/core/src/handlers.ccallsdebug_log_init()when the monitor handling thread starts.- Driver or runtime code writes optional messages with
debug_log_printf(). common/core/src/monitor.creportsAck.status.debug_available.- A host
Req_debugrequest drains oneAck.debug_messagepayload withdebug_log_read().
When the module is absent, debug_log.h provides no-op inline helpers. That
lets shared drivers keep low-cost diagnostic calls in place while tags that do
not select debug_log pay no buffer or stream cost. The implementation uses a
1024-byte queue and guards against the early-boot case: reads and writes return
0 until debug_log_init() has initialized the backing memory stream.
Use debug logging for short diagnostics that may help explain hardware bring-up, intermittent device failures, or self-test results. It is not a general event log; the buffer is sized to one protobuf debug-message payload and is drained by the monitor protocol.
Self Tests
tag_core provides the shared monitor-facing diagnostic entry point. Existing
active targets may still list tag_test in TAG_MODULES, but that module is
now only a compatibility marker:
TAG_MODULES += \
tag_core \
tag_test
The shared driver lives in common/core/src/test.c. It does not know how to
exercise each device directly, and it does not select tests with TAG_*
preprocessor branches. Instead, tag or family devices.c files export a
TagTestCase table through tagTestCases(). Each entry maps a monitor
TestReq value to the hook that exercises a concrete device, plus an optional
descriptor context pointer for reusable descriptor-backed tests. Hooks return
ALL_PASSED or a device-specific TestResult, so richer tests can choose the
failure result themselves.
Current hook examples:
tag_test_adxl362()lives with the ADXL362 accelerometer module.tag_test_rtc()lives with the RV-3028 RTC module.tag_test_external_flash()lives with the external-flash support code.tag_test_lis2du12()lives with the CompassTag family LIS2DU12 code.tag_test_lps22hh()lives with the common LPS22HH pressure module.tag_test_ak09940a()lives with the common AK09940A module and takes the magnetometer descriptor from each tag or family test table.
Module and project manifests still decide which hook sources are compiled. If a
tag selects sensor_accel_adxl362, for example, the module compiles
adxl362_test.c; the tag or family devices.c table decides whether
tag_test_adxl362() is part of that firmware's monitor self-test list and
passes the selected accelerometer descriptor as context.
Modern monitor request names describe the actual device under test, for example
RUN_LSM6DSV16X, RUN_AK09940A, RUN_LPS22HH, RUN_MX25L,
RUN_MX25U12843, and RUN_RV3028. Some legacy request names are still
accepted for compatibility:
RUN_AIS2may be mapped to the active accelerometer or IMU hook.RUN_MMC5633is currently mapped to the AK09940A hook.RUN_LPSis shared by the pressure-sensor hooks.
The hook names and returned failure results should still describe the actual device being tested.
When adding a device self-test:
- Put the test source beside the driver that owns the device, in the relevant
module, family, or tag-local
srcdirectory. - Name the hook for the actual device, for example
tag_test_newsensor(). - Declare the hook in
test_support.hif a tag or family table needs to call it from another translation unit. - Add the hook source basename to the owning module or family manifest.
- Add the request-to-hook mapping to the tag or family
devices.cTagTestCasetable. The hook should returnALL_PASSEDon success and a specificTestResulton failure. - Update
design/custom-defines.mdand rebuild the active tag targets.
Avoid adding a tag-local src/test.c unless the entire diagnostic entry point
really must be replaced. Most tags should share common/core/src/test.c and
provide only the devices.c test table plus device-specific hooks.
BitTag-legacy preserves the older BitTag firmware tree, including its local
runtime, monitor, persistence, ADXL362, RV3028, and test implementations. The
active BitTag target now follows the common-module pattern: its bt_*.c
files keep BitTag-specific activity logging behavior, while tag_core,
rtc_rv3028, and sensor_accel_adxl362 provide shared runtime and driver
code. Its power qualification procedure -- the idle, running and finished
currents, and why a once-a-minute tag cannot be measured with IMUTag's
windows -- is
BitTag/design/power-test-plan.md.
Tag-specific build constants live in each target's inc/custom.h; module-owned
feature switches come from TAG_MODULES. See
custom-defines.md for the current inventory and the code
paths affected by each define.
Shared device code should prefer explicit descriptors over preprocessor line
aliases. Standard semantic line names such as LINE_MAG_CS, LINE_MAG_SCK,
LINE_MAG_MISO, and LINE_MAG_MOSI are still useful when a device owns a
simple bus. For boards with shared or mixed wiring, bind the exact lines in the
tag or family devices.c descriptor instead of inventing duplicate board names
or adding aliases in custom.h.
Tag Families
Some hardware comes up first as a breakout and later as a production tag. Those
variants often need separate board files and custom.h values, but most of the
application code should remain identical. Put that shared application code in a
family directory under embedded/tags/families.
For example, CompassTag, CompassTagAT25, and CompassTagAT25Breakout
include:
include ../families/CompassTag/family.mk
The inactive BitTagNG variants follow the same pattern with:
include ../families/BitTagNG/family.mk
The family manifest adds shared config/include/source directories and lists the
shared source basenames. The target directories still own board selection,
custom.h, storage-module selection, and any intentionally divergent local
configuration or sources.
Template Tag Directory
A tag target should generally have this shape:
embedded/tags/NewTag/
CMakeLists.txt CMake target and board/proto dependencies
Makefile Usually just: include ../common/make.mk
project.mk Board include, TAG_MODULES, and tag-local source list
cfg/ ChibiOS overrides, only if this target differs from family
inc/ tag-local headers and overrides
custom.h tag identity, constants, bus choices, and pin aliases
config.h stored configuration type and tag type
datalog.h log record layout, if the tag logs data
src/ tag-local implementation and overrides
config.c
datalog.c
pwr.c optional override for common/core/src/pwr.c
state_run.c
The minimal CMakeLists.txt names the firmware target, selects the nanopb
protocol target, and declares the generated board dependency:
add_embedded_target(NewTag newtag_proto)
add_dependencies(NewTag board-newtag)
Targets using the shared STM32L432 linker script default to TAG_FLASH_SIZE=256K.
Boards built on 128 KiB parts can override this in CMake:
add_embedded_target(NewTag newtag_proto TAG_FLASH_SIZE=128K)
The same make variable can be set in project.mk or on the make command line.
The Makefile normally delegates to the shared ChibiOS scaffold:
include ../common/make.mk
The project.mk is the target's build manifest. It pulls in the generated
board make fragment, declares shared modules, then lists tag-local sources:
USE_HAL_I2C_FALLBACK = yes
include $(BOARDDIR)/NewTagBoard/board.mk
TAG_MODULES += \
protocol_nanopb \
tag_core \
tag_test \
rtc_rv3028 \
flash_at25xe \
sensor_pressure_lps27
include ../common/modules/modules.mk
include ../families/NewTagFamily/family.mk # only when this target is a variant
ALLCSRC += \
datalog.c \
state_run.c
USE_HAL_I2C_FALLBACK = yes selects ChibiOS' global software-I2C fallback.
Targets that need hardware I2C should leave that variable unset and use the
common TagSoftI2cDriver backend for any software-I2C development buses.
common/make.mk supplies the ChibiOS build plumbing. It includes ChibiOS
startup, HAL, OSAL/RTOS, platform, and rules makefiles, adds local/family,
module, generated nanopb, and board include paths, and points the build at the
repository ChibiOS submodule through the CHIBIOS variable passed by CMake.
The config include order is variant cfg/ first and family cfg/ second, so a
variant can override a shared chconf.h, halconf.h, or mcuconf.h without
copying the family defaults.
ChibiOS Configuration Requirements
Every tag implementation that uses tag_core and the SWD monitor must set the
DebugMonitor system-handler priority during ChibiOS system initialization. The
DebugMonitor exception resets to a high priority that can preempt ChibiOS kernel
critical sections. The monitor handler uses ChibiOS IRQ-safe services, so it
must run at a normal kernel-callable priority, not as a fast interrupt.
Put the priority setup in the tag or family cfg/chconf.h
CH_CFG_SYSTEM_INIT_HOOK() so it runs at the end of chSysInit(), before
interrupts are enabled and multitasking begins:
#define CH_CFG_SYSTEM_INIT_HOOK() { \
extern void tagSystemInitHook(void); \
tagSystemInitHook(); \
}
The shared core implementation provides tagSystemInitHook() and sets
DebugMonitor_IRQn to TAG_DEBUG_MONITOR_PRIORITY, which defaults to priority
8. Tags may override that priority, but it must remain in the ChibiOS
kernel-callable interrupt range. Do not leave DebugMonitor at the reset default
priority.
Board code is generated under the CMake build tree by embedded/boards and
included through the board fragment selected in project.mk:
include $(BOARDDIR)/NewTagBoard/board.mk
Protocol code is generated under the CMake build tree by embedded/proto-c.
The protocol_nanopb module lists the generated source basenames:
default_config.c
pb_common.c
pb_decode.c
pb_encode.c
tag.pb.c
tagdata.pb.c
Local Overrides
tag_core contributes default runtime sources such as main.c, monitor.c,
persistent.c, pwr.c, bus_power.c, and the state-machine framework. If a
target provides a same-named source in its local src directory, the repaired
VPATH order makes that local file override the common default. Local override
files should not be listed again in ALLCSRC; the module source basename is
enough.
bus_power.c is intentionally separate from pwr.c. It provides generic
SPI/I2C descriptor helpers, the default SPI1 controller setup, and standby
pullup/pulldown helpers that operate directly on board-defined LINE_xxx
names. Tag-local or module-local power code should describe a device's pins and
off/sleep policy with those helpers instead of duplicating SPI register setup
and STM32 standby-pull register writes.
The shared tag makefile uses this search order:
INCDIR = $(CONFDIR) ./inc $(TAG_FAMILY_INC_DIRS) $(MODULE_INC_DIRS) ../common/inc $(ALLINC) $(TESTINC)
VPATH := $(BUILDDIR) ./src $(TAG_FAMILY_SRC_DIRS) $(MODULE_SRC_DIRS) ../common/src $(PROTODIR) $(NANOPBDIR) $(VPATH)
That means tag-local files can override module or shared defaults by using the same basename:
./inc/config.his found before../common/inc/config.h../inc/custom.his found before any sharedcustom.h../inc/sensors.his found before a familysensors.h../inc/rv3028.hwould be found before../common/rtc/inc/rv3028.h../src/config.cis found before a familyconfig.c../src/handlers.cwould be found before../common/core/src/handlers.c../src/monitor.cwould be found before../common/core/src/monitor.c../src/rtc_rv3028.cwould be found before../common/rtc/src/rtc_rv3028.c.../common/rtc/src/hal_rtc_lld.cis found before the ChibiOS HAL RTC implementation because the RTC module adds that directory toVPATH../src/state_machine.cis found before../common/core/src/state_machine.c.
This ordering is deliberate. ChibiOS rules.mk normally sorts include paths and
source search paths while building its internal variables. common/make.mk
therefore recomputes IINCDIR and restores VPATH after including ChibiOS
rules so tag-local inc and src files can override shared defaults.
The module fragments intentionally list source basenames rather than full paths,
which keeps the override behavior obvious at the point of declaration. Note
though that a full path in ALLCSRC does not disable overriding: ChibiOS
rules.mk derives each object name with $(notdir ...) and then resolves the
%.c prerequisite through VPATH, so a tag-local basename still wins.
families/BitPresTag/family.mk lists full paths and its UIUCTag variant still
overrides four of them. Prefer basenames so the intent is readable, but do not
assume a full path means the family file is the one being compiled.
To confirm which file a target actually built, read the dependency file rather than the makefiles:
grep devices.c <build-dir>/embedded/tags/<Tag>/dep/devices.o.d
A tag-local override prints src/devices.c; otherwise the family or module path
appears. This is also the quickest way to catch a stale object after changing a
header that common code resolves through the tag's include path.
Prefer avoiding new same-name overrides when practical; add a clearly named tag-local file instead if the behavior is not truly an override of a shared default.
The active tag targets use the shared test driver in common/core/src/test.c
and provide their concrete test lists from tag or family devices.c files.
Device modules, tag families, or tag-local drivers provide named self-test
hooks such as tag_test_adxl362() or tag_test_lis2du12(); do not add a new
local src/test.c unless the target truly needs to replace the shared
diagnostic entry point. BitTag-legacy keeps a local test.c because it is a
frozen legacy target.
Design Notes
TODO.mdis the open firmware work: defects, low-power and recovery questions, NAND bring-up, and the remaining next-release items.common/core/design/u375-low-power.mdis how STM32U375 tags sleep: Stop 3 as the terminal sleep, Stop 2 or Stop 1 between IMU wakes, Sleep as the idle default, scoped STOP around SPI transfers, the monitor guards, and the layout sensitivity that makes every image change need a current measurement.common/core/design/restart-recovery.mdis the recovery contract after a reset: reset classification, reattach versus failure, and the IDLE-means- empty-log invariant.common/core/design/i2c-bus-recovery.mdcovers clearing a stuck I2C bus on the U375 IMUTag targets.design/investigations/andcommon/core/design/investigations/hold the dated fault investigations; decisions are indocs/decisions/.
Adding Shared Code
- Add source files to the narrowest appropriate module in
common/modules. - Create a new module when a source group has a distinct owner.
- Keep tag-specific runtime behavior in the tag directory until at least two targets truly share it.
- Update
BUILD_SOURCES.mdafter changing build membership and rebuilding the active targets.