跳转至

CAN总线驱动开发实战

概述

CAN(Controller Area Network,控制器局域网)是一种广泛应用于汽车电子和工业控制领域的串行通信总线。CAN总线具有高可靠性、实时性强、抗干扰能力好等特点,是现代汽车电子系统的核心通信网络。

本教程将深入讲解CAN总线的工作原理和驱动开发方法,通过实战项目带你掌握CAN在嵌入式系统中的应用。

完成本教程后,你将能够:

  • 理解CAN总线的工作原理和协议规范
  • 掌握CAN控制器的配置方法
  • 实现CAN报文的发送和接收
  • 配置和使用CAN过滤器
  • 处理CAN总线错误和异常
  • 实现CAN总线监控和诊断

背景知识

CAN总线基础

CAN总线是一种多主机的串行通信总线,采用差分信号传输,支持多节点通信。

CAN总线特点

优点:
- 实时性强:最高速率1Mbps
- 可靠性高:CRC校验、错误检测
- 抗干扰:差分信号传输
- 多主机:无主从之分
- 灵活性:支持广播和点对点

应用场景:
- 汽车电子:发动机控制、车身控制
- 工业控制:PLC、传感器网络
- 医疗设备:监护仪、诊断设备
- 航空航天:飞行控制系统

CAN总线拓扑

终端电阻(120Ω)                    终端电阻(120Ω)
    |                                    |
    |----[节点1]----[节点2]----[节点3]----|
         CAN_H ━━━━━━━━━━━━━━━━━━━━━━━━━
         CAN_L ━━━━━━━━━━━━━━━━━━━━━━━━━

注意:
- 总线两端需要120Ω终端电阻
- 最大总线长度取决于波特率
- 节点数量最多110个

CAN报文格式

CAN协议定义了两种报文格式:标准帧和扩展帧。

标准帧格式(CAN 2.0A)

|SOF|  ID(11位)  |RTR|IDE|r0| DLC(4位) |  数据(0-8字节)  |  CRC(15位)  |ACK|EOF|
| 1 |    11      | 1 | 1 | 1 |    4     |      0-64       |     16      | 2 | 7 |

字段说明:
- SOF: 帧起始位(Start Of Frame)
- ID: 标识符,决定优先级(越小优先级越高)
- RTR: 远程传输请求位
- IDE: 标识符扩展位(0=标准帧,1=扩展帧)
- r0: 保留位
- DLC: 数据长度代码(0-8字节)
- CRC: 循环冗余校验
- ACK: 应答位
- EOF: 帧结束位

扩展帧格式(CAN 2.0B)

|SOF|  ID(11位)  |SRR|IDE|  ID(18位)  |RTR|r1|r0| DLC |  数据  | CRC |ACK|EOF|
| 1 |    11      | 1 | 1 |    18      | 1 | 1| 1|  4  |  0-64  | 16  | 2 | 7 |

扩展帧特点:
- 标识符29位(11位基本ID + 18位扩展ID)
- 兼容标准帧
- 提供更多的ID空间

STM32 CAN控制器

STM32F4系列集成了bxCAN控制器(Basic Extended CAN),支持CAN 2.0A和2.0B协议。

bxCAN架构

┌─────────────────────────────────────┐
│         bxCAN控制器                  │
├─────────────────────────────────────┤
│  发送邮箱 (3个)                      │
│  ├─ 邮箱0 (最高优先级)               │
│  ├─ 邮箱1                            │
│  └─ 邮箱2 (最低优先级)               │
├─────────────────────────────────────┤
│  接收FIFO (2个)                      │
│  ├─ FIFO0 (3个报文深度)              │
│  └─ FIFO1 (3个报文深度)              │
├─────────────────────────────────────┤
│  过滤器组 (28个)                     │
│  ├─ 标识符列表模式                   │
│  └─ 标识符屏蔽模式                   │
└─────────────────────────────────────┘

CAN控制器特性

特性 说明
波特率 最高1Mbps
发送邮箱 3个,支持优先级
接收FIFO 2个,各3个报文深度
过滤器 28个,可配置
时间戳 支持16位时间戳
自测试 支持环回模式

CAN波特率计算

CAN波特率由位时序参数决定,需要根据系统时钟精确配置。

位时序结构

一个位时间 = 同步段(SS) + 时间段1(BS1) + 时间段2(BS2)

|<────────────── 1 Bit Time ──────────────>|
|  SS  |<──── BS1 ────>|<──── BS2 ────>|
| 1TQ  |   1-16 TQ     |   1-8 TQ      |
        ↑采样点

TQ = 时间量子 = (BRP + 1) / APB1时钟
波特率 = APB1时钟 / [(BRP + 1) × (1 + BS1 + BS2)]

常用波特率配置(APB1 = 42MHz):

波特率 BRP BS1 BS2 采样点
1Mbps 2 13 2 87.5%
500Kbps 5 13 2 87.5%
250Kbps 11 13 2 87.5%
125Kbps 23 13 2 87.5%
100Kbps 29 13 2 87.5%

环境准备

硬件要求

  • STM32F4系列开发板(如STM32F407VET6)
  • CAN收发器模块(如TJA1050、SN65HVD230)
  • CAN总线终端电阻(120Ω × 2)
  • USB转CAN分析仪(可选,用于调试)
  • 示波器(可选,用于信号分析)

软件要求

  • Keil MDK 5.x 或 STM32CubeIDE
  • STM32F4 HAL库或标准外设库
  • CAN总线分析软件(如CANoe、PCAN-View)

硬件连接

STM32F407开发板连接:

CAN1:
- PB8  (CAN1_RX) ---> CAN收发器 RXD
- PB9  (CAN1_TX) ---> CAN收发器 TXD

CAN2:
- PB12 (CAN2_RX) ---> CAN收发器 RXD
- PB13 (CAN2_TX) ---> CAN收发器 TXD

CAN收发器连接:
- CANH ---> CAN总线 H
- CANL ---> CAN总线 L
- VCC  ---> 5V或3.3V(根据收发器型号)
- GND  ---> GND

总线终端:
- 总线两端各接一个120Ω电阻(CANH到CANL)

注意事项: - 确保CAN收发器供电正确 - 总线两端必须有终端电阻 - 检查CAN_H和CAN_L不要接反 - 总线长度和波特率要匹配

核心内容

步骤1:CAN基本配置

首先学习如何初始化和配置CAN控制器。

#include "stm32f4xx.h"

/**
 * @brief  CAN配置结构体
 */
typedef struct {
    uint32_t prescaler;      // 预分频器 (1-1024)
    uint32_t mode;           // 工作模式
    uint32_t sjw;            // 重新同步跳跃宽度 (1-4)
    uint32_t bs1;            // 时间段1 (1-16)
    uint32_t bs2;            // 时间段2 (1-8)
    uint8_t  ttcm;           // 时间触发通信模式
    uint8_t  abom;           // 自动离线管理
    uint8_t  awum;           // 自动唤醒模式
    uint8_t  nart;           // 非自动重传
    uint8_t  rflm;           // 接收FIFO锁定模式
    uint8_t  txfp;           // 发送FIFO优先级
} CAN_Config_t;

/**
 * @brief  初始化CAN1 GPIO
 * @param  无
 * @retval 无
 */
void CAN1_GPIO_Init(void) {
    // 使能GPIOB时钟
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;

    // 配置PB8(RX)和PB9(TX)为复用功能
    GPIOB->MODER &= ~(GPIO_MODER_MODER8 | GPIO_MODER_MODER9);
    GPIOB->MODER |= (GPIO_MODER_MODER8_1 | GPIO_MODER_MODER9_1);

    // 配置为推挽输出,高速
    GPIOB->OTYPER &= ~(GPIO_OTYPER_OT_8 | GPIO_OTYPER_OT_9);
    GPIOB->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR8 | GPIO_OSPEEDER_OSPEEDR9);

    // 配置为上拉
    GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR8 | GPIO_PUPDR_PUPDR9);
    GPIOB->PUPDR |= (GPIO_PUPDR_PUPDR8_0 | GPIO_PUPDR_PUPDR9_0);

    // 配置复用功能为CAN1 (AF9)
    GPIOB->AFR[1] &= ~(0xFF << 0);  // PB8和PB9在AFR[1]
    GPIOB->AFR[1] |= (9 << 0) | (9 << 4);
}

/**
 * @brief  初始化CAN1控制器
 * @param  config: CAN配置结构体指针
 * @retval 0=成功,1=失败
 */
uint8_t CAN1_Init(CAN_Config_t *config) {
    uint32_t timeout = 0;

    // 1. 使能CAN1时钟
    RCC->APB1ENR |= RCC_APB1ENR_CAN1EN;

    // 2. 请求进入初始化模式
    CAN1->MCR |= CAN_MCR_INRQ;

    // 等待进入初始化模式
    timeout = 0;
    while (!(CAN1->MSR & CAN_MSR_INAK)) {
        if (++timeout > 10000) {
            return 1;  // 超时
        }
    }

    // 3. 退出睡眠模式
    CAN1->MCR &= ~CAN_MCR_SLEEP;

    // 4. 配置CAN参数
    CAN1->MCR &= ~(CAN_MCR_TTCM | CAN_MCR_ABOM | CAN_MCR_AWUM |
                   CAN_MCR_NART | CAN_MCR_RFLM | CAN_MCR_TXFP);

    if (config->ttcm) CAN1->MCR |= CAN_MCR_TTCM;
    if (config->abom) CAN1->MCR |= CAN_MCR_ABOM;
    if (config->awum) CAN1->MCR |= CAN_MCR_AWUM;
    if (config->nart) CAN1->MCR |= CAN_MCR_NART;
    if (config->rflm) CAN1->MCR |= CAN_MCR_RFLM;
    if (config->txfp) CAN1->MCR |= CAN_MCR_TXFP;

    // 5. 配置位时序
    CAN1->BTR = 0;
    CAN1->BTR |= (config->mode << 30);                    // 工作模式
    CAN1->BTR |= ((config->sjw - 1) << 24);              // SJW
    CAN1->BTR |= ((config->bs1 - 1) << 16);              // BS1
    CAN1->BTR |= ((config->bs2 - 1) << 20);              // BS2
    CAN1->BTR |= (config->prescaler - 1);                 // 预分频器

    // 6. 请求退出初始化模式
    CAN1->MCR &= ~CAN_MCR_INRQ;

    // 等待退出初始化模式
    timeout = 0;
    while (CAN1->MSR & CAN_MSR_INAK) {
        if (++timeout > 10000) {
            return 1;  // 超时
        }
    }

    return 0;  // 成功
}

// CAN工作模式定义
#define CAN_MODE_NORMAL     (0 << 30)  // 正常模式
#define CAN_MODE_LOOPBACK   (1 << 30)  // 环回模式
#define CAN_MODE_SILENT     (2 << 30)  // 静默模式
#define CAN_MODE_LOOPBACK_SILENT (3 << 30)  // 环回静默模式

/**
 * @brief  配置CAN1为500Kbps(APB1=42MHz)
 * @param  无
 * @retval 无
 */
void CAN1_Config_500K(void) {
    CAN_Config_t config = {
        .prescaler = 6,           // 预分频器
        .mode = CAN_MODE_NORMAL,  // 正常模式
        .sjw = 1,                 // SJW = 1
        .bs1 = 13,                // BS1 = 13
        .bs2 = 2,                 // BS2 = 2
        .ttcm = 0,                // 禁用时间触发
        .abom = 1,                // 使能自动离线管理
        .awum = 0,                // 禁用自动唤醒
        .nart = 0,                // 使能自动重传
        .rflm = 0,                // 接收FIFO非锁定
        .txfp = 0                 // 发送优先级由ID决定
    };

    CAN1_GPIO_Init();
    CAN1_Init(&config);
}

配置参数说明

  1. 工作模式
  2. 正常模式:正常收发
  3. 环回模式:自发自收,用于测试
  4. 静默模式:只接收,不发送
  5. 环回静默模式:自发自收,不影响总线

  6. 自动离线管理(ABOM)

  7. 使能后,总线错误恢复自动进行
  8. 禁用时,需要软件干预恢复

  9. 自动重传(NART)

  10. 禁用时,发送失败自动重传
  11. 使能时,只发送一次

步骤2:CAN过滤器配置

CAN过滤器用于筛选接收的报文,只接收感兴趣的ID。

/**
 * @brief  CAN过滤器配置结构体
 */
typedef struct {
    uint8_t  filter_num;      // 过滤器编号 (0-27)
    uint8_t  filter_mode;     // 过滤器模式(标识符列表/屏蔽)
    uint8_t  filter_scale;    // 过滤器位宽(16位/32位)
    uint8_t  filter_fifo;     // 关联的FIFO(0或1)
    uint8_t  filter_enable;   // 过滤器使能
    uint32_t filter_id_high;  // 标识符高16位
    uint32_t filter_id_low;   // 标识符低16位
    uint32_t filter_mask_high;// 屏蔽码高16位
    uint32_t filter_mask_low; // 屏蔽码低16位
} CAN_Filter_t;

/**
 * @brief  配置CAN过滤器
 * @param  filter: 过滤器配置结构体指针
 * @retval 无
 */
void CAN_FilterConfig(CAN_Filter_t *filter) {
    // 1. 进入过滤器初始化模式
    CAN1->FMR |= CAN_FMR_FINIT;

    // 2. 禁用过滤器
    CAN1->FA1R &= ~(1 << filter->filter_num);

    // 3. 配置过滤器模式
    if (filter->filter_mode == 0) {
        // 标识符屏蔽模式
        CAN1->FM1R &= ~(1 << filter->filter_num);
    } else {
        // 标识符列表模式
        CAN1->FM1R |= (1 << filter->filter_num);
    }

    // 4. 配置过滤器位宽
    if (filter->filter_scale == 0) {
        // 16位位宽
        CAN1->FS1R &= ~(1 << filter->filter_num);
    } else {
        // 32位位宽
        CAN1->FS1R |= (1 << filter->filter_num);
    }

    // 5. 配置过滤器关联的FIFO
    if (filter->filter_fifo == 0) {
        CAN1->FFA1R &= ~(1 << filter->filter_num);
    } else {
        CAN1->FFA1R |= (1 << filter->filter_num);
    }

    // 6. 配置过滤器寄存器
    CAN1->sFilterRegister[filter->filter_num].FR1 = 
        (filter->filter_id_high << 16) | filter->filter_id_low;
    CAN1->sFilterRegister[filter->filter_num].FR2 = 
        (filter->filter_mask_high << 16) | filter->filter_mask_low;

    // 7. 使能过滤器
    if (filter->filter_enable) {
        CAN1->FA1R |= (1 << filter->filter_num);
    }

    // 8. 退出过滤器初始化模式
    CAN1->FMR &= ~CAN_FMR_FINIT;
}

/**
 * @brief  配置过滤器接收指定标准ID
 * @param  filter_num: 过滤器编号
 * @param  std_id: 标准ID (11位)
 * @param  fifo: FIFO编号 (0或1)
 * @retval 无
 */
void CAN_Filter_StdID(uint8_t filter_num, uint16_t std_id, uint8_t fifo) {
    CAN_Filter_t filter = {
        .filter_num = filter_num,
        .filter_mode = 0,  // 屏蔽模式
        .filter_scale = 1, // 32位
        .filter_fifo = fifo,
        .filter_enable = 1,
        // 标准帧ID在高16位的高11位
        .filter_id_high = (std_id << 5),
        .filter_id_low = 0,
        // 屏蔽码:只比较ID,不比较其他位
        .filter_mask_high = (0x7FF << 5),
        .filter_mask_low = 0
    };

    CAN_FilterConfig(&filter);
}

/**
 * @brief  配置过滤器接收指定扩展ID
 * @param  filter_num: 过滤器编号
 * @param  ext_id: 扩展ID (29位)
 * @param  fifo: FIFO编号 (0或1)
 * @retval 无
 */
void CAN_Filter_ExtID(uint8_t filter_num, uint32_t ext_id, uint8_t fifo) {
    CAN_Filter_t filter = {
        .filter_num = filter_num,
        .filter_mode = 0,  // 屏蔽模式
        .filter_scale = 1, // 32位
        .filter_fifo = fifo,
        .filter_enable = 1,
        // 扩展帧ID在32位中的排列
        .filter_id_high = (ext_id >> 13) & 0xFFFF,
        .filter_id_low = ((ext_id << 3) & 0xFFF8) | 0x04,  // IDE=1
        // 屏蔽码:比较所有ID位和IDE位
        .filter_mask_high = 0xFFFF,
        .filter_mask_low = 0xFFFC
    };

    CAN_FilterConfig(&filter);
}

/**
 * @brief  配置过滤器接收所有报文
 * @param  filter_num: 过滤器编号
 * @param  fifo: FIFO编号 (0或1)
 * @retval 无
 */
void CAN_Filter_AcceptAll(uint8_t filter_num, uint8_t fifo) {
    CAN_Filter_t filter = {
        .filter_num = filter_num,
        .filter_mode = 0,  // 屏蔽模式
        .filter_scale = 1, // 32位
        .filter_fifo = fifo,
        .filter_enable = 1,
        .filter_id_high = 0,
        .filter_id_low = 0,
        // 屏蔽码全0:接收所有报文
        .filter_mask_high = 0,
        .filter_mask_low = 0
    };

    CAN_FilterConfig(&filter);
}

过滤器工作原理

屏蔽模式:
接收ID & 屏蔽码 == 过滤器ID & 屏蔽码

示例:接收ID 0x123-0x12F
过滤器ID:  0x120 (0001 0010 0000)
屏蔽码:    0x7F0 (0111 1111 0000)
结果:只比较高8位,低4位任意

列表模式:
接收ID == 过滤器ID1 或 接收ID == 过滤器ID2

示例:只接收0x123和0x456
过滤器ID1: 0x123
过滤器ID2: 0x456

步骤3:CAN报文发送

实现CAN报文的发送功能。

/**
 * @brief  CAN报文结构体
 */
typedef struct {
    uint32_t id;          // 标识符
    uint8_t  ide;         // 标识符类型(0=标准,1=扩展)
    uint8_t  rtr;         // 帧类型(0=数据帧,1=远程帧)
    uint8_t  dlc;         // 数据长度(0-8)
    uint8_t  data[8];     // 数据
} CAN_Message_t;

/**
 * @brief  发送CAN报文
 * @param  msg: CAN报文指针
 * @retval 邮箱号(0-2),0xFF=失败
 */
uint8_t CAN_Transmit(CAN_Message_t *msg) {
    uint8_t mailbox = 0xFF;

    // 1. 查找空闲邮箱
    if (CAN1->TSR & CAN_TSR_TME0) {
        mailbox = 0;
    } else if (CAN1->TSR & CAN_TSR_TME1) {
        mailbox = 1;
    } else if (CAN1->TSR & CAN_TSR_TME2) {
        mailbox = 2;
    } else {
        return 0xFF;  // 没有空闲邮箱
    }

    // 2. 配置标识符
    if (msg->ide == 0) {
        // 标准帧
        CAN1->sTxMailBox[mailbox].TIR = (msg->id << 21);
    } else {
        // 扩展帧
        CAN1->sTxMailBox[mailbox].TIR = (msg->id << 3) | CAN_TI0R_IDE;
    }

    // 3. 配置帧类型
    if (msg->rtr) {
        CAN1->sTxMailBox[mailbox].TIR |= CAN_TI0R_RTR;
    }

    // 4. 配置数据长度
    CAN1->sTxMailBox[mailbox].TDTR = msg->dlc & 0x0F;

    // 5. 填充数据
    CAN1->sTxMailBox[mailbox].TDLR = 
        (msg->data[3] << 24) | (msg->data[2] << 16) |
        (msg->data[1] << 8)  | (msg->data[0]);
    CAN1->sTxMailBox[mailbox].TDHR = 
        (msg->data[7] << 24) | (msg->data[6] << 16) |
        (msg->data[5] << 8)  | (msg->data[4]);

    // 6. 请求发送
    CAN1->sTxMailBox[mailbox].TIR |= CAN_TI0R_TXRQ;

    return mailbox;
}

/**
 * @brief  等待发送完成
 * @param  mailbox: 邮箱号(0-2)
 * @param  timeout: 超时时间(毫秒)
 * @retval 0=成功,1=超时,2=失败
 */
uint8_t CAN_WaitTxComplete(uint8_t mailbox, uint32_t timeout) {
    uint32_t start = GetTick();
    uint32_t flag = 0;

    // 等待发送完成标志
    switch (mailbox) {
        case 0: flag = CAN_TSR_RQCP0; break;
        case 1: flag = CAN_TSR_RQCP1; break;
        case 2: flag = CAN_TSR_RQCP2; break;
        default: return 2;
    }

    while (!(CAN1->TSR & flag)) {
        if (GetTick() - start > timeout) {
            return 1;  // 超时
        }
    }

    // 检查是否发送成功
    uint32_t ok_flag = 0;
    switch (mailbox) {
        case 0: ok_flag = CAN_TSR_TXOK0; break;
        case 1: ok_flag = CAN_TSR_TXOK1; break;
        case 2: ok_flag = CAN_TSR_TXOK2; break;
    }

    if (CAN1->TSR & ok_flag) {
        return 0;  // 成功
    } else {
        return 2;  // 失败
    }
}

/**
 * @brief  取消发送
 * @param  mailbox: 邮箱号(0-2)
 * @retval 无
 */
void CAN_AbortTransmit(uint8_t mailbox) {
    switch (mailbox) {
        case 0: CAN1->TSR |= CAN_TSR_ABRQ0; break;
        case 1: CAN1->TSR |= CAN_TSR_ABRQ1; break;
        case 2: CAN1->TSR |= CAN_TSR_ABRQ2; break;
    }
}

/**
 * @brief  发送标准数据帧(简化接口)
 * @param  id: 标准ID (11位)
 * @param  data: 数据指针
 * @param  len: 数据长度(0-8)
 * @retval 0=成功,1=失败
 */
uint8_t CAN_SendStdData(uint16_t id, const uint8_t *data, uint8_t len) {
    CAN_Message_t msg = {
        .id = id,
        .ide = 0,  // 标准帧
        .rtr = 0,  // 数据帧
        .dlc = len
    };

    // 复制数据
    for (int i = 0; i < len && i < 8; i++) {
        msg.data[i] = data[i];
    }

    // 发送
    uint8_t mailbox = CAN_Transmit(&msg);
    if (mailbox == 0xFF) {
        return 1;  // 失败
    }

    // 等待发送完成
    return CAN_WaitTxComplete(mailbox, 100);
}

/**
 * @brief  发送扩展数据帧(简化接口)
 * @param  id: 扩展ID (29位)
 * @param  data: 数据指针
 * @param  len: 数据长度(0-8)
 * @retval 0=成功,1=失败
 */
uint8_t CAN_SendExtData(uint32_t id, const uint8_t *data, uint8_t len) {
    CAN_Message_t msg = {
        .id = id,
        .ide = 1,  // 扩展帧
        .rtr = 0,  // 数据帧
        .dlc = len
    };

    // 复制数据
    for (int i = 0; i < len && i < 8; i++) {
        msg.data[i] = data[i];
    }

    // 发送
    uint8_t mailbox = CAN_Transmit(&msg);
    if (mailbox == 0xFF) {
        return 1;  // 失败
    }

    // 等待发送完成
    return CAN_WaitTxComplete(mailbox, 100);
}

使用示例

int main(void) {
    SystemInit();
    UART1_Init(115200);
    CAN1_Config_500K();
    CAN_Filter_AcceptAll(0, 0);

    printf("CAN TX Test\r\n");

    uint8_t data[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};

    while (1) {
        // 发送标准帧
        if (CAN_SendStdData(0x123, data, 8) == 0) {
            printf("Send OK\r\n");
        } else {
            printf("Send Failed\r\n");
        }

        // 更新数据
        data[0]++;

        Delay_Ms(1000);
    }
}

步骤4:CAN报文接收

实现CAN报文的接收功能,支持中断和轮询两种方式。

/**
 * @brief  从FIFO接收报文(轮询方式)
 * @param  fifo: FIFO编号(0或1)
 * @param  msg: 接收报文缓冲区
 * @retval 0=成功,1=FIFO空
 */
uint8_t CAN_Receive(uint8_t fifo, CAN_Message_t *msg) {
    uint32_t fmp_flag, rfr_flag;

    // 选择FIFO
    if (fifo == 0) {
        fmp_flag = CAN_RF0R_FMP0;
        rfr_flag = CAN_RF0R_RFOM0;
    } else {
        fmp_flag = CAN_RF1R_FMP1;
        rfr_flag = CAN_RF1R_RFOM1;
    }

    // 检查FIFO是否有报文
    if (fifo == 0) {
        if ((CAN1->RF0R & fmp_flag) == 0) {
            return 1;  // FIFO空
        }
    } else {
        if ((CAN1->RF1R & fmp_flag) == 0) {
            return 1;  // FIFO空
        }
    }

    // 读取标识符
    uint32_t rir = CAN1->sFIFOMailBox[fifo].RIR;
    if (rir & CAN_RI0R_IDE) {
        // 扩展帧
        msg->ide = 1;
        msg->id = (rir >> 3) & 0x1FFFFFFF;
    } else {
        // 标准帧
        msg->ide = 0;
        msg->id = (rir >> 21) & 0x7FF;
    }

    // 读取帧类型
    msg->rtr = (rir & CAN_RI0R_RTR) ? 1 : 0;

    // 读取数据长度
    msg->dlc = CAN1->sFIFOMailBox[fifo].RDTR & 0x0F;

    // 读取数据
    uint32_t rdlr = CAN1->sFIFOMailBox[fifo].RDLR;
    uint32_t rdhr = CAN1->sFIFOMailBox[fifo].RDHR;

    msg->data[0] = (rdlr >> 0) & 0xFF;
    msg->data[1] = (rdlr >> 8) & 0xFF;
    msg->data[2] = (rdlr >> 16) & 0xFF;
    msg->data[3] = (rdlr >> 24) & 0xFF;
    msg->data[4] = (rdhr >> 0) & 0xFF;
    msg->data[5] = (rdhr >> 8) & 0xFF;
    msg->data[6] = (rdhr >> 16) & 0xFF;
    msg->data[7] = (rdhr >> 24) & 0xFF;

    // 释放FIFO
    if (fifo == 0) {
        CAN1->RF0R |= rfr_flag;
    } else {
        CAN1->RF1R |= rfr_flag;
    }

    return 0;  // 成功
}

/**
 * @brief  获取FIFO中的报文数量
 * @param  fifo: FIFO编号(0或1)
 * @retval 报文数量(0-3)
 */
uint8_t CAN_GetRxCount(uint8_t fifo) {
    if (fifo == 0) {
        return CAN1->RF0R & CAN_RF0R_FMP0;
    } else {
        return CAN1->RF1R & CAN_RF1R_FMP1;
    }
}

// 接收缓冲区
#define CAN_RX_BUFFER_SIZE 32
CAN_Message_t can_rx_buffer[CAN_RX_BUFFER_SIZE];
volatile uint8_t can_rx_head = 0;
volatile uint8_t can_rx_tail = 0;

/**
 * @brief  配置CAN接收中断
 * @param  fifo: FIFO编号(0或1)
 * @retval 无
 */
void CAN_EnableRxIT(uint8_t fifo) {
    if (fifo == 0) {
        // 使能FIFO0接收中断
        CAN1->IER |= CAN_IER_FMPIE0;
        NVIC_SetPriority(CAN1_RX0_IRQn, 2);
        NVIC_EnableIRQ(CAN1_RX0_IRQn);
    } else {
        // 使能FIFO1接收中断
        CAN1->IER |= CAN_IER_FMPIE1;
        NVIC_SetPriority(CAN1_RX1_IRQn, 2);
        NVIC_EnableIRQ(CAN1_RX1_IRQn);
    }
}

/**
 * @brief  CAN1 RX0中断服务函数
 * @param  无
 * @retval 无
 */
void CAN1_RX0_IRQHandler(void) {
    CAN_Message_t msg;

    // 接收报文
    if (CAN_Receive(0, &msg) == 0) {
        // 存入缓冲区
        can_rx_buffer[can_rx_head] = msg;
        can_rx_head = (can_rx_head + 1) % CAN_RX_BUFFER_SIZE;

        // 检查缓冲区溢出
        if (can_rx_head == can_rx_tail) {
            can_rx_tail = (can_rx_tail + 1) % CAN_RX_BUFFER_SIZE;
        }
    }
}

/**
 * @brief  从缓冲区读取报文
 * @param  msg: 接收报文缓冲区
 * @retval 0=成功,1=缓冲区空
 */
uint8_t CAN_ReadBuffer(CAN_Message_t *msg) {
    if (can_rx_head == can_rx_tail) {
        return 1;  // 缓冲区空
    }

    *msg = can_rx_buffer[can_rx_tail];
    can_rx_tail = (can_rx_tail + 1) % CAN_RX_BUFFER_SIZE;

    return 0;  // 成功
}

/**
 * @brief  打印CAN报文
 * @param  msg: CAN报文指针
 * @retval 无
 */
void CAN_PrintMessage(CAN_Message_t *msg) {
    // 打印ID
    if (msg->ide == 0) {
        printf("ID: 0x%03X (Std) ", msg->id);
    } else {
        printf("ID: 0x%08X (Ext) ", msg->id);
    }

    // 打印类型
    if (msg->rtr) {
        printf("RTR ");
    } else {
        printf("Data ");
    }

    // 打印长度
    printf("DLC: %d ", msg->dlc);

    // 打印数据
    printf("Data: ");
    for (int i = 0; i < msg->dlc; i++) {
        printf("%02X ", msg->data[i]);
    }
    printf("\r\n");
}

使用示例

int main(void) {
    SystemInit();
    UART1_Init(115200);
    CAN1_Config_500K();
    CAN_Filter_AcceptAll(0, 0);
    CAN_EnableRxIT(0);

    printf("CAN RX Test\r\n");

    CAN_Message_t msg;

    while (1) {
        // 从缓冲区读取报文
        if (CAN_ReadBuffer(&msg) == 0) {
            printf("Received: ");
            CAN_PrintMessage(&msg);
        }

        Delay_Ms(10);
    }
}

步骤5:CAN错误处理

实现CAN总线错误检测和处理功能。

/**
 * @brief  CAN错误类型
 */
typedef enum {
    CAN_ERROR_NONE = 0,      // 无错误
    CAN_ERROR_STUFF,         // 填充错误
    CAN_ERROR_FORM,          // 格式错误
    CAN_ERROR_ACK,           // 应答错误
    CAN_ERROR_BIT_RECESSIVE, // 隐性位错误
    CAN_ERROR_BIT_DOMINANT,  // 显性位错误
    CAN_ERROR_CRC,           // CRC错误
    CAN_ERROR_SOFTWARE       // 软件错误
} CAN_Error_t;

/**
 * @brief  获取CAN错误状态
 * @param  无
 * @retval 错误代码
 */
uint32_t CAN_GetError(void) {
    return CAN1->ESR;
}

/**
 * @brief  获取发送错误计数器
 * @param  无
 * @retval 错误计数(0-255)
 */
uint8_t CAN_GetTxErrorCount(void) {
    return (CAN1->ESR >> 16) & 0xFF;
}

/**
 * @brief  获取接收错误计数器
 * @param  无
 * @retval 错误计数(0-255)
 */
uint8_t CAN_GetRxErrorCount(void) {
    return (CAN1->ESR >> 24) & 0xFF;
}

/**
 * @brief  获取最后错误代码
 * @param  无
 * @retval 错误类型
 */
CAN_Error_t CAN_GetLastError(void) {
    return (CAN1->ESR >> 4) & 0x07;
}

/**
 * @brief  检查总线状态
 * @param  无
 * @retval 0=正常,1=错误被动,2=离线
 */
uint8_t CAN_GetBusStatus(void) {
    uint32_t esr = CAN1->ESR;

    if (esr & CAN_ESR_BOFF) {
        return 2;  // 离线
    } else if (esr & CAN_ESR_EPVF) {
        return 1;  // 错误被动
    } else {
        return 0;  // 正常(错误主动)
    }
}

/**
 * @brief  清除错误标志
 * @param  无
 * @retval 无
 */
void CAN_ClearError(void) {
    // 清除LEC(最后错误代码)
    CAN1->ESR &= ~CAN_ESR_LEC;
}

/**
 * @brief  使能CAN错误中断
 * @param  无
 * @retval 无
 */
void CAN_EnableErrorIT(void) {
    // 使能错误中断
    CAN1->IER |= CAN_IER_ERRIE;
    CAN1->IER |= CAN_IER_LECIE;
    CAN1->IER |= CAN_IER_BOFIE;
    CAN1->IER |= CAN_IER_EPVIE;
    CAN1->IER |= CAN_IER_EWGIE;

    // 配置NVIC
    NVIC_SetPriority(CAN1_SCE_IRQn, 2);
    NVIC_EnableIRQ(CAN1_SCE_IRQn);
}

/**
 * @brief  CAN1 SCE中断服务函数(状态改变和错误)
 * @param  无
 * @retval 无
 */
void CAN1_SCE_IRQHandler(void) {
    uint32_t msr = CAN1->MSR;
    uint32_t esr = CAN1->ESR;

    // 错误警告
    if (msr & CAN_MSR_ERRI) {
        CAN_Error_t error = CAN_GetLastError();

        switch (error) {
            case CAN_ERROR_STUFF:
                printf("CAN Error: Stuff Error\r\n");
                break;
            case CAN_ERROR_FORM:
                printf("CAN Error: Form Error\r\n");
                break;
            case CAN_ERROR_ACK:
                printf("CAN Error: ACK Error\r\n");
                break;
            case CAN_ERROR_BIT_RECESSIVE:
                printf("CAN Error: Bit Recessive Error\r\n");
                break;
            case CAN_ERROR_BIT_DOMINANT:
                printf("CAN Error: Bit Dominant Error\r\n");
                break;
            case CAN_ERROR_CRC:
                printf("CAN Error: CRC Error\r\n");
                break;
            default:
                break;
        }

        // 清除错误标志
        CAN1->MSR |= CAN_MSR_ERRI;
        CAN_ClearError();
    }

    // 错误被动
    if (esr & CAN_ESR_EPVF) {
        printf("CAN: Error Passive\r\n");
        printf("TEC: %d, REC: %d\r\n", 
               CAN_GetTxErrorCount(), CAN_GetRxErrorCount());
    }

    // 离线
    if (esr & CAN_ESR_BOFF) {
        printf("CAN: Bus Off\r\n");

        // 如果使能了自动离线管理,会自动恢复
        // 否则需要软件复位
        if (!(CAN1->MCR & CAN_MCR_ABOM)) {
            // 软件复位CAN
            CAN1->MCR |= CAN_MCR_RESET;
            CAN1->MCR &= ~CAN_MCR_RESET;
        }
    }
}

/**
 * @brief  打印CAN状态信息
 * @param  无
 * @retval 无
 */
void CAN_PrintStatus(void) {
    uint8_t status = CAN_GetBusStatus();
    uint8_t tec = CAN_GetTxErrorCount();
    uint8_t rec = CAN_GetRxErrorCount();

    printf("CAN Status: ");
    switch (status) {
        case 0:
            printf("Error Active\r\n");
            break;
        case 1:
            printf("Error Passive\r\n");
            break;
        case 2:
            printf("Bus Off\r\n");
            break;
    }

    printf("TEC: %d, REC: %d\r\n", tec, rec);
}

CAN错误状态机

错误主动 (Error Active)
  TEC < 128 && REC < 128
  ↓ 错误增加
错误被动 (Error Passive)
  TEC >= 128 || REC >= 128
  ↓ 错误继续增加
离线 (Bus Off)
  TEC >= 256
  ↓ 自动恢复或软件复位
错误主动

错误计数规则

发送错误:
- 发送错误:TEC += 8
- 发送成功:TEC -= 1(如果TEC > 0)

接收错误:
- 接收错误:REC += 1或8(取决于错误类型)
- 接收成功:REC -= 1(如果REC > 0)

步骤6:CAN总线监控

实现CAN总线的监控和诊断功能。

/**
 * @brief  CAN统计信息结构体
 */
typedef struct {
    uint32_t tx_count;       // 发送计数
    uint32_t rx_count;       // 接收计数
    uint32_t tx_error_count; // 发送错误计数
    uint32_t rx_error_count; // 接收错误计数
    uint32_t bus_off_count;  // 离线计数
    uint32_t last_tx_time;   // 最后发送时间
    uint32_t last_rx_time;   // 最后接收时间
} CAN_Stats_t;

CAN_Stats_t can_stats = {0};

/**
 * @brief  更新发送统计
 * @param  success: 是否成功(0=失败,1=成功)
 * @retval 无
 */
void CAN_UpdateTxStats(uint8_t success) {
    if (success) {
        can_stats.tx_count++;
        can_stats.last_tx_time = GetTick();
    } else {
        can_stats.tx_error_count++;
    }
}

/**
 * @brief  更新接收统计
 * @param  无
 * @retval 无
 */
void CAN_UpdateRxStats(void) {
    can_stats.rx_count++;
    can_stats.last_rx_time = GetTick();
}

/**
 * @brief  打印CAN统计信息
 * @param  无
 * @retval 无
 */
void CAN_PrintStats(void) {
    printf("=== CAN Statistics ===\r\n");
    printf("TX Count: %lu\r\n", can_stats.tx_count);
    printf("RX Count: %lu\r\n", can_stats.rx_count);
    printf("TX Errors: %lu\r\n", can_stats.tx_error_count);
    printf("RX Errors: %lu\r\n", can_stats.rx_error_count);
    printf("Bus Off: %lu\r\n", can_stats.bus_off_count);
    printf("Last TX: %lu ms ago\r\n", GetTick() - can_stats.last_tx_time);
    printf("Last RX: %lu ms ago\r\n", GetTick() - can_stats.last_rx_time);
    printf("====================\r\n");
}

/**
 * @brief  重置统计信息
 * @param  无
 * @retval 无
 */
void CAN_ResetStats(void) {
    can_stats.tx_count = 0;
    can_stats.rx_count = 0;
    can_stats.tx_error_count = 0;
    can_stats.rx_error_count = 0;
    can_stats.bus_off_count = 0;
    can_stats.last_tx_time = 0;
    can_stats.last_rx_time = 0;
}

/**
 * @brief  CAN总线负载监控
 * @param  period_ms: 监控周期(毫秒)
 * @retval 总线负载百分比(0-100)
 */
uint8_t CAN_GetBusLoad(uint32_t period_ms) {
    static uint32_t last_count = 0;
    static uint32_t last_time = 0;

    uint32_t current_time = GetTick();
    uint32_t current_count = can_stats.tx_count + can_stats.rx_count;

    if (current_time - last_time >= period_ms) {
        uint32_t msg_count = current_count - last_count;
        uint32_t time_diff = current_time - last_time;

        // 计算负载(假设每个报文平均130位,波特率500Kbps)
        // 负载 = (报文数 × 130位) / (时间 × 波特率)
        uint32_t bits = msg_count * 130;
        uint32_t max_bits = (time_diff * 500000) / 1000;  // 500Kbps
        uint8_t load = (bits * 100) / max_bits;

        last_count = current_count;
        last_time = current_time;

        return (load > 100) ? 100 : load;
    }

    return 0;
}

/**
 * @brief  CAN环回测试
 * @param  无
 * @retval 0=成功,1=失败
 */
uint8_t CAN_LoopbackTest(void) {
    printf("CAN Loopback Test...\r\n");

    // 1. 进入环回模式
    CAN1->MCR |= CAN_MCR_INRQ;
    while (!(CAN1->MSR & CAN_MSR_INAK));

    CAN1->BTR &= ~(3 << 30);
    CAN1->BTR |= CAN_MODE_LOOPBACK;

    CAN1->MCR &= ~CAN_MCR_INRQ;
    while (CAN1->MSR & CAN_MSR_INAK);

    // 2. 配置过滤器接收所有报文
    CAN_Filter_AcceptAll(0, 0);

    // 3. 发送测试报文
    uint8_t test_data[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    uint8_t result = CAN_SendStdData(0x123, test_data, 8);

    if (result != 0) {
        printf("Send failed\r\n");
        return 1;
    }

    // 4. 接收测试报文
    Delay_Ms(10);
    CAN_Message_t msg;
    result = CAN_Receive(0, &msg);

    if (result != 0) {
        printf("Receive failed\r\n");
        return 1;
    }

    // 5. 验证数据
    if (msg.id != 0x123 || msg.dlc != 8) {
        printf("ID or DLC mismatch\r\n");
        return 1;
    }

    for (int i = 0; i < 8; i++) {
        if (msg.data[i] != test_data[i]) {
            printf("Data mismatch at byte %d\r\n", i);
            return 1;
        }
    }

    printf("Loopback test passed!\r\n");

    // 6. 恢复正常模式
    CAN1->MCR |= CAN_MCR_INRQ;
    while (!(CAN1->MSR & CAN_MSR_INAK));

    CAN1->BTR &= ~(3 << 30);
    CAN1->BTR |= CAN_MODE_NORMAL;

    CAN1->MCR &= ~CAN_MCR_INRQ;
    while (CAN1->MSR & CAN_MSR_INAK);

    return 0;
}

实践示例

示例1:CAN双节点通信

实现两个STM32节点之间的CAN通信。

/**
 * @brief  节点1:发送节点
 */
void Node1_Task(void) {
    static uint8_t counter = 0;
    uint8_t data[8];

    // 准备数据
    data[0] = counter++;
    data[1] = 0x01;  // 节点ID
    data[2] = 0xAA;
    data[3] = 0xBB;
    data[4] = 0xCC;
    data[5] = 0xDD;
    data[6] = 0xEE;
    data[7] = 0xFF;

    // 发送数据
    if (CAN_SendStdData(0x100, data, 8) == 0) {
        printf("Node1 TX: %02X\r\n", data[0]);
        CAN_UpdateTxStats(1);
    } else {
        printf("Node1 TX Failed\r\n");
        CAN_UpdateTxStats(0);
    }
}

/**
 * @brief  节点2:接收节点
 */
void Node2_Task(void) {
    CAN_Message_t msg;

    // 接收数据
    if (CAN_ReadBuffer(&msg) == 0) {
        printf("Node2 RX: ID=0x%03X, Data[0]=%02X\r\n", 
               msg.id, msg.data[0]);
        CAN_UpdateRxStats();

        // 回复确认
        uint8_t ack_data[2] = {msg.data[0], 0x02};  // 节点ID=2
        CAN_SendStdData(0x200, ack_data, 2);
    }
}

int main(void) {
    SystemInit();
    UART1_Init(115200);
    CAN1_Config_500K();

    // 节点1配置
    #ifdef NODE1
    CAN_Filter_StdID(0, 0x200, 0);  // 接收节点2的回复
    printf("Node1 Started\r\n");

    while (1) {
        Node1_Task();
        Delay_Ms(1000);
    }
    #endif

    // 节点2配置
    #ifdef NODE2
    CAN_Filter_StdID(0, 0x100, 0);  // 接收节点1的数据
    CAN_EnableRxIT(0);
    printf("Node2 Started\r\n");

    while (1) {
        Node2_Task();
        Delay_Ms(10);
    }
    #endif
}

示例2:CAN多节点广播

实现一个主节点向多个从节点广播数据。

/**
 * @brief  主节点:广播数据
 */
void Master_Broadcast(void) {
    static uint32_t timestamp = 0;
    uint8_t data[8];

    // 准备广播数据
    timestamp = GetTick();
    data[0] = (timestamp >> 24) & 0xFF;
    data[1] = (timestamp >> 16) & 0xFF;
    data[2] = (timestamp >> 8) & 0xFF;
    data[3] = (timestamp >> 0) & 0xFF;
    data[4] = 0x12;  // 命令
    data[5] = 0x34;
    data[6] = 0x56;
    data[7] = 0x78;

    // 广播(使用ID 0x000,最高优先级)
    CAN_SendStdData(0x000, data, 8);
    printf("Master Broadcast: %lu\r\n", timestamp);
}

/**
 * @brief  从节点:接收广播
 */
void Slave_ReceiveBroadcast(uint8_t slave_id) {
    CAN_Message_t msg;

    if (CAN_ReadBuffer(&msg) == 0) {
        if (msg.id == 0x000) {
            // 解析时间戳
            uint32_t timestamp = (msg.data[0] << 24) |
                                (msg.data[1] << 16) |
                                (msg.data[2] << 8) |
                                (msg.data[3]);

            printf("Slave%d RX Broadcast: %lu\r\n", slave_id, timestamp);

            // 从节点响应(使用不同的ID)
            uint8_t response[2] = {slave_id, 0xOK};
            CAN_SendStdData(0x100 + slave_id, response, 2);
        }
    }
}

示例3:CAN协议栈实现

实现一个简单的CAN应用层协议。

/**
 * @brief  CAN协议帧格式
 * 
 * 字节0: 命令码
 * 字节1: 数据长度
 * 字节2-7: 数据
 */

// 命令定义
#define CMD_READ_PARAM    0x01
#define CMD_WRITE_PARAM   0x02
#define CMD_READ_STATUS   0x03
#define CMD_CONTROL       0x04
#define CMD_ACK           0x80
#define CMD_NACK          0x81

/**
 * @brief  发送命令
 * @param  dest_id: 目标节点ID
 * @param  cmd: 命令码
 * @param  data: 数据指针
 * @param  len: 数据长度
 * @retval 0=成功,1=失败
 */
uint8_t CAN_SendCommand(uint16_t dest_id, uint8_t cmd, 
                        const uint8_t *data, uint8_t len) {
    uint8_t frame[8] = {0};

    frame[0] = cmd;
    frame[1] = len;

    // 复制数据(最多6字节)
    for (int i = 0; i < len && i < 6; i++) {
        frame[2 + i] = data[i];
    }

    return CAN_SendStdData(dest_id, frame, 8);
}

/**
 * @brief  处理接收到的命令
 * @param  msg: CAN报文指针
 * @retval 无
 */
void CAN_ProcessCommand(CAN_Message_t *msg) {
    uint8_t cmd = msg->data[0];
    uint8_t len = msg->data[1];
    uint8_t *data = &msg->data[2];

    switch (cmd) {
        case CMD_READ_PARAM:
            printf("Read Param: ");
            for (int i = 0; i < len; i++) {
                printf("%02X ", data[i]);
            }
            printf("\r\n");

            // 发送响应
            uint8_t response[4] = {0x12, 0x34, 0x56, 0x78};
            CAN_SendCommand(msg->id, CMD_ACK, response, 4);
            break;

        case CMD_WRITE_PARAM:
            printf("Write Param: ");
            for (int i = 0; i < len; i++) {
                printf("%02X ", data[i]);
            }
            printf("\r\n");

            // 发送确认
            CAN_SendCommand(msg->id, CMD_ACK, NULL, 0);
            break;

        case CMD_READ_STATUS:
            printf("Read Status\r\n");

            // 发送状态
            uint8_t status[2] = {0x01, 0x00};  // 状态正常
            CAN_SendCommand(msg->id, CMD_ACK, status, 2);
            break;

        case CMD_CONTROL:
            printf("Control: ");
            for (int i = 0; i < len; i++) {
                printf("%02X ", data[i]);
            }
            printf("\r\n");

            // 执行控制
            // ...

            // 发送确认
            CAN_SendCommand(msg->id, CMD_ACK, NULL, 0);
            break;

        case CMD_ACK:
            printf("ACK received\r\n");
            break;

        case CMD_NACK:
            printf("NACK received\r\n");
            break;

        default:
            printf("Unknown command: 0x%02X\r\n", cmd);
            CAN_SendCommand(msg->id, CMD_NACK, NULL, 0);
            break;
    }
}

深入理解

CAN仲裁机制

CAN总线采用非破坏性仲裁机制,多个节点可以同时发送,ID小的获胜。

仲裁过程

节点A发送ID=0x123 (0001 0010 0011)
节点B发送ID=0x456 (0100 0101 0110)

位时间:  1  2  3  4  5  6  7  8  9  10 11
节点A:   0  0  0  1  0  0  1  0  0  1  1
节点B:   0  1  X  X  X  X  X  X  X  X  X
      节点B检测到冲突,停止发送

结果:节点A获得总线,节点B等待重发

仲裁规则: - 显性位(0)优先于隐性位(1) - ID越小,优先级越高 - 标准帧优先于扩展帧(同ID时) - 数据帧优先于远程帧(同ID时)

CAN位时序详解

CAN位时序由4个时间段组成,精确配置可以提高通信可靠性。

位时序参数

同步段(SS): 1 TQ,固定
传播段(PTS): 1-8 TQ,补偿总线延迟
相位段1(PBS1): 1-8 TQ,可调整
相位段2(PBS2): 1-8 TQ,可调整

采样点位置 = (SS + PTS + PBS1) / (SS + PTS + PBS1 + PBS2)

推荐采样点:
- 高速网络(>500Kbps): 75%-87.5%
- 低速网络(<500Kbps): 87.5%-90%

波特率计算示例

/**
 * @brief  计算CAN波特率参数
 * @param  apb1_clk: APB1时钟频率(Hz)
 * @param  baudrate: 目标波特率(bps)
 * @param  sample_point: 采样点位置(75-90)
 * @param  config: 输出配置结构体
 * @retval 0=成功,1=失败
 */
uint8_t CAN_CalculateTiming(uint32_t apb1_clk, uint32_t baudrate,
                            uint8_t sample_point, CAN_Config_t *config) {
    // 计算位时间(TQ数量)
    uint32_t bit_time_tq = 16;  // 推荐16 TQ

    // 计算预分频器
    uint32_t prescaler = apb1_clk / (baudrate * bit_time_tq);

    if (prescaler < 1 || prescaler > 1024) {
        return 1;  // 无法实现该波特率
    }

    // 计算采样点位置(TQ数)
    uint32_t sample_point_tq = (bit_time_tq * sample_point) / 100;

    // 分配时间段
    config->prescaler = prescaler;
    config->sjw = 1;  // 通常设为1
    config->bs1 = sample_point_tq - 1;  // SS=1已包含
    config->bs2 = bit_time_tq - sample_point_tq;

    // 验证参数范围
    if (config->bs1 < 1 || config->bs1 > 16 ||
        config->bs2 < 1 || config->bs2 > 8) {
        return 1;  // 参数超出范围
    }

    return 0;  // 成功
}

CAN过滤器高级应用

场景1:接收ID范围

/**
 * @brief  配置过滤器接收ID范围
 * @param  filter_num: 过滤器编号
 * @param  id_start: 起始ID
 * @param  id_end: 结束ID
 * @param  fifo: FIFO编号
 * @retval 无
 */
void CAN_Filter_IDRange(uint8_t filter_num, uint16_t id_start, 
                        uint16_t id_end, uint8_t fifo) {
    // 计算屏蔽码:找出ID的公共位
    uint16_t mask = 0x7FF;
    for (int i = 0; i < 11; i++) {
        if (((id_start >> i) & 1) != ((id_end >> i) & 1)) {
            mask &= ~(1 << i);
        }
    }

    CAN_Filter_t filter = {
        .filter_num = filter_num,
        .filter_mode = 0,  // 屏蔽模式
        .filter_scale = 1, // 32位
        .filter_fifo = fifo,
        .filter_enable = 1,
        .filter_id_high = (id_start << 5),
        .filter_id_low = 0,
        .filter_mask_high = (mask << 5),
        .filter_mask_low = 0
    };

    CAN_FilterConfig(&filter);
}

场景2:多ID列表

/**
 * @brief  配置过滤器接收多个ID(16位模式)
 * @param  filter_num: 过滤器编号
 * @param  id1-id4: 4个标准ID
 * @param  fifo: FIFO编号
 * @retval 无
 */
void CAN_Filter_MultiID(uint8_t filter_num, uint16_t id1, uint16_t id2,
                        uint16_t id3, uint16_t id4, uint8_t fifo) {
    CAN_Filter_t filter = {
        .filter_num = filter_num,
        .filter_mode = 1,  // 列表模式
        .filter_scale = 0, // 16位
        .filter_fifo = fifo,
        .filter_enable = 1,
        .filter_id_high = (id1 << 5) | (id2 << 5) >> 16,
        .filter_id_low = (id3 << 5) | (id4 << 5) >> 16,
        .filter_mask_high = 0,
        .filter_mask_low = 0
    };

    CAN_FilterConfig(&filter);
}

CAN性能优化

1. 减少中断开销

/**
 * @brief  批量处理接收报文
 * @param  无
 * @retval 处理的报文数量
 */
uint8_t CAN_ProcessRxBatch(void) {
    uint8_t count = 0;
    CAN_Message_t msg;

    // 一次性处理FIFO中的所有报文
    while (CAN_GetRxCount(0) > 0) {
        if (CAN_Receive(0, &msg) == 0) {
            // 处理报文
            CAN_ProcessCommand(&msg);
            count++;
        }
    }

    return count;
}

2. 使用DMA发送

/**
 * @brief  使用DMA批量发送报文
 * @param  msgs: 报文数组
 * @param  count: 报文数量
 * @retval 无
 */
void CAN_SendBatch_DMA(CAN_Message_t *msgs, uint8_t count) {
    for (int i = 0; i < count; i++) {
        // 等待邮箱空闲
        while (!(CAN1->TSR & (CAN_TSR_TME0 | CAN_TSR_TME1 | CAN_TSR_TME2)));

        // 发送报文(不等待完成)
        CAN_Transmit(&msgs[i]);
    }
}

3. 优化过滤器配置

/**
 * @brief  优化过滤器配置,减少CPU负载
 * @param  无
 * @retval 无
 */
void CAN_OptimizeFilters(void) {
    // 策略1:使用屏蔽模式而不是列表模式(更灵活)
    // 策略2:将高优先级报文分配到FIFO0
    // 策略3:将低优先级报文分配到FIFO1
    // 策略4:过滤掉不需要的报文,减少中断次数

    // 高优先级:控制命令(ID 0x000-0x0FF)
    CAN_Filter_IDRange(0, 0x000, 0x0FF, 0);

    // 中优先级:数据传输(ID 0x100-0x1FF)
    CAN_Filter_IDRange(1, 0x100, 0x1FF, 1);

    // 低优先级:状态信息(ID 0x200-0x2FF)
    CAN_Filter_IDRange(2, 0x200, 0x2FF, 1);
}

常见问题

Q1: CAN通信不工作,无法收发数据?

可能原因: 1. 硬件连接错误 2. 波特率配置不匹配 3. 终端电阻缺失或不正确 4. 过滤器配置错误

排查步骤

// 1. 检查硬件连接
// - 用万用表测量CANH和CANL之间的电阻(应该约60Ω)
// - 检查收发器供电是否正常

// 2. 检查波特率配置
printf("BTR: 0x%08X\r\n", CAN1->BTR);
// 计算实际波特率并与预期对比

// 3. 使用环回模式测试
if (CAN_LoopbackTest() == 0) {
    printf("CAN controller OK\r\n");
} else {
    printf("CAN controller error\r\n");
}

// 4. 检查过滤器配置
printf("Filter 0: ID=0x%08X, Mask=0x%08X\r\n",
       CAN1->sFilterRegister[0].FR1,
       CAN1->sFilterRegister[0].FR2);

Q2: CAN发送失败,邮箱一直忙?

可能原因: 1. 总线离线(Bus Off) 2. 没有应答(ACK Error) 3. 仲裁失败 4. 邮箱配置错误

解决方案

/**
 * @brief  诊断发送失败原因
 * @param  mailbox: 邮箱号
 * @retval 无
 */
void CAN_DiagnoseTxFailure(uint8_t mailbox) {
    uint32_t tsr = CAN1->TSR;
    uint32_t esr = CAN1->ESR;

    // 检查总线状态
    if (esr & CAN_ESR_BOFF) {
        printf("Bus Off! TEC=%d\r\n", CAN_GetTxErrorCount());
        // 需要复位CAN或等待自动恢复
        return;
    }

    // 检查邮箱状态
    uint32_t terr_flag = 0;
    uint32_t alst_flag = 0;

    switch (mailbox) {
        case 0:
            terr_flag = CAN_TSR_TERR0;
            alst_flag = CAN_TSR_ALST0;
            break;
        case 1:
            terr_flag = CAN_TSR_TERR1;
            alst_flag = CAN_TSR_ALST1;
            break;
        case 2:
            terr_flag = CAN_TSR_TERR2;
            alst_flag = CAN_TSR_ALST2;
            break;
    }

    if (tsr & terr_flag) {
        printf("Transmission Error (No ACK)\r\n");
        // 检查是否有其他节点在总线上
    }

    if (tsr & alst_flag) {
        printf("Arbitration Lost\r\n");
        // 正常现象,会自动重发
    }
}

Q3: 接收到错误的数据或数据丢失?

可能原因: 1. 总线干扰 2. 波特率不匹配 3. FIFO溢出 4. 过滤器配置错误

解决方案

/**
 * @brief  检查接收错误
 * @param  无
 * @retval 无
 */
void CAN_CheckRxErrors(void) {
    uint32_t rf0r = CAN1->RF0R;
    uint32_t rf1r = CAN1->RF1R;

    // 检查FIFO溢出
    if (rf0r & CAN_RF0R_FOVR0) {
        printf("FIFO0 Overflow!\r\n");
        CAN1->RF0R |= CAN_RF0R_FOVR0;  // 清除标志
    }

    if (rf1r & CAN_RF1R_FOVR1) {
        printf("FIFO1 Overflow!\r\n");
        CAN1->RF1R |= CAN_RF1R_FOVR1;  // 清除标志
    }

    // 检查接收错误计数
    uint8_t rec = CAN_GetRxErrorCount();
    if (rec > 96) {
        printf("High REC: %d\r\n", rec);
        // 可能存在总线干扰或波特率问题
    }

    // 检查CRC错误
    CAN_Error_t error = CAN_GetLastError();
    if (error == CAN_ERROR_CRC) {
        printf("CRC Error detected\r\n");
        // 检查总线质量和终端电阻
    }
}

Q4: 如何调试CAN通信问题?

调试工具和方法

/**
 * @brief  CAN调试信息输出
 * @param  无
 * @retval 无
 */
void CAN_DebugInfo(void) {
    printf("\r\n=== CAN Debug Info ===\r\n");

    // 1. 寄存器状态
    printf("MCR: 0x%08X\r\n", CAN1->MCR);
    printf("MSR: 0x%08X\r\n", CAN1->MSR);
    printf("TSR: 0x%08X\r\n", CAN1->TSR);
    printf("RF0R: 0x%08X\r\n", CAN1->RF0R);
    printf("RF1R: 0x%08X\r\n", CAN1->RF1R);
    printf("ESR: 0x%08X\r\n", CAN1->ESR);

    // 2. 错误状态
    printf("\r\nError Status:\r\n");
    CAN_PrintStatus();

    // 3. 统计信息
    printf("\r\n");
    CAN_PrintStats();

    // 4. 过滤器配置
    printf("\r\nFilter Config:\r\n");
    for (int i = 0; i < 4; i++) {
        if (CAN1->FA1R & (1 << i)) {
            printf("Filter %d: FR1=0x%08X, FR2=0x%08X\r\n",
                   i,
                   CAN1->sFilterRegister[i].FR1,
                   CAN1->sFilterRegister[i].FR2);
        }
    }

    printf("====================\r\n\r\n");
}

使用示波器/逻辑分析仪

观察要点:
1. CANH和CANL的差分信号
   - 隐性电平:约2.5V(CANH=CANL)
   - 显性电平:CANH约3.5V,CANL约1.5V,差值约2V

2. 位时间
   - 测量实际位时间,验证波特率

3. 信号质量
   - 检查上升/下降沿
   - 检查是否有振铃或过冲
   - 检查终端电阻是否正确

4. 仲裁过程
   - 观察多节点同时发送时的仲裁

Q5: 如何实现CAN Bootloader?

基本思路

/**
 * @brief  CAN Bootloader跳转
 * @param  app_addr: 应用程序地址
 * @retval 无
 */
void CAN_JumpToApp(uint32_t app_addr) {
    // 1. 检查应用程序是否有效
    uint32_t sp = *(__IO uint32_t*)app_addr;
    if ((sp & 0x2FFE0000) != 0x20000000) {
        printf("Invalid application\r\n");
        return;
    }

    // 2. 禁用所有中断
    __disable_irq();

    // 3. 复位所有外设
    CAN1->MCR |= CAN_MCR_RESET;

    // 4. 设置向量表偏移
    SCB->VTOR = app_addr;

    // 5. 设置堆栈指针
    __set_MSP(sp);

    // 6. 跳转到应用程序
    uint32_t jump_addr = *(__IO uint32_t*)(app_addr + 4);
    void (*app_reset_handler)(void) = (void*)jump_addr;
    app_reset_handler();
}

/**
 * @brief  CAN固件升级协议
 */
#define CMD_BOOTLOADER_START  0xF0
#define CMD_BOOTLOADER_DATA   0xF1
#define CMD_BOOTLOADER_END    0xF2
#define CMD_BOOTLOADER_JUMP   0xF3

void CAN_BootloaderProcess(CAN_Message_t *msg) {
    static uint32_t write_addr = 0x08010000;  // 应用程序起始地址

    switch (msg->data[0]) {
        case CMD_BOOTLOADER_START:
            printf("Bootloader: Start\r\n");
            write_addr = 0x08010000;
            // 擦除Flash
            Flash_Erase(0x08010000, 0x08020000);
            break;

        case CMD_BOOTLOADER_DATA:
            // 写入数据
            Flash_Write(write_addr, &msg->data[1], 7);
            write_addr += 7;
            break;

        case CMD_BOOTLOADER_END:
            printf("Bootloader: End\r\n");
            break;

        case CMD_BOOTLOADER_JUMP:
            printf("Bootloader: Jump to app\r\n");
            Delay_Ms(100);
            CAN_JumpToApp(0x08010000);
            break;
    }
}

总结

本教程全面介绍了CAN总线驱动开发的核心知识和实践技能。让我们回顾一下要点:

核心知识点: - CAN总线原理:差分信号、非破坏性仲裁、错误检测 - CAN报文格式:标准帧和扩展帧的结构 - bxCAN架构:发送邮箱、接收FIFO、过滤器组 - 波特率配置:位时序参数和采样点设置 - 过滤器机制:屏蔽模式和列表模式

实践技能: - CAN控制器初始化和配置 - 过滤器配置:接收指定ID或ID范围 - 报文发送:标准帧和扩展帧 - 报文接收:轮询和中断方式 - 错误处理:错误检测和总线恢复 - 总线监控:统计信息和负载监控

最佳实践: - 合理配置波特率和采样点 - 使用过滤器减少CPU负载 - 实现完善的错误处理机制 - 使用环回模式进行自测试 - 记录统计信息便于调试

应用场景: - 汽车电子:ECU通信、车身控制 - 工业控制:PLC网络、传感器总线 - 医疗设备:设备互联、数据采集 - 机器人:多模块协同控制

CAN总线是工业和汽车领域最重要的通信协议之一,掌握CAN驱动开发可以为你打开嵌入式系统高级应用的大门。通过本教程的学习和实践,你应该能够在各种项目中灵活运用CAN技术。

延伸阅读

推荐进一步学习的内容:

同模块内容: - UART串口驱动开发与调试 - 串行通信基础 - I2C驱动开发:传感器数据读取 - 另一种总线协议 - DMA驱动开发:高效数据传输 - 提高CAN性能

相关主题: - 中断系统基础概念 - 理解CAN中断 - 驱动性能优化技术 - 高级优化技巧 - 通用驱动框架设计与实现 - 驱动架构设计

高级应用: - CANopen协议栈 - J1939协议(重型车辆) - CAN FD(灵活数据速率) - 汽车诊断协议(UDS)

官方文档: - STM32F4xx参考手册 - bxCAN章节 - CAN规范2.0 - CAN协议标准 - ISO 11898标准 - CAN物理层和数据链路层

开源项目: - CANopen协议栈 - 开源CANopen实现 - SocketCAN - Linux CAN工具集 - python-can - Python CAN库

参考资料

  1. STM32F4xx参考手册 - STMicroelectronics
  2. CAN Specification 2.0 - Robert Bosch GmbH
  3. 汽车CAN总线系统原理、设计与应用 - 机械工业出版社
  4. 嵌入式系统设计与实践 - Elecia White
  5. Controller Area Network (CAN) Implementation Guide - Texas Instruments

练习题

基础练习

  1. CAN基本配置练习
  2. 配置CAN为500Kbps波特率
  3. 实现标准帧的发送和接收
  4. 测试环回模式

  5. 过滤器配置练习

  6. 配置过滤器接收指定ID
  7. 配置过滤器接收ID范围
  8. 测试过滤器的屏蔽功能

  9. 错误处理练习

  10. 实现错误检测和报告
  11. 模拟总线错误并观察恢复过程
  12. 记录错误统计信息

进阶练习

  1. 多节点通信
  2. 实现两个节点的双向通信
  3. 实现主从模式通信
  4. 实现广播通信

  5. 协议栈实现

  6. 设计简单的应用层协议
  7. 实现命令-响应机制
  8. 实现数据分包和重组

  9. 性能优化

  10. 测试不同波特率下的性能
  11. 优化过滤器配置减少CPU负载
  12. 实现批量发送和接收

综合项目

  1. CAN网关
  2. 实现CAN到UART的网关
  3. 支持报文转发和过滤
  4. 实现统计和监控功能

  5. CAN Bootloader

  6. 实现基于CAN的固件升级
  7. 支持固件校验和回滚
  8. 实现安全机制

  9. 车载诊断系统

  10. 实现OBD-II协议
  11. 读取车辆故障码
  12. 实现实时数据流

思考题

  1. CAN总线为什么采用差分信号传输?相比单端信号有什么优势?

  2. CAN的非破坏性仲裁机制是如何工作的?为什么ID越小优先级越高?

  3. 为什么CAN总线需要终端电阻?如果终端电阻不正确会有什么影响?

  4. CAN的错误检测机制有哪些?如何保证通信的可靠性?

  5. CAN FD相比传统CAN有哪些改进?在什么场景下需要使用CAN FD?

实验任务

任务1:CAN基本通信(必做)

要求: - 实现两个STM32节点的CAN通信 - 节点1每秒发送一次数据 - 节点2接收并回复确认 - 统计通信成功率

任务2:CAN过滤器应用(必做)

要求: - 配置多个过滤器 - 实现不同优先级的报文分类 - 测试过滤器的屏蔽和列表模式 - 对比不同配置的性能

任务3:CAN错误处理(选做)

要求: - 实现完整的错误检测和处理 - 模拟各种错误场景 - 实现自动恢复机制 - 记录和分析错误日志

任务4:CAN协议栈(选做)

要求: - 设计并实现应用层协议 - 支持多种命令类型 - 实现超时和重传机制 - 提供易用的API接口

评分标准: - 代码规范性(20分) - 功能完整性(30分) - 可靠性和稳定性(30分) - 创新性和扩展性(20分)


下一步学习建议

完成本教程后,建议按以下顺序继续学习:

  1. RTC实时时钟驱动与时间管理 - 学习时间管理
  2. SDIO驱动开发:SD卡接口 - 学习存储接口
  3. 通用驱动框架设计与实现 - 深入驱动架构

学习路线图

graph LR
    A[CAN驱动] --> B[CANopen协议]
    A --> C[J1939协议]
    A --> D[CAN FD]
    B --> E[工业自动化]
    C --> F[商用车辆]
    D --> G[高速通信]
    A --> H[汽车诊断]

祝你学习顺利!如有问题,欢迎在社区讨论。


文档信息: - 最后更新:2024-01-15 - 版本:v1.0 - 作者:嵌入式知识平台 - 许可:CC BY-NC-SA 4.0