引言

控制器局域网(Controller Area Network, CAN)是一种广泛应用于汽车电子和工业控制领域的强大、可靠的串行通信总线协议。它以其多主架构、高抗干扰性和优秀的错误处理机制而闻名。对于嵌入式开发者,尤其是从事汽车电子或工业项目的开发者来说,掌握CAN总线至关重要。

本文将带领大家使用STM32微控制器和ST提供的HAL库,从零开始实现CAN总线的基础通信功能。

本文主要讲解应用,如果想要了解CAN总线其中的内涵请看这篇文章⬇⬇⬇

CAN总线:深入浅出,从原理到应用,读懂工业与汽车的网络骨干-CSDN博客https://blog.csdn.net/banxian_a/article/details/151070167?spm=1001.2014.3001.5502

一、CAN总线基础概念

在敲代码之前,先快速了解几个核心概念:

  1. 差分信号:CAN使用CAN_H和CAN_L两条信号线,通过两者的电压差来表示信号。这种设计使其对共模噪声(如电磁干扰)有极强的免疫力。

  2. 多主多从:总线上所有设备都是平等的(Multi-Master),任何节点都可以在总线空闲时发起通信,通过标识符(ID)来仲裁决定谁占用总线。

  3. 消息标识符(ID):代表消息的优先级。ID值越小,优先级越高。

  4. 标准帧与扩展帧

    • 标准帧:11位标识符

    • 扩展帧:29位标识符

  5. 波特率:由波特率预分频器(Prescaler)时间段1(BS1) 和时间段2(BS2) 共同决定。一个位时间(Bit Time)由三部分组成:同步段(SYNC_SEG)BS1 和 BS2

二、硬件准备

STM32开发板:需要带有CAN控制器的,我使用的是f103

CAN收发器模块:如TJA1050或SN65HVD230。STM32的CAN控制器输出的是数字信号(CAN_Tx, CAN_Rx),需要收发器将其转换为差分信号(CAN_H, CAN_L)。我是有的是TJA1050。

连接线:将两个(或以上)相同的节点连接起来。

  • CAN_H 接 CAN_H

  • CAN_L 接 CAN_L

三、软件配置(STM32CubeMX)

简单的基础配置我就不再多说了,时钟,Debug配置好,主要是can的配置

1. Bit Timings Parameters (位时序参数)

这是 CAN 配置中最核心、最关键的部分,它直接决定了通信的波特率(速度)和可靠性。

  • Prescaler (for Time Quantum) (预分频器) : 36

    • 解释:CAN 控制器的工作时钟来源于 APB 总线时钟(例如 72 MHz)。预分频器的作用是将这个高速时钟进行分频,得到一个更慢的、用于构造 CAN 位时间的基础时钟,即 Time Quantum

    • 计算Tq = (Prescaler) / (APB Clock Frequency)。假设 APB 时钟为 72 MHz,则 Tq = 36 / 72,000,000 Hz = 0.0000005 s = 500 ns。但您下面显示 Time Quantum 为 1000 ns,这意味着您的 APB 时钟可能是 36 MHz (36 / 36,000,000 = 1 us = 1000 ns)。

  • Time Quantum (时间量子) : 1000.0 ns

    • 解释:这是构成 CAN 位时间的基本时间单位。所有位时序参数都以 Tq 的整数倍来表示。它由上面的预分频器决定。

  • Time Quanta in Bit Segment 1 (BS1) : 3 Times

    • 解释:BS1 又称为传播时间段相位缓冲段 1。它包含了信号在总线上传播的物理延迟时间、输入比较器延迟和输出驱动器的上升下降时间。这个时间段可以被重新同步机制稍微延长

  • Time Quanta in Bit Segment 2 (BS2) : 6 Times

    • 解释:BS2 又称为相位缓冲段 2。它主要用于补偿网络中的边沿相位误差。这个时间段可以被重新同步机制稍微缩短

  • Time for one Bit (一个位的时间) : 10000 ns

    • 解释:一个完整的 CAN 数据位的总时间。计算公式为:T1bit = (1 + BS1 + BS2) * Tq

    • 计算(1 + 3 + 6) * 1000 ns = 10 * 1000 ns = 10,000 ns (0.00001 s)

  • Baud Rate (波特率) : 100000 bit/s

    • 解释:通信速度,即每秒传输的位数。它是 T1bit 的倒数。

    • 计算1 / (0.00001 s) = 100,000 bit/s。也就是标准的 100 kbps。这是汽车中非常常见的一种速率(例如,用于车身控制网络)。

  • ReSynchronization Jump Width (重新同步跳跃宽度) : 2 Times

    • 解释:为了补偿不同节点间的时钟偏差,CAN 控制器可以在检测到边沿时进行重新同步。RJW 定义了在一次重新同步中,允许最大调整多少个 Tq 来延长 BS1 或缩短 BS2。

    • 规则:RJW 必须小于等于 BS2 的值(您的配置中 2 < 6,符合规则)。设置得太大会在噪声环境中失去同步,太小则时钟容差能力差。

小结:您的位时序配置将 CAN 总线速度设置为 100 kbps,每个位时间由 10 个 Tq 组成(1Tq 同步段 + 3Tq BS1 + 6Tq BS2)。采样点(Sample Point,即读取位值的时间点)位于 (1 + BS1) / (1 + BS1 + BS2) = (1+3)/10 = 40% 的位置。对于 100kbps 的中低速总线,40% 的采样点是可行的,但更常见的做法是设置在 75%-80% 左右(例如 BS1=8, BS2=2)。

2. Basic Parameters (基本参数)

这些参数控制 CAN 控制器的工作模式和特性。

  • Time Triggered Communication Mode (时间触发通信模式) : Disable

    • 解释:如果启用,CAN 硬件会使用一个内部定时器来为发送/接收的消息打上时间戳,并管理一些与时间相关的功能。通常用于 CANopen 等需要精确时间同步的高级协议。禁用此模式是常见选择,使用基本的异步通信。

  • Automatic Bus-Off Management (自动总线关闭管理) : Enable

    • 解释:当 CAN 节点出现过多错误(发送错误计数器 TEC > 255)时,它会进入“Bus-Off”状态,与总线完全断开以保护网络。

    • 启用:硬件会自动尝试从 Bus-Off 状态恢复(等待 128 次出现 11 个连续的隐性位后,自动复位错误计数器并重回正常工作状态)。

    • 禁用:需要软件手动干预来恢复。通常建议启用,以实现 robust 的自动错误恢复。

  • Automatic Wake-Up Mode (自动唤醒模式) : Enable

    • 解释:当 CAN 控制器处于低功耗的睡眠模式时,如果检测到总线上的任何活动(报文),它会自动唤醒自己。启用此功能对于需要省电的节点非常有用。

  • Automatic Retransmission (自动重传) : Disable

    • 解释:如果一条报文因为仲裁丢失或错误而发送失败,CAN 控制器会自动尝试重新发送它。

    • 启用:这是标准行为,符合 CAN 协议标准,保证了数据的最终成功送达。

    • 禁用:无论发送成功与否,每个报文只尝试发送一次。这通常仅在软件需要完全控制发送流程的特定调试或测试场景下使用。对于正常应用,应保持启用

  • Receive Fifo Locked Mode (接收 FIFO 锁定模式) : Disable

    • 解释:如果启用,当接收 FIFO 已满时,新来的报文会覆盖掉最旧的报文。

    • 禁用:当接收 FIFO 已满时,新来的报文会被丢弃,直到软件读出数据腾出空间。禁用是更安全的选择,可以避免重要数据被意外覆盖。

  • Transmit Fifo Priority (发送 FIFO 优先级) : Disable

    • 解释:当邮箱中有多条报文等待发送时,此设置决定优先级仲裁方式。

    • 禁用(标准模式):按报文标识符的优先级来仲裁发送顺序(标识符值越小,优先级越高)。

    • 启用(FIFO 模式):按请求发送的顺序来仲裁(先请求的先发),忽略标识符优先级。通常应禁用,以遵循 CAN 标准的总线仲裁机制。

3. Advanced Parameters (高级参数)

  • Test Mode (测试模式) : Loopback combined with Silent

    • 解释:这是非常重要的一个调试模式。

      • Loopback (环回模式):控制器将自己发送的报文同时回收到自己的接收邮箱中。它不会真正将报文发送到外部 CAN 总线上

      • Silent (静默模式):控制器可以正常接收报文并产生应答位,但不会向总线发送任何内容(包括错误帧),是一个“只听不说”的模式。

    • 组合模式 Loopback + Silent

      1. 不会干扰外部总线(因为 Silent)。

      2. 它可以自发自收,用于测试 CAN 控制器的发送和接收功能是否正常,而不需要另一个真实的 CAN 节点。

      3. 这是硬件自测试软件调试的完美模式。在编写代码初期,可以用此模式验证通信逻辑是否正确,而不用担心损坏或干扰实际网络。

重要提示:当您完成测试,准备接入真实 CAN 网络时,必须将此模式改回 Normal,否则节点将无法正常参与总线通信。

四、代码实现

1.过滤器配置

由于cubemx没有过滤器配置,所有这个得由我们自己代码手动配置

//过滤器配置
void CAN_Filter() {
  CAN_FilterTypeDef CAN_FilterConfig;
  //设置过滤id  这里我们先配置为不过滤
  CAN_FilterConfig.FilterIdHigh = 0x000<<5;
  CAN_FilterConfig.FilterIdLow = 0x000<<5;
  //过滤器掩码  这里我们先配置为不过滤
  CAN_FilterConfig.FilterMaskIdHigh = 0x000<<5;
  CAN_FilterConfig.FilterMaskIdLow = 0x000<<5;
  //关联FIFO0
  CAN_FilterConfig.FilterFIFOAssignment=CAN_RX_FIFO0;
  //编号
  CAN_FilterConfig.FilterBank=1;
  //过滤器工作模式  屏蔽--列表  这里是屏蔽
  CAN_FilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
  //位宽32--16
  CAN_FilterConfig.FilterScale=CAN_FILTERSCALE_16BIT;
  //使能
  CAN_FilterConfig.FilterActivation=ENABLE;

  HAL_CAN_ConfigFilter(&hcan, &CAN_FilterConfig);
}
2.发送函数
//发送数据
void CAN_TX(uint16_t ID,uint8_t *Data,uint8_t len) {
  uint32_t pTxMailbox;
  CAN_TxHeaderTypeDef CAN_TxHeader;

  CAN_TxHeader.StdId = ID;  //标准ID  11位
  CAN_TxHeader.ExtId = 0x0000; //扩展ID 暂时不用
  CAN_TxHeader.IDE=CAN_ID_STD;  // 使用标准ID
  CAN_TxHeader.RTR=CAN_RTR_DATA;  // 数据帧
  CAN_TxHeader.DLC=len;          // 数据长度 (0-8)
  CAN_TxHeader.TransmitGlobalTime=DISABLE;    //时间戳
  HAL_CAN_AddTxMessage(&hcan,&CAN_TxHeader,Data,&pTxMailbox); //HAL库发送函数

  //等待发送完成  这里注意如果没有接收设备给应答  程序会卡死在这里 一直发送
  while (__HAL_CAN_GET_FLAG(&hcan,CAN_FLAG_TXOK0)==0);
}
3.接收函数
//定义一个接收结构体
typedef struct {
  uint16_t ID;
  uint8_t Data[8];
  uint8_t len;
}Rxmsg;

//接收数据
void CAN_RX(Rxmsg rxmsg[],uint8_t *count) {
  // 获取CAN接收FIFO0中待处理的消息数量
  *count = HAL_CAN_GetRxFifoFillLevel(&hcan,CAN_RX_FIFO0);
  // 循环处理所有待处理的消息
  CAN_RxHeaderTypeDef RxHeader;
  for (uint8_t i=0;i<*count;i++) {
    // 从FIFO0中读取一条消息
    HAL_CAN_GetRxMessage(&hcan,CAN_RX_FIFO0,&RxHeader,rxmsg[i].Data);
    // 将消息的数据长度码(DLC)保存到自定义结构体中
    rxmsg[i].len = RxHeader.DLC;
    // 将消息的标准ID保存到自定义结构体中
    rxmsg[i].ID = RxHeader.StdId;

  }

单机测试

不需要外接模块,自发自收测试,将程序直接写进开发板

  HAL_UART_Transmit(&huart1, (uint8_t *)"start", strlen("start"), 100);
  HAL_UART_Transmit(&huart1, (uint8_t *)"\r\n", strlen("\r\n"), 100);

  HAL_CAN_Start(&hcan);//开启can总线
  CAN_Filter();     //配置过滤器
    //定义第一个发送数据
  uint16_t CAN_id=0x66;
  uint8_t *CAN_data="123";
  CAN_TX(CAN_id,CAN_data,strlen(CAN_data));
 //定义第二个发送数据
  CAN_id=0x77;
  CAN_data="123456";
  CAN_TX(CAN_id,CAN_data,strlen(CAN_data));
 //定义第三个发送数据
  CAN_id=0x88;
  CAN_data="ABC";
  CAN_TX(CAN_id,CAN_data,strlen(CAN_data));

  Rxmsg rxmsg[3];  //定义接收结构体
  uint8_t count;     //存放接收数据个数


  CAN_RX(rxmsg,&count); //获取接收数据放入接收结构体
  //打印数据个数
  sprintf(buffer,"count:%d \r\n",count);
  HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), 100);
    //数据逐个打印
  for(char i=0;i<count;i++) {
    sprintf(buffer,"ID:%#x, len=%d, data=%s \r\n",rxmsg[i].ID,rxmsg[i].len,rxmsg[i].Data);
    HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), 100);
    
  }
效果演示

多机通信

注意将cubemx中的Advanced Parameters配置选项选择:Normal,不然无法通信

接线图

需要使用两个模块直接进行通信

代码实现

我们只需要将发送的代码和接收的代码分别烧到两个开发板就可以了,写到循环里

发送

  HAL_CAN_Start(&hcan);//开启can总线
  CAN_Filter();     //配置过滤器

  while(1){
  uint16_t CAN_id=0x66;
  uint8_t *CAN_data="123";
  CAN_TX(CAN_id,CAN_data,strlen(CAN_data));

  CAN_id=0x77;
  CAN_data="123456";
  CAN_TX(CAN_id,CAN_data,strlen(CAN_data));

  CAN_id=0x88;
  CAN_data="ABC";
  CAN_TX(CAN_id,CAN_data,strlen(CAN_data));
  HAL_Delay(1000);
  }

接收

  HAL_CAN_Start(&hcan);//开启can总线
  CAN_Filter();     //配置过滤器

  while(1){
    
    for(char i=0;i<count;i++) {
      sprintf(buffer,"count:%d \r\n",count);
      HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), 100);
      sprintf(buffer,"ID:%#x, len=%d, data=%s \r\n",rxmsg[i].ID,rxmsg[i].len,rxmsg[i].Data);
      HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), 100);
      HAL_Delay(10);
    }
    HAL_Delay(500);
  }
效果演示

五、总结与下一步

恭喜!你已经成功实现了STM32的CAN总线基础通信。总结一下步骤:

  1. CubeMX配置时钟、CAN工作模式和波特率。

  2. 配置过滤器(必不可少!)。

  3. 启动CAN,并根据需要使能中断。

  4. 实现发送和接收函数

下一步可以探索

  • 使用扩展帧:在报文头中设置IDE = CAN_ID_EXT,并使用ExtId

  • 更精确的过滤器配置:使用标识符列表模式(ID List Mode) 来精确接收特定ID的消息。

  • 使用其他接收FIFO:FIFO1。

  • 实现CAN总线上的多节点通信:连接更多的STM32节点,让它们互相收发数据。

  • 了解更高级的协议:如CANOpen或J1939,它们都是在CAN物理层之上构建的应用层协议。

CAN总线学习是一个循序渐进的过程,从点对点通信到复杂网络管理,需要不断实践和积累经验。希望本文能为您的STM32 CAN总线开发之旅提供坚实的起点。

订阅专栏吧,还会有更多内容发布哦!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐