This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
rusEFI is an open-source engine control unit firmware for STM32 microcontrollers.
Default to building with 12 threads unless otherwise specified (-j12 etc).
Each board+chip combination has its own compile script in firmware/config/boards/<board>/:
# Example: Build for Proteus F7
cd firmware/config/boards/proteus
./compile_proteus_f7.shOutputs are placed in firmware/deliver/:
rusefi.bin- Complete image (bootloader + firmware) for blank ECUsrusefi_update.srec- Update image for bootloader flashing
cd unit_tests
./test.sh
# Run a specific test
./test.sh TestNametest.sh is the recommended way to run tests as it automatically handles both the build (make) and execution.
Coverage reports are generated using gcovr (requires Python 3).
cd unit_tests
./run_coverage.shThis script:
- Sets up a local Python virtual environment in
unit_tests/venv/ - Installs
gcovr - Builds tests with
COVERAGE=yes - Runs all tests
- Generates HTML and JSON reports in
unit_tests/coverage_reports/
Exclusion patterns (e.g., for googletest or the unit_tests directory itself) are defined in unit_tests/coverage_common.sh.
Unit tests use Google Test and run on PC, not on the ECU.
To inspect what a test actually scheduled/executed (events, timings, sniffer/logic traces) call setUnitTestCreateLogs(true) (declared in unit_tests/test-framework/engine_test_helper.h) before constructing EngineTestHelper — typically from main.cpp or at the top of an individual test. When enabled, each test writes per-test artifacts (e.g. unittest_<Suite>_<Name>_trace.json, logic-data, and engine-sniffer files) into the unit_tests/test_results/ directory (TEST_RESULTS_DIR in unit_test_logger.h); the absolute path is printed at process exit by sayByeBye(). This is the recommended way to diagnose unexpected scheduler/RPM/injection behavior instead of adding ad-hoc printfs.
See also unit_tests/test_results/readme.md for unit tests output.
Cross-platform requirement: Unit test code MUST build and run on all supported host platforms — Linux (GCC/Clang), macOS (Clang), and Windows (MSVC and MinGW). Avoid POSIX-only APIs (e.g. realpath, PATH_MAX, dirent.h without guards) unless wrapped in #ifdef guards or replaced by portable C++ equivalents. Prefer std::filesystem over POSIX path APIs.
# Generate configs for a specific board
firmware/gen_config_board.sh <board>
# Generate all board configs
firmware/gen_config.sh
# Generate enum-to-string conversions
firmware/gen_enum_to_string.shfirmware/config/boards/- Hardware configuration and defaults for different ECU hardwarefirmware/config/engines/- Hardware-agnostic configuration for engines (orthogonal to what ECU you run)firmware/controllers/- Core control logicalgo/- Fuel, ignition, and air calculationsactuators/- Control for engine-asynchronous outputs like electonic throttle, idle, AC, boost, VVT, etc.engine_cycle/- Control for engine-synchronous outputs like injection, ignitionsensors/- Input processing (ADC, thermistors, pressure)trigger/- Crank/cam position decoding and synccan/- CAN bus communicationlua/- Runtime scripting
firmware/hw_layer/- Hardware abstraction layerfirmware/libfirmware/- Reusable library codefirmware/util/- Self-contained utilities (no external dependencies)unit_tests/- Google Test suitesimulator/- Windows/Linux firmware simulator
For detailed technical documentation intended for AI assistants, see:
- Fueling System - Mass-based fueling pipeline (17 stages).
- Ignition System - Timing calculation and spark scheduling.
- Engine Protection - LimpManager and cut logic.
- Event-driven execution: Trigger events from crank/cam sensors drive the main control loop
- Angle-based scheduling: Events scheduled by crank angle, not just time
- Configuration-driven: Board and engine parameters externalized; firmware adapts via configuration
- Calibration Compatibility: Maintaining compatibility with older tunes when adding new parameters.
- ChibiOS RTOS: Real-time operating system foundation
firmware/integration/rusefi_config.txtdefines the parameters stored in persistent configuration (both "configuration", ie which pins do what, and the "calibration" or "tune", like the VE table, timing, etc.). This is the primary input that describes the mainengine_configuration_sstruct and the top-level persistent config layout.firmware/integration/config_page_*.txtfiles define additional TunerStudio memory pages, each containing its own struct (e.g.page2_s,page3_s,page4_s). These pages hold data that lives outside the main configuration image — for example, high-speed scatter offsets (page2_s/ TS page 2), long-term fuel trim tables (page3_s/ TS page 3), and secondary VE tables with blend controls (page4_s/ TS page 4). The struct and file numbers match the TunerStudio page numbers.- Both
rusefi_config.txtand theconfig_page_*.txtfiles are processed by the Java tool atjava_tools/configuration_definitionto generate several outputs. It is critical that these match, so that each part of the system can communicate and agree about the in-memory config format.- C/C++ headers in
firmware/controllers/generated/— the main config producesengine_configuration_generated_structures.h, while each config page produces a correspondingpage_N_generated.h. - Along with
firmware/tunerstudio/tunerstudio.template.ini, generates the ini file used by TunerStudio to communicate with the ECU. All tuner-adjustable parameters MUST appear in these input files to be useful.
- C/C++ headers in
firmware/integration/LiveData.yamldefines objects processed by the same tool to be transmitted from the ECU about the current state of the world. For example sensors, output values, and intermediate calculations useful for logging.
Code generation is integrated into the Makefile for all four delivery units: each firmware board build, unit tests (unit_tests/), the simulator (simulator/), and the Java tools. Running make in any of these automatically regenerates the required configuration headers and INI files — there is no reason to invoke gen_config_board.sh, gen_config.sh, or gen_enum_to_string.sh directly. Do not attempt to commit any generated files.
- C99 with GNU extensions for C code
- C++20 for firmware code
- C++17 (minimum) for unit tests and host-side tooling; portable C++17 features such as
std::filesystem,std::optional,std::string_viewand structured bindings are preferred over platform-specific APIs to keep unit tests cross-platform (Linux/macOS/Windows-MSVC/MinGW) - No RTTI, no exceptions (
-fno-rtti -fno-exceptions) - LTO enabled by default
Key preprocessor flags that control compilation. These three are mutually exclusive — any given translation unit is compiled in exactly one of these modes:
EFI_PROD_CODE=1- Production firmware build (cross-compiled for STM32, ChibiOS available, real HAL).EFI_SIMULATOR=1- Desktop simulator build (simulator/), ChibiOS available via the simulator port.EFI_UNIT_TEST=1- Host-side Google Test build underunit_tests/. No ChibiOS, no real HAL — runs as a plain native binary on Linux (GCC/Clang), macOS (Clang) and Windows (MSVC and MinGW).
#if EFI_UNIT_TEST— include a host-only path: stub out HAL/ChibiOS/board-specific calls, expose extra accessors for tests, or substitute portable C++ for embedded primitives.#if !EFI_UNIT_TEST— exclude code that cannot compile on the host (board pin macros, ChibiOS threads/HAL, MCU registers, etc.).- For three-way splits, combine with
EFI_PROD_CODE/EFI_SIMULATORrather than negating one flag. - The flag is set by
unit_tests/Makefile(and the unit-test CMake/IDE projects). It is never set by boardcompile_*.shscripts or the simulator build.
Any code reachable from a unit-test build (unit_tests/ itself, plus firmware sources guarded by #if EFI_UNIT_TEST or compiled unconditionally into the test binary) must build on all four host toolchains: Linux GCC/Clang, macOS Clang, Windows MSVC, Windows MinGW. Prefer portable C++17 (std::filesystem, std::string, std::string_view, std::optional, structured bindings) over POSIX-only (realpath, PATH_MAX, dirent.h) or Win32-only APIs. Guard any unavoidable platform-specific call with an explicit #ifdef.
EFI_UNIT_TEST is the compile-time gate; the runtime test scaffolding it enables lives in unit_tests/test-framework/ — most notably EngineTestHelper and setUnitTestCreateLogs(true) (see the "Troubleshooting test output" section above).
- Stage new files immediately: When you create a new source file (C/C++ headers/sources, Java/Kotlin sources, unit tests, scripts, build files, resources, docs, etc.), run
git add <path>as part of the same change so it shows up ingit status/git diffand is not lost on the next clean or branch switch. - Do not stage build artifacts or generated files (see "Do not attempt to commit any generated files" above), IDE-local files, or user-specific configs.
- Always use curly brackets for
ifstatements, even for single-line blocks.
- Static allocation only: Embedded firmware uses only static memory allocation. No heap usage (
new,malloc,std::vector,std::string,std::map, etc.) is permitted in production firmware code. Use fixed-size containers likecyclic_bufferfromrusefi/containers/cyclic_buffer.hinstead. Memory is limited and fragmentation must be avoided. - Performance matters: This is a hard real-time application. Fuel and ignition events must fire at precise crank angles. Avoid unnecessary computation in hot paths. Use lower priority threads for expensive computation.
- No exceptions: C++ exceptions are disabled. Use return values or error codes for error handling. rusEFI distinguishes three kinds of errors —
warning()(recoverable runtime),configError()(recoverable bad tune) andfirmwareError()/criticalError()(unrecoverable). See the header comment infirmware/controllers/core/error_handling.hfor when to use which. - No RTTI:
dynamic_castandtypeidare unavailable. - Interrupt safety: Be mindful of code that runs in interrupt context vs. thread context. Use appropriate synchronization primitives.
- Stack usage: Keep stack allocations small. Large arrays should be static or global, not local variables.
rusEFI provides two MCP (Model Context Protocol) servers for LLM-driven tooling over stdio JSON-RPC:
:mcp_ecu(java_console/mcp_ecu) —EcuMcpServer: connect to an ECU, upload/download Lua scripts, send commands, and capture ECU messages. Entry point:com.rusefi.mcp.EcuMcpServer.:mcp_can(java_console/mcp_can) —CanSnifferMcp: read-only CAN bus sniffing via PCAN hardware (connect, read packets, wait for packet, status). Entry point:com.rusefi.mcp.CanSnifferMcp.
All rusEFI serial connections use the USB CDC (Communications Device Class) profile. Baud rate is irrelevant and never a concern — the USB serial profile handles throughput natively regardless of any baud rate setting in host software or code.
- Supported IDE: Visual Studio Code
- Requires Unix-like OS (Linux, macOS, or Windows WSL)
- All PRs must pass CI gates (firmware builds for all boards, unit tests)
- Wiki: https://wiki.rusefi.com/
- Adding a new trigger:
docs/adding-new-trigger.md - TunerStudio General Notes:
.junie/ts-readme.md
See also .junie/guidelines.md file