Basic CAN configuration with STM32F407
Controller Area Network (CAN) is one of the most common protocols in the Embedded Systems world. Through this article my intention is to take you with me on my journey of learning CAN protocol using the STM32 eco system. I use STM32 here simply because I have an STM32F407-Disco board at my disposal. Please note that this article wouldn’t teach you CAN protocol, rather, it refers to materials that already exist in the Internet and builds upon that knowledge to implement basic communication scheme on actual hardware.
The main use case for CAN is in the automotive industry. It is one of the most common communication protocols used in modern automobiles. Most of the sensors transmit their data through the CAN bus that is connected to the control unit of the automobile. You can find hundreds of articles about these use cases in the Internet.
This article is the first in a series. I plan to expand on this foundation and implement a mock CAN network in the coming weeks.
Board bring up
As the first step of any embedded project, it’s a best practice to write a simple program to blink an LED. This is one of the most powerful programs, because it proves that your compiler, programmer and the board works properly. Therefore, naturally, this was my first step.
Learning CAN protocol
It’s very difficult to start learning how to use a protocol without understanding the basics of the protocol. One doesn’t need to understand all the nitty-gritties of the protocol, however, a basic understanding is a must to be ready for what comes next in datasheets and reference manuals.
I referred the Introduction to the Controller Area Network (CAN) document by Texas Instruments to get the basic understanding.
Once I could understand the basic concepts, I had a look at the bxCAN implementation in the STM32F407 reference manual. It explains the mechanics of the STM32 adaptation and realization of the CAN protocol.
CAN interface configuration
Afterwards, I enabled the CAN interface using the STM32CubeMX software. I selected CAN1 and it automatically configured the required GPIO pins. The only other configuration I changed was the operating mode under the CAN Advanced Parameters configuration. I set it to “Loopback combined with Silent”. This mode is designed to test CAN configuration of nodes without interrupting any network that they are connected to. The configuration is explained in detail in the section 32.5 of the reference manual. My idea is to make use of the loopback mode to test the implementation without depending on any external components.
Understanding the STM32Cube HAL API for CAN
One thing I really like about the STM32Cube HAL is their API documentation. All the relevant information is usually available at the begining of the source file. In this case, for CAN API, all the information is available in the stm32f4xx_hal_can.c file.
If I summarize the required APIs,
Define HAL_CAN_MspInit()
Auto generated in stm32f4xx_hal_msp.c Called by HAL_CAN_Init() GPIO configuration and clock configuration is autogenerated inside this function
Call HAL_CAL_Init()
This function is called within the autogenerated MX_CAN1_Init() in main.c
Configure RX filters using HAL_CAN_ConfigFilter()
This function call is NOT auto generated
Start the CAN peripheral by calling HAL_CAN_Start()
This function call is NOT auto generated
Use a function to transmit a CAN frame
This function call is NOT auto generated
Use a function to recieve a CAN frame
This function call is NOT auto generated
First two steps are already done by the STM32CubeMx software. Therefore, we only need to manually implement the last four steps in the sequence above.
Filter configuration
This is the most confusing configuration in this example. I’ll try to explain the simplest filtering scheme here. However, before we start talking about the filtering we need to briefly look at the format of a CAN frame.
In the above image, the CAN frame is the physical data that transmits through the network. Once the bxCAN peripheral receives it, the standard ID, extended ID, Identifier Extension bit, and Remote Transmission Request bit are extracted from the message. These form the identifier of the CAN frame as recognized by the bxCAN implementation.
The identifier register contains the value that the peripheral compares against this received packet.
However, there is another register called the mask register that is used as a bit mask to select what are the must match bits and what are the don’t care bits.
In the simplest filter configuration, we can set the mask register to be 0xFFFFFFFF such that all the bits in the extracted identifier gets compared with all the bits of the filter identifier register. In the diagram below, I attempted to summarize this whole operation using two logic AND operations. If value1 and value2 are equal, that is considered a match in the filter.
Assume the STDID is 0x02 and all other IDs are 0. In that case the maching filter value should be 0x0040 0000. If the filter mask register is 0xFFFF FFFF, then our peripheral would recieve this message without any issue.
With this information let’s have a look at the API.
|
|
FilterIdHigh and FilterIdLow
Filter identifier to match
FilterMaskIdLow and FilterMaskIdHigh
Filter mask to match Since it is 0xFFFF all the bits must match
FilterFIFOAssignment
There are two recieve FIFOs Which FIFO to use In our example, I decided to use FIFO0
FilterBank
There are 28 filter banks Since we only have one identifier this is set to 0
FilterMode
This is set to mask mode because we compare identifier with the bit mask
FilterScale
The full 32 bit register is used for matching If we set this to 16, the identifier mapping is different and it is not covered in this example
FilterActivation
This simply activates the filter
That’s the full filter configuration and this is one of the most complex configurations in the bxCAN peripheral.
Creating the transmit message
Next step is transmitting a message over the wire. This is not that complex, however, let’s have a look at this line by line.
|
|
DLC
Data Length Code is a field in both standard and extended CAN frames. Data Length Code specifies the byte count of data. The maximum value is 8
ExtId
Value of the extended identifier field in the CAN frame
StdId
Value of the standard identifier field in the CAN frame
IDE
Identifier Extensions bit specifies whether the frame is extended or standard Since we set the IDE bit to 0 in the filter, we have to use the CAN_ID_STD here. If we want extended CAN frames, then we have to change the filter as well
RTR
This bit is set when one node requests data from another node. In our case, since we send data, the bit needs to be set to DATA
TransmitGlobalTime
ENABLING this would send the timestamp information in DATA6 and DATA7 bits I don’t intend to use this feature in our example
Finally, let’s have look at the API.
tx_data
Actual user data payload Maximum length is 8, however, I’ve set DLC to 1 which means the length is just 1 byte
tx_mailbox_used
This is returned from the API and stores what mailbox is used to send the data out There are three mailboxes in bxCAN peripeheral The value can be used as an input to some other APIs to get the status of the mailbox, etc.
Waiting for the tx to complete
This is straight forward, I waited in a while loop until all transmit mailboxes are free.
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) != 3) {};
Since the peripheral contains 3 transmit mailboxes, if all three mailboxes are free, we are sure that TX has completed.
Receiving the frame
Since I used silent loopack mode for CAN peripheral, the transmitted frame should end up in the receiver. CAN peripheral automatically receives, filters and stores the incoming messages without any CPU intervention. Which is pretty awesome!
|
|
This code is somewhat self explanatory, in that, it first checks receive fifo level. If it sees a message in the FIFO, it would then read it out. The CAN header and data are read into two separate data structures to make it easier to parse.
CAN module is stuck in INITIALIZATION
I added some additional code to blink an LED everytime we receive a packet. Otherwise, it will be annoying to keep on looking at breakpoints to ensure that everything works.
I compiled the code and programmed the MCU. Since I configured the CAN peripheral in the silent-loopback-mode I thought it would just loopback the message internally and work like a charm. However, soon I found my self in stackoverflow. The problem was, the CAN peripheral is stuck in the INITIALIZATION mode. I had to add breakpoints and look at the return codes of these APIs to find this out.
According to the reference manual, the CAN module requires a total of 11 consecutive recessive bits on the CANRX to move into NORMAL mode. For some reason, it didn’t receive the 11 consecutive recessive bits on the CAN_RX pin. Without trying to figure this out myself I referred this stackoverflow question.
It seems that for some unknown reason we need to configure an internal PULL_UP on the RX pin. This surpirsed me because based on the reference manual, the CAN peripheral must be detached from the physical pin.
After enabling the internal pull up on CAN_RX line, the peripheral successfully changed its state to NORMAL.
Bit timing issue
Once the initialization is sorted, I came across another issue where the receiver couldn’t see my data on the bus. I figured that a few things can go wrong here,
- I might have incorrectly configured the filter
- There is some problem in the default peripheral configuration
I was fairly confident with my understanding of the filter configuration, therefore, this time I started doubting the default peripheral configuration. In order to make sure that my CAN init parameters are correct, I referred one of the CAN examples shipped with the STM HAL library. This example can be found in STM32Cube_FW_F4_V1.26.2\Projects\STM324x9I_EVAL\Examples\CAN\CAN_LoopBack\Src folder.
I noticed that along with the filter configuration it updates the bit timing configuration as well. Till this point, I didn’t look at bit timing at all and it became apparant that I have to look at bit timing configuration now. I had a quick read on the bit timing explanation in the STM reference manual and then here
Bit timing calculation
CAN1 is connected to APB1 clock domain. Default clock for APB1 is 6.25 MHz. Let’s start our caculation there.
Bit timing for default configuration in CubeMX
Bit segment 1 = 1 Bit segment 2 = 1 Prescalar = 16
Let’s calculate the bit rate:
Pre-scalar = 16
Clock used for CAN1 = 6.25e6 / 16
Duration of time quanta for CAN1 = 16 / 6.25e6 = 2560 ns
Time quantas used for a nominal bit = 1 (Synch) + 1 (Bit segment 1) + 1 (Bit segment 2) = 3
I had a look at a calculator I found here which claims that minimum number of time quantas required for CAN is 8. It also claims that 16 is the best value to use, however, I didn’t refer other documents to understand this claim. This could be true, because when I looked at the STM CAN example that I mentioned before, I can see that they use 9 time quantas per nominal bit. Eventhough it is not 16, it is still more than 8. In the default configuration the amount of time quantas is just 3.
CANOpen is one of the most populer CAN communication protocol over CAN. It claims that bit sampling point shall be set to 87.5% of the nominal bit. The sampling point is the boundary between the Bit segment 1 and the bit segment 2. In the default configuration, the sampling point is at the 66.7% of the nominal bit.
Therefore, I decided to change the configuration such that,
- We have 16 time quantas per nominal bit
- The sampling point is at 87.5% of the nominal bit
| SYNC | ...Bit Segment 1... | .....Bit segment 2..... |
^ (Sampling Point)
Updated calculation
Total time quantas = 16
Time quantas till the optimum sampling time = 16 * 87.5% = 14
Time quantas for bit segment 1 = 14 - 1 (synch) = 13
Time quantas for bit segment 2 = 16 - 14 = 2
Resulting nominal bit length = 16 * 2560 ns = 40960 ns
Resulting bit rate = 24.414 kbps
We can reduce CAN1 pre-scalar or increase APB1 clock rate to increase the bit rate. However, for out example I don’t concentrate too much on the bit rate. We can set these parameters in the STM32CubeMX, under CAN configuration.
Success
After all these changes, I could finally receive the CAN frame through my firmware. At this stage, it doesn’t do anything. However, this proves that the CAN peripheral configuration is valid and my understanding of the protocol is correct.
In the next article I’ll expand on this example and introduce interrupts. In this example, I added all this logic in the super loop.
The example is available in the following repository.
Repository: https://github.com/pradeepa-s/pradeepas_content.git
Tag: can_example_1
File path: <repo_root>/006_can_articles/can_example
Thank you!
-Pradeepa