Skip to main content
Visitor II
June 20, 2026
Question

STM32G474RE FDCAN Bus-Off with MS72450 motor controller — signals visible on scope but no ACK

  • June 20, 2026
  • 1 reply
  • 12 views

Hi,

I am trying to establish CAN communication between a 
NUCLEO-G474RE and a YMMOTOR MS72450 motor controller 
using the FDCAN1 peripheral.

HARDWARE SETUP:
- STM32 NUCLEO-G474RE (STM32G474RETx)
- Transceiver: WCMCU-230 (SN65HVD230), 3.3V, connected 
  to PA11 (FDCAN1_RX) and PA12 (FDCAN1_TX) via AF9
- Motor controller: YMMOTOR MS72450 (CAN 2.0B, 500kbps, 
  standard frame, Motorola byte order)
- Termination: 120 ohm on motor controller side (factory 
  default built-in). 120 ohm on transceiver side.
- Common GND connected between STM32 and motor controller
- Motor controller ACC pin (pin 10, 48-85V) is powered

CLOCK CONFIGURATION:
- HSI 16MHz -> PLL -> 170MHz system clock
- FDCAN kernel clock = PLLQ = 170MHz
- NominalPrescaler=17, TimeSeg1=15, TimeSeg2=4, SJW=1
- Calculated bitrate = 170MHz / (17 x 20TQ) = 500kbps

SYMPTOM:
- Green LED blinks initially = TX frames being queued 
  successfully (HAL_FDCAN_AddMessageToTxFifoQ returns HAL_OK)
- Oscilloscope on CANH/CANL shows clear CAN signals from 
  the motor controller side
- But STM32 always goes into Bus-Off after a few seconds
- busOffCount variable keeps incrementing
- rxCounter stays at 0 — no frames received from motor 
  controller
- Motor controller is broadcasting 0x411/0x412 every 50ms 
  (confirmed on scope)

WHAT I THINK IS HAPPENING:
Motor controller receives our 0x415 frame but does not 
ACK it, possibly due to bitrate mismatch from HSI 
oscillator tolerance (+/-1%). STM32 error counter climbs 
to Bus-Off.

QUESTION:
1. Is HSI clock tolerance causing the bitrate mismatch 
   that prevents ACK?
2. Should I switch to HSE (ST-Link MCO 8MHz bypass) for 
   more accurate timing?
3. Is there anything wrong with my FDCAN configuration?

MY FDCAN INIT CODE:
hfdcan1.Init.ClockDivider         = FDCAN_CLOCK_DIV1;
hfdcan1.Init.FrameFormat          = FDCAN_FRAME_CLASSIC;
hfdcan1.Init.Mode                 = FDCAN_MODE_NORMAL;
hfdcan1.Init.AutoRetransmission   = DISABLE;
hfdcan1.Init.NominalPrescaler     = 17;
hfdcan1.Init.NominalSyncJumpWidth = 1;
hfdcan1.Init.NominalTimeSeg1      = 15;
hfdcan1.Init.NominalTimeSeg2      = 4;
hfdcan1.Init.StdFiltersNbr        = 1;

MY SYSTEM CLOCK CODE:
HSI, PLLM=DIV4, PLLN=85, PLLQ=DIV2
-> FDCAN kernel clock = 170MHz
-> 170MHz / (17 x 20TQ) = 500,000 bps

OSCILLOSCOPE:
[ATTACH YOUR SCOPE PHOTO HERE]
Time div: 20us/div
CH1 (CANH): 2V/div
CH2 (CANL): 2V/div
Clear CAN frame activity visible from motor controller.

Thank you

/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body - CAN Motor Controller Test
******************************************************************************
*/
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private variables ---------------------------------------------------------*/
COM_InitTypeDef BspCOMInit;
FDCAN_HandleTypeDef hfdcan1;

/* USER CODE BEGIN PV */
volatile uint32_t rxCounter = 0;
volatile uint8_t lastRxData[8];
volatile uint32_t lastRxID = 0;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_FDCAN1_Init(void);

/**
* @brief The application entry point.
*/
int main(void)
{
/* MCU Configuration */
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_FDCAN1_Init();

/* Initialize LED */
BSP_LED_Init(LED_GREEN);

/* Initialize USER push-button */
BSP_PB_Init(BUTTON_USER, BUTTON_MODE_EXTI);

/* Initialize COM1 port (115200, 8 bits, no parity) */
BspCOMInit.BaudRate = 115200;
BspCOMInit.WordLength = COM_WORDLENGTH_8B;
BspCOMInit.StopBits = COM_STOPBITS_1;
BspCOMInit.Parity = COM_PARITY_NONE;
BspCOMInit.HwFlowCtl = COM_HWCONTROL_NONE;
if (BSP_COM_Init(COM1, &BspCOMInit) != BSP_ERROR_NONE)
{
Error_Handler();
}

/* Infinite loop */
while (1)
{
FDCAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8];

// CAN ID 0x415 = VCU -> Motor Controller command frame
TxHeader.Identifier = 0x415;
TxHeader.IdType = FDCAN_STANDARD_ID;
TxHeader.TxFrameType = FDCAN_DATA_FRAME;
TxHeader.DataLength = FDCAN_DLC_BYTES_8;
TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
TxHeader.BitRateSwitch = FDCAN_BRS_OFF;
TxHeader.FDFormat = FDCAN_CLASSIC_CAN;
TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
TxHeader.MessageMarker = 0;

// Byte 0: Throttle percentage (0x00 = 0%, safe start)
// Change to 0x64 (=100 decimal = 100%) to command full throttle later
TxData[0] = 0x00;

// Byte 1: Gear + CAN control bits
// 0x08 = binary 00001000
// bit[7:5] = 000 -> P gear (safe, motor won't move)
// bit[4] = 0 -> no reverse
// bit[3] = 1 -> gear/throttle is CAN-controlled (not hardwire)
// bit[2:0] = 000 -> reserved
// Change to 0x68 (= 01101000) for 1st gear + CAN controlled once comms confirmed
TxData[1] = 0x08;

// Byte 2: Function switch (all off for now)
TxData[2] = 0x00;

// Bytes 3-7: unused, set to 0
TxData[3] = 0x00;
TxData[4] = 0x00;
TxData[5] = 0x00;
TxData[6] = 0x00;
TxData[7] = 0x00;

// Send the frame - LED blinks on every successful TX
if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, TxData) == HAL_OK)
{
BSP_LED_Toggle(LED_GREEN);
}

// Motor controller expects 0x415 every 50ms per the CjPower spec
HAL_Delay(50);
}
}

/**
* @brief System Clock Configuration
* HSI -> PLL -> 170MHz system clock
* FDCAN kernel clock = PLLQ = 170MHz
* Bit timing: 170MHz / (Prescaler17 x 20TQ) = 500kbps
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST);

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV4;
RCC_OscInitStruct.PLL.PLLN = 85;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}

RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}

/**
* @brief FDCAN1 Initialization
* Classic CAN, Normal mode, 500kbps
*/
static void MX_FDCAN1_Init(void)
{
hfdcan1.Instance = FDCAN1;
hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1;
hfdcan1.Init.FrameFormat = FDCAN_FRAME_CLASSIC;
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan1.Init.AutoRetransmission = DISABLE;
hfdcan1.Init.TransmitPause = DISABLE;
hfdcan1.Init.ProtocolException = DISABLE;

// Bit timing for 500kbps @ 170MHz FDCAN kernel clock
// 170MHz / (17 x 20TQ) = 500,000 bps
hfdcan1.Init.NominalPrescaler = 17;
hfdcan1.Init.NominalSyncJumpWidth = 1;
hfdcan1.Init.NominalTimeSeg1 = 15;
hfdcan1.Init.NominalTimeSeg2 = 4;

// Data phase (unused in Classic CAN, kept for HAL compatibility)
hfdcan1.Init.DataPrescaler = 17;
hfdcan1.Init.DataSyncJumpWidth = 4;
hfdcan1.Init.DataTimeSeg1 = 15;
hfdcan1.Init.DataTimeSeg2 = 4;

hfdcan1.Init.StdFiltersNbr = 1;
hfdcan1.Init.ExtFiltersNbr = 0;
hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;

if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK)
{
Error_Handler();
}

// Filter: accept ALL standard IDs (catch-all for debugging)
// Once comms confirmed, narrow to 0x411/0x412 only
FDCAN_FilterTypeDef sFilterConfig;
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_MASK;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x000; // ID
sFilterConfig.FilterID2 = 0x000; // Mask 0x000 = accept everything
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}

// Accept non-matching frames into FIFO0 as well (belt and suspenders)
if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan1,
FDCAN_ACCEPT_IN_RX_FIFO0,
FDCAN_ACCEPT_IN_RX_FIFO0,
FDCAN_FILTER_REMOTE,
FDCAN_FILTER_REMOTE) != HAL_OK)
{
Error_Handler();
}

// Enable FDCAN interrupt in NVIC so RxFifo0Callback actually fires
HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn);

// Start peripheral FIRST
if (HAL_FDCAN_Start(&hfdcan1) != HAL_OK)
{
Error_Handler();
}

// THEN activate RX interrupt notification
if (HAL_FDCAN_ActivateNotification(&hfdcan1,
FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK)
{
Error_Handler();
}
}

/**
* @brief GPIO Initialization
* Configures PA11/PA12 as FDCAN1 RX/TX alternate function
*/
static void MX_GPIO_Init(void)
{
// Enable GPIO port clocks
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();

// Configure PA11 (FDCAN1_RX) and PA12 (FDCAN1_TX)
// as Alternate Function - connects pins to FDCAN1 peripheral internally
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_FDCAN1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

/**
* @brief RX Callback - fires automatically when a CAN frame arrives
* Updates rxCounter, lastRxID, lastRxData for Live Expressions monitoring
*/
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
FDCAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8];

if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK)
{
rxCounter++;
lastRxID = RxHeader.Identifier;
for (int i = 0; i < 8; i++)
{
lastRxData[i] = RxData[i];
}
}
}

/**
* @brief Error Handler
*/
void Error_Handler(void)
{
__disable_irq();
while (1) { }
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line) { }
#endif

 

1 reply

mƎALLEm
ST Technical Moderator
June 22, 2026

Hello,

It could be a bitrate configuration or hardware / connection issue.

Check the connections using the continuity test.

Please share also your project as well as the schematic.

Use a crystal instead of internal HSI for FDCAN clock source to avoid communication issues.

To give better visibility on the answered topics, please click "Best answer" on the reply which solved your issue or answered your question.