一、CAN 总线与 STM32 bxCAN 简介

CAN(Controller Area Network)是一种多主串行总线,采用差分信号传输,抗干扰能力强。STM32 的 bxCAN 控制器支持 CAN 协议 2.0A 和 2.0B,具备 3 个发送邮箱、2 个接收 FIFO(每个深度 3 帧),以及 14 个(部分系列 28 个)硬件过滤器。

过滤器的作用是硬件级筛选接收到的报文,只有通过过滤的数据才会存入 FIFO,极大减轻 CPU 负担。理解过滤器的工作模式,是 CAN 开发的关键。

二、硬件连接与时钟使能

以 STM32F103 为例,CAN1 的引脚通常复用为 PA11(RX)和 PA12(TX)。接线时注意:

  • CAN 收发器(如 TJA1050)的 TXD 接 STM32_TX(PA12),RXD 接 STM32_RX(PA11)。
  • 总线两端需各接 120Ω 终端电阻。
  • 共地,否则通信异常。

使能时钟时,除了 GPIO 和 CAN1,必须同时使能 AFIO 时钟,并将引脚重映射配置为默认(或所需映射)。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);

三、CAN 初始化配置

CAN 初始化主要包括 GPIO 配置、CAN 工作模式、波特率设置。波特率由预分频器(Prescaler)和位时间(BS1、BS2)决定:

  • 位时钟 = APB1 时钟 / Prescaler
  • 波特率 = 位时钟 / (1 + BS1 + BS2)

例如 APB1 为 36MHz,目标 500kbps,可选 Prescaler=4,BS1=9,BS2=6(采样点位置约 62.5%)。

void CAN_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // RX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // TX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void CAN_Init_Default(void)
{
    CAN_InitTypeDef CAN_InitStructure;
    CAN_GPIO_Config();

    CAN_DeInit(CAN1);
    CAN_StructInit(&CAN_InitStructure);

    CAN_InitStructure.CAN_TTCM = DISABLE;        // 时间触发模式
    CAN_InitStructure.CAN_ABOM = ENABLE;         // 自动离线管理
    CAN_InitStructure.CAN_AWUM = ENABLE;         // 自动唤醒
    CAN_InitStructure.CAN_NART = DISABLE;        // 自动重传
    CAN_InitStructure.CAN_RFLM = DISABLE;        // 接收 FIFO 锁定
    CAN_InitStructure.CAN_TXFP = DISABLE;        // 发送优先级由报文 ID 决定
    CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; // 正常模式,调试时可设为 LoopBack

    CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
    CAN_InitStructure.CAN_BS1 = CAN_BS1_9tq;
    CAN_InitStructure.CAN_BS2 = CAN_BS2_6tq;
    CAN_InitStructure.CAN_Prescaler = 4;         // 500kbps @ 36MHz APB1
    CAN_Init(CAN1, &CAN_InitStructure);
}

四、报文发送与接收

发送流程:填写 CanTxMsg 结构体 → 选择发送邮箱 → CAN_Transmit()。需检查发送状态,返回 CAN_TxStatus_Ok 表示已放入邮箱。

uint8_t CAN_Send_Msg(uint32_t id, uint8_t *data, uint8_t len)
{
    CanTxMsg TxMessage;
    uint8_t i;

    TxMessage.StdId = id;          // 标准 ID,如需扩展帧使用 ExtId + IDE
    TxMessage.IDE = CAN_Id_Standard;
    TxMessage.RTR = CAN_RTR_Data;
    TxMessage.DLC = len;
    for (i = 0; i < len; i++)
        TxMessage.Data[i] = data[i];

    return CAN_Transmit(CAN1, &TxMessage); // 返回状态
}

接收有两种方式:查询 FIFO 或中断。初始化时通过 CAN_ITConfig() 开启 CAN_IT_FMP0 中断,并在中断服务函数中读取 CanRxMsg

void USB_LP_CAN1_RX0_IRQHandler(void)  // 中断向量名因系列而异
{
    CanRxMsg RxMessage;

    if (CAN_GetITStatus(CAN1, CAN_IT_FMP0))
    {
        CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
        // 处理 RxMessage.StdId、RxMessage.Data、RxMessage.DLC 等
        CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
    }
}

五、过滤器配置详解

过滤器可配置为列表模式掩码模式,每个过滤器有两个 32 位寄存器:过滤器寄存器 CAN_FxR0 和 CAN_FxR1。

  • 掩码模式:R0 为 ID 掩码,R1 为 ID。对应位为 1 则必须匹配,为 0 则忽略。适合匹配一组 ID。
  • 列表模式:R0 和 R1 分别存放两个独立的 ID,匹配任意一个即通过。适合精确匹配少量 ID。

单掩码示例:只接收 ID 为 0x100 或 0x101(标准帧,扩展帧需设置 CAN_ID_EXT)。

void CAN_Filter_Config(void)
{
    CAN_FilterInitTypeDef CAN_FilterInitStructure;

    CAN_FilterInitStructure.CAN_FilterNumber = 0;
    CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; // 掩码模式
    // ID 掩码:高 16 位对应标准 ID(左移 21 位),只比较 bit[3:0] 为标准 ID 低 4 位
    CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xFFF0; // 标准 ID 高 11 位,低 4 位忽略
    CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;

    CAN_FilterInitStructure.CAN_FilterIdHigh = (0x100 << 5); // 标准 ID 左移 5 位填充
    CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;

    CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;
    CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
    CAN_FilterInit(&CAN_FilterInitStructure);
}

对于列表模式,设置 CAN_FilterMode_IdList,此时 R0 和 R1 分别存储两个 ID。例如只接收 0x100 和 0x200:

CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;
CAN_FilterInitStructure.CAN_FilterIdHigh = (0x100 << 5); // 第一个 ID
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = (0x200 << 5); // 第二个 ID
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;

注意:过滤器寄存器的位布局与 CAN 帧 ID 的对应关系非常容易出错,调试时建议使用逻辑分析仪或官方示例验证。

六、完整代码示例

下面是一个初始化 + 发送 + 接收并回显的整合示例(省略具体回调):

// main.c 关键部分
int main(void)
{
    uint8_t msg[] = {0x01, 0x02, 0x03};

    NVIC_Config();      // 配置 CAN RX0 中断优先级
    CAN_Init_Default();
    CAN_Filter_Config();
    CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);

    while (1)
    {
        CAN_Send_Msg(0x100, msg, 3);
        Delay_ms(100);
    }
}

void USB_LP_CAN1_RX0_IRQHandler(void)
{
    CanRxMsg RxMsg;
    if (CAN_GetITStatus(CAN1, CAN_IT_FMP0))
    {
        CAN_Receive(CAN1, CAN_FIFO0, &RxMsg);
        // 收到后回显相同 ID 和内容
        CAN_Send_Msg(RxMsg.StdId, RxMsg.Data, RxMsg.DLC);
        CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
    }
}

七、注意事项

  • 波特率必须一致:总线所有节点需相同波特率和采样点,否则通信不稳定。
  • 引脚复用:确认所使用系列引脚的默认复用功能,避免和调试口冲突。
  • 过滤器初始化顺序:必须在 CAN_Init() 之后,否则过滤器可能无效。
  • 中断服务函数名:不同型号的 IRQHandler 名称可能不同(如 CAN1_RX0_IRQHandler),请查阅对应启动文件。
  • 发送状态检查CAN_Transmit() 返回值需检查,邮箱满时可能失败。
  • 总线离线处理:启用 ABOM(自动离线管理)可自动恢复,否则需手动恢复。
  • 调试模式建议:先用 CAN_Mode_LoopBack 自测,排除硬件问题后再联机。

掌握以上要点,你已能独立完成 STM32 CAN 的基本收发和过滤配置。实际项目中可进一步探索 CANopen、J1939 等高层协议,但扎实的底层基础永远是第一步。