Embedded – Microcontroller – CAN Driver for STM32

Last year, the product developed by the company utilized CAN, and it is still in use now. I would like to recall and organize the previous work for future reference.First, let me present the circuit diagram.Embedded - Microcontroller - CAN Driver for STM32

1. Header File (can_driver.h)

#ifndef __CAN_DRIVER_H
#define __CAN_DRIVER_H
#include "stm32f10x.h"
// CAN frame type definition
typedef enum {
    CAN_FRAME_STD = 0,  // Standard frame (11-bit ID)
    CAN_FRAME_EXT       // Extended frame (29-bit ID)
} CAN_FrameTypeDef;
// CAN transmit data structure
typedef struct {
    CAN_FrameTypeDef FrameType;  // Frame type
    uint32_t ID;                 // Frame ID (only low 11 bits valid for standard frame, low 29 bits valid for extended frame)
    uint8_t DataLen;             // Data length (0~8 bytes)
    uint8_t Data[8];             // Data content
} CAN_TxMsgTypeDef;
// CAN receive data structure
typedef struct {
    CAN_FrameTypeDef FrameType;  // Frame type
    uint32_t ID;                 // Received frame ID
    uint8_t DataLen;             // Data length
    uint8_t Data[8];             // Received data
} CAN_RxMsgTypeDef;
// Function declarations
void CAN_InitConfig(void);                                  // CAN initialization
uint8_t CAN_SendData(CAN_TxMsgTypeDef *txMsg);              // CAN send data
void CAN_ReceiveData(CAN_RxMsgTypeDef *rxMsg);              // Read receive buffer
uint8_t CAN_CheckReceive(void);                             // Check if there is received data
#endif  // __CAN_DRIVER_H

2. Source File (can_driver.c)

#include "can_driver.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_can.h"
#include "stm32f10x_rcc.h"
// Receive buffer (circular buffer, size can be adjusted as needed)
#define CAN_RX_BUF_SIZE 16
static CAN_RxMsgTypeDef CAN_RxBuf[CAN_RX_BUF_SIZE];
static uint8_t CAN_RxHead = 0;
static uint8_t CAN_RxTail = 0;
/**
 * @brief  CAN pin initialization (PA11=CAN_RX, PA12=CAN_TX)
 */
static void CAN_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    // Enable GPIOA and CAN1 clock
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
    // Configure PA11 (CAN_RX) as floating input
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;  // Pull-up input (enhanced anti-interference)
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    // Configure PA12 (CAN_TX) as alternate push-pull output
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;  // Alternate push-pull
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
}
/**
 * @brief  CAN interrupt initialization (receive interrupt)
 */
static void CAN_NVIC_Init(void) {
    NVIC_InitTypeDef NVIC_InitStruct;
    // Enable CAN1 RX0 interrupt (receive FIFO0 full interrupt)
    NVIC_InitStruct.NVIC_IRQChannel = CAN1_RX0_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;  // Preemption priority
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;         // Response priority
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
}
/**
 * @brief  CAN initialization configuration (baud rate 500Kbps, clock APB1=36MHz)
 * @note   Baud rate calculation: CAN_BRP = (APB1 clock / (18 * baud rate)) - 1
 *         Example: 36MHz / (18 * 500Kbps) = 4 → BRP=3 (need to subtract 1)
 */
void CAN_InitConfig(void) {
    CAN_InitTypeDef CAN_InitStruct;
    CAN_FilterInitTypeDef CAN_FilterInitStruct;
    CAN_GPIO_Init();   // Pin initialization
    CAN_NVIC_Init();   // Interrupt initialization
    // CAN mode configuration
    CAN_InitStruct.CAN_TTCM = DISABLE;          // Disable time-triggered communication mode
    CAN_InitStruct.CAN_ABOM = ENABLE;           // Automatic offline management (automatically recover after exception)
    CAN_InitStruct.CAN_AWUM = ENABLE;           // Automatic wake-up mode (automatically wake up after sleep)
    CAN_InitStruct.CAN_NART = DISABLE;          // Enable automatic retransmission (retransmit after send failure)
    CAN_InitStruct.CAN_RFLM = DISABLE;          // Receive FIFO non-locking mode (overwrite old data when full)
    CAN_InitStruct.CAN_TXFP = DISABLE;          // Send priority determined by ID
    CAN_InitStruct.CAN_Mode = CAN_Mode_Normal;  // Normal operation mode (not loopback)
    // CAN_InitStruct.CAN_Mode = CAN_Mode_LoopBack; // Loopback mode (for self-test)
    // Baud rate configuration (500Kbps)
    CAN_InitStruct.CAN_SJW = CAN_SJW_1tq;       // Synchronization jump width: 1 time quantum
    CAN_InitStruct.CAN_BS1 = CAN_BS1_8tq;       // Time segment 1: 8 time quanta
    CAN_InitStruct.CAN_BS2 = CAN_BS2_9tq;       // Time segment 2: 9 time quanta
    CAN_InitStruct.CAN_Prescaler = 3;           // Prescaler (BRP=3 → baud rate 500Kbps)
    CAN_Init(CAN1, &CAN_InitStruct);    // CAN filter configuration (default to receive all IDs, can be modified as needed)
    CAN_FilterInitStruct.CAN_FilterNumber = 0;                  // Filter 0
    CAN_FilterInitStruct.CAN_FilterMode = CAN_FilterMode_IdMask; // Mask mode
    CAN_FilterInitStruct.CAN_FilterScale = CAN_FilterScale_32bit; // 32-bit filter
    CAN_FilterInitStruct.CAN_FilterIdHigh = 0x0000;             // Filter ID high 16 bits (all 0)
    CAN_FilterInitStruct.CAN_FilterIdLow = 0x0000;              // Filter ID low 16 bits (all 0)
    CAN_FilterInitStruct.CAN_FilterMaskIdHigh = 0x0000;         // Mask high 16 bits (all 0 → no filtering)
    CAN_FilterInitStruct.CAN_FilterMaskIdLow = 0x0000;          // Mask low 16 bits (all 0 → no filtering)
    CAN_FilterInitStruct.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; // Store in FIFO0 after matching
    CAN_FilterInitStruct.CAN_FilterActivation = ENABLE;         // Enable filter
    CAN_FilterInit(&CAN_FilterInitStruct);    // Enable CAN1 RX0 interrupt (FIFO0 full interrupt)
    CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
}
/**
 * @brief  CAN send data
 * @param  txMsg: Transmit data structure (frame type, ID, data length, data)
 * @retval 0: Send success; 1: Send failure (mailbox full)
 */
uint8_t CAN_SendData(CAN_TxMsgTypeDef *txMsg) {
    CAN_TxMailBox_TypeDef txMailBox;
    CAN_TxHeaderTypeDef txHeader;
    // Check data length validity
    if (txMsg->DataLen > 8) return 1;
    // Configure send frame header
    txHeader.StdId = (txMsg->FrameType == CAN_FRAME_STD) ? (txMsg->ID & 0x7FF) : 0; // Standard ID (11 bits)
    txHeader.ExtId = (txMsg->FrameType == CAN_FRAME_EXT) ? (txMsg->ID & 0x1FFFFFFF) : 0; // Extended ID (29 bits)
    txHeader.RTR = CAN_RTR_Data;  // Data frame (not remote frame)
    txHeader.IDE = (txMsg->FrameType == CAN_FRAME_EXT) ? CAN_ID_EXT : CAN_ID_STD; // Frame type
    txHeader.DLC = txMsg->DataLen; // Data length
    txHeader.TransmitGlobalTime = DISABLE; // Do not include send timestamp
    // Wait for send mailbox to be free, timeout 10ms
    uint32_t timeout = 10000;
    while ((CAN_GetTxMailboxesFreeLevel(CAN1) == 0) && (timeout-- > 0));
    if (timeout == 0) return 1;
    // Send data
    txMailBox = CAN_AddTxMessage(CAN1, &txHeader, txMsg->Data, NULL);
    // Wait for send completion (optional, depending on whether blocking is needed)
    timeout = 10000;
    while ((CAN_GetTxStatus(CAN1, txMailBox) == CAN_TxStatus_Pending) && (timeout-- > 0));
    if (timeout == 0) return 1;
    return 0;
}
/**
 * @brief  Read data from receive buffer
 * @param  rxMsg: Receive data structure (output parameter)
 * @retval None */
void CAN_ReceiveData(CAN_RxMsgTypeDef *rxMsg) {
    if (CAN_RxHead == CAN_RxTail) return; // No data
    // Read circular buffer
    *rxMsg = CAN_RxBuf[CAN_RxTail];
    CAN_RxTail = (CAN_RxTail + 1) % CAN_RX_BUF_SIZE;
}
/**
 * @brief  Check if there is received data
 * @retval 1: There is data; 0: No data */
uint8_t CAN_CheckReceive(void) {
    return (CAN_RxHead != CAN_RxTail) ? 1 : 0;
}
/**
 * @brief  CAN1 RX0 interrupt service function (receive FIFO0 full interrupt)
 */
void CAN1_RX0_IRQHandler(void) {
    CAN_RxHeaderTypeDef rxHeader;
    uint8_t rxData[8];
    // Read receive frame header and data
    if (CAN_GetRxMessage(CAN1, CAN_RX_FIFO0, &rxHeader, rxData) == SUCCESS) {
        // Fill receive structure
        CAN_RxMsgTypeDef rxMsg;
        rxMsg.FrameType = (rxHeader.IDE == CAN_ID_EXT) ? CAN_FRAME_EXT : CAN_FRAME_STD;
        rxMsg.ID = (rxMsg.FrameType == CAN_FRAME_EXT) ? rxHeader.ExtId : rxHeader.StdId;
        rxMsg.DataLen = rxHeader.DLC;
        memcpy(rxMsg.Data, rxData, rxHeader.DLC);
        // Store in circular buffer (to avoid overflow)
        uint8_t nextHead = (CAN_RxHead + 1) % CAN_RX_BUF_SIZE;
        if (nextHead != CAN_RxTail) {
            CAN_RxBuf[CAN_RxHead] = rxMsg;
            CAN_RxHead = nextHead;
        }
    }
    // Clear interrupt flag
    CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
}

3. Usage Instructions

1. Initialization

Call the CAN initialization function in <span>main.c</span>:

#include "can_driver.h"
int main(void) {
    // System initialization (clock, NVIC, etc., according to your project configuration)
    SystemInit();
    CAN_InitConfig();  // Initialize CAN peripheral
    while (1) {
        // Business logic
    }
}

2. Example of Sending Data, add the following code in the main program for testing, or write it as a function

// Example of sending standard frame (ID=0x123, data=0x01,0x02,0x03)
CAN_TxMsgTypeDef txMsg;
txMsg.FrameType = CAN_FRAME_STD;
txMsg.ID = 0x123;
txMsg.DataLen = 3;
txMsg.Data[0] = 0x01;
txMsg.Data[1] = 0x02;
txMsg.Data[2] = 0x03;
if (CAN_SendData(&txMsg) == 0) {
    // Send success
} else {
    // Send failure
}
// Example of sending extended frame (ID=0x12345678, data=0xAA,0xBB)
txMsg.FrameType = CAN_FRAME_EXT;
txMsg.ID = 0x12345678;
txMsg.DataLen = 2;
txMsg.Data[0] = 0xAA;
txMsg.Data[1] = 0xBB;
CAN_SendData(&txMsg);

3. Example of Receiving Data

In the <span>main</span> function’s infinite loop, check and read received data:

while (1) {
    CAN_RxMsgTypeDef rxMsg;
    if (CAN_CheckReceive() == 1) {  // Check if there is received data
        CAN_ReceiveData(&rxMsg);    // Read data
        // Process received data (example: print ID and data)
        if (rxMsg.FrameType == CAN_FRAME_STD) {
            printf("Standard Frame ID: 0x%X, Length: %d, Data: ", rxMsg.ID, rxMsg.DataLen);
        } else {
            printf("Extended Frame ID: 0x%X, Length: %d, Data: ", rxMsg.ID, rxMsg.DataLen);
        }
        for (uint8_t i=0; i<rxMsg.DataLen; i++) {
            printf("%02X ", rxMsg.Data[i]);
        }
        printf("\r\n");
    }
}

4. Key Parameter Adjustments

  1. Modifying Baud Rate:

  • Baud rate calculation formula:<span>Baud Rate = APB1 Clock / ( (SJW + BS1 + BS2) * BRP )</span>
  • Example: APB1=36MHz, need to configure 250Kbps baud rate:
CAN_InitStruct.CAN_SJW = CAN_SJW_1tq;
CAN_InitStruct.CAN_BS1 = CAN_BS1_8tq;
CAN_InitStruct.CAN_BS2 = CAN_BS2_9tq;
CAN_InitStruct.CAN_Prescaler = 7;  // 36MHz / (18 * 250Kbps) = 8 → BRP=7

2. Filter Configuration:

  • If you need to filter specific IDs, modify the filter ID and mask:
// Only receive data with standard frame ID=0x123
CAN_FilterInitStruct.CAN_FilterIdHigh = (0x123 << 5) & 0xFFFF;  // Standard ID shifted left by 5 bits (to fit 32-bit filter format)
CAN_FilterInitStruct.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStruct.CAN_FilterMaskIdHigh = 0x7FF << 5;  // Mask all 1s (strict match)
CAN_FilterInitStruct.CAN_FilterMaskIdLow = 0x0000;

3. Interrupt Configuration:If you need to disable interrupts, remove the <span>CAN_NVIC_Init()</span> call, and switch to polling mode for receiving:

// Polling mode receive example
if (CAN_MessagePending(CAN1, CAN_RX_FIFO0) > 0) {
    CAN_GetRxMessage(CAN1, CAN_RX_FIFO0, &rxHeader, rxData);
    // Data processing...
}

5. Precautions

  1. Ensure the APB1 clock configuration is correct (maximum APB1 clock for STM32F103 is 36MHz).
  2. A CAN transceiver must be used (I used SN65HVD232), otherwise communication with the CAN bus will not be possible.
  3. Termination resistors: 120Ω termination resistors must be connected at both ends of the CAN bus (to match bus impedance).
  4. Loopback mode: During debugging, you can enable <span>CAN_Mode_LoopBack</span><span> mode for self-transmission and reception without external hardware.</span>
  5. Data length: The length of a CAN data frame is 0~8 bytes; exceeding this will cause send failure.

This driver is suitable for the STM32F103 series; other series (such as F4, L4) can refer to modify pin configurations, clock parameters, and register definitions (core logic remains consistent).

Leave a Comment