Firmware for a self-balancing inverted pendulum robot, written in C++20 with strict
real-time and memory constraints. The repository was bootstrapped from
embedded-pro/embedded-scaffold
and carries its structure, build system, dev container, CI, and documentation
scaffolding, so the project starts from a working, testable baseline.
Status: the structure is in place; the
example_*components andblinky_cliare the scaffold's worked examples and are expected to be replaced by the robot's own components (sensing, control, actuation).
The project separates portable logic from hardware through one key seam: an
abstract platform::Platform interface in core/platform_abstraction/. Application
logic in core/ depends only on that interface; each board provides a concrete
PlatformImpl in targets/platform_implementations/. The same application is
therefore compiled once and runs on the host (for testing) and on the
microcontroller.
The current headline example is blinky_cli: blink a status LED and serve a tiny
UART command-line interface (ping, id). It is written once against the interface
and builds for the host and the ST NUCLEO-WB55RG.
- Platform abstraction: application logic depends on the
platform::Platforminterface, not on an MCU — so it is built once and unit-tested on the host against aPlatformMock, then run on real hardware. - Real firmware output:
blinky_clibuilds to a flashable.elf/.hexfor the NUCLEO-WB55RG (LED blink + UART CLI). - No heap allocation in runtime/embedded code — bounded containers from
infra/embedded-infra-lib(infra::BoundedVector,infra::BoundedString). - Worked examples in every top-level folder showing the conventions to follow.
- Unit tests (GoogleTest) and BDD integration tests (cucumber-cpp / Gherkin).
- Dev container with the full toolchain (CMake, Ninja, ccache, ARM GCC, Qt6).
- CI for build, linting/formatting, static analysis, documentation and requirements validation, and release-please versioning.
- Docker (the project is developed inside a Dev Container) or a local toolchain with CMake ≥ 3.24, Ninja, a C++20 compiler, and (for embedded) ARM GCC.
- VS Code with the Dev Containers extension is the recommended workflow.
-
Clone with submodules:
git clone --recursive https://github.com/embedded-pro/e-Inverted-pendulum-bot.git cd e-Inverted-pendulum-bot -
Configure & build for the host:
cmake --preset host cmake --build --preset host-Debug
-
Run unit + integration tests:
ctest --preset host
-
Run the example tool / app on the host:
./build/host/bin/Debug/inverted_pendulum_bot.tool.example 2 3 4 # -> total = 9 ./build/host/bin/Debug/inverted_pendulum_bot.example_app # -> accumulator total = 5
-
Build the
blinky_clifirmware for the board (produces.elf/.hex):cmake --preset NUCLEO-WB55RG # ST Nucleo-68 cmake --build --preset NUCLEO-WB55RG-DebugOn hardware the status LED (LD2, green, PB0) blinks and a UART command-line interface (115200 8N1) accepts
pingandid. The CLI is on USART1 (PB6 TX / PB7 RX), which the NUCLEO-WB55RG routes to the on-board ST-LINK virtual COM port — no USB-UART adapter needed.
All presets are defined in CMakePresets.json (host, host-single-Debug,
windows, coverage, NUCLEO-WB55RG).
e-Inverted-pendulum-bot/
├── core/ # Reusable libraries only — no entry points
│ ├── platform_abstraction/ # platform::Platform interface (+ mock) — the seam
│ ├── blinky_cli/ # portable app: LED blink + UART CLI (+ unit test)
│ └── example_component/ # trivial interfaces/ + implementations/ (+ unit test)
├── targets/ # Application entry points + platform implementations
│ ├── blinky_cli/ # one Main.cpp reused across host/st
│ ├── example_app/ # a trivial host-only entry point
│ └── platform_implementations/
│ ├── host/ # PlatformImpl: stubs + loopback serial (host build)
│ └── st/ # PlatformImpl: NUCLEO-WB55RG (LED PB0, USART1)
├── tools/ # Host-side developer tools
│ └── example_tool/ # a CLI reusing a core library
├── integration_tests/ # BDD integration tests (cucumber-cpp / Gherkin)
├── infra/ # Infrastructure submodules
│ ├── embedded-infra-lib/ # bounded containers, build helpers, toolchains
│ ├── numerical-toolbox/ # PID, filters, fixed-point algorithms
│ └── hal/st/ # STM32 hardware abstraction layer
├── documentation/ # Architecture/design/theory/requirements (+ templates)
├── scripts/ # Build and utility scripts
└── build/ # Build artifacts (generated, not committed)
Each top-level folder has its own README.md describing its conventions and how to
add new components.
- No heap in runtime code: all memory statically allocated; bounded containers instead of STL containers. Host tools and tests may use the heap.
- Interface-driven design + dependency injection: hardware is injected via the
platform::Platforminterface through constructors, never global state. - Documentation-first: update the relevant
documentation/doc before or alongside behavioural changes. Diagrams use Mermaid or ASCII art only. - SOLID / DRY: reuse
infra/numerical-toolbox/algorithms; do not duplicate.
| Document | Description |
|---|---|
| System Architecture | Components, interfaces and cross-cutting concerns of the robot |
| Use Cases | Actors, flows and the Gherkin scenarios every requirement traces to |
| Requirements | Requirement sets, validated against documentation/tools/requirement.schema.json |
| Safety Supervisor | Arm/disarm/fault state machine and the disarm conditions |
| Balance Control | The selectable control-strategy interface and setpoint handling |
| Attitude Estimation | Pitch estimation, bias calibration and estimate validity |
| Motion Actuation | Motor driver, effort mapping and quadrature decoding |
| BLE Service | GATT layout for teleoperation, telemetry and tuning |
| Platform Abstraction | The peripheral roles each board must supply |
| Pendulum Dynamics | Equations of motion, linearisation and the fall time constant |
| Control Laws | Cascaded PID and LQR derived from the shared plant model |
| Pitch Estimation Theory | Complementary and Kalman formulations, drift and noise |
| Wheel Odometry | Quadrature decoding and differential-drive kinematics |
| Documentation Templates | Starting points for new architecture/design/theory/requirements docs |
| Performance Optimization Guide | Embedded performance techniques, assembly analysis, cycle budgets |
| AI Agent Instructions | Development guidelines, patterns, and constraints |
This project is licensed under the terms in the LICENSE file.
Third-party components and submodules keep their own license terms as documented in their respective directories.