2. CAN控制器初始化与回环模式自测

任务说明

本案例在 STM32F103(bxCAN)上把 CAN 控制器配置为回环模式(Loopback),发送一帧标准数据帧(ID=0x123,8 字节数据),并在同一控制器内接收回来,验证寄存器配置、位定时与收发路径是否正确。回环模式下 TX 引脚内部短接到 RX,无需外部收发器和总线即可自测。

位定时与关键配置

  • APB1 时钟 36 MHz,目标波特率 500 kbps:Prescaler=4,BS1=15 Tq,BS2=2 Tq,SJW=1 Tq,总 Tq = 1+15+2 = 18,36 MHz/4/18 = 500 kbps。
  • 回环模式:CAN_BTR.LBKM=1,同时保持 INRQM=0(非静默回环)。
  • 过滤器:接收所有帧(掩码全 0),FIFO0。

完整代码(main.c,寄存器版)

#include "stm32f10x.h"

#define CAN_ID   0x123u

static void can_gpio_init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN;
    /* PB8 = CAN_RX 上拉输入, PB9 = CAN_TX 复用推挽 50MHz */
    GPIOB->CRH &= ~(0xFFu << 0);
    GPIOB->CRH |=  (0x8u << 0);          /* PB8: CNF=10, MODE=00 */
    GPIOB->CRH |=  (0xBu << 4);          /* PB9: CNF=10, MODE=11 */
}

static void can_filter_init(void)
{
    CAN1->FMR |= CAN_FMR_FINIT;          /* 进入过滤器初始化 */
    CAN1->FA1R &= ~1u;                   /* 关闭过滤器0 */
    CAN1->FS1R |= 1u;                    /* 32 位掩码模式 */
    CAN1->FM1R &= ~1u;                   /* 掩码模式 */
    CAN1->sFilterRegister[0].FR1 = 0u;   /* ID 全 0 */
    CAN1->sFilterRegister[0].FR2 = 0u;   /* 掩码全 0,接收所有 */
    CAN1->FFA1R &= ~1u;                  /* 关联 FIFO0 */
    CAN1->FA1R |= 1u;                    /* 使能过滤器0 */
    CAN1->FMR &= ~CAN_FMR_FINIT;         /* 退出初始化 */
}

static void can_init_loopback(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_CAN1EN;
    can_gpio_init();

    CAN1->MCR |= CAN_MCR_INRQ;           /* 请求进入初始化 */
    while (!(CAN1->MSR & CAN_MSR_INAK)) {}

    CAN1->MCR &= ~CAN_MCR_SLEEP;
    CAN1->MCR |=  CAN_MCR_NART;          /* 禁止自动重传,便于观察 */
    CAN1->MCR &= ~CAN_MCR_TXFP;

    /* 位定时: BRP=3(即分频4), TS1=14(15Tq), TS2=1(2Tq), SJW=0(1Tq) */
    CAN1->BTR = (3u << 0) | (14u << 16) | (1u << 20) | (0u << 24)
              | CAN_BTR_LBKM;

    can_filter_init();

    CAN1->MCR &= ~CAN_MCR_INRQ;          /* 退出初始化,进入正常模式 */
    while (CAN1->MSR & CAN_MSR_INAK) {}
}

static void can_send_std(uint16_t id, const uint8_t *data, uint8_t len)
{
    while (!(CAN1->TSR & CAN_TSR_TME0)) {}   /* 等待邮箱0空 */
    CAN1->sTxMailBox[0].TDTR = len;
    CAN1->sTxMailBox[0].TDLR = (uint32_t)data[0]
        | ((uint32_t)data[1] << 8)
        | ((uint32_t)data[2] << 16)
        | ((uint32_t)data[3] << 24);
    CAN1->sTxMailBox[0].TDHR = (uint32_t)data[4]
        | ((uint32_t)data[5] << 8)
        | ((uint32_t)data[6] << 16)
        | ((uint32_t)data[7] << 24);
    CAN1->sTxMailBox[0].TIR = ((uint32_t)id << 21) | CAN_TI0R_TXRQ;
}

static int can_recv_std(uint16_t *id, uint8_t *data, uint8_t *len)
{
    if (!(CAN1->RF0R & CAN_RF0R_FMP0)) return 0;
    uint32_t rir = CAN1->sFIFOMailBox[0].RIR;
    uint32_t rdtr = CAN1->sFIFOMailBox[0].RDTR;
    uint32_t rdlr = CAN1->sFIFOMailBox[0].RDLR;
    uint32_t rdhr = CAN1->sFIFOMailBox[0].RDHR;
    CAN1->RF0R |= CAN_RF0R_RFOM0;        /* 释放邮箱 */

    *id  = (uint16_t)((rir >> 21) & 0x7FFu);
    *len = (uint8_t)(rdtr & 0xFu);
    data[0] = (uint8_t)(rdlr);
    data[1] = (uint8_t)(rdlr >> 8);
    data[2] = (uint8_t)(rdlr >> 16);
    data[3] = (uint8_t)(rdlr >> 24);
    data[4] = (uint8_t)(rdhr);
    data[5] = (uint8_t)(rdhr >> 8);
    data[6] = (uint8_t)(rdhr >> 16);
    data[7] = (uint8_t)(rdhr >> 24);
    return 1;
}

int main(void)
{
    uint8_t tx[8] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88};
    uint8_t rx[8] = {0};
    uint16_t rx_id = 0;
    uint8_t  rx_len = 0;

    can_init_loopback();
    can_send_std(CAN_ID, tx, 8);

    /* 等待接收(回环下几乎立即到达) */
    for (volatile uint32_t i = 0; i < 1000000u; i++) {
        if (can_recv_std(&rx_id, rx, &rx_len)) break;
    }

    /* 校验:ID、长度、数据一致则通过 */
    int ok = (rx_id == CAN_ID) && (rx_len == 8);
    for (int i = 0; i < 8; i++) if (rx[i] != tx[i]) ok = 0;

    if (ok) {
        /* 通过:可在此点亮 LED 或置断点 */
        __asm volatile ("nop");
    } else {
        /* 失败:可在此进入死循环便于调试 */
        while (1) {}
    }
    while (1) {}
}

运行与验证

  1. 用 Keil/STM32CubeIDE 新建 STM32F103 工程,加入上述 main.c,系统时钟配置为 72 MHz(APB1=36 MHz)。
  2. 编译下载到开发板,无需连接 CAN 收发器或总线。
  3. 在 ok 判定处设断点:命中且 rx_id==0x123、rx_len==8、rx[] 与 tx[] 完全一致,即判定通过。
  4. 若未命中,检查 CAN1->MSR 的 INAK 是否已清零、CAN1->BTR 的 LBKM 位是否为 1、过滤器是否已使能。

常见问题

  • 收不到帧:多为过滤器未退出初始化(FINIT 未清)或 FA1R 未使能。
  • 波特率不符:确认 APB1 实际为 36 MHz,否则按 36MHz/(BRP+1)/(1+TS1+1+TS2+1) 重算。
  • 回环下 TX 引脚仍会输出波形,属正常现象,不影响自测。