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) {}
}
运行与验证
- 用 Keil/STM32CubeIDE 新建 STM32F103 工程,加入上述
main.c,系统时钟配置为 72 MHz(APB1=36 MHz)。 - 编译下载到开发板,无需连接 CAN 收发器或总线。
- 在
ok判定处设断点:命中且rx_id==0x123、rx_len==8、rx[]与tx[]完全一致,即判定通过。 - 若未命中,检查
CAN1->MSR的INAK是否已清零、CAN1->BTR的LBKM位是否为 1、过滤器是否已使能。
常见问题
- 收不到帧:多为过滤器未退出初始化(
FINIT未清)或FA1R未使能。 - 波特率不符:确认 APB1 实际为 36 MHz,否则按
36MHz/(BRP+1)/(1+TS1+1+TS2+1)重算。 - 回环下 TX 引脚仍会输出波形,属正常现象,不影响自测。