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 VideoParts 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.
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.
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.
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.
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.
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.
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.
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
Yes, but their bit timing parameters (Prescaler, Time Quanta, etc.) must be calculated separately to achieve the exact same nominal baud rate (e.g., 500 kHz) on both nodes for reliable communication.
The RX interrupt is typically triggered upon a successful filter match. The handler should quickly retrieve the message from the Rx FIFO and exit, allowing the core to process the data in the main loop or a queue, preventing missed messages.
They are functionally identical. The primary use for having two FIFOs is to prioritize certain messages by assigning higher-priority filters to one FIFO and lower-priority to the other, each with its own dedicated interrupt line.
Ensure you are using a terminated differential pair (120Ω resistor at each end of the bus) to prevent signal reflections. Also, verify the transceiver’s supply voltage and ensure the physical layer is properly isolated.
You must strategically partition the shared RAM in the master FDCAN instance’s initialization, allocating dedicated sections for each instance’s Rx/Tx elements and buffers to prevent memory conflicts and data corruption.
Support Us by Disabling Adblock
We rely on ad revenue to keep Controllerstech free and regularly updated. If you enjoy the content and find it helpful, please consider whitelisting our website in your ad blocker.
We promise to keep ads minimal and non-intrusive.
Thank you for your support! 💙
Recent Posts
Random Picks

StdFiltersNbr=1, not 0 as in the screenshot of Setting for FDCAN2 Configuration.
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?
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.
How can it work without the transceivers ? What would be the need for the transceivers then ?
In loopback mode, the Tx pin The Tx pin is internally connected to the Rx pin, so it’s like you sort the pins together. The message sent on the Tx pin just reverts back to Rx pin.
Loopback mode is just to test if the configuration is correct. It has nothing else to do with the normal mode.
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.