This is a debugging story. Not a tutorial. The kind of story where everything looks correct, the CAN bus analyser shows clean traffic, your oscilloscope shows clean signals, and yet your microcontroller is receiving absolutely nothing. Silently. No errors. No flags. Just silence.
This happened to us during the Pylontech BMS integration on devibot. The BMS communicates via CANopen using 29-bit extended CAN frames (FDCAN_EXTENDED_ID). Our STM32G491 FDCAN peripheral was configured — we thought — to receive everything. It was not.
The symptom
After wiring up the Pylontech BMS to the CAN bus, our battery monitoring task was reporting stale data. Voltage frozen. Current frozen. SoC frozen. The last known values from bootup, never updating.
First instinct: hardware. We checked the CAN bus with a USB-CAN analyser (PEAK PCAN-USB). The BMS was transmitting correctly — clean frames every 100ms, proper CAN IDs, correct data. The bus looked healthy.
Second instinct: firmware receive callback not being called. We added a counter inside HAL_FDCAN_RxFifo0Callback(). Counter stayed at zero. The callback was never being called, even though the bus was clearly active.
The cause: global filter default behaviour
The STM32 FDCAN peripheral has two levels of filtering: individual filters (per-ID or per-range), and a global filter that handles frames not matching any individual filter. The key parameter is NonMatchingStd and NonMatchingExt — what to do with standard (11-bit) and extended (29-bit) frames that don’t match any configured filter.
The default value for both is FDCAN_FILTER_REJECT. Reject everything that doesn’t match a filter.
Our individual filters were configured for standard 11-bit IDs only (for the motor controllers, which use standard frames). We had no extended ID filters configured. The global filter was rejecting all extended frames by default. The BMS, sending extended frames, was being silently dropped.
The fix
FDCAN_FilterTypeDef filter_config;
filter_config.IdType = FDCAN_EXTENDED_ID;
filter_config.FilterIndex = 0;
filter_config.FilterType = FDCAN_FILTER_RANGE;
filter_config.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
filter_config.FilterID1 = 0x00000000; // Accept all
filter_config.FilterID2 = 0x1FFFFFFF; // Extended ID max
HAL_FDCAN_ConfigFilter(&hfdcan1, &filter_config);
// Also update global filter to not reject non-matching extended frames
FDCAN_GlobalFilterTypeDef global_filter;
global_filter.NonMatchingStd = FDCAN_FILTER_REJECT; // Still reject unknown std
global_filter.NonMatchingExt = FDCAN_FILTER_TO_RXFIFO0; // Accept all extended
global_filter.RejectRemoteStd = DISABLE;
global_filter.RejectRemoteExt = DISABLE;
HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, &global_filter);
Key insight: Even with individual extended ID filters configured, if NonMatchingExt is set to FDCAN_FILTER_REJECT, frames that fall outside your filter range are still dropped. Always set the global filter explicitly — never rely on defaults.
How to verify
After applying the fix, verify in three ways:
- Add a frame counter in
HAL_FDCAN_RxFifo0Callback()and confirm it increments - Print the received CAN ID: confirm you’re seeing the BMS frame IDs (0x351, 0x355, 0x356 for Pylontech)
- Check the FDCAN_IR (interrupt register) for
RF0N(Rx FIFO 0 new message) flag
Lessons
Never trust CAN filter defaults. The HAL initialises most peripheral settings, but the global filter default (reject all non-matching) will silently drop traffic from devices you haven’t explicitly filtered for. Always configure both individual filters and the global filter explicitly.
29-bit extended vs 11-bit standard is not negotiable. The BMS manufacturer chose extended IDs for a reason — the extended ID space is much larger and avoids conflicts with other devices on the same bus. Check your device’s CAN specification before writing a single line of filter configuration code.
A CAN analyser is essential. Without external hardware showing that the BMS was actually transmitting, this would have been diagnosed as a BMS hardware fault. Always verify at the bus level before debugging the receiver.
Amit Jagnani is the Founder & CTO of Peribott Dynamic LLP. This debugging session was part of integrating the Pylontech BMS into the devibot AMR platform. Questions? Email directly.