FDCAN in Normal Mode || STM32H7
Today we will see how to use the FDCAN in Normal mode. I have already covered the FDCAN loopback mode along with the configuration in the previous tutorial, and I would advise you to check that out first. This tutorial is more like a continuation from that one, so check out the previous one first.
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.
The connection
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.
CubeMX 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.
As I mentioned, the only change is going to be in the message RAM which we will cover now. 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.
As you can see the message RAM offset for the FDCAN1 is 0, and that of the FDCAN2 is 11 words.
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.
Some Insight into the CODE
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 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).