BLOG8
跳转至

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