cancel
Showing results for 
Search instead for 
Did you mean: 

Guide to CAN (bxCAN/CAN2.0) configuration in Loop back mode on STM32 MCUs

mƎALLEm
ST Employee

Introduction

Many STM32 MCUs feature a CAN2.0 interface named bxCAN. The Loop back mode is one of the modes implemented in this peripheral. It is mainly used to test and debug transmit and receive CAN messages and mainly to validate the filter configuration.
This article describes step by step how to create a project for CAN in Loop back mode on an STM32 MCU.
The example described in this article is based on the STM32F103 MCU/NUCLEO-F103RB board.

This article applies to all STM32 products having bxCAN interface such as STM32F0xx, STM32F1xx, STM32F2xx, STM32F3xx, STM32F4xx, STM32F7xx etc. Refer to the related product datasheet.

Table of contents

1. Prerequisites

1.1 Software

1.2 Hardware

Note that the steps described further can be applied to any STM32 with differences, mostly in the RCC config and the CAN timings.

We provide a GitHub hotspot that contains a simple project on several STM32 part numbers where the bxCAN is configured in Loop back mode.

The example is available on the following boards:

2. Starting a project with STM32CubeMX and CAN configuration

2.1 Starting the project based on the board

The example is based on the NUCLEO-F103RB board. The first thing to think about is the pins to be used by the CAN interface. So, you need to find which pins are free on the board. Although an example is using Loop back mode, you need to select CAN_Tx and CAN_RX with an externally unconnected I/O. For that we need to refer to the board’s schematic.
On the STM32F103RB MCU, there is only one CAN instance which is CAN1.
We need to start a project based on the board in STM32CubeMX:

  1. Create a new project starting from the “ST board”
  2. Select the [Access to board selector]
  3. Find the board “NUCLEO-F103ERE”
  4. In the bottom right of the window, double-click on the board image.

Figure 1. Start a project based on a ST boardFigure 1. Start a project based on a ST board

Figure 2. Select NUCLEO-F103RBFigure 2. Select NUCLEO-F103RB

STM32CubeMX asks you to initialize the peripherals with their default values.
Click [Yes]:

Figure 3. Click yes to initialize the peripheralsFigure 3. Click yes to initialize the peripherals

 

At this stage, the board default pinout is set and the used GPIOs are shown in the pinout view:

Figure 4. Default pinout viewFigure 4. Default pinout view

 

The first thing to do is to enable CAN1 from the “Connectivity” menu:

Figure 5. CAN activationFigure 5. CAN activation

 

Figure 6. CAN pins activatedFigure 6. CAN pins activated

After enabling CAN1, PA11 and PA12 GPIOs are enabled by default for this product. There were no conflicts between the already enabled GPIOs on the board and the CAN Ios. This is confirmed by looking at the board’s schematic, PA11 and PA12 are free, and nothing connected to them. So, these GPIOs can be used for the CAN application.

Figure 7. CAN pins on the board are freeFigure 7. CAN pins on the board are free

After the CAN interface activation, there are three stuff to configure: the clock configuration, the CAN timing configuration and the GPIO configuration.

2.2 CAN mode configuration

The Loop back mode is used. This mode is a configuration where transmitted CAN messages are looped back to the Rx input. In this mode, a CAN transceiver is not mandatory as the signals are looped back internally in the CAN peripheral. The Tx and Rx signals are internally connected in the peripheral.

Figure 8. CAN in Loop Back modeFigure 8. CAN in Loop Back mode

Note that in the Loop back mode, we can monitor the Tx messages sent by the CAN peripheral on CAN_Tx pin. You can probe the transmitted CAN messages using an oscilloscope or a logic analyzer. See Section 4.

In STM32CubeMX, go to the “Parameter Settings” tab and setting the Loop back mode in the “Advanced Parameters” menu:

Figure 9. Configuring CAN in Loop Back modeFigure 9. Configuring CAN in Loop Back mode

For the basic CAN parameters, we keep them to their default settings. Not needed in our basic example:

Figure 10. Basic CAN parameters settingFigure 10. Basic CAN parameters setting

 

2.3 Clock configuration

As the Loop back mode is used, there is no need for a precise clock source because the bit sampler and the bit generator have the same clock source. So, HSI is used as the clock source in the example. In the case of STM32F103, the CAN peripheral is clocked from the APB1 domain as stated in the reference manual:

Figure 11. The CAN clock is on APB1 domainFigure 11. The CAN clock is on APB1 domain

So, the CAN bit time is generated and sampled based on this clock source.

In STM32CubeMX, we select an HSI clock source over PLL. The system clock source is 64 MHz and the APB1 clock is be 32 MHz (the maximum).

In STM32CubeMX, in “Clock Configuration view”:

  1. Select HSI clock source (8MHz/2)
  2. Select PLLCLK to enable the PLL

Figure 12. System clock configurationFigure 12. System clock configuration

 

2.4 CAN timing configuration

In this example, we set the CAN bitrate at 500 KB/s. Knowing that in the Loop back mode, the bitrate configuration is not a must. The CAN bit time configuration applies for both Tx and Rx and the transmitted messages are received at the same CAN bitrate.

Take precaution. Setting BS1 and BS2 to very low values such as BS1=1 and BS2=1 leads to a communication issue. Better to decrease the CAN clock prescaler and increase BS1 and BS2 as much as possible fitting the CAN bitrate.

In STM32CubeMX, in “Parameter Settings”, the default timing config values set are:

  • Prescaler = 16
  • Time Quanta in Bit Segment 1 = 1
  • Time Quanta in Bit Segment 2 = 1

These are not ideal settings.

Figure 13. Default CAN timing configuration in STM32CubeMXFigure 13. Default CAN timing configuration in STM32CubeMX

The first thing to do is to increase BS1 and BS2 to their maximum values (BS1=16, BS2=8) in STM32CubeMX. Decrease the prescaler step by step until the CAN bitrate target value is found. If the operation does not allow to converge to the wished exact bitrate, try to play with the prescaler.

As said, in our example the CAN bitrate is set to 500 KB/s. The sample point needs to be selected between 75% and 87% of the bit time.

In STM32CubeMX, we choose these settings:Figure 14. CAN timing configurationFigure 14. CAN timing configuration

The bit time = SyncSeg + BS1 + BS2. In our case, the bit time = 1 + 12 + 3 = 16

Where SyncSeg is always = 1.

The sample point = (SyncSeg + BS1) / bit time = (1 + 12) /16 = 0,8125 à Sample point at: 0,8125%

In this configuration, the sample point is set at ~81% of the bit time.

You can use the attached excel file calculator (CAN bitrate calculation.xlsx) to find the suitable timing configurations for the CAN bitrate, including the sample point.

2.5 CAN GPIO configuration

Even if the CAN signals are internally connected by hardware, it is mandatory to configure the I/Os. This is already done when CAN is activated in STM32CubeMX as shown in the figures 5 and 6. To avoid issues, we need to activate the internal pull-up resistor manually on the CAN_Rx pin.

In that case, we need to go to the “GPIO settings” tab and set the pull-up on CAN_Rx pin which is PA11 in this case.

In STM32CubeMX, go to the “GPIO Settings” tab, and:

  1. Click on the CAN_RX pin
  2. Go to the PA11 configuration then to “GPIO Pull-up / Pull-down” and select [Pull-up] to set the internal resistor.

Figure 15. Setting the pull-up on CAN_Rx pinFigure 15. Setting the pull-up on CAN_Rx pin

2.6 CAN interrupt settings

In the example, we use the CAN message reception in the interrupt mode. There are three NVIC interrupts available for CAN: two for the FIFOs' reception (FIFO0 and FIFO1) and one for the CAN error management. On STM32F1 family, the CAN Rx FIF0 NVIC interrupt line is shared with a USB low-priority interrupt line. In our case, we activate the interrupt only on FIFO0 as we do not use FIFO1 or the CAN error.

In STM32CubeMX, go to the “NVIC Settings” tab, and check [USB low priority or CAN RX0 interrupts] option:

Figure 16. Enable CAN RX0 NVIC interrupt channelFigure 16. Enable CAN RX0 NVIC interrupt channel

We can keep the “Preemption Priority” and “Sub Priorities” set to their default values 0.

At this stage, all is set in the STM32CubeMX. It is time to generate the code. See section 3.

Note that it is not possible to use USB and CAN at the same time on the STM32F103. So, it is not possible to create a USB to CAN converter with it while some other products and families do.

3. Adding CAN application code in STM32CubeIDE

In this section, we describe how to proceed for the code generation for STM32CubeIDE and what is the code needed to run the CAN communication in Loop back mode.

3.1 Generating the code

In STM32CubeMX, after setting all the parameters of the RCC and the CAN peripheral, we need to name the project. Select where the project will be saved and set the toolchain to use. In our case, it is STM32CubeIDE.

In STM32CubeMX, 

  1. Go to the Project Manager view
  2. Name the project. Example: NUCLEO-F103_CAN_Loopback
  3. Browse to the location where the project will be saved
  4. Select your preferred toolchain. In our case, it is STM32CubeIDE
  5. Click on “Generate code”

Figure 17. Project code generationFigure 17. Project code generation

After generating the code, and opening STM32CubeIDE, this is what you need to see in the “Project Explorer”:

Figure 18. Project explorer view after generating the codeFigure 18. Project explorer view after generating the code

3.2 Adding the CAN code to the application 

We focus on the main.c file where we will add the CAN application.

The purpose of the application is very simple: Send 8 bytes CAN data frame and receive them in Rx FIFO0 using interrupt.

Variables to declare/add between /* USER CODE BEGIN PV */ and /* USER CODE END PV */. Insert this code in between:

CAN_TxHeaderTypeDef   TxHeader; /* Header containing the information of the transmitted frame */
CAN_RxHeaderTypeDef   RxHeader; ; /* Header containing the information of the received frame */
uint8_t               TxData[8] = {0};  /* Buffer of the data to send */
uint8_t               RxData[8]; /* Buffer of the received data */
uint32_t              TxMailbox;  /* The number of the mail box that transmitted the Tx message */

In MX_CAN_Init(), we add additional code: the CAN filter configuration, the CAN start preparation and the CAN interrupt. For filters, we use a very simple configuration, i.e., passing all the IDs.  The code is added in between /* USER CODE BEGIN CAN_Init 2 */ and /* USER CODE END CAN_Init 2 */ . Insert this code in between:

/* The CAN filter configuration */
  sFilterConfig.FilterBank = 0;
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
  sFilterConfig.FilterIdHigh = 0x0000;
  sFilterConfig.FilterIdLow = 0x0000;
  sFilterConfig.FilterMaskIdHigh = 0x0000;
  sFilterConfig.FilterMaskIdLow = 0x0000;
  sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; /* The data will be received in FIFO0 */
  sFilterConfig.FilterActivation = ENABLE;
  sFilterConfig.SlaveStartFilterBank = 14;
  if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
  {
    /* Filter configuration Error */
    Error_Handler();
  }
 /* Starting the CAN peripheral */
  if (HAL_CAN_Start(&hcan) != HAL_OK)
  {
    /* Start Error */
    Error_Handler();
  }
 /* Activate CAN RX notification on FIFO0 */
  if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
  {
    /* Notification Error */
    Error_Handler();
  }

Do not forget to add the declaration of the filter structure in between /* USER CODE BEGIN CAN_Init 0 */ and /* USER CODE END CAN_Init 0 */. Insert this code in between:

CAN_FilterTypeDef  sFilterConfig;

Now, need to declare the CAN FIFO0 interrupt callback. You can add it in between

/* USER CODE BEGIN 4 */ and /* USER CODE END 4 */. Insert this code in between:

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
{
  /* Get RX message */
  if (HAL_CAN_GetRxMessage(CanHandle, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
  {
    /* Reception Error */
    Error_Handler();
  }
}

Getting the message with HAL_CAN_GetRxMessage() fills the data buffer RxData with the received data and the RxHeader with the frame header that contains the information about the received frame like the ID, the data length etc. 

Finally, add the transmit operation in main(). The code to insert is between  /* USER CODE BEGIN 2 */ and /* USER CODE END 2 */:

The transmitted frame CAN ID is in a standard format with a value of 0x123 and acts as a data frame.

The data length is 8 bytes. The first frame that is transmitted, transmits Byte0 = 0 and the last byte Byte7 = 0xFF (255).

  TxHeader.StdId = 0x123;
  TxHeader.RTR = CAN_RTR_DATA;
  TxHeader.IDE = CAN_ID_STD;
  TxHeader.DLC = 8;
  TxHeader.TransmitGlobalTime = DISABLE;
  TxData[0] = 0;
  TxData[7] = 0xFF;

Now, it is time to add the code responsible for the CAN transmission in the while loop. Note that we need to check first if there is one Tx mail box available to send the current CAN frame. Otherwise, HAL_CAN_AddTxMessage() returns an error if you do not wait until the previous frame is sent.

Some users put HAL_Delay(). It works but for efficiency reason, it is preferable to poll on the Tx free mail box.

The application transmits 8 bytes where the first byte (byte 1) is incremented, and the last byte (byte 8 is decremented.

Insert this code between while (1) { and /* USER CODE END WHILE */

	  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(&hcan) == 0); /* Wait till a Tx mailbox is free. Using while loop instead of HAL_Delay() */
      if (HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox) != HAL_OK)
      {
        /* Transmission request Error */
        Error_Handler();
      }

Now, compile the project by clicking on the hammer button:

Figure 19. Compile the projectFigure 19. Compile the project

4. Running the application and tests

In this section, we show how to test the CAN in Loop back mode. For that we use the Saleae logic analyzer in CAN mode. We probe CAN_Tx (PA12). On NUCLEO-F103 the connector pin CN10/12 is used.

Connect the GND of the logic analyzer to the GND of the board and connect the data 0 (D0) of the logic analyzer input to CAN_Tx (PA12).

In the Saleae logic analyzer software interface, enable the digital channel 0. Go to the analyzer menu “Add Analyzer” and search for “CAN” and select it.:

Figure 20. Configure the Saleae logic analyzer digital channel 0 input in CAN modeFigure 20. Configure the Saleae logic analyzer digital channel 0 input in CAN mode

A window is shown to configure the CAN bitrate. In our case 500 KB/s is used. Set the value to 500000 (in Bits/s) and click [Save]:

Figure 21. CAN configuration with Saleae logic analyzerFigure 21. CAN configuration with Saleae logic analyzer

Now, connect the board to your PC/laptop with a USB data cable over STLINK USB connector and in STM32CubeIDE, start a debug session:

Figure 22. Start a debug session of the application in STM32CubeIDEFigure 22. Start a debug session of the application in STM32CubeIDE

Wait until the debug to start.

You can add TxData and RxData to the “Live Expressions” view, to check how they update while the application runs:

Figure 23. Adding TxData and RxData to the Live Expressions in STM32CubeIDEFigure 23. Adding TxData and RxData to the Live Expressions in STM32CubeIDE

In the logic analyzer, click the [Start] button to start the frame capture.

Now, run the application from STM32CubeIDE by clicking the green triangle:

Figure 24. Running the application on STM32CubeIDEFigure 24. Running the application on STM32CubeIDE

The logic analyzer starts capturing the CAN frames sent on CAN_Tx pin (PA12).

These are the frames captured by the logic analyzer:

Zoom out of the capture:

Figure 25. Zoom-out of the CAN frames captured by the logic analyzerFigure 25. Zoom-out of the CAN frames captured by the logic analyzer

In the first frame sent by the application, the analyzer indicated that the frame ID is 0x123. The first byte sent in the frame is 0x01 and the last data sent is 0xFE. Which corresponds well to what the application is doing.

Figure 26. Zoom-in on the first CAN frame sent by the STM32 and captured by the logic analyzerFigure 26. Zoom-in on the first CAN frame sent by the STM32 and captured by the logic analyzer

Note that the pulses related to the zeroed data correspond to the bit stuffing.

In the debugger we can see that RxData is changing in live in the “Live Expressions”. Only the bytes 0 and 7 are changing (in yellow color):

Figure 27. Tx and Rx buffers are changing in live in the STM32CubeIDE debuggerFigure 27. Tx and Rx buffers are changing in live in the STM32CubeIDE debugger

Conclusion

This article described step by step how to configure the CAN in Loop back mode to receive CAN frame in interrupt mode. It showed how to run the CAN application and how to test with a logic analyzer.

Related links

Version history
Last update:
‎2025-03-28 6:35 AM
Updated by: