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 |
|---|---|---|---|
LVPDB/ |
Low Voltage Power Distribution Board | STM32F446RE | |
BMS/ |
Battery Management System | STM32F446RE | |
DASH/ |
Dashboard | STM32F469RE | FreeRTOS, FatFS |
DART/ |
DART | STM32F446RE | |
DCU/ |
Data Control Unit | STM32F446RE | |
PCU/ |
Powertrain Control Unit | STM32F446RE | |
Sensor_Nodes/ |
Sensor Nodes | STM32F446RE | |
UART_TEST/ |
UART/Console Test Board | STM32U575ZI | Test platform for UART/Console libraries |
All boards are fully buildable with CMake.
cmake/ # Shared toolchain files
gcc-arm-none-eabi.cmake # ARM GCC cross-compiler config
common/
FEB_CAN_Library_SN4/ # CAN library (git submodule)
gen/ # Generated C pack/unpack code
*_messages.py # Python message definitions
generate_can.sh # Generation script
scripts/
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
.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.).
Add to your shell profile (~/.zshrc or ~/.bashrc):
export CUBE_BUNDLE_PATH=/opt/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 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 7 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, DART, DCU, Sensor_Nodes | 512 KB | STM32F446RE |
| DASH | 2 MB | STM32F469RE |
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 7 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:
common/
FEB_CAN_Library_SN4/ # Git submodule - CAN message definitions
gen/ # Generated C code (feb_can.c, feb_can.h)
*_messages.py # Python message definitions per board
generate_can.sh # Generation script
FEB_UART_Library/ # Printf/logging with DMA
Inc/ # Headers (feb_uart.h, feb_uart_log.h, etc.)
Src/ # Implementation (feb_uart.c)
FEB_Console_Library/ # CLI interface
Inc/ # Headers (feb_console.h, feb_console_commands.h)
Src/ # Implementation (feb_console.c, feb_console_commands.c)
See FEB_UART_Library/README.md and FEB_Console_Library/README.md for detailed usage documentation.
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.