2025-10-24 4:10 AM
Hi, I am using three STM32 devices for CAN communication:
Hardware
Device A: STM32G431 (FDCAN)
Device B: STM32F303 (Classic CAN)
Device C: STM32H7A3 (FDCAN)
Setup
A sends a continuous sequence of commands to B.
B processes each command and replies to A over the same CAN bus.
One specific command requires B to perform a synchronization sequence with C (also over CAN).
While B is synchronizing with C, A continues to send commands to B.
Issue
During the B↔C synchronization, B intermittently misses some frames from A (i.e., A transmits, but B does not process/see a corresponding message).
The failure is not immediate: A↔B + B↔C run together fine for a while (sometimes short, sometimes long), then a frame from A is skipped.
When the synchronization with C is disabled, A↔B runs reliably with no missed frames.
CAN ID from A to B: 0x12C
CAN ID from B to A: 0x12D
CAN IDs between B and C (sync purposes): 0x2BC, 0x2BD and 0x2BE
As you can see from the inserted can config the sampling point for 3 devices is at 87.5%. Bitrate is 500000 bps for three of them.
The CAN configurations for device A:
static void MX_FDCAN1_Init(void)
{
/* USER CODE BEGIN FDCAN1_Init 0 */
/* USER CODE END FDCAN1_Init 0 */
/* USER CODE BEGIN FDCAN1_Init 1 */
/* USER CODE END FDCAN1_Init 1 */
hfdcan1.Instance = FDCAN1;
hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1;
hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_NO_BRS;
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan1.Init.AutoRetransmission = ENABLE;
hfdcan1.Init.TransmitPause = DISABLE;
hfdcan1.Init.ProtocolException = DISABLE;
hfdcan1.Init.NominalPrescaler = 40;
hfdcan1.Init.NominalSyncJumpWidth = 1;
hfdcan1.Init.NominalTimeSeg1 = 6;
hfdcan1.Init.NominalTimeSeg2 = 1;
hfdcan1.Init.DataPrescaler = 25;
hfdcan1.Init.DataSyncJumpWidth = 1;
hfdcan1.Init.DataTimeSeg1 = 6;
hfdcan1.Init.DataTimeSeg2 = 1;
hfdcan1.Init.StdFiltersNbr = 8;
hfdcan1.Init.ExtFiltersNbr = 0;
hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN FDCAN1_Init 2 */
FDCAN_FilterTypeDef sFilterConfig;
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x12C;
sFilterConfig.FilterID2 = 0x18F;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
Error_Handler();
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 1;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_REJECT;
sFilterConfig.FilterID1 = 0x00;
sFilterConfig.FilterID2 = 0x12B;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
Error_Handler();
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 2;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_REJECT;
sFilterConfig.FilterID1 = 0x190;
sFilterConfig.FilterID2 = 0x31F;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
Error_Handler();
/* USER CODE END FDCAN1_Init 2 */
}Device B:
static void MX_CAN_Init(void)
{
/* USER CODE BEGIN CAN_Init 0 */
/* USER CODE END CAN_Init 0 */
/* USER CODE BEGIN CAN_Init 1 */
/* USER CODE END CAN_Init 1 */
hcan.Instance = CAN;
hcan.Init.Prescaler = 9;
hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_6TQ;
hcan.Init.TimeSeg2 = CAN_BS2_1TQ;
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = DISABLE;
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = ENABLE;
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN CAN_Init 2 */
CAN_FilterTypeDef canfilterconfig;
canfilterconfig.FilterActivation = CAN_FILTER_ENABLE;
canfilterconfig.FilterBank = 0; // which filter bank to use from the assigned ones
canfilterconfig.FilterFIFOAssignment = CAN_FILTER_FIFO0;
canfilterconfig.FilterIdHigh = 0x12C<<5;
canfilterconfig.FilterIdLow = 0;
canfilterconfig.FilterMaskIdHigh = 0x7FF<<5;
canfilterconfig.FilterMaskIdLow = 0x0000;
canfilterconfig.FilterMode = CAN_FILTERMODE_IDMASK;
canfilterconfig.FilterScale = CAN_FILTERSCALE_32BIT;
HAL_CAN_ConfigFilter(&hcan, &canfilterconfig);
canfilterconfig.FilterActivation = CAN_FILTER_ENABLE;
canfilterconfig.FilterBank = 1; // which filter bank to use from the assigned ones
canfilterconfig.FilterFIFOAssignment = CAN_FILTER_FIFO1;
canfilterconfig.FilterIdHigh = 0x2BD<<5;
canfilterconfig.FilterIdLow = 0;
canfilterconfig.FilterMaskIdHigh = 0x7FF<<5;
canfilterconfig.FilterMaskIdLow = 0x0000;
canfilterconfig.FilterMode = CAN_FILTERMODE_IDMASK;
canfilterconfig.FilterScale = CAN_FILTERSCALE_32BIT;
HAL_CAN_ConfigFilter(&hcan, &canfilterconfig);
/* USER CODE END CAN_Init 2 */
}Device C:
void MX_FDCAN1_Init(void)
{
/* USER CODE BEGIN FDCAN1_Init 0 */
/* USER CODE END FDCAN1_Init 0 */
/* USER CODE BEGIN FDCAN1_Init 1 */
/* USER CODE END FDCAN1_Init 1 */
hfdcan1.Instance = FDCAN1;
hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_NO_BRS;
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan1.Init.AutoRetransmission = ENABLE;
hfdcan1.Init.TransmitPause = DISABLE;
hfdcan1.Init.ProtocolException = DISABLE;
hfdcan1.Init.NominalPrescaler = 12;
hfdcan1.Init.NominalSyncJumpWidth = 1;
hfdcan1.Init.NominalTimeSeg1 = 6;
hfdcan1.Init.NominalTimeSeg2 = 1;
hfdcan1.Init.DataPrescaler = 12;
hfdcan1.Init.DataSyncJumpWidth = 1;
hfdcan1.Init.DataTimeSeg1 = 6;
hfdcan1.Init.DataTimeSeg2 = 1;
hfdcan1.Init.MessageRAMOffset = 0;
hfdcan1.Init.StdFiltersNbr = 8;
hfdcan1.Init.ExtFiltersNbr = 0;
hfdcan1.Init.RxFifo0ElmtsNbr = 4;
hfdcan1.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_64;
hfdcan1.Init.RxFifo1ElmtsNbr = 0;
hfdcan1.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_8;
hfdcan1.Init.RxBuffersNbr = 0;
hfdcan1.Init.RxBufferSize = FDCAN_DATA_BYTES_8;
hfdcan1.Init.TxEventsNbr = 0;
hfdcan1.Init.TxBuffersNbr = 0;
hfdcan1.Init.TxFifoQueueElmtsNbr = 1;
hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
hfdcan1.Init.TxElmtSize = FDCAN_DATA_BYTES_64;
if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN FDCAN1_Init 2 */
FDCAN_FilterTypeDef sFilterConfig;
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x01;
sFilterConfig.FilterID2 = 0x12B;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
Error_Handler();
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 1;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_REJECT;
sFilterConfig.FilterID1 = 0x12C;
sFilterConfig.FilterID2 = 0x18F;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
Error_Handler();
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 2;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x190;
sFilterConfig.FilterID2 = 0x1F3;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
Error_Handler();
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 3;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_REJECT;
sFilterConfig.FilterID1 = 0x1F4;
sFilterConfig.FilterID2 = 0x257;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
Error_Handler();
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 4;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x258;
sFilterConfig.FilterID2 = 0x351;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
Error_Handler();
/* USER CODE END FDCAN1_Init 2 */
}For sending and receiving CAN messages, the functions are as follows:
Device A:
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
CanFrameBuffer *frame = &canRxBuffer[canRxWriteIdx];
memset(frame, 0, sizeof(CanFrameBuffer));
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, frame->data) != HAL_OK)
{
Error_Handler();
}
canRxWriteIdx++;
if (canRxWriteIdx >= CAN_RX_BUF_SIZE)
canRxWriteIdx = 0;
}
void ProcessUSBData(uint8_t *Buf, uint16_t Len)
{
uint16_t bytesSent = 0;
while (bytesSent < Len)
{
uint8_t chunk[CAN_PAYLOAD_SIZE] = {0};
uint8_t chunkSize = (Len - bytesSent) > CAN_PAYLOAD_SIZE ?
CAN_PAYLOAD_SIZE : (Len - bytesSent);
TxHeader.DataLength = dlc_from_len(chunkSize);
while (HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan1) == 0);
if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, &Buf[bytesSent]) != HAL_OK)
Error_Handler();
bytesSent += chunkSize;
}
// Always send an explicit 0x00 terminator frame
uint8_t terminator = 0x00;
TxHeader.DataLength = FDCAN_DLC_BYTES_1;
while (HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan1) == 0);
if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, &terminator) != HAL_OK)
Error_Handler();
}
Device B. I use two Fifos:
Fifo0: messages from A
Fifo1: messages from C
void HAL_CAN_RxFifo0FullCallback(CAN_HandleTypeDef *hcan)
{
while (HAL_CAN_GetRxFifoFillLevel(hcan, CAN_RX_FIFO0) > 0) {
CAN_RxHeaderTypeDef hdr;
uint8_t d[8];
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &hdr, d) != HAL_OK)
{
Error_Handler(); // Handle receive error
}
if (hdr.StdId == Sync_Res) {
syncAckReceived = 1;
rx1_sync_isr_count++;
continue;
}
uint8_t next = (uint8_t)((RxWrite + 1u) % RXBUFSIZE);
if (next == RxRead) {
// overflow: drop oldest (or set a flag/counter)
g_swDrops++;
continue;
}
// Use real DLC (0..8) and clear tail
uint8_t dlc = (uint8_t)(hdr.DLC & 0x0Fu);
if (dlc > 8) dlc = 8;
RxBuf[RxWrite].id = hdr.StdId; // you’re using standard IDs
RxBuf[RxWrite].dlc = dlc;
memcpy(RxBuf[RxWrite].data, d, dlc);
if (dlc < 8) {
memset(RxBuf[RxWrite].data + dlc, 0, 8 - dlc);
}
RxWrite = next;
}
}
For sending messages:
while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0);
CAN_SendCommand(cmdID, chunk, idx);
bool CAN_SendCommand(PIPMOD_COMMANDS cmdID, uint8_t *data, uint8_t idx)
{
TxHeader.DLC = 8; // data length
TxHeader.IDE = CAN_ID_STD;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.StdId = cmdID;
if (data == NULL)
{
TxHeader.DLC = 0;
}
if (HAL_CAN_AddTxMessage(&hcan, &TxHeader, (uint8_t *)data, &TxMailbox) != HAL_OK)
{
sprintf(responseBuffer, "CAN Transmission failed: %s", GetCommandName(cmdID));
EnqueueLCErrorResponse(&arCommUnits[idx], UTF8create(responseBuffer, 4), ERROR_INTERFACE_PORTAL);
return false;
}
while (HAL_CAN_IsTxMessagePending(&hcan, TxMailbox));
return true;
}void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
while (HAL_CAN_GetRxFifoFillLevel(hcan, CAN_RX_FIFO1) > 0) {
CAN_RxHeaderTypeDef hdr;
uint8_t data[8];
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO1, &hdr, data) != HAL_OK) {
Error_Handler();
}
rx1_isr_count++;
if (hdr.StdId == Sync_Res) {
syncAckReceived = 1;
rx1_sync_isr_count++;
}
}
}Device C:
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
{
Error_Handler();
}
if (RxHeader.Identifier == SYNC_START) IsSyncStart = 1;
if (RxHeader.Identifier == SYNC_END) IsSyncRes = 1;
}
bool CAN_SendCommand(PORTAL_COMMANDS cmdID, uint8_t *data, uint8_t idx)
{
if (cmdID == SYNC_RES) { // Device B uses normal CAN (only 8 bytes)
TxHeader.FDFormat = FDCAN_CLASSIC_CAN;
TxHeader.DataLength = FDCAN_DLC_BYTES_8;
}
else {
TxHeader.FDFormat = FDCAN_FD_CAN;
TxHeader.DataLength = FDCAN_DLC_BYTES_64;
}
if (data == NULL)
{
TxHeader.DataLength = FDCAN_DLC_BYTES_0; // no payload
}
TxHeader.Identifier = cmdID;
TxHeader.IdType = FDCAN_STANDARD_ID;
TxHeader.TxFrameType = FDCAN_DATA_FRAME;
TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
TxHeader.BitRateSwitch = FDCAN_BRS_OFF;
TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
TxHeader.MessageMarker = 0;
if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, (uint8_t *)data) != HAL_OK)
{
sprintf(responseBuffer, "CAN Transmission failed: %s", GetCommandName(cmdID));
EnqueueLCErrorResponse(&arCommUnits[idx], UTF8create(responseBuffer, 4), ERROR_INTERFACE_PORTAL);
return false;
}
return true;
}The function for the sync between B and C:
Device B:
bool DoHwSync()
{
bool finishCmd = false;
PIPMOD_COMMANDS cmdID1, cmdID2;
cmdID1 = Sync_Start; //0x2BC
cmdID2 = Sync_End; //0x2BD
if(IS_FLAG_SET(pipettorState, STATE_WAIT_FOR_SYNC))
{
if(!IsSyncResSent) {
while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0);
if(!CAN_SendCommand(cmdID1, NULL, NULL)) return finishCmd;
}
IsSyncResSent = 1;
// Wait for sync res from C
if(!syncAckReceived) return finishCmd;
syncAckReceived = 0;
while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0);
if(!CAN_SendCommand(cmdID2, NULL, NULL)) return finishCmd;
IsSyncResSent = 0;
finishCmd = true;
return finishCmd;
}
return finishCmd;
}Device C:
bool HardSync(uint8_t asyncCmdIdx)
{
bool finishCmd = false;
uint32_t startTime = 0;
PORTAL_COMMANDS cmdID;
cmdID = SYNC_RES; //0x2BD
int32_t _timeout = 0;
if(IS_FLAG_SET(portalState, STATE_HARD_SYNC_PENDING) && IS_FLAG_SET(portalState, STATE_HARD_SYNC_WAITING))
{
if(!IsSyncStart) return finishCmd;
IsSyncStart = 0;
if(!CAN_SendCommand(cmdID, NULL, asyncCmdIdx))
return finishCmd;
uint32_t startTime = HAL_GetTick();
while (!IsSyncRes)
{
if (HAL_GetTick() - startTime > 5000) // 5 seconds
{
IsSyncRes = 0;
return ErrorHandlerSynchronize(asyncCmdIdx, 2);
}
}
IsSyncRes = 0;
RESET_FLAG(portalState, STATE_HARD_SYNC_PENDING);
RESET_FLAG(portalState, STATE_HARD_SYNC_WAITING);
finishCmd = true;
return finishCmd;
}
return finishCmd;
}As I said earlier, device B skipped one or two messages from A when sync action takes place between B and C, but it does not happen immediately (for example after 5 successful sync actions then it fails).
Another thing is take the CAN topology in the line (there are not only 3 CAN devices) is kind of tree/star, which is mistake made from the beginning. However, CAN transmission still works as expected until we have this sync action. The whole CAN line has only 2 terminator resistors (60 ohm is measured). I also tried to set bitrate to 250000 and 125000 bps but it still did not work.
I apologise for the long messages but I hope someone could help me with this.
Thank you in advance.
2025-10-24 5:04 AM - edited 2025-10-24 5:45 AM
Hello @RockCoder and welcome to the community,
More likely it's an implementation issue also depends on the load of your CAN bus.
My comments:
Setting BS2 to 1 is not a good idea. Try to increase BS1 and BS2 to increase them as much as possible and decreasing CAN prescaler.
I suggest to use CLASSIC frame format from FDCAN sides instead of FDCAN_FRAME_FD_NO_BRS.
Try to handle the received frames in a ring buffer filled in the Rx call backs and handled in main.
Play with the Rx interrupt priorities.. Increase it.
Check also if a FIFO overrun has occurred: FOVR bit is set in the CAN_RFR register:
The behavior depends on if the FIFO Lock is enabled or disabled: