Firmware for the FEB SN5 Formula E vehicle. Each subdirectory corresponds to a board on the car, built around STM32F4 microcontrollers.
| Directory | Board | MCU | Notes | Docs |
|---|---|---|---|---|
BMS/ |
Battery Management System | STM32F446RE | FreeRTOS, cell-monitor front-end | BMS/README.md |
DART/ |
Fan / Tachometer Controller | STM32F042K6Tx | Cortex-M0, bare-metal, -Os |
DART/README.md |
DASH/ |
Driver Dashboard | STM32F469NI | FreeRTOS, LVGL, SDRAM, FATFS | DASH/README.md |
DCU/ |
Data Control Unit | STM32F446RE | Placeholder (empty user code) | DCU/README.md |
LVPDB/ |
Low Voltage Power Distribution | STM32F446RE | Dual CAN, 7× TPS | LVPDB/README.md |
PCU/ |
Powertrain Control Unit | STM32F446RE | Dual CAN, triple ADC, RMS gateway | PCU/README.md |
Sensor_Nodes/ |
Sensor Aggregator | STM32F446RE | IMU + GPS + WSS; third-party lwgps | Sensor_Nodes/README.md |
UART/ |
Serial / Console Bridge | STM32F446RE | FreeRTOS | UART/README.md |
UART_TEST/ |
STM32U5 Validation Fixture | STM32U575ZI | Cortex-M33, GPDMA, FreeRTOS | UART_TEST/README.md |
All boards are fully buildable with CMake.
Shared across boards, under common/. See common/README.md for the library index.
| Target | Role | Docs |
|---|---|---|
feb_io |
Umbrella: UART + Log + Console + Commands + Version + Time + String_Utils | FEB_Serial_Library |
feb_can |
FreeRTOS-safe CAN runtime (dispatcher) | FEB_CAN_Library |
| (submodule) | CAN message definitions + code generator | FEB_CAN_Library_SN4 |
feb_tps |
TPS2482 power-monitor driver | FEB_TPS_Library |
feb_time |
64-bit microsecond monotonic clock | FEB_Time_Library |
feb_rtos_utils |
REQUIRE_RTOS_HANDLE fail-fast macro |
FEB_RTOS_Utils |
cmake/ # Shared toolchain files
gcc-arm-none-eabi.cmake # ARM GCC cross-compiler config
FEB_Version.cmake # Per-board build provenance generator
common/ # Shared libraries (see common/README.md)
FEB_Serial_Library/ # feb_io umbrella + sub-libraries
FEB_CAN_Library/ # feb_can runtime dispatcher
FEB_CAN_Library_SN4/ # CAN message defs (git submodule)
FEB_TPS_Library/ # feb_tps
FEB_Time_Library/ # feb_time
FEB_RTOS_Utils/ # feb_rtos_utils
scripts/ # Developer scripts (see scripts/README.md)
setup.sh # First-time dev environment setup
build.sh # Build firmware (interactive/batch/release)
flash.sh # Flash firmware to boards
format.sh # Code formatting (--check for CI)
setup-hooks.sh # Install pre-commit hooks
version.sh # Create version tags for releases
cubemx.sh # Generate HAL code from .ioc files
third-party/ # Vendored external code (lwgps, etc.)
.github/workflows/ # CI/CD pipelines
# Clone with submodules (required for CAN library)
git clone --recursive https://github.com/Formula-Electric-Berkeley/FEB_FIRMWARE_SN5.git
# If already cloned without --recursive:
git submodule update --init --recursiveFor first-time setup, run the setup script:
./scripts/setup.sh # Full setup with initial build
./scripts/setup.sh --quick # Skip initial buildThis script:
- Verifies toolchain installation (ARM GCC, CMake, Ninja)
- Initializes git submodules
- Installs pre-commit hooks
- Configures CMake
- Runs an initial build to verify everything works
- STM32CubeCLT -- bundles ARM GCC, CMake, Ninja, and STM32_Programmer_CLI
- Download from ST website
- STM32CubeMX -- for modifying
.iocperipheral configurations - Python 3 -- only needed to regenerate CAN message definitions; dependencies are managed automatically (see CAN Library)
- clang-format -- for code formatting (enforced in CI)
Set CUBE_BUNDLE_PATH to your STM32CubeCLT install directory. This is required for VSCode IntelliSense to find the cross-compiler's system headers (stdio.h, etc.).
./scripts/setup.sh configures this automatically on both macOS and Windows Git Bash — it globs the install directory, detects your shell (zsh vs bash), and appends the export to the right profile file. If you prefer to set it manually, add to your shell profile:
# macOS (zsh is the default since Catalina — use ~/.zshrc)
export CUBE_BUNDLE_PATH=/opt/ST/STM32CubeCLT_1.19.0 # or wherever the installer placed it
# Windows Git Bash (use ~/.bashrc)
export CUBE_BUNDLE_PATH=/c/ST/STM32CubeCLT_1.19.0Then restart your terminal (and VSCode, if open).
arm-none-eabi-gcc --version
cmake --version # 3.22+
ninja --version
echo $CUBE_BUNDLE_PATH # should print your CubeCLT pathThe repository scripts are bash-based and work with Git Bash (included with Git for Windows).
Quick Setup:
- Install Git for Windows -- includes Git Bash terminal
- Install STM32CubeCLT to default location (
C:\ST\STM32CubeCLT)- Download from ST website
- Run setup script in Git Bash:
The script will detect STM32CubeCLT and offer to configure your PATH automatically.
./scripts/setup.sh
Manual Setup (if needed):
If the automatic setup doesn't work, manually add to ~/.bashrc:
# STM32CubeCLT tools (adjust path if installed elsewhere)
export PATH="/c/ST/STM32CubeCLT/GNU-tools-for-STM32/bin:$PATH"
export PATH="/c/ST/STM32CubeCLT/CMake/bin:$PATH"
export PATH="/c/ST/STM32CubeCLT/Ninja/bin:$PATH"
export PATH="/c/ST/STM32CubeCLT/STM32CubeProgrammer/bin:$PATH"
export CUBE_BUNDLE_PATH="/c/ST/STM32CubeCLT"If ~/.bash_profile doesn't exist, create it to source ~/.bashrc:
# ~/.bash_profile
if [ -f "$HOME/.bashrc" ]; then
source "$HOME/.bashrc"
fiThen restart Git Bash or run source ~/.bashrc.
Verify tools are found:
ninja --version
arm-none-eabi-gcc --version
cmake --versionAlternative: Use WSL2 with Ubuntu for a native Linux development experience on Windows.
Generate HAL code from .ioc files without opening the STM32CubeMX GUI:
./scripts/cubemx.sh # Interactive menu
./scripts/cubemx.sh -g -b BMS # Generate code for BMS
./scripts/cubemx.sh -i -b LVPDB # Inspect LVPDB configuration
./scripts/cubemx.sh -a -g # Generate code for all boards
./scripts/cubemx.sh --list-boards # List all boards with .ioc statusThe script can:
- Generate code (
-g): Run STM32CubeMX headlessly to regenerate HAL code - Inspect (
-i): View MCU, clock, and peripheral configuration - Show pins (
--show-pins): Display GPIO pin assignments - Show peripherals (
--show-peripherals): List enabled peripherals
Note: Code generation requires STM32CubeMX. Inspection commands work without it by parsing
.iocfiles directly.
The project uses clang-format with an LLVM-based style (2-space indent, 120-char lines, Allman braces). The .clang-format file in the repo root is automatically detected by editors and tools.
- Install the C/C++ extension (Microsoft) or clang-format extension
- Add to your
.vscode/settings.json(workspace or user):{ "editor.formatOnSave": true, "[c]": { "editor.defaultFormatter": "ms-vscode.cpptools" }, "[cpp]": { "editor.defaultFormatter": "ms-vscode.cpptools" } }
Format all user code across all boards:
./scripts/format.sh # Format all Core/User/ files
./scripts/format.sh --check # Check only (CI mode, exits 1 if changes needed)The repository includes pre-commit hooks that run automatically before each commit:
./scripts/setup-hooks.sh # Install hooks
./scripts/setup-hooks.sh --remove # Remove hooksHooks include:
- clang-format - Formats C code in
Core/User/ - cppcheck - Static analysis for warnings and portability issues
- CAN validation - Verifies generated CAN files match definitions
- Trailing whitespace and end-of-file fixers
To run hooks manually:
pre-commit run --all-files # Run all hooks on all files
pre-commit run clang-format # Run specific hook
git commit --no-verify # Skip hooks (use sparingly)All builds are driven from the project root.
./scripts/build.sh # Interactive menu
./scripts/build.sh -a # Build all boards (Debug)
./scripts/build.sh -b LVPDB # Build specific board
./scripts/build.sh -b LVPDB -b PCU # Build multiple boards
./scripts/build.sh -r # Build in Release mode
./scripts/build.sh -c # Clean and rebuild
./scripts/build.sh -l # Loop mode (build multiple boards interactively)The script validates prerequisites, shows build progress, and displays Flash/RAM usage summary.
# Configure (Debug) -- configures all boards
cmake --preset Debug
# Build a specific board
cmake --build build/Debug --target LVPDBcmake -S . -B build/Debug -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_TOOLCHAIN_FILE=cmake/gcc-arm-none-eabi.cmake
cmake --build build/Debug --target LVPDBUse --target to build a single board:
cmake --build build/Debug --target BMSBuild all boards at once by omitting --target:
cmake --build build/DebugIn VSCode with the CMake Tools extension, select the target from the Build Target dropdown in the status bar.
After a successful build, outputs are in build/Debug/<BOARD>/:
<BOARD>.elf-- ELF executable (for debugging)<BOARD>.bin-- Raw binary (for flashing)<BOARD>.hex-- Intel HEX (for flashing)<BOARD>.map-- Linker map file
Download the latest firmware zip from GitHub Releases:
- Download
FEB_Firmware_latest.zip(or a versioned release) - Extract the zip
- Run:
chmod +x flash.sh ./flash.sh
- Select the board you want to flash from the interactive menu
The zip contains firmware for all boards organized in subdirectories, plus the flash script which auto-detects available firmware.
The scripts/flash.sh script provides an easy interface for flashing boards with prerequisite checking and interactive selection:
./scripts/flash.sh # Interactive menu (shows build timestamps)
./scripts/flash.sh -b LVPDB # Flash specific board
./scripts/flash.sh -f firmware.elf # Flash specific file
./scripts/flash.sh -l # Loop mode (flash multiple boards)
./scripts/flash.sh --list-probes # List connected programmers
./scripts/flash.sh -h # Show helpNote: If you downloaded
flash.shfrom a GitHub release, make it executable first:chmod +x flash.sh
The script checks for STM32CubeCLT installation and provides platform-specific setup instructions if needed. If the firmware hasn't been built yet, it offers to build it for you.
STM32_Programmer_CLI --connect port=swd --download build/Debug/LVPDB/LVPDB.elf -hardRst -rst --startIf you have the STM32 VSCode extension installed, use the CubeProg: Flash project (SWD) task (defined in LVPDB/.vscode/tasks.json).
The CAN message library is a git submodule located at common/FEB_CAN_Library_SN4/. It provides auto-generated C pack/unpack functions from Python message definitions. The generated files are committed to the submodule, so you do not need to regenerate them to build.
After modifying Python message definitions in common/FEB_CAN_Library_SN4/*_messages.py:
cd common/FEB_CAN_Library_SN4
./generate_can.shThe script automatically manages a Python virtual environment and installs the correct version of cantools.
| Command | Description |
|---|---|
./generate_can.sh |
Regenerate all CAN files |
./generate_can.sh --list |
List all messages with frame IDs |
./generate_can.sh --ids |
Show frame ID allocation map |
./generate_can.sh --check |
CI mode: verify files are up to date |
If upstream CAN library changes:
git submodule update --remote common/FEB_CAN_Library_SN4See common/FEB_CAN_Library_SN4/README.md for detailed documentation on adding new CAN messages.
GitHub Actions runs on pushes and pull requests to main:
| Workflow | Trigger | Description |
|---|---|---|
Build (build.yml) |
Push/PR to main | Matrix build of all 9 boards. Skips boards missing CMakeLists.txt or Core/. |
Code Quality (quality.yml) |
Push/PR to main | clang-format on Core/User/ files, cppcheck static analysis. |
CAN Validation (can-validate.yml) |
Push/PR to main | Checks submodule is up-to-date with upstream, validates generated files match definitions. |
Firmware Size (size.yml) |
Push/PR to main | Tracks Flash/RAM usage per board. Warns at 90%, fails at 98%. |
Security (security.yml) |
Push/PR to main, weekly | CodeQL security analysis + TruffleHog secret scanning. |
Latest Release (latest-release.yml) |
Push to main | Auto-updates "latest" pre-release with current firmware binaries. |
Tagged Release (release.yml) |
Tag v* |
Builds Release binaries, creates versioned GitHub Release. |
| Board | Flash Limit | MCU |
|---|---|---|
| BMS, PCU, LVPDB, DCU, Sensor_Nodes, UART | 512 KB | STM32F446RE |
| DART | 32 KB | STM32F042K6Tx |
| DASH | 2 MB | STM32F469RE |
| UART_TEST | 2 MB | STM32U575ZI |
Pre-built firmware binaries are available from GitHub Releases:
-
Latest Build: The
latestpre-release is automatically updated on every push tomain. Download from:https://github.com/Formula-Electric-Berkeley/FEB_FIRMWARE_SN5/releases/tag/latest -
Versioned Releases: Stable releases are tagged with version numbers (e.g.,
v1.0.0).
Each release includes:
.elf,.bin, and.hexfiles for all 9 boardsFEB_Firmware_{version}.zip- Complete firmware package with bundledflash.shSHA256SUMS.txt- Checksums for individual firmware files.zip.sha256- Checksum for the release archive
Verify integrity of downloaded files using the SHA256 checksums:
# Verify the zip archive
sha256sum -c FEB_Firmware_latest.zip.sha256
# Verify individual firmware files
sha256sum -c SHA256SUMS.txtUse the version script to create and push a new version tag:
./scripts/version.sh # Auto-increment patch (v1.0.0 → v1.0.1)
./scripts/version.sh patch # Same as above
./scripts/version.sh minor # Bump minor (v1.0.1 → v1.1.0)
./scripts/version.sh major # Bump major (v1.1.0 → v2.0.0)
./scripts/version.sh 2.5.0 # Set explicit version (→ v2.5.0)This automatically:
- Creates a git tag with the new version
- Pushes the tag to GitHub
- Triggers the Tagged Release workflow
- Creates a GitHub Release with all firmware binaries
The common/ directory contains shared code across all boards. Start at common/README.md for the full library index. Each library has its own README; see the Common Libraries table above for quick links.
Within each board directory:
Core/
Inc/ # CubeMX-generated headers (do not edit manually)
Src/ # CubeMX-generated source (do not edit manually)
User/
Inc/ # Your headers
Src/ # Your source code
Drivers/
STM32F4xx_HAL_Driver/ # HAL library (included in repo)
CMSIS/ # CMSIS library (included in repo)
All custom application code goes in Core/User/. CubeMX-generated files in Core/Inc/ and Core/Src/ should only be modified through STM32CubeMX to avoid losing changes on regeneration.