Professional BMS implementation based on STM32F446RE + FreeRTOS
FSAE 2026 Compliant • Melasta SLPB9270175HV Optimized • LTC6811-1 Based
This BMS (Battery Management System) is specifically designed for Polimarche Racing Team's Formula SAE electric vehicle. The system manages a 138-cell lithium polymer battery pack using Melasta SLPB9270175HV cells, ensuring safety, performance, and FSAE 2026 regulatory compliance.
- Microcontroller: STM32F446RE @ 128MHz
- RTOS: FreeRTOS v10.3.1 with clean architecture separation
- Battery Pack: 138 cells (6 modules × 23 cells/module)
- Monitoring IC: 12× LTC6811-1 (2 per module)
- Communication: isoSPI (LTC6820) + CAN bus
- Compliance: FSAE 2026 regulations
bms-freertos/
├── Core/
│ ├── Inc/ # Header Files
│ │ ├── Algorithms/ # Advanced Algorithms
│ │ │ ├── cell_balancing.h # Passive cell balancing (LTC6811 DCC)
│ │ │ ├── measurements.h # LTC6811 cell/aux/stat sampling
│ │ │ └── soc.h # State of Charge estimator
│ │ │
│ │ ├── BMS/ # Core BMS Management
│ │ │ ├── bms_state.h # System state machine
│ │ │ ├── pack.h # Pack configuration & live data
│ │ │ ├── mutexes.h # Thread synchronization
│ │ │ └── safety.h # Unified safety management
│ │ │
│ │ ├── Communication/ # Communication Protocols
│ │ │ ├── can.h # CAN bus interface
│ │ │ ├── charger.h # EVO11KA charger communication
│ │ │ └── serial.h # UART debug interface
│ │ │
│ │ ├── Drivers/ # Hardware Drivers
│ │ │ ├── LTC/
│ │ │ │ ├── LTC6811.h # LTC6811-1 AFE driver
│ │ │ │ └── LTC681x.h # LTC681x common functions
│ │ │ ├── EVO11KA/
│ │ │ │ └── charger.h # EVO11KA charger driver
│ │ │ └── CAB/ # Current sense driver
│ │ │
│ │ ├── Tasks/ # FreeRTOS Task Handlers
│ │ │ ├── bms_manager.h # High-level BMS state machine
│ │ │ ├── charger_manager.h # Charger control & charging FSM
│ │ │ ├── data_logger.h # Real-time diagnostics telemetry
│ │ │ └── safety_monitor.h # Watchdog & safety heartbeat
│ │ │
│ │ ├── main.h # Main application header
│ │ ├── FreeRTOSConfig.h # FreeRTOS configuration
│ │ ├── stm32f4xx_hal_conf.h # HAL configuration
│ │ └── stm32f4xx_it.h # Interrupt handlers
│ │
│ └── Src/ # Source Files (mirrors Inc/ structure)
│ ├── Algorithms/ # Algorithm implementations
│ │ ├── cell_balancing.c # Passive balancing logic (hysteresis)
│ │ ├── measurements.c # LTC6811 sampling & averaging
│ │ └── soc.c # SoC coulomb counting + OCV blending
│ │
│ ├── BMS/ # BMS core implementations
│ │ ├── bms_state.c # State machine logic
│ │ ├── pack.c # Pack initialization & accessors
│ │ ├── mutexes.c # FreeRTOS mutex wrappers
│ │ ├── built_in_led.c # LED status indicators
│ │ └── safety.c # Safety checks & callbacks
│ │
│ ├── Communication/ # Communication implementations
│ │ ├── can.c # CAN bus handlers
│ │ └── serial.c # UART debug output
│ │
│ ├── Drivers/ # Driver implementations
│ │ ├── LTC/ # LTC6811 driver impl.
│ │ ├── EVO11KA/ # Charger driver impl.
│ │ └── CAB/ # Current sense impl.
│ │
│ ├── Tasks/ # Task implementations
│ │ ├── bms_manager.c # BMS state transitions
│ │ ├── charger_manager.c # Charger FSM
│ │ ├── data_logger.c # Diagnostic logging loop
│ │ └── safety_monitor.c # Watchdog & heartbeat
│ │
│ ├── main.c # Main application entry
│ ├── freertos.c # FreeRTOS tasks & configuration
│ ├── stm32f4xx_hal_msp.c # HAL MSP functions
│ ├── stm32f4xx_it.c # Interrupt handlers
│ ├── syscalls.c # Embedded C library stubs
│ ├── sysmem.c # Memory management
│ └── system_stm32f4xx.c # System initialization
│
├── Startup/ # Startup assembly file
├── Debug/ # Debug build artifacts
├── Drivers/ # STM32 HAL & CMSIS
│ ├── CMSIS/ # ARM CMSIS headers
│ └── STM32F4xx_HAL_Driver/ # ST HAL drivers
├── Middlewares/Third_Party/FreeRTOS/ # FreeRTOS kernel & portable layer
└── Linker Scripts/ # Memory layout & linking
Key Modules:
- Algorithms: Core battery management algorithms (balancing, SoC, measurements)
- BMS: System configuration, state, synchronization, and safety
- Communication: External interfaces (CAN, charger, debug UART)
- Drivers: Hardware abstraction for LTC6811, charger, current sense
- Tasks: FreeRTOS task handlers for concurrent operations
The BMS_Pack_t structure (defined in pack.h) serves as the single source of truth for all pack-related data:
typedef struct {
/* Topology */
uint8_t total_ic; /* number of LTC6811 in chain (max 12) */
uint16_t total_cells; /* total pack cells */
uint8_t temp_sensors; /* total temperature sensors (AUX) */
const Pack_CellMap_t *cell_map; /* per-IC cell mapping */
/* Live measurements */
uint16_t cell_mv[12][12]; /* up to 12 IC × 12 cells each */
int16_t temp_c[60]; /* up to 60 temperature sensors */
float pack_current_A;
float pack_voltage_V;
/* Balancing policy & state */
float balance_on_delta_mv; /* turn ON if v_cell > avg + this [V] */
float balance_off_delta_mv; /* turn OFF if v_cell < avg + this [V] */
float balance_min_cell_voltage; /* do not balance below this V */
float balance_max_temperature_C; /* do not balance above this °C */
uint8_t balance_only_when_charging; /* 1: only in CHARGING state */
bool balancing_active; /* true if any cell is currently discharging */
/* ... other fields ... */
} BMS_Pack_t;Access Pattern: All modules access pack data through thread-safe accessors:
const BMS_Pack_t *Pack_Get()— Read-only accessBMS_Pack_t *Pack_GetMutable()— Mutable access (requires Pack_Lock)
Pack topology and electrical parameters are defined in the Pack module (Core/Inc/BMS/pack.h + Core/Src/BMS/pack.c).
Choose one of the supported configurations at initialization time:
PACK_CFG_FULL— 138-cell full pack (6 modules × 23 cells; 12 LTC6811)PACK_CFG_MODULE_SINGLE— Single module (23 cells; 2 LTC6811)PACK_CFG_MODULE_CYLINDRICAL— Cylindrical test module (12 cells; 1 LTC6811)
Each configuration provides:
- Total ICs and cells, temperature sensors, and per-IC cell map
- Chemistry and voltage limits (UV/OV)
- Charging thresholds and balancing policy
- SoC estimator parameters (idle threshold and OCV blend factor)
Per-IC cell distribution is defined by the pack’s cell map for each configuration (LTC6811 supports up to 12 cells per IC). The standard patterns are:
| Pack Config | Cells | IC | Distribution |
|---|---|---|---|
| 6 mod × 23 cells | 138 | 12 | 6 ICs with 12 + 6 ICs with 11 |
| 1 mod × 23 cells | 23 | 2 | 1 IC with 12 + 1 IC with 11 |
| 1 mod × 12 cells | 12 | 1 | 1 IC with 12 |
✅ Maximum Control: Each IC max 12 cells (LTC6811 limit) ✅ Total Validation: Compile-time verification of configuration ✅ Compile-Time Errors: Impossible configurations fail at compilation ✅ Runtime Diagnostics: Detailed debugging messages
The BMS implements hysteresis-based passive cell balancing using LTC6811 DCC bits:
- Algorithm: Averages cell voltages, applies on/off thresholds (±15/±8 mV for HV LiPo)
- Gating: Respects min voltage, max temperature, and charging state
- State Tracking:
pack->balancing_activeflag for real-time monitoring - Optimizations: Single lock per cycle + batch CFGR writes (one SPI transaction instead of per-IC)
Configuration (per pack type):
g_pack.balance_on_delta_mv = 0.015f; /* 15 mV hysteresis ON */
g_pack.balance_off_delta_mv = 0.008f; /* 8 mV hysteresis OFF */
g_pack.balance_min_cell_voltage = 3.20f; /* don't balance below 3.2V */
g_pack.balance_max_temperature_C = 45.0f; /* disable if too hot */The charger manager implements configuration-aware Constant Current / Constant Voltage (CC/CV) charging:
- CC Phase: Charges at maximum current until target voltage is reached
- CV Phase: Maintains constant voltage (OV limit - 2.0V safety margin) while current naturally decreases
- Termination: Stops when charging current drops below threshold (pack-specific)
- Thermal De-rating: Linear current reduction from 45°C to 80°C (minimum 10%)
- Pack-Aware: Automatically adapts to selected configuration (full pack, single module, cylindrical)
- Hardware Validation: Commands validated against CAN protocol limits (0.1-10000V, 0.1-1500A)
⚠️ - Safety: Automatic stop on charger error, SoC >99%, or temperature limits exceeded
Charging Parameters (per pack type):
g_pack.charge_cc_to_cv_tol_percent = 1.0f; /* CC→CV transition tolerance */
g_pack.charge_cc_to_cv_tol_min_v = 0.5f; /* minimum tolerance in volts */
g_pack.charge_cv_current_stop_ratio = 0.05f; /* stop at 5% of max current */
g_pack.charge_cv_current_stop_min_a = 0.30f; /* minimum stop current */- Configure your charger's voltage/current ranges via its manual settings
- The BMS will enforce these protocol limits but respects the pack's electrical limits (OV/UV per cell)
- Pack configuration automatically adapts to your chosen pack type (full, single module, cylindrical)
Charging State Transitions:
- ACTIVE → CHARGING: Triggered when charger is connected
- CC Phase: Full current ramp to CV voltage (target = OV_pack - 2.0V)
- CC → CV: Automatic transition when voltage reaches threshold
- CV Phase: Constant voltage with current naturally decreasing
- CV → Stop: Termination when charging current falls below threshold (5% of max_charge_current or 0.3A minimum, whichever is higher)
- CHARGING → ACTIVE: Exit on error, completion, or charger disconnect
Charger Thermal Management (EVO11KA - from manual):
- Fan Auto-Activation: Charger fan automatically activates at ≥40°C internal temperature (per MT4404-D manual)
- De-rating Zone: Linear power reduction from 45°C to maximum operating temperature
- Thermal Protection: Charger throttles or shuts down above 80°C
- Note: BMS implements complementary thermal de-rating on charging current (45-80°C range with 10% minimum)
Charger Hardware Specifications (from MT4404-D CAN Bus Manual):
The EVO11KA is a 11 kW on-board charger with CAN v2.0B control (125kbit/s - 1Mbit/s):
- AC Input Voltage:
- 1-phase: 90-265 Vac (reduced power), 185-265 Vac (full power)
- 3-phase: 155-460 Vac (various configurations)
- Frequency: 47-63 Hz
- Power Factor: >0.98
- LED Status Indicator: The charger's LED is controlled via CAN command and turns ON when AC power is detected and available. The LED serves as a visual indicator that AC mains voltage is present and the charger is ready to receive charging commands.
- DC Output Models (EVO11KAR variants - Factory-Fixed, cannot be changed):
- R1: 420V, 40A (10 kW)
- R2: 500V, 33A (10 kW)
- R3: 670V, 25A (10 kW)
- R4: 840V, 20A (10 kW)
The charger is pre-configured at the factory with specific settings that cannot be changed via CAN:
- Hardware Model (R1/R2/R3/R4): Determined at order time, permanently fixed in charger firmware
- AC Input Class (100V vs 400V): Automatic detection based on AC voltage provided - NOT configured
- DC Output Voltage/Current Limits: Fixed by hardware model selection (cannot exceed)
- Fan Management: Automatic (fan activates at ≥40°C per MT4404-D)
- Baudrate & CAN Format: 500kbit/s, 11-bit Standard (default factory setting)
CAN Message Communication (LEVEL 1 - Real-Time Control):
The BMS communicates with the charger via 5 real-time CAN messages:
| CAN ID | Direction | Name | Frequency | Purpose |
|---|---|---|---|---|
| 0x618 | TX (BMS→Charger) | CTL | 100 ms | Control: Voltage/current targets |
| 0x610 | RX (Charger→BMS) | STAT | 1000 ms | Status flags (error, warning, de-rating) |
| 0x611 | RX (Charger→BMS) | ACT1 | 100 ms | Actual Values: Iac, Vout, Iout, Temp |
| 0x614 | RX (Charger→BMS) | ACT2 | 1000 ms | Actual Values: Temp logic, Power, Limits |
| 0x615 | RX (Charger→BMS) | TST1 | 100 ms | Diagnostic: Status flags + hours counter |
BMS Implementation - How Data is Handled:
All charger CAN messages are received and decoded into the Charger_State_t structure (protected by Mutex_CHARGER):
/* From Drivers/EVO11KA/charger.h */
typedef struct {
/* Status from STAT (0x610) */
bool status_power_enable; /* Power pin active */
bool status_error_latch; /* Error occurred */
bool status_warn_limit; /* Warning condition */
bool status_lim_temp; /* Temperature de-rating active */
/* Actual values from ACT1 (0x611) */
float actual_iac_A; /* AC input current */
float actual_vout_V; /* DC output voltage */
float actual_iout_A; /* DC output current */
float actual_temp_power_C; /* Power stage temperature */
/* Actual values from ACT2 (0x614) */
float actual_temp_logic_C; /* Logic stage temperature */
float actual_ac_power_kW; /* AC input power */
float actual_prox_limit_A; /* Proximity current limit */
float actual_pilot_limit_A; /* Pilot current limit */
/* Diagnostics from TST1 (0x615) - 27 status flags */
bool diag_ack; /* AC Mains connected */
bool diag_pwr_ok; /* Charger providing power */
bool diag_ovp; /* Output over-voltage */
bool diag_thermal_fail; /* Thermal de-rating active */
bool diag_rx_timeout; /* Control message timeout (>600ms) */
/* ... additional 22 diagnostic flags ... */
} Charger_State_t;What the BMS Sends (Control Message - ID 0x618):
Ctl.VoutSet = desired_voltage_V × 10 [hex] /* Pack voltage (100-600V) */
Ctl.IoutSet = desired_current_A × 10 [hex] /* Pack current (varies per config) */
Ctl.IacSet = 16.0f × 10 [hex] /* Fixed: 16A/phase for EVO11K */
Ctl.Enable = true/false /* Enable/disable charging */- Voltage/Current automatically adapt to selected pack configuration (FULL/MODULE_SINGLE/MODULE_CYLINDRICAL)
- No manual setup required - charger receives correct targets per pack type
- Charger auto-selects output model based on commanded voltage (charger validates against its hardware limits)
Standard Setup Values Reference (from manual - for verification only):
EVO11KA (Air-Cooled, R1 @ 420V): 18 01 50 10 68 01 90 A5
EVO11KA (Air-Cooled, R2 @ 500V): 1A 01 50 13 88 01 4A A5 ← Typical for POUCH packs
EVO11KA (Air-Cooled, R3 @ 670V): 1C 01 50 1A 2C 00 FA A5
EVO11KA (Air-Cooled, R4 @ 840V): 1E 01 50 20 D0 00 C8 A5
These are factory defaults only - included for verification. BMS does NOT modify these.
For development and testing purposes, the BMS supports a standalone charger debug mode that bypasses pack-level constraints from slave modules (SoC limits, cell temperature thresholds, etc.).
Enable Debug Mode:
Set the following define to 1 in Core/Inc/Drivers/EVO11KA/charger_debug_config.h:
#define DEBUG_CHARGER_STANDALONE 1 /* Set to 1 to enable standalone debug mode */What Debug Mode Does:
- Bypasses SoC Limits: Charges to the configured target voltage regardless of pack SoC (normally capped at 99%)
- Ignores Cell Temperatures: Disables thermal de-rating based on cell temperatures
- Direct Charger Control: Allows testing the charger's CC/CV charging algorithm independently from pack monitoring
- Fixed Parameters: Uses standalone configuration parameters (voltage/current targets) defined in
charger_debug_config.h - Useful For:
- Validating charger firmware behavior
- Testing DC output voltage/current accuracy
- Verifying thermal management without pack constraints
- Hardware integration debugging
DEBUG_CHARGER_STANDALONE = 0) for production use and normal BMS operation.
The system implements BMS logic functions called by the FreeRTOS tasks generated by CubeMX:
- LTC6811-1: Cell voltage monitoring with ±0.1mV accuracy
- LTC6820: Galvanic isolation via isoSPI
- Emergency Shutdown (SDC): Hardware-based safety circuit
- Unified safety system:
BMS_Safetywith centralized thresholds - Immediate callbacks: <50μs response time for critical emergencies
- Multilevel protection: Voltage, temperature, communication
- Watchdog: Task responsiveness monitoring
- Interlock Safety: The safety system continuously monitors the vehicle interlock status to prevent charging when the interlock circuit is open, providing hardware-level protection against unauthorized or unsafe charging scenarios
- Safe state management: Automatic emergency procedures
Unified Safety Thresholds (in Core/Inc/BMS/safety.h):
#define SAFETY_CELL_OVERVOLTAGE_LIMIT_MV 4250U
#define SAFETY_CELL_UNDERVOLTAGE_LIMIT_MV 2500U
#define SAFETY_TEMP_OVERTEMP_LIMIT_C 60
#define SAFETY_TEMP_UNDERTEMP_LIMIT_C -20The BMS system uses FreeRTOS binary mutexes to protect shared resources. All mutexes are defined in Core/Inc/BMS/mutexes.h:
| Mutex | Protected Resource | Status | Usage |
|---|---|---|---|
Mutex_PACK |
Pack data structure | ✅ Active | Pack_Lock() / Pack_Unlock() |
Mutex_LTC_Buffer |
LTC6811 buffer | ✅ Active | Mutex_LTC_Buffer_Lock() / Mutex_LTC_Buffer_Unlock() |
Mutex_BMS_State |
BMS state machine | ✅ Active | Mutex_BMS_State_Lock() / Mutex_BMS_State_Unlock() |
Mutex_Charger |
Charger state | ✅ Active | Mutex_Charger_Lock() / Mutex_Charger_Unlock() |
Mutex_USART |
Serial communication | ⏸️ Reserved | Not currently used (data logger outputs without mutex) |
Mutex_SPI |
SPI bus access | ⏸️ Reserved | Not currently used (defined for future expansion) |
Mutex_CAN |
CAN message queue | ⏸️ Reserved | Not currently used (defined for future expansion) |
The FreeRTOS mutex implementation uses BINARY mutexes, NOT recursive mutexes.
- ✅ Safe: Different tasks acquiring different mutexes
- ✅ Safe: Collecting data under brief locks, then releasing before long operations (e.g., UART output without mutex)
- ❌ UNSAFE: Same task attempting to acquire the SAME mutex twice → DEADLOCK
- ❌ UNSAFE: Holding a mutex during long blocking operations (e.g., UART transmission) → STARVATION
Pattern Used in Data Logger: Data logger collects all required data under brief mutex locks (copy pack structure, copy PEC counters), then releases all mutexes before performing UART output. This prevents deadlock and starvation.
- Cell Voltages: 138 cells individually monitored via LTC6811
- Pack Voltage: Automatically calculated from cells
- Temperatures: Multi-point thermal monitoring
- System Status: Continuous initialization with hardware status
- Safety Status: Unified system with centralized thresholds
- Balancing Status: Real-time active/inactive state displayed in logs
- Double-blink followed by long solid: System initializing (hardware detection)
- Solid LED On: System operational (LTC6811 active)
- Countinous Blink LED: Charging
- LED Off: Fault detected, unsafe condition, or system inactive (e.g., at power-up)
- Continuous Init: Automatic detection of LTC6811 and Charger hardware
- Response Times: <50μs for critical emergency functions
- Memory Monitoring: Real-time heap usage
- Hardware Status: Online/Offline for each component
- Hardware Detection: LTC6811 and Charger automatically detected
- Fault Logging: Unified system via BMS_Safety
- Communication Status: isoSPI and CAN monitoring
- Balancing Monitoring: Real-time status (ON/OFF) in serial debug output
- Serial Debug: Continuous status via UART2 115200 baud
Debug Output Example:
State: ACTIVE | Slaves: ONLINE | Balancing: ON | Charger: ONLINE | Charging: OFF
Charger output: Vout=0.0V | Iout=0.00A
Current: Ipck=0.23A
SoC: 87.5%
IC[0]: 4.150V 4.145V 4.140V 4.138V 4.142V 4.145V 4.143V 4.140V 4.139V 4.142V 4.141V 4.144V
IC[1]: 4.151V 4.146V 4.139V 4.140V 4.143V 4.145V 4.144V 4.141V 4.140V 4.143V 4.142V 4.145V
TEMP[0]: 25.0C 26.1C {24.8C - not connected} 25.3C 26.0C
TEMP[1]: 24.9C 26.2C {24.7C - not connected} 25.4C 26.1C
Pack: Vpack=552.145V | Cell Vmin=4.138V - IC[0][3] | Cell Vmax=4.151V - IC[1][0] | DeltaV=0.013V
Temps: Tmin=24C | Tmax=26C
CFG mismatches (CFGA/CFGRB): 0
PEC totals (cum.): overall=0 | config(cum.)=0
PEC per-IC (cum.): IC[0]=0 IC[1]=0
PEC last-read mismatches: CV=0 | AUX=0 | STAT=0
PEC status: OK
--------------------------------------------------
Author: Davide Ronchini (Polimarche Racing Team) Date: November 2026 Version: 1.0 - Production Release (Cleaned Architecture) Compliance: FSAE 2026, Melasta SLPB9270175HV optimized
Architecture:
- Algorithms: Advanced algorithms in
Core/Inc/Algorithms/(balancing, SoC, measurements) - Unified Safety System:
Core/Inc/BMS/safety.handCore/Src/BMS/safety.c - Logic Separation: Logical BMS functions separated from FreeRTOS tasks
- Continuous Initialization: Automatic hardware detection
Hardware Credits:
- STMicroelectronics: STM32F446RE microcontroller
- Analog Devices: LTC6811-1 and LTC6820 ICs
- FreeRTOS: Real-time operating system kernel
- Melasta: SLPB9270175HV Li-Polymer cells
⚡ Ready for FSAE 2026 Competition ⚡
This BMS provides a professional, safety-critical foundation for Formula SAE electric vehicle battery management with comprehensive monitoring, fault protection, and regulatory compliance.