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 andcliare 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 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:
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
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
│ ├── cli/ # portable app: LED blink + UART CLI (+ unit test)
│ └── example_component/ # trivial interfaces/ + implementations/ (+ unit test)
├── targets/ # Application entry points + platform implementations
│ ├── 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 | Mode state machine: self-test, calibration, arming and the disarm conditions |
| Balance Control | The selectable control-strategy interface and setpoint handling |
| Attitude Estimation | Pitch estimation with a selectable complementary or Kalman filter |
| Control Loop | Sample-driven pacing of estimation, balance and outer loops, and loop timing |
| Motion Actuation | Motor driver, effort mapping and the bridge disable states |
| Wheel Odometry Design | Wrap-safe count accumulation, wheel velocity and chassis motion |
| Inertial Sensing Design | MPU9250 wiring, body frame, gyroscope bias calibration and sample validity |
| 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 Theory | 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.