HomeSTM32STM32 FDCAN Normal Mode Tutorial – Filters, RAM, and HAL Examples

STM32 FDCAN Normal Mode Tutorial – Message RAM & Filter Examples

This tutorial explains how to use STM32 FDCAN in Normal Mode with a focus on message RAM configuration and filter setup. While the previous guide covered loopback mode, here we will set up two FDCAN peripherals on STM32H7 to communicate with each other. Developers often search for stm32 fdcan example, hal_fdcan_addmessagetotxfifoq, or fdcan filter configuration, so we’ll cover those with code examples. 

You’ll also learn about HAL_FDCAN_ActivateNotification, HAL_FDCAN_GetRxMessage, and FIFO callbacks. By the end, you’ll be ready to implement reliable FDCAN communication in your STM32 projects.

As you already know we need at least 2 can devices to use the normal mode. I don’t have another FDCAN device, but my STM32H7 board has 2 FDCAN peripherals. So I am going to communicate between those 2 FDCAN peripherals.

This brings us to a very important parameter in the FDCAN configuration, the message RAM. When using more than 1 FDCAN peripheral, we need to assign the message RAM to each peripheral. This is something we will see new in this tutorial, whereas the basic configuration will remain same as the previous tutorial.

R307 Fingerprint Module Video Tutorial

While the written guide below provides all the details and code for reference, sometimes a visual demonstration can make all the difference. I’ve created a complete video walkthrough that runs through the entire process in real-time. Follow the written steps here while watching the implementation in the video to solidify your understanding and catch any subtle details

Watch the Video

Parts Required

You can purchase all the parts and components used in this project through the links provided below.

Hardware Connection for FDCAN Communication (CANH & CANL Wiring)

As I mentioned above, I am going to use the 2 peripherals of the same STM32 board. The board already have 2 transceivers, so all I needed to do was connect the CANH together and CANL together. Below is the mage of the connections.

STM32 FCAN Connection
STM32 FDCAN Connection

STM32CubeMX Configuration

The basic configuration of the CubeMX will remain same as we did in the previous tutorial. The reason for this configuration is also explained there.

Below are the configurations for FDCAN1 and FDCAN2.

FDCAN1 Configuration
FDCAN2 Configuration

Other than the message RAM, one more change you can notice is that I am using the RX Fifo 0 for the FDCAN1, and RX Fifo 1 for the FDCAN2. This is simply because the 2 Fifos are available and I want to write completely separate codes for them.

Note that the message RAM offset for the FDCAN1 is 0, and that of the FDCAN2 is 11 words.

Why Message RAM Partitioning Matters in STM32 FDCAN

When using more than one STM32 FDCAN peripheral, correct message RAM partitioning is critical. Queries like an5348 stm32 show developers struggle with this.

Key points:

  • STM32H7 provides 2560 words (~10 KB) of message RAM.
  • You must allocate offsets for each FDCAN instance (e.g., FDCAN1 at offset 0, FDCAN2 at 11 words).
  • Each filter, RX FIFO, and TX buffer consumes message RAM depending on data length.

As per the application note AN5348, the FDCAN message RAM is 2560 words in size, which is equivalent to 10KB. This RAM is provided to store the different filters, RX buffers/FIFOs, the TX buffers etc.

If we are using more than 1 FDCAN instance, we have to carefully partition the RAM such that the total size of these sections do not exceed the ram size. Below is the image shown as per the application note.

STM32 CAN Message RAM Map

Note that different sections have different number of elements available. These elements are basically the number of these sections we can use. For eg- There are 128 elements available in the standard filter (11 bit filter), so we can use 128 different standard filters.

Similarly there are 128 elements in the RX buffer, so we can use 128 different buffers to receive data, and each buffer is capable of storing upto 64 bytes of data.

How much if the space will be occupied by each element of these sections is shown in the picture below.

STM32 CAN Message RAM Map

For most of the sections, the calculation here is pretty simple, (number of words available / number of elements). Though this rule does not apply in case of TX and RX buffers/Fifos. For these sections, the occupied space also depends on the data size we are using.

STM32 CAN Element Size Number

In the FDCAN1 configuration I have set the message Ram offset to 0, so the allocation will start at the beginning of the RAM.

I am using 1 standard filter (1 word) + 1 RX Fifo element with 12 bytes of data (5 words) + 1 TX Fifo element with 12 bytes of data (5 words). This totally makes 11 words.

These 11 words will be needed by the FDCAN1 to work properly, so the offset for the FDCAN2 is set to 11. The rest of the RAM is available to the FDCAN2, though it will also use 11 words.

STM32 FDCAN Data arrangement

The Filter configuration for the FDCAN1 is shown below

FDCAN_FilterTypeDef sFilterConfig;

sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_MASK;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x22;
sFilterConfig.FilterID2 = 0x22;
sFilterConfig.RxBufferIndex = 0;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
{
  /* Filter configuration Error */
  Error_Handler();
}
  • The IdType defines if we are using the Standard IDs or Extended IDs
  • Filterindex is used in case of configuring the multiple filters. Since I am only using 1 filter, it is set to 0.
  • FilterType is the type of filter we are using. Here I am using the MASK Filter.
    • For this MASK Filter, the ID1 (0x22) will act as the ID and ID2 (0x22) will act as the mask bits.
    • If you want to learn more about how this works, I would suggest that you watch https://youtu.be/JfWlIY0zAIc
  • FilterConfig decides what should be done to the messages that pass through the filter. It is set to be sent to RX FIFO 0.
  • RxBufferIndex is used if you use the RX Buffer instead of FIFO, so it is set 0 her.

So all the messages coming from the ID 0x22 will be passed through the filter and get stored in the RXFIFO0.

The Filter configuration for the FDCAN2 is shown below

FDCAN_FilterTypeDef sFilterConfig;

sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_MASK;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1;
sFilterConfig.FilterID1 = 0x11;
sFilterConfig.FilterID2 = 0x11;
sFilterConfig.RxBufferIndex = 0;
if (HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig) != HAL_OK)
{
  /* Filter configuration Error */
  Error_Handler();
}

Here everything is similar except the ID is set to 0x11, and we are using RXFIFO1.

So all the messages coming from the ID 0x11 will be passed through the filter and get stored in the RXFIFO1.

Code Explanation and Walkthrough

This section breaks down the essential code, explaining how each configuration step initializes the FDCAN peripheral for normal operation.

Understanding HAL_FDCAN Functions in STM32

Many searches are for STM32 HAL functions directly. Here’s what they do:

  • HAL_FDCAN_AddMessageToTxFifoQ → Queues data for transmission.
  • HAL_FDCAN_ActivateNotification → Enables interrupts for RX FIFO events.
  • HAL_FDCAN_GetRxMessage → Retrieves incoming messages from RX FIFO.
  • HAL_FDCAN_ConfigFilter → Defines filter IDs and routing.
  • HAL_FDCAN_RxFifo0Callback / HAL_FDCAN_RxFifo1Callback → Called when new messages arrive.

Let’s dive into the implementation. The following code initializes the FDCAN peripheral, configures the message RAM, and sets up filters to handle incoming messages.

First we will see some definitions

// FDCAN1 Defines
FDCAN_TxHeaderTypeDef   TxHeader1;
FDCAN_RxHeaderTypeDef   RxHeader1;
uint8_t               TxData1[12];
uint8_t               RxData1[12];


// FDCAN2 Defines
FDCAN_TxHeaderTypeDef   TxHeader2;
FDCAN_RxHeaderTypeDef   RxHeader2;
uint8_t               TxData2[12];
uint8_t               RxData2[12];

As shown above, the RX and TX headers are defined separately for both the CANs. Also the RxData and TxData arrays are 12 bytes in size.

// STart FDCAN1
if(HAL_FDCAN_Start(&hfdcan1)!= HAL_OK)
{
 Error_Handler();
}

// STart FDCAN2
if(HAL_FDCAN_Start(&hfdcan2)!= HAL_OK)
{
 Error_Handler();
}

// Activate the notification for new data in FIFO0 for FDCAN1
if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK)
{
  /* Notification Error */
  Error_Handler();
}


// Activate the notification for new data in FIFO1 for FDCAN2
if (HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0) != HAL_OK)
{
  /* Notification Error */
  Error_Handler();
}

Inside the main function, we will first start both the FDCANs.

Then Activate the notification for new message to arrive in respective Fifos. This notification will trigger the interrupt, which will eventually call a callback function, and there we will handle the incoming data.

// Configure TX Header for FDCAN1
TxHeader1.Identifier = 0x11;
TxHeader1.IdType = FDCAN_STANDARD_ID;
TxHeader1.TxFrameType = FDCAN_DATA_FRAME;
TxHeader1.DataLength = FDCAN_DLC_BYTES_12;
TxHeader1.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
TxHeader1.BitRateSwitch = FDCAN_BRS_OFF;
TxHeader1.FDFormat = FDCAN_FD_CAN;
TxHeader1.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
TxHeader1.MessageMarker = 0;


// Configure TX Header for FDCAN2
TxHeader2.Identifier = 0x22;
TxHeader2.IdType = FDCAN_STANDARD_ID;
TxHeader2.TxFrameType = FDCAN_DATA_FRAME;
TxHeader2.DataLength = FDCAN_DLC_BYTES_12;
TxHeader2.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
TxHeader2.BitRateSwitch = FDCAN_BRS_OFF;
TxHeader2.FDFormat = FDCAN_FD_CAN;
TxHeader2.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
TxHeader2.MessageMarker = 0;

Next we will configure the TX Header, which will be transmitted along with the data on the can bus. The ID of the FDCAN1 is set to 0x11, and that of the FDCAN2 is set to 0x22. The rest of the parameters are same in both cases.

  • The Identifier is the ID of the Transmitter, which is going to be 0x11 for the FDCAN1 and 0x22 for the FDCAN2.
  • The IdType is set to Standard ID as we are not using Extended ID in this tutorial.
  • TxFrameType implies whether we are sending a Data frame or Remote frame. It is set to Data Frame.
  • DataLength is set to 12 bytes. This is the length of the actual Data we are going to send.
  • ErrorStateIndicator is active and it will notify us if there is any error in transmission.
  • BitrateSwitch is OFF, as I mentioned we will be using the same bitrate for both Arbitration and Data Fields
  • FDFormat implies whether you want to use the standard CAN or FD CAN. It is set to FD CAN
  • We are not using the TxEvent or MessageMarker.
while (1)
{
 sprintf ((char *)TxData1, "FDCAN1TX %d", indx++);

 if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader1, TxData1)!= HAL_OK)
 {
  Error_Handler();
 }
 HAL_Delay (1000);
}
  • Inside the while loop, the FDCAN1 will transmit the data every 1 second.
  • The TxData contains a string (To indicate the data is coming from the FDCAN1) along with the incremented value of the indx variable.
  • Then the function HAL_FDCAN_AddMessageToTxFifoQ will add the message to the FIFO queue.
  • Once the message is added to the FIFO Queue, the message will be transferred to the CAN Bus

Once the message has been sent to the can bus, it will be received by all the devices that are attached to this bus (though we have only 1 more). And after the message passes through the filter of the FDCAN2, the new message callback will be called.

// FDCAN2 Callback
void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs)
{
  if((RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE) != RESET)
  {
    /* Retreive Rx messages from RX FIFO0 */
    if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO1, &RxHeader2, RxData2) != HAL_OK)
    {
    /* Reception Error */
    Error_Handler();
    }

    if (HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0) != HAL_OK)
    {
      /* Notification Error */
      Error_Handler();
    }

	  sprintf ((char *)TxData2, "FDCAN2TX %d", indx++);

	  if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan2, &TxHeader2, TxData2)!= HAL_OK)
	  {
		  Error_Handler();
	  }
  }
}

The RxFifo1Callback will be triggered since we are using the Fifo1 for the FDCAN2.

  • Here we will first copy the Header information from the RX FIFO1 into the RxHeader, and data into the RxData array.
  • We will then activate the notification for the new message again.
  • At last we will send some data on the CAN bus.
    • This data contains the string (To indicate the data is coming from the FDCAN2) along with the incremented value of the indx variable.

Once the message has been sent to the can bus, it will pass through the filter of the FDCAN1 and the new message callback will be called.

// FDCAN1 Callback
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
  if((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != RESET)
  {
    /* Retreive Rx messages from RX FIFO0 */
    if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader1, RxData1) != HAL_OK)
    {
    /* Reception Error */
    Error_Handler();
    }

    if (HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK)
    {
      /* Notification Error */
      Error_Handler();
    }
  }
}

The RxFifo0Callback will be triggered since we are using the Fifo0 for the FDCAN1.

  • Here we will first copy the Header information from the RX FIFO0 into the RxHeader, and data into the RxData array.
  • We will then activate the notification for the new message again.
  • Unlike the FDCAN2 callback, we are not transmitting any data here, since the data is being transmitted every second by the FDCAN1 in the while loop.

This way the FDCAN1 will keep transmitting every 1 second, and FDCAN2 will always send a response to that.

RESULT

The images below shows the data transmitted by one instance and received by another instance of the FDCAN.

Data transmitted by the FDCAN1 and Received by the FDCAN2
Data transmitted by the FDCAN2 and Received by the FDCAN1

The first picture shows the data transmitted by the FDCAN1 (TxData1) and the same data is received by the FDCAN2 (RxData2).
The second picture shows a different data transmitted by the FDCAN2 (TxData2) and the same data is received by the FDCAN1 (RxData1).

With this setup, you now understand how to configure STM32 FDCAN in normal mode using message RAM, filters, and callbacks. We implemented HAL_FDCAN_AddMessageToTxFifoQ for transmission and HAL_FDCAN_GetRxMessage inside callbacks for reception. This is a practical STM32 FDCAN example covering two peripherals on the STM32H7, useful for projects requiring robust CAN-FD communication. In future tutorials, we’ll explore FDCAN interrupt handling, error states (e.g., FIFO full), and advanced filter configurations for real-world automotive and industrial applications.

Other STM32 CAN Tutorials

PROJECT DOWNLOAD

Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

Project FAQs

Subscribe
Notify of

5 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Steven
1 year ago

StdFiltersNbr=1, not 0 as in the screenshot of Setting for FDCAN2 Configuration.

Bert
2 years ago

Hi,
I have tried the exact same code on a H750 but it doesn’t seem to work, and I can’t figure out why. It uses the same FDCAN controller, so the timings should match. I get a TX signal that I can see on my oscilloscope, but FDCAN2 is not triggering the interrupt. I have tried changing just about every parameter, redid the whole project 3 times, but no success.
Any ideas?

Ashok
2 years ago

Hi,
I have two FDCAN Devices, STM32H743 FDCAN1 and MCP2518FD-SPI CAN.
I am able to Transmit and Receive FDCAN messages between both FDCAN using Transceivers
[FDCAN1 <-Tx/Rx-> TCVR <-CAN_H/L-> TCVR <-Tx/Rx-> SPI-MCP2518FD]
But when I Connect FDCAN1’s Tx/Rx with SPI-MCP-FDCAN’s Rx/Tx,
[FDCAN1 <-Tx/Rx-> <-Rx/Tx-> SPI-MCP2518FD]
I am unable to Transmit and Receive Data between both FDCANs.
I am using Tx/Rx Buffers, Not Fifo for FDCAN1 and FIFO for SPI-MCP-CAN.
Also I am using FDCAN1 Polling, NOT interrupt method.
Both FDCAN are working fine with Internal Loopback Testing.

I have problem in Tx/Rx in Normal FDCAN Mode only.

João Pedro Castro de Souza
2 years ago

Thanks for your videos. I tried it with a NUCLEO-H7A3ZI-Q and SN65HVD233DR, but the Txbuffer is full after two cycle of task and I got error code 512 (HAL_FDCAN_ERROR_FIFO_FULL).

Please, help me.