CAN Protocol in STM32

This tutorial will cover the Basic Can protocol in STM32. Here we will see how to communicate between two STM32 MCUs using the CAN protocol. Of course we would need 2 can transceivers (at least) to do that, and that’s why I am using MCP2551 can transceivers.

A Little info about the CAN Protocol

I am not going to explain every small detail here, instead we will just focus on some important things. For more details about the Protocol, you can google it.

CAN (Controlled Area Network) Protocol is a way of communication between different devices, but under certain rules. These rules must be followed when a message is transmitted over the CAN bus. Here we are going to see these rules.

Below is the image showing the Standard CAN Frame.

  • Here, Identifier is the ID of the transmitting Device. It can be either 11 bits (Standard ID) or 29 bits (Extended ID).
  • RTR (Remote Transmission Request) Specifies if the data is Remote frame or Data frame.
  • IDE specifies if we are using Standard ID or Extended ID.
  • r is the Reserved bit.
  • DLC specifies the data length in Bytes.
  • Data Field is where we send the actual data bytes. It can be upto 8 bytes in size.
  • CRC is the checksum data byte.
  • ACK is the acknowledgment bit.

In this Tutorial, we will see upto the Data Field only. The CRC and ACK will be handled by the HAL Library.



Connection and Configuration

The CubeMX Configuration is as shown below.

I am using CAN1 for this tutorial.

  • Here the BAUD RATE is set to 500000 bps. You can try different different combinations for Prescalar and Time Quanta to achieve this.
  • The Operating Mode is NORMAL Mode.
  • Pins PA11 and PA12 are configured as CAN_RX and CAN_TX.

We also need to enable the pull up for the RX pin as shown below.


The Connection between F446 and F103 is shown below.

  • Here the CAN_Tx and CAN_Rx from the Transceivers are connected to PA12 and PA11 of the Respective controllers
  • CANH and CANL are connected to each other
  • Also there is a 120 ohms Resistance at the node. This is very important, or else you will not get the data.


How to Modify the CAN Data Frame

To do this, we will define some variables, where we can store the header and the data information.

CAN_TxHeaderTypeDef   TxHeader;
uint8_t               TxData[8];
uint32_t              TxMailbox;
  • Here TxHeader will be used to store the header information, like RTR, DLC, etc. This is the type CAN_TxHeaderTypeDef.
  • TxData is used to store the data that we are going to transmit over the CAN bus.
  • TxMailbox is the mailbox, which will be sent to the CAN bus.

Now we will store the required values in the TxHeader, and in the TxData.

TxHeader.IDE = CAN_ID_STD;
TxHeader.StdId = 0x446;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.DLC = 2;

TxData[0] = 50;  
TxData[1] = 0xAA;
  • Here CAN_ID_STD means that we are using the Standard ID (not extended)
  • 0x446 is the Identifier. This is the ID of the Transmitter, and it should be maximum 11 bit wide for the Standard ID.
  • CAN_RTR_DATA indicates that we are sending a data frame
  • DLC is the Length of data bytes, and here we are sending 2 data Bytes
  • Now we will store the 2 data bytes in the TxData array

We have the information ready to be transmitted, and now we will finally transmit it on the CAN bus

if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK)
{
   Error_Handler ();
}

This can be done by using the function HAL_CAN_AddTxMessage. It have the following parameters

  • hcan1 is the instance of the CAN, that we are using.
  • TxHeader is the Header of the message.
  • TxData is the Data field.
  • TxMailbox is the mailbox, which will carry the header and data message.

You can see above the data on the TX Line.

  • Identifier is 0x446, the STD ID of the transmitter
  • Control Field is 0x2, it contains DLC, RTR, IDE
  • 2 Bytes of data Field
  • And at last there is CRC Value, which was added by the HAL

This message is sent to the CAN bus, and now all the CAN devices on this bus will sort of receive this message. I said sort of, because whether to receive the message or not, depends on the Filter Configuration for each device.

If the message satisfies the conditions as per the FILTER, only then it will be allowed to pass.



Filter Configuration

In order to reduce CPU Load to filter out messages, the STM32 have the Filters built inside the CAN peripheral. Let’s Check them out

  CAN_FilterTypeDef canfilterconfig;

  canfilterconfig.FilterActivation = CAN_FILTER_ENABLE;
  canfilterconfig.FilterBank = 18;  // which filter bank to use from the assigned ones
  canfilterconfig.FilterFIFOAssignment = CAN_FILTER_FIFO0;
  canfilterconfig.FilterIdHigh = 0x446<<5;
  canfilterconfig.FilterIdLow = 0;
  canfilterconfig.FilterMaskIdHigh = 0x446<<5;
  canfilterconfig.FilterMaskIdLow = 0x0000;
  canfilterconfig.FilterMode = CAN_FILTERMODE_IDMASK;
  canfilterconfig.FilterScale = CAN_FILTERSCALE_32BIT;
  canfilterconfig.SlaveStartFilterBank = 20;  // how many filters to assign to the CAN1 (master can)

  HAL_CAN_ConfigFilter(&hcan1, &canfilterconfig);
  1. FilterActivation specifies if we want to enable Filters or not. Obviously we have to enable them
  2. SlaveStartFilterBank specifies How many Filter Banks do we want to assign to CAN1. Basically the controllers with dual CAN peripheral have 28 Filter Banks, which can be distributed between these 2 CAN. Here I am assigning 20 Filter Banks to the CAN1, and Rest to the CAN 2.
    • This parameter is useless for the controllers with single CAN peripheral. And these Controllers have 14 Filter Banks ( 0 to 13)
  3. FilterBank specifies which Filter Bank do we want to use for the filter Process. Here I have assigned 20 Banks for CAN 1, and I can only choose Out of these 20 Banks. So I am choosing Bank number 18.
    • In case of Single CAN Peripheral, you can choose any value between 0 to 13
  4. FilterFIFOAssignment specifies which FIFO are we going to use for the Receive message. Generally we have 2 FIFOs ( FIFO 0, and FIFO 1). I am choosing FIFO 0
  1. FilterMode specifies which type of Filter do we want to use. We have 2 types of filters in STM32. MASK MODE, where the Mask register will be used to compare some particular bits in the ID register to the incoming ID. And the LIST MODE, where the incoming ID is directly compared with the ID set in the ID Register.
    • I am using MASK Mode here, as It seems to be more useful
  2. FilterScale specifies If we want to use one 32 bit Filter Register, or 2 16 bit Filter Registers.
    • I am using one 32 Bit Register here.
  3. FilterIdHigh is the Higher 16 Bits of the ID register. The value set in this register will be compared to the incoming Identifier.
    • Here I have decided to only compare the STD ID of the incoming message, and that’s why I am shifting the value by 5. The STD ID starts from 5th bit in the ID HIGH Register
  4. FilterMaskIdHigh is the Higher 16 Bits of the MASK register. The value set in this register will enable the comparison of that particular bit in the ID register to that of the incoming ID.

The Last 2 points might be hard to understand, so I would suggest that you watch the video below. It could be better explained with the working example, and that’s shown in the VIDEO.

Check out the Video Below



Receiving DATA

We will use the interrupt for the RX FIFO, so whenever a message is passed through the Filter an interrupt will be triggered.

First of all We will enable the CAN1 RX0 interrupt in the CubeMX


Now Inside the main Function, we will Activate the Notification for the Received message.

  if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
  {
	  Error_Handler();
  }

Here we will choose CAN_IT_RX_FIFO0_MSG_PENDING. This would trigger the interrupt whenever there is some pending message in the RX_FIFO 0. Once the interrupt is triggered, a callback function will be called. In this case, it will be HAL_CAN_RxFifo0MsgPendingCallback

CAN_RxHeaderTypeDef   RxHeader;
uint8_t               RxData[8];

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
  if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
  {
    Error_Handler();
  }
  if ((RxHeader.StdId == 0x103))
  {
	  datacheck = 1;
  }
}
  • Here we will Receive the message from RX_FIFO 0.
  • The message Header will be stored in the RxHeader, and the data will be stored in RxData.
  • We can do further checks, like if the message was received from the ID 0x103, then the datacheck flag will be set.
  • Later in the while loop we can perform some actions based on this flag
if (datacheck)
{
 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
}

For example, If the Flag is set, The LED will turn ON.



RESULT

The result here is hard to put in images, so I would suggest that you watch the video for more detailed working.

Check out the Video Below




Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

Subscribe
Notify of

35 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
keyboard_arrow_up