HomeSTM32STM32 CAN Protocol Tutorial – Step by Step Guide with Example Code

STM32 CAN Bus Communication Tutorial

In this STM32 CAN protocol tutorial, you will learn how to set up CAN bus communication using CubeMX and HAL. We will connect two STM32 boards through MCP2551 CAN transceivers and exchange data between them. Along the way, you will see how to configure CAN filters, interrupts, and data frames in a clear step-by-step manner. This guide is perfect if you want to understand CAN communication in STM32 microcontrollers and use it in your own embedded projects.

This tutorial covers the basic CAN protocol in STM32. We will learn how to set up communication between two STM32 microcontrollers using the CAN bus. To make this possible, we need at least two CAN transceivers, and in this guide, we will use the MCP2551 CAN transceivers.

STM32 CAN Protocol Video Tutorial

This guide explains every step you need to set up STM32 CAN bus communication — from wiring and CubeMX configuration to writing the code. Still, seeing the process in action makes it easier to follow. That’s why I created a complete video tutorial where I show the setup, coding, and live CAN communication between two STM32 boards using MCP2551 transceivers. You can read the instructions here and watch the video together to understand each step clearly and avoid common mistakes.

Watch the Video

Introducing the CAN Peripheral in STM32 Microcontrollers

The CAN (Controller Area Network) peripheral in STM32 microcontrollers makes it easy to exchange data between multiple devices over a single bus. Originally built for cars, CAN is now used in industrial machines, robots, and many other embedded systems because it’s reliable, fast, and fault-tolerant.

In STM32, the CAN peripheral handles the low-level protocol mechanics, including message framing, filtering, error handling, and arbitration. It supports both standard (11-bit) and extended (29-bit) identifiers and can operate in normal, loopback, or silent modes, making it suitable for both development and deployment.

STM32 devices often include one or more FDCAN (Flexible Data-rate CAN) peripherals in newer series like STM32G4, H7, and L5, offering higher data throughput and improved flexibility over classic CAN.

Here are some important features of STM32 CAN:

  1. Standard and Extended Frame Support
    Supports both 11-bit (standard) and 29-bit (extended) identifiers, making it compatible with a wide range of CAN-based systems.
  2. Hardware Message Filtering
    Built-in filter banks allow selective message reception, reducing CPU overhead and improving efficiency.
  3. Error Detection and Automatic Retransmission
    Includes robust error handling with automatic retransmission, fault confinement, and error counters for reliable communication.
  4. Interrupt and DMA Support
    Offers both interrupt-driven and DMA-based data handling, enabling fast and efficient message processing.

What Is the CAN Bus Protocol and How Does It Work?

The CAN (Controller Area Network) bus is a reliable communication protocol that allows multiple microcontrollers or devices, called nodes, to share data without needing a central controller. Bosch originally developed it for automotive applications, but today it is also common in industrial automation, embedded systems, and medical devices because of its fault tolerance, real-time performance, and noise immunity.

Key Features of the CAN Protocol

  • Two-wire differential communication
    CAN uses two lines — CANH and CANL — for differential signaling. This improves noise resistance and supports reliable data transfer over long distances.
  • Decentralized communication
    Unlike master–slave protocols, any node can send data when the bus is free. This makes the system flexible and efficient.
  • Message priority with arbitration
    Each CAN message has an identifier. Lower values mean higher priority. If two nodes transmit at the same time, the one with the higher priority continues while the other waits.
  • Built-in error handling
    CAN automatically manages errors using CRC checks, acknowledgment bits, and retransmission. This ensures high data integrity even in noisy environments.

Why use a CAN transceiver?

An STM32 microcontroller with CAN support still requires an external CAN transceiver such as the MCP2551 to work with the bus. The transceiver converts the logic-level signals from the microcontroller into the differential voltages used by the CAN bus and ensures proper communication between nodes.

Basics of the CAN Protocol

I won’t go into every small detail of the CAN protocol here. Instead, we will focus only on the key points you need to understand before working with STM32 CAN bus communication. If you want to explore more, you can always look up the detailed protocol specifications.

The CAN (Controller Area Network) protocol defines how different devices communicate with each other under specific rules. Users must always follow these rules while sending or receiving messages over CAN Bus.

The image below shows the structure of a Standard CAN frame, which is the most common format in CAN communication.

Structure of a Standard CAN frame, a common format used in CAN communication.
  • 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 up to the Data Field only. The CRC and ACK will be handled by the HAL Library.

Hardware Requirements

Here are the components used in this project. Some links are affiliate links that help support this work at no extra cost to you:

STM32 CAN Configuration

To set up CAN communication in STM32, we need to configure the CAN peripheral in CubeMX and generate the initialization code with HAL. This setup includes selecting the CAN mode, setting bit timings, and enabling the required GPIO pins for CANH and CANL.

The CubeMX Configuration for the CAN Peripheral is shown below.

STM32 CAN Configuration shown in STM32CubeMX

I am using CAN1 for this tutorial.

  • Here the BAUD RATE is set to 500000 bps. You can try different different combinations for Prescaler 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 resistor for the RX pin. This ensures the pin stays at a defined logic level when no signal is present, which helps avoid unwanted noise or false readings on the CAN bus.

STM32 CAN GPIO Configuration

Using a STM32 with CAN Transceiver (MCP2551)

To enable CAN bus communication between two STM32 boards, we use the MCP2551 transceivers. Each STM32 board connects to its own MCP2551, and then both transceivers share the CANH and CANL lines through the bus. A termination resistor is placed between CANH and CANL to ensure proper signal integrity.

STM32 CAN bus connection diagram showing two STM32 boards connected to MCP2551 CAN transceivers with CAN_TX (PA12) and CAN_RX (PA11) pins, 5V, GND, CANH, CANL lines, and a 120 Ω termination resistor.

STM32 to MCP2551 Connections

For each STM32 board:

  • CAN_TX (PA12) → TXD pin of MCP2551
  • CAN_RX (PA11) → RXD pin of MCP2551
  • 5V (VCC) → VDD pin of MCP2551
  • GND → VSS pin of MCP2551

MCP2551 to CAN Bus Connections

  • CANH of MCP2551 → CANH of the other MCP2551
  • CANL of MCP2551 → CANL of the other MCP2551
  • Place a 120 Ω termination resistor between CANH and CANL (as shown in the diagram).

Transmitting the CAN Data Frame in STM32

After configuring the CAN peripheral and connecting the STM32 with the MCP2551 transceiver, the next step is to actually send data over the CAN bus. To do this, we first prepare the CAN frame by setting the header and filling in the data bytes. Then, we use the HAL functions to transmit the frame, which allows the STM32 to communicate with other nodes on the bus in real time.

Preparing the CAN Data Frame

Before we send any data, we need to prepare the CAN frame. This involves setting up the TxHeader with the message identifier, frame type, and data length, and then filling the TxData array with the actual bytes we want to transmit.

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.

Next, we will place the required values into TxHeader and TxData. The header will hold details like the identifier and data length, while the data array will store the actual bytes we want to transmit over the CAN bus.

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
  • Finally, store the 2 data bytes to be transmitted in the TxData array

Sending the CAN Data Frame

Once the frame is ready, we can send it over the CAN bus using the HAL function provided in STM32CubeMX. At this stage, the STM32 passes the header and data to the CAN peripheral, which then communicates with the MCP2551 transceiver.

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.

Result of CAN Data Transmission in STM32

The image below shows the waveform captured during a CAN data frame transmission from the STM32. It highlights different fields of the frame, such as the identifier, control bits, data bytes, and CRC value.

Logic analyzer screenshot showing STM32 CAN bus transmission with standard identifier 0x446, control field 0x2, data field bytes 0x64 and 0x0A, and CRC value 0x1459.
  • Standard CAN Identifier (0x446): This is the message ID that defines the priority of the frame.
  • Control Field (0x2): It specifies the type of frame and the data length (DLC, RTR, IDE).
  • Data Field Bytes (0x64, 0x0A): The 2 bytes of data (Payload) we are transmitting.
  • CRC Value (0x1459): This field ensures error detection by verifying data integrity.
  • ACK/NACK: At the end of the frame, acknowledgment bits confirms successful reception or failure.

This result confirms that the STM32 successfully prepared and transmitted a valid CAN bus frame with the configured data.

When the CAN bus transmits a message, all connected devices can see it. However, each device’s filter configuration decides whether to accept it. The device receives and processes the message only if it matches the defined filter rules.

Configuring CAN Filters in STM32

To reduce CPU load and avoid handling unwanted messages in software, STM32 microcontrollers include built-in filters inside the CAN peripheral. These filters decide which messages should be accepted and which should be ignored. Let’s take a closer look at how they work.

  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. Enable Filters with FilterActivation
    You must enable filters using the FilterActivation parameter. Without enabling them, no filtering takes place.
  2. Assign and Select Filter Banks
    • On controllers with dual CAN peripherals, you can distribute 28 Filter Banks between CAN1 and CAN2. For example, assign 20 Banks to CAN1 (SlaveStartFilterBank) and the rest to CAN2, then select a specific Bank (e.g., Bank 18) for filtering.
    • On controllers with a single CAN peripheral, you can only use Banks 0–13.
  3. Configure Filter Parameters
    • Use FilterFIFOAssignment to decide which FIFO (FIFO0 or FIFO1) will store received messages.
    • Set FilterMode to either Mask Mode (compare selected bits) or List Mode (compare full IDs). Mask Mode is often more practical.
    • Choose FilterScale to use either one 32-bit register or two 16-bit registers.
    • Set FilterIdHigh to define which ID bits to compare (e.g., shift the STD ID by 5 because it starts at bit 5).
    • Set FilterMaskIdHigh to enable comparison of specific bits between the ID register and the incoming ID.

I recommend watching the video below. It demonstrates the working example in action, which explains the concept more clearly than text alone.

Receiving CAN Data in STM32

To handle incoming messages efficiently, we will use an interrupt for the RX FIFO. Whenever a message passes the filter, the CAN peripheral triggers an interrupt, allowing the STM32 to capture the data immediately. The first step in this process is enabling the CAN1 RX0 interrupt in CubeMX.

Enabling the CAN1 RX0 Interrupt in CubeMX

This image below shows the configuration of the CAN1 RX0 interrupt in STM32CubeMX.

STM32CubeMX configuration window showing CAN1 settings. The NVIC Settings tab is open, with the "CAN1 RX0 interrupt" option enabled. On the right, the STM32 pinout view highlights PA12 as CAN1_TX and PA11 as CAN1_RX.

When you enable this interrupt, the STM32 automatically triggers the Interrupt Service Routine (ISR) each time a new CAN message passes through the filter and enters the RX FIFO 0 buffer. This allows the microcontroller to receive messages instantly without continuous polling, which reduces CPU load and improves overall efficiency.


Handling CAN Message Reception with Interrupts

To receive messages efficiently, we enable CAN notifications inside the main() function. This mechanism triggers an interrupt whenever a new message arrives in RX FIFO0 and automatically executes the corresponding callback function.

In the main function, we activate the notification using:

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

Here, we enable the CAN_IT_RX_FIFO0_MSG_PENDING interrupt, which triggers whenever a new message is pending in RX FIFO0.


Once the interrupt occurs, the callback function HAL_CAN_RxFifo0MsgPendingCallback is called. In this function, we retrieve the received message header and data, and perform further checks if required.

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 program stores the message header in RxHeader and the data in RxData.
  • It then checks whether the message was received from ID 0x103; if true, it sets the datacheck flag.

In the main loop, we can use the datacheck flag to perform actions. For example, if the flag is set, we turn on an LED:

if (datacheck)
{
 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
}

This way, only when a valid CAN message with ID 0x103 is received, the LED will light up.

Step-by-Step Flow of CAN Message Reception

To better understand how CAN reception works in STM32, let’s break the process into clear steps. The STM32 follows a well-defined sequence from the moment a CAN message is transmitted on the bus to the final action, such as turning on an LED. This flow makes it easier to see how filters, interrupts, and callbacks work together to handle incoming CAN messages efficiently.

Step-by-Step Flow

  1. Message Sent on CAN Bus → Another node transmits a CAN frame.
  2. Message Arrives at STM32 → If it passes the configured filter, it is stored in RX FIFO0.
  3. Interrupt Triggered → The CAN_IT_RX_FIFO0_MSG_PENDING interrupt is raised.
  4. Callback ExecutesHAL_CAN_RxFifo0MsgPendingCallback() is called.
  5. Message Retrieved → Header goes into RxHeader, data into RxData.
  6. ID Check → If ID matches 0x103, set the datacheck flag.
  7. Action Performed → In the main loop, the LED is turned ON when datacheck is set.

The image below shows the step-by-step flow of how a CAN message is received in STM32.

Flowchart showing CAN message reception in STM32. Steps include message sent on bus, received by STM32, filter check (discard or accept), stored in RX FIFO0, retrieved, ID checked (0x103), flag set, and final action LED ON.

Conclusion

In this tutorial, we learned how to use the CAN protocol with STM32, from basic setup and connections to sending and receiving messages using filters and interrupts. This approach ensures reliable communication while reducing CPU load. With this knowledge, you can expand to more advanced projects involving multiple nodes or complex CAN networks.

If you want to go further, check out my guide on FDCAN Normal Mode in STM32 to explore flexible data-rate CAN for faster and more powerful communication.

Other STM32 CAN Tutorials

PROJECT DOWNLOAD

Info

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

STM32 CAN Protocol Project FAQs

Subscribe
Notify of

36 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Raj
4 months ago

I am working on the same project you explained here, but my boards are nucleo and discovery. I am a completely beginner in it can you please help me in my project

Arseny
11 months ago

Why are there two reserved bits (r0, r1) after the ID and before the data length code, if according to the documentation of Bosch there is one reserved bit r0?

Mohammadreza Mehrdad
1 year ago

Hello,
Thanks for your useful tutorials.

I am going to use Extended CAN of stm32 but I could not find how I can do it. Is it possible you help and tell me which part of the code need to be changed.

Regards,
Mehrdad

Alex
1 year ago

Olá amigo, qualo modelo das placas, a menor é a BluePill e a outra é que núcleo?

Jayaprakash
2 years ago

Hi, I am using stm32f407 I tried your code but there is no data is received,i don’t know what I
Missed

Roman
2 years ago

Hey man! Big thanks, your code examples are a great help, especially this one. Video tutorials on youtube are great too. (if only not for the robotic voice-over, but I understand how hard it is to make a proper voice-over)

kaasi
3 years ago

super ra kaasi

Annika
3 years ago

Thank you very much for this helpful tutorial.
I also watched the vid on youtube for the can one master multiple slaves.
Do I understand right: I can only tell a controller to which other controllers (IDs) to listen to, but I cannot tell the master to send a message to a certain slave?
So all slaves listen to the master, and the master message passes the filter of all slaves, and I have to pack a “address information” into the data part of the message, which every slave has to check then?
I would be very happy if you could clarify this for me 🙂

Annika
Reply to  ControllersTech
2 years ago

forgot to say thanks for the fast reply.
so: thank you!

Thamanoon Kedwiriyakarn
3 years ago

Thank you very much Sir.

Fabian
3 years ago

I have to establish CAN bus communication between two stm32f407VET6 boards and it doesn’t work for me, can I directly connect tx to tx and rx to rx? because I understand that the boards have two integrated can, I do not have the MCP2551, I have two MCP2515

MrH
3 years ago

Hi, I am using STM32F103C8 and I have tried your code and setup. However, your code is working on “loopback mode” and not working in “Normal Mode”. I don’t know why, it won’t transmit and wont receive message. I believe there is other people commented this problem on your youtube (CAN Normal Mode) video but has yet get an answer, also there are many other facing this issue as well (stack forum or st community) both also do not have answer. If anyone reading this and have solution please share it here. Thank you.
Extra Info:
If I configure in loopback mode, I can transmit message from MCU to other CAN analyzer device and to internal Rx (I verified by oscilloscope and CAN analyzer device successfully received.)

KOM
Reply to  MrH
3 years ago

Do you using STM32F103C8 2 board right ? I have problem same with you.

MrH
Reply to  KOM
3 years ago

I am not using STM32F103C8 blue pill board. Mine is just the microcontroller(STM32F103C8T6) itself. I owned a STM32F303 Nucleo board as well and Have tried controllertech’s code and it is working.
Also list of configurations I have tried on STM32F103:
1) adjusting the sample point to ~80%
2) tried internal clk and external clk
3) FIFO0 & FIFO1
4) different baudrate
5) CAN port remapping
6) CAN Config Filter ID etc
7) check on hardware wiring, connection etc
8) new or spare STM32F103
9) ST’s example code in STM32Cube_FW_F1_V1.X.X
Unfortunately, non of them is working. At this point I think STM32F103C8 CAN peripheral is broken. I wish I have a blue pill, I believe STM32F103 on blue pill will not have this issue.
Also If anyone has solution please share it here Thank you.

MrH
Reply to  MrH
3 years ago

Finally I get it to work. The problem is Vcc. I am supplying 3.3V to transceiver which is not enough. The transceiver I am using required Vcc in range of 4.7 – 5.2 V. Lesson learned, read datasheet thoroughly.

Petar Lalov
Reply to  MrH
3 years ago

Thanks for sharing!

Your comment helped me to realize where the past 6 hours have gone.

I am using MCP2562 and it has 2 power supplies, Vio and Vdd. I thought both are 3.3V, but it turns out that Vdd should be 5V.

Lesson learned – read carefully the documentation before soldering 😉

Vyy
Reply to  KOM
1 year ago

can i see your program and setting on stm cube??

Ronter
3 years ago

Hello,

ı want to know, which is id transmitted message to me? How can ı lean?

“HAL_CAN_AddTxMessage” methods accept uint8_t data, can ı transmit array? 

In my scenario I have a master and 5 slaves. 5 slaves are sending messages to master. And I want to know, which slave sent the message to the master?

Thank you for this post.

Last edited 3 years ago by Ronter
Иван
3 years ago

Нужны stm32 can

Jaboop
3 years ago

Do you have any good sources on what modifications need to be made to use the FDCAN peripheral on the H7 processors? I can see some signals making it back and forth between the two boards but they analyze as Errors and after two exchanges back and forth the CAN State get stuck in BUSY.

Jaboop
Reply to  Jaboop
3 years ago

All fixed. The CAN was BUSY because I was forgetting to call GetMessage and the buffer I’m guessing was full. Using this tutorial mixed with the example project from HAL on FDCAN worked great. Thanks!

Lakshminarayana
3 years ago

Hello Thanks for the tutorial it was helpful but i have one problem, i can only receive the data in Blue Pill but the data sent by the Bull pill is not received at F4 controller and i am using CAN BUS 2 for the communication i need as all suggested by you. but still no way i could achieve it.

could you please guide me what wrong i am doing.

Thanks
With regards
Lakshminarayana KS

Lakshminarayana
Reply to  Lakshminarayana
3 years ago

I solved the issue it was wit my filter Bank Selection.
Is their anyway to hold the bus and send more than 8 bytes of Data ???

thanks

velan vs
3 years ago

super bro

Sharan
3 years ago

Hey, This worked well.

Now is it possible to use multiple addresses with a device to transmit data?
That is the slave has multiple addresses for a single device, each address has different data values. Now the master accesses the slave with multiple addresses according to the required data values.

For example:- The master requires the data”time” in the slave, the master uses the particular address(0x102) for the data “time”.If the master requires the data “Brightness” in the slave, the master uses the particular address(0x103) for the data “Brightness”.Likewise….

Can you give me a solution to work out this in STM32F103C8T6?

tue nguyen
4 years ago

I use CAN1 for F407VG, it works normal but use CAN2 it does not receive data. I dont know why?

Last edited 4 years ago by tue nguyen
tue nguyen
Reply to  ControllersTech
4 years ago

I don’t use the filter configuration for CAN2, it sends data normally but still does not receive data.

VAN-NHI NGUYEN
4 years ago

Hello, i don’t know what the value 0x443 in STD ID is for, while on the receiver side we check the value 0x103.

Har
4 years ago

Hi Admin,
Thank you so much for your tutorials…!
I am STM32F103 CAN (MCP2561 as CAN transceiver) with 500kbps and CAN BUS ANALYZER to check the CAN msgs.
Here am sending 3 CAN msgs for every 100ms but some times some msgs are missing so that am getting the CAN cycle time as 200ms and I have followed the same procedure as above but some times my CAN msgs are missing.
what I have to do eliminate this error ?
Thanks again