on 2025-06-25 7:00 AM
This is a continuation of the Using CAN (bxCAN) in Normal mode with STM32 microcontrollers (Part 1). Part 1 of the article provided the theorical part and described the hardware and software needed for CAN in Normal mode. This part (part 2) intends to describe the particle part of the article. It provides a step-by-step configuration of CAN in Normal mode from hardware and software stand points. The example is implemented on many STM32 microcontrollers to cover the maximum of the series: STM32F042, STM32F103, STM32F207, STM32F302, STM32F439, STM32F767.
The example is available on this GitHub Hotspot link and was deployed on the following boards:
NUCLEO-F042K6, NUCLEO-F103RB, NUCLEO-F207ZG, NUCLEO-F302R8, NUCLEO-F439ZI, and NUCLEO-F767ZI.
The example can be tailored to any STM32 that has a bxCAN interface.
Nucleo boards are used in purpose as they are accessible by all the users considering their low cost.
The used boards are NUCLEO-F042K6 (NUCLEO-32), NUCLEO-F103RB (NUCLEO-64), NUCLEO-F207ZG (NUCLEO-144), NUCLEO-F302R8 (NUCLEO-64), NUCLEO-F439ZI (NUCLEO-144) and NUCLEO-F767ZI (NUCLEO-144). Each board represents a CAN node. A CAN transceiver needs to be added for each board externally.
The example provided in the GitHub Hotspot link uses two CAN transceiver’s part numbers: SN65HVD230 and MCP2562FD. The hardware and the setup are shown in figures 6 and 7 in part 1 of the article. Two types of transceivers are used on purpose to show that it is possible to use different types of CAN transceivers on the same bus.
Figure 1. The opted hardware while running the examples
As shown in figure 1, six nodes are connected on the CAN bus based on Nucleo boards. Two of the six nodes (2 and 3) use SN65HVD230 transceiver while the rest of the nodes are using MCP2562FD transceiver.
A common ground is also considered while testing. It is not a connection that must be put in place, but it's better to do it.
As stated in the section 2.1 Hardware considerations, an accurate clock source needs to be used for a normal CAN operation. Table 1 shows the clock sources used by the different STM32 platforms in the example.
Platform |
External clock |
Frequency value |
NUCLEO-F103RE |
From ST-LINK/V2 |
8 MHz |
NUCLEO-F207ZG |
From ST-LINK/V2 |
8 MHz |
NUCLEO-F302R8 |
From ST-LINK/V2 |
8 MHz |
NUCLEO-F439ZI |
From ST-LINK/V2 |
8 MHz |
NUCLEO-F767ZI |
From ST-LINK/V2 |
8 MHz |
Table 1. The clock source value feeding HSE in Bypass mode
To connect ST-LINK-MCO output to the HSE of the target MCU:
NUCLEO-F042: SB4 needs to be soldered.
NUCLEO-F103 & NUCLEO-F302: SB50 needs to be soldered.
NUCLEO-F207, NUCLEO-F439 and NUCLEO-F767: SB149 needs to be soldered.
In this section, we describe how the example works and how to implement it using STM32CubeMX to initialize the CAN peripheral with a user code to add to implement a complete example.
The example has been simplified as much as possible with a minimal code to let all STM32 (nodes) can communicate on the same CAN bus.
Each node is identified by two specific ID formats: standard (11-bit ID) and extended (29-bit ID). Each ID corresponds to an STM32 family.
As an example for the STM32F103 (F1 family), the CAN frame to be sent by this device has a standard ID = 0xF1. It also sends an extended ID where the most significant bit of the 29-bit is set to 1. So, the extended ID = 0x100000F1
For STM32F439 (F4) family the standard ID = 0xF4. So, the extended ID = 0x100000F4. The same thing applies for the rest of the MCUs used in the example.
8 bytes are used to transmit a frame for all nodes. For each node, the first byte (byte 0), value starting from 0, is incremented by 1. While for the last byte (byte 7), the value starting from 0xFF, is decremented by 1.
The example scenario has the possibility to use more than one MCU of the same family (maximum 8 nodes of the same family). For that bits 8 to 10 are reserved for that specific device in the ID.
Example: if more than STM32F1xx is used, we can use the IDs 0x0F1, 01F1, 0x2F1, and 0x7F1.
Table 2 shows the IDs details used when different STM32 families are used in the example. In this table, the first 8 bits of the ID are represented in hexadecimal while the rest is in binary.
STM32 part number | Only extended IDs (bits 11 to 28) | CAN ID bits bits 8 to 10 (standard and extended) | CAN ID bits 0 to 7 (standard and extended) |
100000000000000000b | ID of the STM32 family | STM32 family | |
STM32F042 (node 1) | 100000000000000000b | 000b | 0xF0 |
STM32F103 (node 2) | 100000000000000000b | 000b | 0xF1 |
STM32F207 (node 3) | 100000000000000000b | 000b | 0xF2 |
STM32F302 (node 4) | 100000000000000000b | 000b | 0xF3 |
STM32F439 (node 5) | 100000000000000000b | 000b | 0xF4 |
STM32F767 (node 6) | 100000000000000000b | 000b | 0xF7 |
Table 2. CAN ID assignment for different MCU families
If many nodes having the same STM32 family (maximum eight nodes). Table 3 shows an example of ID assignments with eight CAN nodes with STM32F042. In this table, the first 8 bits of the ID are represented in hexadecimal while the rest is in binary.
STM32 part number | Only extended IDs (bits 11 to 28) | CAN ID bits 8 to 10 (standard and extended) | CAN ID bits 0 to 7 (standard and extended) |
100000000000000000b | ID inside the STM32 family | STM32 family | |
STM32F042 (node 1) | 100000000000000000b | 000b | 0xF0 |
STM32F042 (node 2) | 100000000000000000b | 001b | 0xF0 |
STM32F042 (node 3) | 100000000000000000b | 010b | 0xF0 |
STM32F042 (node 4) | 100000000000000000b | 011b | 0xF0 |
STM32F042 (node 5) | 100000000000000000b | 100b | 0xF0 |
STM32F042 (node 6) | 100000000000000000b | 101b | 0xF0 |
STM32F042 (node 7) | 100000000000000000b | 110b | 0xF0 |
STM32F042 (node 8 ) | 100000000000000000b | 111b | 0xF0 |
Table 3. CAN ID assignment inside the same STM32 family
The ID assignment is done in the code and it will be described in the next section.
Each node is sending CAN frames indefinitely in a while loop. Two kinds of ID frames are sent: standard and extended. Both ID types are alternately sent each 1 millisecond. This delay is used to avoid overloading the CAN bus.
Each CAN frame is received by all the rest of the nodes (in broadcast). The filter is configured in all CAN nodes in a manner that the standard IDs are received in the FIFO0 while the extended IDs are received in the FIFO1. The Rx interrupt is used and both Rx FIFO callbacks are used:
HAL_CAN_RxFifo0MsgPendingCallback() and HAL_CAN_RxFifo1MsgPendingCallback(). In the first callback, we read all the standard ID frames. In the second callback, we read all the extended ID frames. On each callback, we test on the ID sent by the STM32 family and we increment a variable which is specific for that family. It was maybe more convenient to use more filters for this kind of comparison but for simplicity we opted for a comparison in the code.
Note that the above-described scenario has no relation with the CAN protocol especially for the ID selection and usage, but it is just a scenario and could be modified to fit your needs.
In this section, we use STM32F439 as an example. All other MCUs have almost the same steps. The most significant difference is in the system clock configuration. The ioc files for each MCU part number used in the example are already available in the GitHub Hotspot link.
The first step is to configure the RCC in HSE Bypass mode (as a Nucleo board is used) and to set the CAN bitrate. The CAN bitrate to be used in the example is 1 Mbit/s (the maximum-allowed bitrate for CAN2.0).
The step by step clock configuration, the CAN activation and its bitrate settings are described in this article: CAN (bxCAN) bit time configuration on STM32 MCUs / section 6. Setting the CAN bitrate on STM32 exemplified using STM32CubeMX. Follow the steps described in that section before proceeding. In that article, the STM32F439 MCU was used as an example and the CAN bitrate was set to 500 kbit/s. Here we need to set the value of the CAN bitrate in the CAN bit timing web tool to 1 Mbit/s.
The settings of the CAN bit timing web tool will be as shown by figure 2. The APB1 clock is set at 45 MHz. The sample point is set at 87.5% and the CAN bitrate is set at 1 Mbit/s (1000 kbit/s).
Figure 2. CAN bit timing web tool for 1 Mbit/s
After clicking the [Request Table] button (see figure 2), the tool generates a table containing different CAN timing configuration as shown by figure 3.
Figure 3. Different timing configuration proposed by the tool
The yellow row highlights the recommended settings. The CAN prescaler = 3, the bit segment 1 = 12 and the bit segment 2 = 2. The sample point is set at 86.7%, which is a good position of the sample point.
In the STM32CubeMX, we set these values as shown by figure 4 in the CAN parameter settings.
Figure 4. Setting the CAN timings parameter in STM32CubeMX
Now, it is time to configure the CAN GPIO pins. The default pins set by STM32CubeMX are PA11 and PA12 for respectively CAN_Rx and CAN_Tx as shown in figure 5.
Figure 5. Default pins selected by STM32CubeMX
In the example, we decided to select PD0 and PD1 for respectively CAN_Rx and CAN_Tx.
In the [Pinout View], we set PD0 as CAN1_Rx and PD1 as CAN1_Tx as shown by figures 6 and 7.
Figure 6. Setting PD0 as CAN1_Rx pin
Figure 7. Setting PD1 as CAN1_Tx pin
Change the I/O speed to “Medium” instead of “High” or “Very High” as it is not necessary to high speed levels. See figure 8.
Figure 8. Setting CAN I/Os speed
Enable the NVIC interrupts for the reception of FIFO 0 and FIFO 1 as shown in figure 9.
Figure 9. Enabling CAN NVIC interrupt lines for Rx
As the CAN needs to be configured in Normal mode, in [Advanced Parameters]/[Operation Mode], we set the mode to Normal.
The other [Basic Parameters] are optional according to your usage. In the example, we enabled the "Automatic Bus-Off Management" and the "Automatic Retransmission." For more details about these modes, refer to the product reference manual/section “Controller area network (bxCAN)”
Figure 10 shows the different settings of the CAN peripheral.
Figure 10. CAN peripheral settings
At this stage, all is done from the CAN configuration side.
Now, we configure two LEDs (green and red) to indicate the CAN communication operation.
LD1 (green LED) to indicate a good CAN operation when it is toggling.
LD3 (red LED) to indicate that, when it is toggling, something went wrong in the program including the CAN operation.
Figures 11, 12, 13 and 14 show the GPIO configuration of these LEDs.
Figure 11. PB0 for LED1 configuration
Figure 12. PB14 for LED3 configuration
Figure 13. LD1 and LD2 GPIO pins settings
At this stage, the needed application configuration is finished. Now, we need to give a name to the project, set its path, and select the toolchain to be used. So, we need to use STM32CubeIDE in our example. See figure 14.
Figure 14. Configuring the project
It's time to generate the CAN project. STM32CubeIDE is used as the toolchain.
As shown by figure 15, click the [Generate Code] button. Click on [Open Project] as shown by figure 16 then finally click the [Launch] button to open the project as shown by figure 17. Figure 18 shows the project explorer in STM32CubeIDE and what files that need to be available in the project.Figure 15. Generate the project
Figure 16. Open the project
Figure 17. Launch the STM32CubeIDE workspace
Figure 18. The STM32CubeIDE project explorer showing the generated project
In this section, we provide the code necessary for the CAN frame transmission. As stated in the section 3.2.1 Example description, the ID to be sent for STM32F439 has the lowest 8 bits = 0xF4.
The board ID corresponds to the ID inside the family 0 to 7 (bits 8 to 10 inside the CAN ID).
For that we add the following defines to be used in the transmission:
#define STM32_FAMILY_ID 0xF4 /* For STM32F4 devices */
#define BOARD_ID 0x0 /* BOARD_ID: from 0 to 7: Device number if two
boards using STM32F4, need to select another value (1 to 7) */
#define TX_ID ((BOARD_ID<<8) | STM32_FAMILY_ID) /* TX ID to send is a format of 0xXF4, where X is the board ID */
Adding the variables needed for the CAN transmission between /* USER CODE BEGIN PV */ and /* USER CODE END PV */ :
uint32_t TxMailbox; /* The number of the mail box that transmitted the Tx message */
CAN_TxHeaderTypeDef TxHeader; /* Header containing the information of the transmitted frame */
uint8_t TxData[8] = {0}; /* Buffer of the data to send */
Adding the code related to the application transmission: data 8 bytes, initialize the standard and extended Ids with their respective values. This is the first block to be added in the User Code 2 as a preliminary transmit setup:
/* USER CODE BEGIN 2 */
TxHeader.StdId = TX_ID; /* The ID value in Standard ID format (11bit) */
TxHeader.ExtId = TX_ID | 0x10000000; /* The ID value in Extended ID format (29bit) */
TxHeader.RTR = CAN_RTR_DATA; /* The frames that will be sent are Data */
TxHeader.DLC = 8; /* The frames will contain 8 data bytes */
TxHeader.TransmitGlobalTime = DISABLE;
/* Only the first byte (data0) and the last byte (data7) will be changed */
TxData[0] = 0; /* The first value to send on byte 0 is 0 */
TxData[7] = 0xFF; /* The last value to send on byte 0 is 0xFF */
/* USER CODE END 2 */
The CAN transmission is handled in the while loop. The code of the transmission will be added after while(1) { and /* USER CODE END WHILE */.
As stated before, a CAN frame is alternately transmitted in standard and extended format with a rate of 1 millisecond using HAL_Delay():
/* USER CODE BEGIN WHILE */
while (1)
{
TxData[0] ++; /* Increment the first byte */
TxData[7] --; /* Increment the last byte */
/* It's mandatory to look for a free Tx mail box */
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0) {} /* Wait till a Tx mailbox is free. Using while loop instead of HAL_Delay() */
if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK) /* Send the CAN frame */
{
/* Transmission request Error */
Error_Handler();
}
/* Toggle sending Standard and Extended ID for the next operation. The first ID sent is a Standard ID frame */
TxHeader.IDE ^= CAN_ID_EXT;
HAL_Delay(1);
/* USER CODE END WHILE */
In Error_Handler(), we need to toggle an LED to indicate an issue. The indicator error is common to the transmit and the receive. The Error_Handler() is as follows:
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
/* Use this delay instead of HAL_Delay() since the interrupts has been disabled */
for(__IO uint32_t i = 0; i < 0xFFFFF; i++);
}
/* USER CODE END Error_Handler_Debug */
}
Start the CAN before performing any operation either transmit or receive.
The following code needs to be added after the CAN configuration. Example in the generated function code MX_CAN1_Init() that is, between /* USER CODE BEGIN CAN1_Init 2 */ and /* USER CODE END CAN1_Init 2 */:
/* USER CODE BEGIN CAN1_Init 2 */
/* Start the CAN peripheral */
if (HAL_CAN_Start(&hcan1) != HAL_OK)
{
/* Start Error */
Error_Handler();
}
The steps described already are to be used for the CAN transmission only. For the reception, it is described in the next section 3.2.3.3 Adding the code related to the CAN frames reception.
In this section, we provide the code necessary for the CAN frame reception.
The reception is handled with interrupts. Figure 20 shows how to enable the interrupt for CAN Rx FIFO1 and FIFO0 in STM32CubeMX. Both callbacks are used for that purpose, as each callback correspond to a specific FIFO reception:
At least a filter must be configured. If it is not the case, no CAN frame is received even if everything is set correctly. Note for a specific and complex CAN filter configuration, better to use the Loopback mode to validate all the filter settings and check the messages to be filtered and received. Refer to this article: Guide to CAN (bxCAN/CAN2.0) configuration in Loop back mode on STM32 MCUs.
Note that during the example development the Loopback mode has been used to validate the filter functionality before starting the development of the CAN in Normal mode example.
Let us add the necessary code for the Rx operation in interrupt mode.
Start by adding the different variables needed for the CAN reception. RxHeaderFIFO0, RxHeaderFIFO1, RxDataFIFO0[8] and RxDataFIFO1[8] are necessary to read the received CAN frames. So, they are mandatory. All the rest of the variables are optional, and they are used to show which node MCU has sent the received CAN frame. They are incremented by one if the corresponding ID is received.
Example: rx_f0_std is incremented by 1 if a CAN in standard ID frame format has been sent by a node having STM32F042 MCU.
Example: rx_f0_ext is incremented by 1 if a CAN in extended ID frame format has been sent by a node having STM32F042 MCU.
The following code will be added just after the variables related to the transmit in /* USER CODE XXX PV */:
CAN_RxHeaderTypeDef RxHeaderFIFO0; /* Header containing the information of the received frame */
CAN_RxHeaderTypeDef RxHeaderFIFO1; /* Header containing the information of the received frame */
uint8_t RxDataFIFO0[8]; /* Buffer of the received data */
uint8_t RxDataFIFO1[8]; /* Buffer of the received data */
__IO uint32_t rx_f0_std = 0; /* Contains the number of standard frames received from a STM32F0 MCU */
__IO uint32_t rx_f0_ext = 0; /* Contains the number of extended frames received from a STM32F0 MCU */
__IO uint32_t rx_f1_std = 0; /* Contains the number of standard frames received from a STM32F1 MCU */
__IO uint32_t rx_f1_ext = 0; /* Contains the number of extended frames received from a STM32F1 MCU */
__IO uint32_t rx_f2_std = 0; /* Contains the number of standard frames received from a STM32F2 MCU */
__IO uint32_t rx_f2_ext = 0; /* Contains the number of extended frames received from a STM32F2 MCU */
__IO uint32_t rx_f3_std = 0; /* Contains the number of standard frames received from a STM32F3 MCU */
__IO uint32_t rx_f3_ext = 0; /* Contains the number of extended frames received from a STM32F3 MCU */
__IO uint32_t rx_f7_std = 0; /* Contains the number of standard frames received from a STM32F7 MCU */
__IO uint32_t rx_f7_ext = 0; /* Contains the number of extended frames received from a STM32F7 MCU */
Add the filter configuration. Each filter is responsible to filter a specific CAN ID format. The filter number 0 (.FilterBank=0) allows only to receive the standard ID frames into FIFO0 while the filter number 1 (.FilterBank=1) allows to receive the extended ID frames into FIFO1. For the parameter .SlaveStartFilterBank = 14 explanation, refer to a detailed explanation in this article and how to set it: STM32 in dual CAN configuration: bxCAN Filter bank explanation and relation with CAN2 Start Bank parameter.
Note: for a very basic test and validation of the reception operations, you can start by configuring a very basic filter passing all the IDs. That is, with mask and id set to 0 in 32 bit filter format in ID mask mode:
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
Add the filter configuration in MX_CAN1_Init():
Add the declaration of filter structure configuration between /* USER CODE BEGIN CAN1_Init 0 */ and /* USER CODE END CAN1_Init 0 */:
/* USER CODE BEGIN CAN1_Init 0 */
CAN_FilterTypeDef sFilterConfig;
/* USER CODE END CAN1_Init 0 */
And add the filter configuration code between /* USER CODE BEGIN CAN1_Init 2 */ and the call of HAL_CAN_Start() call as the following:
/* USER CODE BEGIN CAN1_Init 2 */
/* The filter configuration */
sFilterConfig.SlaveStartFilterBank = 14; /* Slave start bank Set only once. */
/* The CAN filter configuration */
sFilterConfig.FilterBank = 0; /* Select the filter number 0 */
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; /* Using ID mask mode .. */
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; /* .. in 32-bit scale */
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000; /* The filter is set to receive only the Standard ID frames */
sFilterConfig.FilterMaskIdHigh = 0x0000; /* Accept all the IDs .. except the Extended frames */
sFilterConfig.FilterMaskIdLow = 0x0004; /* The filter is set to check only on the ID format */
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; /* All the messages accepted by this filter will be received on FIFO1 */
sFilterConfig.FilterActivation = ENABLE; /* Enable the filter number 0 */
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
/* Filter configuration Error */
Error_Handler();
}
sFilterConfig.FilterBank = 1; /* Select the filter number 1 */
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; /* Using ID mask mode .. */
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; /* .. in 32-bit scale */
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0004; /* The filter is set to receive only the Extended ID frames */
sFilterConfig.FilterMaskIdHigh = 0x0000; /* Accept all the IDs .. except the Standard frames */
sFilterConfig.FilterMaskIdLow = 0x0004; /* The filter is set to check only on the ID format */
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO1; /* All the messages accepted by this filter will be received on FIFO1 */
sFilterConfig.FilterActivation = ENABLE; /* Enable the filter number 1 */
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
/* Filter configuration Error */
Error_Handler();
}
/* Start the CAN peripheral */
if (HAL_CAN_Start(&hcan1) != HAL_OK)
{
/* Start Error */
Error_Handler();
}
Adding the code corresponding to the interrupt notification activation related to FIFO 0 and FIFO 1:
Add the code just after the HAL_CAN_Start() call, as follows:
/* Start the CAN peripheral */
if (HAL_CAN_Start(&hcan1) != HAL_OK)
{
/* Start Error */
Error_Handler();
}
/* Activate CAN RX notifications on FIFO0 and on FIFO1 */
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_RX_FIFO1_MSG_PENDING) != HAL_OK)
{
/* Notification Error */
Error_Handler();
}
/* USER CODE END CAN1_Init 2 */
Adding the Rx interrupt callbacks between /* USER CODE BEGIN 4 */ and /* USER CODE END 4 */:
For Rx FIFO 0 interrupt callback, add the following code:
/* CAN callback of FIF00. It will be called each time FIF00 receives a frame.
As a filter has been set to receive only Standard Id frames on FIFO0, This callback
will be called only when the Standard ID frame is received */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
{
/* Get RX message from FIFO0 and fill the data on the related FIFO0 user declared header
(RxHeaderFIFO0) and table (RxDataFIFO0) */
if (HAL_CAN_GetRxMessage(CanHandle, CAN_RX_FIFO0, &RxHeaderFIFO0, RxDataFIFO0) != HAL_OK)
{
/* Reception Error */
Error_Handler();
}
else
{
/* Toggle the LED when a message is received. Note that the toggling is not seen as the
transmit/receive of the frames are performed in a high frequency.
If you need to see the LED toggling, you need to increase the delay that was called just after HAL_CAN_AddTxMessage() from the sender side for example HAL_Delay(500) */
HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin);
}
if ((RxHeaderFIFO0.StdId & 0xFF) == 0xF0)
{
rx_f0_std++; /* Increment this if a standard CAN frame is received from the NUCLEO-F042 board */
}
else if ((RxHeaderFIFO0.StdId & 0xFF) == 0xF1)
{
rx_f1_std++; /* Increment this if a standard CAN frame is received from the NUCLEO-F103 board */
}
else if ((RxHeaderFIFO0.StdId & 0xFF) == 0xF2)
{
rx_f2_std++; /* Increment this if a standard CAN frame is received from the NUCLEO-F207 board */
}
else if ((RxHeaderFIFO0.StdId & 0xFF) == 0xF3)
{
rx_f3_std++; /* Increment this if a standard CAN frame is received from the NUCLEO-F302 board */
}
else if ((RxHeaderFIFO0.StdId & 0xFF) == 0xF7)
{
rx_f7_std++; /* Increment this if a standard CAN frame is received from the NUCLEO-F767 board */
}
}
For Rx FIFO 1 interrupt callback, add the following code:
/* CAN callback of FIF01. It will be called each time FIF01 receives a frame.
As a filter has been set to receive only Extended ID frames on FIFO1, This callback
will be called only when an Extended ID frame is received */
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
{
/* Get RX message from FIFO1 and fill the data on the related FIFO1 user declared header
(RxHeaderFIFO1) and table (RxDataFIFO1) */
if (HAL_CAN_GetRxMessage(CanHandle, CAN_RX_FIFO1, &RxHeaderFIFO1, RxDataFIFO1) != HAL_OK)
{
/* Reception Error */
Error_Handler();
}
else
{
/* Toggle the LED when a message is received. Note that the toggling is not seen as the
transmit/receive of the frames are performed in a high frequency.
If you need to see the LED toggling, you can add HAL_Delay(500) just after HAL_CAN_AddTxMessage()
on the sender code in the while loop in the main */
HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin);
}
if ((RxHeaderFIFO1.ExtId & 0xFF) == 0xF0)
{
rx_f0_ext++; /* Increment this if an Extended CAN frame is received from the NUCLEO-F042 board */
}
else if ((RxHeaderFIFO1.ExtId & 0xFF) == 0xF1)
{
rx_f1_ext++; /* Increment this if an Extended CAN frame is received from the NUCLEO-F103 board */
}
else if ((RxHeaderFIFO1.ExtId & 0xFF) == 0xF2)
{
rx_f2_ext++; /* Increment this if an Extended CAN frame is received from the NUCLEO-F207 board */
}
else if ((RxHeaderFIFO1.ExtId & 0xFF) == 0xF3)
{
rx_f3_ext++; /* Increment this if an Extended CAN frame is received from the NUCLEO-F302 board */
}
else if ((RxHeaderFIFO1.ExtId & 0xFF) == 0xF7)
{
rx_f7_ext++; /* Increment this if an Extended CAN frame is received from the NUCLEO-F767 board */
}
}
All the necessary code has been added. It is time to compile the code and run it.
Note that to run the code, you need at least another node connected to the bus. You can use the same code described in the previous section for another NUCLEO-F439ZI board. This is done by modifying the value of the define and selecting another value (1 to 7):
#define BOARD_ID 0x0
Refer to section 2.1 The example description for its usage.
Otherwise, use one of the examples running on any other MCU part number which is provided in the GitHub Hotspot link to validate the example we provided above.
If you have a NUCLEO-F439 board and another Nucleo board in the list mentioned in the introduction, compile the project related to the NUCLEO-F439. Additionally, the project of a second example corresponding to the second platform (a second node) you have already chosen (NUCLEO-F042 or/and NUCLEO-F103 etc.).
In the current demo, and as stated previously, six nodes are connected over the CAN bus with different MCU part numbers as shown in figure 1. So, BOARD_ID keeps the value 0x0 for all nodes.
We build all the project corresponding to all the platforms from NUCLEO-F042 to NUCLEO-F767 using the button shown in figure 19.
Figure 19. Build project button
We upload all the examples to their corresponding platforms one by one (all other ST-LINK are disconnected). We keep the NUCLEO-F439 board as the last one to upload its program, and start a debug session (figure 20) of the application.
Figure 20. Starting a debug session for the current project
In the [Live Expression] we can add all the variables related to the CAN reception operation (Figure 21).
Figure 21. Variables related the CAN Rx operation are added to the Live Expression
It's time to run the application. Click on the run button shown by figure 22.
Figure 22. Running the example button
Figure 23 shows the different variables related to the CAN Rx operation, including the data received in the CAN frame. Additionally, their corresponding number of CAN IDs received in live by the NUCLEO-F439 board.
Figure 23. Live expression showing the RX variables content
For more analysis, we can use a logic analyzer featuring a CAN analyzer like Saleae Logic Pro 16.
Each of the CAN_Tx and CAN_Rx pins of all nodes are probed by the analyzer.
For example for NUCLEO-F042, CAN_Tx is probed by the digital channel 0 while CAN_Rx is probed by the digital channel 1.
NUCLEO-F103: CAN_Tx probed by the digital channel 2, CAN_Rx is probed by the digital channel 3, and so on.
The CAN analyzer is configured in CAN mode at 1 Mbit/s. For more details about how to configure the Logic analyzer in CAN mode, refer to the article: Guide to CAN (bxCAN/CAN2.0) configuration in Loop back mode on STM32 MCUs section 4. Running the application and tests.
Figure 24 provides a global view of the frames captured by the CAN analyzer of all nodes on their corresponding CAN_Tx and CAN_Rx pins. CAN_H and CAN_L lines are also probed using two analog channels.
Figure 24. A screenshot of the captured frame by the Logic analyzer
Figure 25. Zoom in on what happens when STM32F439 sent a CAN frame
1. The box in blue color shows that the STM32F439 is sending a standard ID frame = 0xF4. This is seen on its Can_Tx pin probed by a digital logic analyzer channel that we named “F4_CAN_Tx”.
2. The box in green color shows that STM32F439 is listening on its CAN_Rx pin what is sending on its CAN_Tx pin. This is used for the arbitration mechanism as well as for error detection (bit modification etc).
3. The box in yellow color shows that the rest of the nodes are seeing the same CAN signal on their CAN_Rx pins. This shows that the CAN frame is broadcasted to all the CAN nodes connected to the bus.
4. The box in purple color shows that there was a reception acknowledgment of that frame (sent by STM32F439) in the acknowledgment slot. No one knows which CAN node has acknowledged the frame. May be one node, may be all the nodes. But at least one node has acknowledged the frame and the acknowledgment bit was asserted on one of the CAN_Tx pin nodes. Note that the acknowledgment bit is not intended to be asserted by the intended recipient.
5. The box in orange color shows that the STM32F439 sees its frame acknowledged on its CAN_Rx pin. Therefore, it knows that the frame it sent has been well received by at least one node on the CAN bus. This is why at least two nodes connected to the CAN bus are needed to complete the acknowledgment mechanism.
For your reference, attached a compressed .sal file that contains the captured CAN frames by the Saleae Logic analyzer in the demo.
This article is the continuation of the article Using CAN (bxCAN) in Normal mode with STM32 microcontrollers (Part 1) It provides the practical part of the article by describing an example of CAN in Normal mode and providing a step by step configuration of it. It provides also a discussion based on the CAN frames captured by a Logic analyzer.