cancel
Showing results for 
Search instead for 
Did you mean: 

How to use FDCAN to create a simple communication with a basic filter

B.Montanari
ST Employee

Summary

This article provides a step-by-step guide on how to use a FDCAN peripheral to establish communication between two NUCLEO-G0B1RE boards. Also, outlining the process from configuring the hardware connections to developing and testing the firmware, leading to successful data transmission between the boards.

Introduction

The "Flexible Data-Rate Controller Area Network" (FDCAN) peripheral is a powerful communication interface that enables high-speed data exchange between two or more devices. It is a newer version of the classic "Controller Area Network" (CAN) protocol, providing improved data rates, higher bandwidth, and enhanced error detection capabilities. The peripheral is compatible with the CAN-FD protocol according to ISO 11898-2 and it is widely used in various industries, including automotive, industrial automation, and aerospace.

In this article, we explore how to use the FDCAN peripheral to communicate between two STM32 Nucleo boards. There are many different filter configurations available in the CAN protocol. This article covers the basic communication without any fancy filters and relying on a simple CAN 2.0 communication. 

For this tutorial, we use two NUCLEO-G0B1RE boards and two HW-021 modules, both with TJA1050 CAN transceivers. The Nucleo board is equipped with the STM32G0B1RET6U microcontroller, and the steps shown here can be easily adapted to any other STM32 that has CAN or FDCAN peripheral. For firmware development, the STM32CubeIDE software is used.

1. Hardware setup

To set up the hardware for communication, we need a transceiver that can convert the digital signals from the G0 MCU into analog signals that can be transmitted over the physical CAN bus. The transceiver also provides the impedance that defines the CAN_High bus and the CAN_Low bus. It also provides protection against voltage spikes and other electrical disturbances that can damage the microcontroller. Below is an image to clarify the connections between the Nucleo board and the transceiver's board:

Figure 1 - CAN transceiver hardwareFigure 1 - CAN transceiver hardware

The power should be connected via the 5 V available in the CN6 in the Nucleo board, as the TJA1050 requires a VCC = 5 V. Connect the FDCAN Tx and Rx pins from each board to the respective pins in the TJA1050 transceiver at connector P1. For this particular demo, the FDCAN1 TX/RX pins are the PC5 and PC4 pins, available in the connector CN9. Refer to the image below for clarification on the connections between the Nucleo and the transceiver's board:

Figure 2 - Hardware connectionFigure 2 - Hardware connection

A small note: The label marks on NUCLEO board indicates that these pins are related to the USART peripheral, but the FDCAN is also available in these pins.

This connection should be replicated for the other Nucleo and transceiver set. Do not share the ground between the two sets. The connection between the transceivers should be made using the CAN_High and the CAN_Low pins, available in the connector P2. This establishes a physical connection between the two as we can see in the following image. By following these steps, we can set up the hardware for reliable communication.

Figure 3 - Connection between CAN transceiversFigure 3 - Connection between CAN transceivers

2. Firmware development

Let us start creating a project for the STM32G0B1RE in the STM32CubeIDE and set the following pinout configurations within the *.ioc file:

In the RCCs tab, enable the HSE in [BYPASS Clock Source]. This allows the application to use the onboard STLINK's 8MHz clock and change the clock settings in the [Clock Configuration] tab to make the HSE as the entry point for the PLL and the overall SYSCLK to 64 MHz

  • [Green LED] -  set PA5 as GPIO_Output
  • [User Button] - set PC13 as GPIO_Input
  • USART2 
    Go to [Connectivity] and enable asynchronous mode with default parameter settings (115200 bit/s, 8 bits, no parity, and 1 stop bit.) Make sure that PA2 is the one selected as USART2_TX and PA3 as USART2_RX    
  • FDCAN1
    In the [Connectivity] tab, locate the FDCAN1 and click the [Activated] checkbox. Make sure that PC4 is set as FDCAN1_RX and PC5 as FDCAN1_TX. It is important to note that the FDCAN protocol divides time into segments, and that this method is fully programmable in STM32CubeIDE.
    By defining the bit timing and synchronizing the transmitter with the receiver, we can ensure reliable communication between the two devices. This demo is selected to 800 Kbit/s, but this is very arbitrary and should be adjusted to the application needs. To set the baud rate, go to [Parameter Settings] and use the values shown in the image below to set the FDCAN baud rate to 800 kbits/s, for example.

 

Figure 4 - FDCAN baudrate settingsFigure 4 - FDCAN baudrate settings

For this example, we transmit data on the FDCAN bus by polling and receive it through interrupts. While the peripheral can use up to 2 FIFOs to handle data reception, we just need the FIFO0. In the [Parameter Settings] tab, navigate to [NVIC Settings] and enable the option FDCAN1_IT0

After completing all these steps, your microcontroller configuration should look like this:

Figure 5 - MCU pin allocationFigure 5 - MCU pin allocation

Now, you can save and click the [Code generation] button. In the main.c file, we must include the stdio.h library to the project and add some global variables. Use the USER CODE sections to locate where you should insert the code snippets in your application:

/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */​
/* USER CODE BEGIN PD */
#define KEY_PRESSED     GPIO_PIN_RESET
#define KEY_NOT_PRESSED GPIO_PIN_SET
/* USER CODE END PD */
/* USER CODE BEGIN PV */
uint8_t ubKeyNumber = 0x0;
uint8_t ubKeyNumberValue = 0x0;
uint8_t ubLedBlinkTime = 0x0;
FDCAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8];
FDCAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8];
/* USER CODE END PV */

The variable ubKeyNumber tracks the current key number, which increments and is transmitted over the FDCAN bus each time the user button is pressed. ubKeyNumberValue sets the maximum key number and resets the current key number to zero when reached. ubLedBlinkTime sets the blink time of an LED for the LED_Display function. RxHeader and RxData receive data over FDCAN, with RxHeader containing message information and RxData is a buffer that contains the actual data. TxHeader and TxData transmit data over FDCAN, with TxHeader storing message information and TxData containing the actual data.

In this demo, the printf function is used to relay the messages via the terminal. So, we need to create the low level implementation for that and use the USART to stream the data.

/* USER CODE BEGIN 0 */
int __io_putchar(char ch)
{
	HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 10);
	return ch;
}
/* USER CODE END 0 */

 

Now, we need to declare two function prototypes we use for the application: FDCAN_Config and Led_Display. FDCAN_Config assists in easily configuring FDCAN filters and the peripheral operational mode. The Led_Display function helps us navigate through different blinking delays.

/* USER CODE BEGIN PFP */
static void FDCAN_Config(void);
static void LED_Display(uint8_t LedStatus);
/* USER CODE END PFP */​

These functions are implemented in the USER CODE BEGIN 4 section, by the end of the main.c file.

2.1 FDCAN_Config 

This is a function that configures the FDCAN module for communication. The function starts by defining a variable of type FDCAN_FilterTypeDef, which is a structure that contains the configuration settings for the FDCAN filters. The function then sets the configuration settings for the filter.

In this case, the range filter is selected, the filter index is set to 0, and the FIFO0 is used to receive the data. Two IDs are provided to create the range. After configuring the filter, the function starts the FDCAN module by calling the HAL_FDCAN_Start function. It then activates the notification for new messages in RX FIFO 0 by calling the HAL_FDCAN_ActivateNotification function. 

Finally, the function prepares the Tx header by setting the identifier, frame type, data length, error indicator, bit rate switch, and many other features. This header is used to configure the data transmission over the FDCAN bus.

/* USER CODE BEGIN 4 */
/**
  * @brief  Configures the FDCAN.
  *   None
  * @retval None
  */
static void FDCAN_Config(void)
{
  FDCAN_FilterTypeDef sFilterConfig;

  /* Configure Rx filter */
  sFilterConfig.IdType = FDCAN_STANDARD_ID;
  sFilterConfig.FilterIndex = 0;
  sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
  sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
  sFilterConfig.FilterID1 = 0x321;
  sFilterConfig.FilterID2 = 0x7FF;
  if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
  {
    Error_Handler();
  }

  /* Start the FDCAN module */
  if (HAL_FDCAN_Start(&hfdcan1) != HAL_OK)
  {
    Error_Handler();
  }

  if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK)
  {
    Error_Handler();
  }

  /* Prepare Tx Header */
  TxHeader.Identifier = 0x321;
  TxHeader.IdType = FDCAN_STANDARD_ID;
  TxHeader.TxFrameType = FDCAN_DATA_FRAME;
  TxHeader.DataLength = FDCAN_DLC_BYTES_2;
  TxHeader.ErrorStateIndicator = FDCAN_ESI_PASSIVE;
  TxHeader.BitRateSwitch = FDCAN_BRS_OFF;
  TxHeader.FDFormat = FDCAN_CLASSIC_CAN;
  TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
  TxHeader.MessageMarker = 0;
}

2.2 LED_Display

Moving on to the LED_Display function implementation, this function is responsible for controlling the blinking of an LED based on the value of the LedStatus parameter. The function sets the blink time by assigning a value to the variable ubLedBlinkTime. However, this function requires the HAL_SYSTICK_Callback function because it is used to implement a delay and toggle the LED pin based on the value of ubLedBlinkTime.

The LED_Display function starts by declaring a static variable u16LedBlinkCounter that counts the number of system ticks that have occurred. The function then checks if the variable is greater than zero and decreases it if the conditional is true. This creates a delay based on the value of ubLedBlinkTime. If the variable value is zero, the function toggles the LED pin using the HAL_GPIO_TogglePin function and sets u16LedBlinkCounter to a value that creates a delay based on the value of ubLedBlinkTime. This creates the blinking effect of the LED. This should be placed in the USER CODE 4 section

void LED_Display(uint8_t LedStatus)
{
  /* Turn OFF all LEDs */
  HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin,GPIO_PIN_RESET) ;

  /* Blink LED every 200ms */
  switch(LedStatus)
   {
     case (1):
     printf("Blink LED every 100ms\r\n");
     ubLedBlinkTime = 1;
       break;
     case (2):
		printf("Blink LED every 200ms\r\n");
     ubLedBlinkTime = 2;
       break;
     case (3):
		printf("Blink LED every 400ms\r\n");
     ubLedBlinkTime = 4;
       break;
     case (4):
		printf("Blink LED every 800ms\r\n");
     ubLedBlinkTime = 8;
       break;
     default:
       break;
   }
}
void HAL_SYSTICK_Callback(void){
	static uint16_t u16LedBlinkCounter = 0;
	if(u16LedBlinkCounter){
			u16LedBlinkCounter--;
	}
	else{
		u16LedBlinkCounter = 100*ubLedBlinkTime;//delay based in the received value
		HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
	}
}
/* USER CODE END 4 */

 

It is important to note that for the LED to work properly, the developer must go to the stm32g0xx_it_c file and call the SysTick callback by adding HAL_SYSTICK_IRQHandler in the SysTick_Handler. This is necessary to ensure that the HAL_SYSTICK_Callback function is executed at the appropriate time during program execution. Failure to do so may result in unexpected behavior or errors in the program.

* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */
  HAL_SYSTICK_IRQHandler();
  /* USER CODE END SysTick_IRQn 1 */
}​
 
Back to the main.c, in the main function, before the main loop, we call the FDCAN_Config function. Set the maximum ubKeyNumberValue. You can add printf statements to indicate that the hardware initialization is complete and filter configuration is starting.
  /* USER CODE BEGIN 2 */
	FDCAN_Config();
	ubKeyNumberValue = 0x4;
  /* USER CODE END 2 */

The program then enters an infinite loop using the while(1) statement. Within this loop, the demo application creates another while loop that waits for the user button press. Once the button is pressed, the program increments the value of ubKeyNumber and then changes the blink LED frequency. The program then sets the data to be transmitted over the FDCAN bus and starts the transmission process.

If the transmission fails, the program calls the Error_Handler function. Next, the program waits for 10 milliseconds before checking if the button has been released. If the button has not been released, the program continues to wait or returns to the beginning of the loop and waits for the button to be pressed again. Refer to the code implementation below:

    /* USER CODE BEGIN 3 */
		while (HAL_GPIO_ReadPin(BTN_GPIO_Port, BTN_Pin) == KEY_PRESSED)
		{
			if (ubKeyNumber >= ubKeyNumberValue)
			{
				ubKeyNumber = 0x00;
			}
			else
			{
				LED_Display(++ubKeyNumber);

				/* Set the data to be transmitted */
				TxData[0] = ubKeyNumber;
				TxData[1] = 0xAD;

				/* Start the Transmission process */
				if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, TxData) != HAL_OK)
				{
					/* Transmission request Error */
					Error_Handler();
				}
				HAL_Delay(10);

				while (HAL_GPIO_ReadPin(BTN_GPIO_Port, BTN_Pin) != KEY_NOT_PRESSED)
				{
				}
			}
		}
	}
  /* USER CODE END 3 */

 

Now that we have the transmission process done, we need to move to the reception handling using the HAL_FDCAN_RxFifo0Callback when a new message is received in RX FIFO 0.

The function starts by checking if the FDCAN_IT_RX_FIFO0_NEW_MESSAGE interrupt flag is set. If it is, the function retrieves the received message using the HAL_FDCAN_GetRxMessage function. If the message retrieval is successful, the function checks if the received message matches the expected identifier, ID type, and data length.

If it does, the function calls the LED_Display function to blink the LED based on the received data. The function also sets the ubKeyNumber variable to the received data and prints a message to the console indicating that the reception was successful. This ensures that both boards are synchronized and display the same LED status based on sharing the value of ubKeyNumber. Add this function as part of the USER CODE 4 section.

void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
  if((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != RESET)
  {
    /* Retrieve Rx messages from RX FIFO0 */
    if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
    {
    Error_Handler();
    }

    /* Display LEDx */
    if ((RxHeader.Identifier == 0x321) && (RxHeader.IdType == FDCAN_STANDARD_ID) && (RxHeader.DataLength == FDCAN_DLC_BYTES_2))
    {
      LED_Display(RxData[0]);
      ubKeyNumber = RxData[0];
    }
  }
}

 

That concludes our example code. To run the application, connect the USB cable from your board's STLINK to your computer, start the debug session, and observe the application running. This process allows you to monitor the behavior of the program and identify any potential issues or errors.

3. Results

Enter in a debug session or program the board and open a virtual COM port terminal on your computer. When the user presses the button on one board, the LED status changes. The new value is transmitted over the FDCAN bus to the other board, which receives the message and updates its ubKeyNumber variable to match the value received. This ensures that both boards are synchronized and display the same LED status based on sharing the value of ubKeyNumber.

To verify the success of the FDCAN communication between the two boards, we displayed messages in a virtual COM terminal as an interface. These messages indicated whether the transmission and reception of data were successful or not.

In this example, the user can press the button to alternate transmission and reception between the boards. The results of this process are shown in the images below:

Figure 6 - Demo validationFigure 6 - Demo validation

 

Figure 7 - Demo validationFigure 7 - Demo validation

Conclusion

This example code provides a simple set up for developers looking to implement FDCAN communication in their own projects. By following the steps outlined article, developers can successfully establish FDCAN communication between two STM32 microcontrollers and transmit/receive data with ease.

Best wishes for your developments and hope you enjoyed this material! 

Related links

Here are some related links that contain the material that was used to create this article, and can be helpful in your developments.

NUCLEO-G0B1RE - STM32 Nucleo-64 development board with STM32G0B1RE MCU, supports Arduino and ST morpho connectivity - STMicroelectronics

TJA1050 High speed CAN transceiver (nxp.com)

 

 

 

Version history
Last update:
‎2024-05-15 04:55 AM
Updated by: