当前位置: 首页 > ai >正文

CAN通信

1. CAN简介

CAN(Controller Area Network)是一种由德国Bosch公司为汽车行业开发的多主机串行通信协议。
其核心优势包括
实时性高、抗干扰强、支持多主通信、传输距离远、可靠性高
等。

应用领域:

  • 汽车电子(发动机、ABS、气囊等)

  • 工业自动化

  • 电梯、医疗设备、船舶、铁路信号等


2. CAN物理层介绍

2.1 总线拓扑图

CAN总线通常为线性总线结构,所有节点并联在一对双绞线上。
各节点通过CAN_H和CAN_L两根线通信。

总线两端必须并联120Ω终端电阻,以防止信号反射。

2.2 电平标准

  • CAN_H空闲为2.5V,主发信时为3.5V

  • CAN_L空闲为2.5V,主发信时为1.5V

  • 差分信号,抗干扰能力强

  • Dominant(显性):CAN_H > CAN_L,逻辑0

  • Recessive(隐性):CAN_H ≈ CAN_L,逻辑1


2.3 CAN控制器与收发器

  • 控制器:实现协议层功能(帧格式、仲裁、校验、滤波、缓冲等)

  • 收发器:将控制器TTL信号转换为CAN总线电平

典型硬件结构:

MCU <--> CAN控制器 <--> CAN收发器 <--> 总线(如STM32内部集成)     (如TJA1050)

3. CAN协议层介绍

3.1 CAN帧种类

  • 数据帧(Data Frame):实际传输数据

  • 远程帧(Remote Frame):请求特定节点发送数据

  • 错误帧(Error Frame):报错

  • 过载帧(Overload Frame):节点无法及时处理数据

3.2 CAN数据帧结构

标准帧(11位ID)/扩展帧(29位ID)
数据段最大8字节。

graph LR
A[帧起始] --> B[仲裁字段(ID+RTR)] --> C[控制字段] --> D[数据段] --> E[CRC] --> F[ACK] --> G[帧结束]

字段详解:

  • 起始位:1位,逻辑0

  • 仲裁字段:ID+远程请求位

  • 控制字段:数据长度(0~8字节)

  • 数据段:实际数据

  • CRC:循环冗余校验

  • ACK:应答

  • 结束位:7位隐性

3.3 CAN位时序

  • 1位时间分为同步段、传播段、相位缓冲段1、相位缓冲段2

  • 波特率常见有1Mbps, 500kbps, 250kbps等

  • 总线长度与速率成反比(长距离选低速)

3.4 CAN仲裁机制

  • 基于标识符ID优先级(ID越小优先级越高)

  • 多主机发送冲突自动解决,优先级高的继续发送


4. STM32 CAN控制器介绍

4.1 CAN控制器介绍

STM32通常内部集成了CAN控制器(如bxCAN)。
支持标准帧、扩展帧、过滤、FIFO、硬件自动重发等。

4.1 CAN控制器模式

  • 正常模式(Normal):正常收发

  • 回环模式(Loopback):自发自收,用于测试

  • 静默模式(Silent):只收不发,用于总线监控

  • 回环+静默:自发自收且不影响总线

4.2 CAN控制器框图

5. CAN寄存器及库函数介绍

  • 主要寄存器:

    • CAN_MCR:主控制寄存器

    • CAN_MSR:主状态寄存器

    • CAN_TSR:发送状态寄存器

    • CAN_RF0R/1R:接收FIFO寄存器

    • CAN_IER:中断使能寄存器

    • CAN_ID、CAN_FxR:滤波器配置

  • HAL库/Std库/裸寄存器三种开发方式

  • 常用API示例(HAL库为例):

CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8] = {0};
uint32_t TxMailbox;// 初始化配置
hcan.Instance = CAN1;
hcan.Init.Prescaler = 16; // 波特率
hcan.Init.Mode = CAN_MODE_NORMAL;
HAL_CAN_Init(&hcan);// 发送数据
TxHeader.DLC = 8;
TxHeader.IDE = CAN_ID_STD;
TxHeader.StdId = 0x321;
HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox);// 接收数据(中断/轮询)
HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, RxData);

6. CAN基本驱动流程

典型CAN通信初始化与收发流程

// 1. 配置GPIO引脚为CAN功能
// 2. 配置CAN参数(波特率、模式等)
// 3. 配置CAN滤波器(使能/屏蔽特定ID)
// 4. 启动CAN模块
HAL_CAN_Start(&hcan1);
// 5. 使能中断(可选)
HAL_CAN_ActivateNotification(&hcan1, ...);
// 6. 发送数据
HAL_CAN_AddTxMessage(...);
// 7. 接收数据(轮询或中断)

注意:中断回调函数一般如下

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{// 处理接收到的数据
}

7. 其他知识点

7.1 CAN滤波器配置

  • 作用:只让关心的ID帧进入接收FIFO,减轻CPU负担

  • 配置方式:屏蔽位、掩码位

  • 例:只接收ID为0x123的数据帧

CAN_FilterTypeDef canFilter;
canFilter.FilterIdHigh = 0x123 << 5;
canFilter.FilterMaskIdHigh = 0x7FF << 5;
canFilter.FilterScale = CAN_FILTERSCALE_32BIT;
canFilter.FilterMode = CAN_FILTERMODE_IDMASK;
canFilter.FilterBank = 0;
canFilter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan1, &canFilter);

7.2 错误管理与调试

  • 典型错误:Acknowledge Error、Bit Error、Stuff Error等

  • 常见调试思路:检查波特率、终端电阻、滤波器、收发器芯片、连线

7.3 多主多节点通信机制

  • 同时多节点可发,利用仲裁避免冲突

  • ID优先级机制示例


8. 常见应用与技巧

  • CANopen、DeviceNet等高层协议

  • 总线负载率计算

  • 常见硬件问题排查法


9. 总结

CAN是现代汽车与工业现场的主流总线之一,实际项目中需熟悉协议、硬件配置、MCU代码实现与调试要点。

波特率的计算

一、CAN波特率的计算原理

CAN总线的波特率(Baud rate)= 1 bit传输所需时间的倒数。

CAN通信中**1位时间(Bit Time)**由多个时段组成:

  • 同步段(Sync_Seg):固定为1个时间单元

  • 传播段(Prop_Seg):信号传播延时补偿

  • 相位缓冲段1(Phase_Seg1)

  • 相位缓冲段2(Phase_Seg2)

STM32常用的CAN模块内部的时钟(f_CANclk)通常来自APB1外设时钟,比如36MHz、42MHz等。

波特率公式如下:

波特率 = f_CANclk / ((Prescaler) × (Sync_Seg + Prop_Seg + Phase_Seg1 + Phase_Seg2))

其中:

  • Prescaler:分频系数(整型,寄存器设置)

  • Sync_Seg:同步段(固定为1)

  • Prop_Seg:传播段(1~8)

  • Phase_Seg1:相位缓冲段1(1~8)

  • Phase_Seg2:相位缓冲段2(1~8)

STM32中的术语(HAL库结构体):

  • Prescaler —— 分频系数

  • TimeSeg1 —— Prop_Seg + Phase_Seg1

  • TimeSeg2 —— Phase_Seg2

二、例子

1.目标

  • f_CANclk:CAN模块时钟频率,这里为36 MHz

  • 目标波特率:500 kbps

  • Prescaler:6(分频系数,寄存器设定的值,实际分频为6)

  • TimeSeg1:13 TQ(包含传播段+相位缓冲段1,寄存器直接设置为13)

  • TimeSeg2:2 TQ(相位缓冲段2,寄存器直接设置为2)

  • 一个比特的总时间TQ数:Sync_Seg + TimeSeg1 + TimeSeg2 = 1 + 13 + 2 = 16 TQ

2. 计算公式

CAN波特率公式如下:

3. 具体计算过程

这里结果为375 kbps.

4. 推导500kbps配置

我们用同样的方法,反推500kbps的参数(TQ=16):

分频只能是整数,所以在36MHz时,TQ=16时无法整除出500kbps。

  • 如果Prescaler取4,波特率= 36,000,000 / (4*16) = 562,500 bps

  • 如果Prescaler取5,波特率= 36,000,000 / (5*16) = 450,000 bps

所以,在36MHz下要精确得到500kbps,TQ数需重新调整!

5. 最佳500kbps配置方法

常见办法1:调TQ(比特宽度)

如果TQ=12,则

这时:

  • Prescaler=6

  • Sync_Seg=1,TimeSeg1+TimeSeg2=11(比如TimeSeg1=9, TimeSeg2=2)

此时就可以实现500kbps波特率!

6. 代码配置举例(500kbps, 36MHz时钟,TQ=12)

hcan1.Init.Prescaler = 6;
hcan1.Init.Mode = CAN_MODE_NORMAL;
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_BS1_9TQ;   // PropSeg+PhaseSeg1=9
hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;   // PhaseSeg2=2

1 + 9 + 2 = 12TQ,总线速率 = 36MHz / (6*12) = 500,000 bps

f_CANclk (MHz)PrescalerTimeSeg1TimeSeg2Bit time(TQ)波特率 (bps)
3669212500,000
36129212250,000
36249212125,000

STM32 HAL库典型配置

CAN_HandleTypeDef hcan1;hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 6;              // 分频系数
hcan1.Init.Mode = CAN_MODE_NORMAL;     // 正常模式
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_BS1_9TQ;    // 9TQ
hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;     // 2TQ
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = DISABLE;
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = ENABLE;
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE;if (HAL_CAN_Init(&hcan1) != HAL_OK)
{// 错误处理
}

7.实用小结

  1. 推荐使用CubeMX自动生成配置,避免计算错误。

  2. 如果手动写代码,优先保证 Prescaler × (Sync_Seg + Prop_Seg + Phase_Seg1 + Phase_Seg2) 能整除 f_CANclk,并且 TQ 一般在8~25之间,推荐16TQ。

  3. 不同芯片的CAN_BS1CAN_BS2含义略有差异,查手册。

  4. 确保主频、分频、段宽参数设定正确,否则收发可能会出现不可预知的错误!

实验:

正常模式:两个CAN设备实现收发。

main.c

#include "sys.h"        
#include "delay.h"        
#include "led.h"          
#include "uart1.h"        
#include "can.h"      
#include "key.h"         uint8_t data_send[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; // 发送数据缓存
uint8_t data_receive[8];                                                  // 接收数据缓存int main(void)
{HAL_Init();                         // 初始化HAL库stm32_clock_init(RCC_PLL_MUL9);     // 设置系统时钟为72MHzled_init();                         // 初始化LEDuart1_init(115200);                 // 初始化串口1,波特率115200can_init();                         // 初始化CAN控制器key_init();                         // 初始化按键扫描printf("hello world!\r\n");         // 启动提示信息uint8_t i = 0;while(1){ if(key_scan() == 1)             // 检查按键是否被按下(返回1表示按下){for(i = 0; i < 8; i++)data_send[i]++;         // 发送内容自增,便于区分多次发送的数据can_send_data(0x12345678, data_send, 8); // 发送8字节数据,扩展帧ID为0x12345678}can_receive_data(data_receive); // 检查是否有数据接收,有则读取到data_receive}
}

can.c

#include "can.h"
#include "stdio.h"CAN_HandleTypeDef can_handle = {0}; // CAN控制器句柄// ===========================
// CAN初始化
// ===========================
void can_init(void)
{can_handle.Instance = CAN1;             // 选择CAN1模块can_handle.Init.Mode = CAN_MODE_NORMAL; // 设置CAN模式// CAN_MODE_NORMAL: 正常工作模式// CAN_MODE_LOOPBACK: 回环自测模式(自发自收,不上总线)// CAN_MODE_SILENT: 静默模式(只收不发)// CAN_MODE_SILENT_LOOPBACK: 静默回环模式(自测且不影响总线)can_handle.Init.Prescaler = 4;          // 分频系数,影响波特率can_handle.Init.TimeSeg1 = CAN_BS1_9TQ; // 时间段1(传播段+相位缓冲1),可选CAN_BS1_xTQ(x=1~16)can_handle.Init.TimeSeg2 = CAN_BS2_8TQ; // 时间段2(相位缓冲2),可选CAN_BS2_xTQ(x=1~8)can_handle.Init.SyncJumpWidth = CAN_SJW_1TQ; // 同步跳转宽度,CAN_SJW_1TQ~CAN_SJW_4TQcan_handle.Init.AutoBusOff           = DISABLE;  // 自动离线管理,ENABLE=自动进入BUS OFF,DISABLE=手动处理can_handle.Init.AutoRetransmission   = DISABLE;  // 自动重发,ENABLE=报文失败后自动重发can_handle.Init.AutoWakeUp           = DISABLE;  // 自动唤醒,ENABLE=休眠后有总线活动自动唤醒can_handle.Init.ReceiveFifoLocked    = DISABLE;  // FIFO锁定,ENABLE=FIFO满时新报文被丢弃,DISABLE=溢出后覆盖can_handle.Init.TimeTriggeredMode    = DISABLE;  // 时间触发模式,ENABLE=开启定时触发can_handle.Init.TransmitFifoPriority = DISABLE;  // 发送优先级,ENABLE=按报文优先级,DISABLE=先到先发HAL_CAN_Init(&can_handle);                     // 初始化CAN硬件// ==============================// 配置CAN滤波器,决定哪些ID可以被接收// ==============================CAN_FilterTypeDef can_filterconfig = {0};can_filterconfig.FilterMode = CAN_FILTERMODE_IDMASK; // 滤波模式// CAN_FILTERMODE_IDMASK: 掩码模式,常用于接收一类ID// CAN_FILTERMODE_IDLIST: 列表模式,精确匹配指定IDcan_filterconfig.FilterScale = CAN_FILTERSCALE_32BIT; // 滤波宽度// CAN_FILTERSCALE_16BIT: 16位宽(可配置更多过滤器),CAN_FILTERSCALE_32BIT: 32位宽can_filterconfig.FilterIdHigh = 0;       // 过滤器ID高16位(对应标准帧和扩展帧ID高位)can_filterconfig.FilterIdLow = 0;        // 过滤器ID低16位can_filterconfig.FilterMaskIdHigh = 0;   // 屏蔽位高16位,0表示不过滤can_filterconfig.FilterMaskIdLow = 0;    // 屏蔽位低16位,0表示不过滤can_filterconfig.FilterBank = 0;         // 过滤器编号(0~13)can_filterconfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 分配到FIFO0// CAN_FILTER_FIFO0 或 CAN_FILTER_FIFO1can_filterconfig.FilterActivation = CAN_FILTER_ENABLE;    // 启用过滤器can_filterconfig.SlaveStartFilterBank = 14; // 单CAN一般不用HAL_CAN_ConfigFilter(&can_handle, &can_filterconfig); // 设置过滤器HAL_CAN_Start(&can_handle);                 // 启动CAN模块
}// ===========================
// CAN底层硬件初始化,主要配置时钟和IO口
// ===========================
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{__HAL_RCC_CAN1_CLK_ENABLE();            // 使能CAN1时钟__HAL_RCC_GPIOA_CLK_ENABLE();           // 使能GPIOA时钟GPIO_InitTypeDef gpio_initstruct;gpio_initstruct.Pin = GPIO_PIN_12;              // CAN1_TX (PA12)gpio_initstruct.Mode = GPIO_MODE_AF_PP;         // 复用推挽输出gpio_initstruct.Pull = GPIO_PULLUP;             // 上拉gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;   // 高速HAL_GPIO_Init(GPIOA, &gpio_initstruct);gpio_initstruct.Pin = GPIO_PIN_11;              // CAN1_RX (PA11)gpio_initstruct.Mode = GPIO_MODE_AF_INPUT;      // 复用输入HAL_GPIO_Init(GPIOA, &gpio_initstruct);
}// ===========================
// 发送CAN数据帧
// 参数:
//   id —— 报文ID(标准帧用StdId,扩展帧用ExtId)
//   buf —— 8字节数据缓存
//   len —— 数据长度(0~8)
// 可选参数:
//   tx_header.IDE: CAN_ID_STD(标准帧),CAN_ID_EXT(扩展帧)
//   tx_header.RTR: CAN_RTR_DATA(数据帧),CAN_RTR_REMOTE(远程帧)
// ===========================
void can_send_data(uint32_t id, uint8_t *buf, uint8_t len)
{CAN_TxHeaderTypeDef tx_header = {0};  // 发送报文头结构体uint32_t tx_mail = CAN_TX_MAILBOX0;   // 发送邮箱,可为CAN_TX_MAILBOX0/1/2tx_header.ExtId = id;                 // 扩展帧IDtx_header.DLC = len;                  // 数据长度(0~8)tx_header.IDE = CAN_ID_EXT;           // 扩展帧// CAN_ID_STD: 标准帧,tx_header.StdId = id// CAN_ID_EXT: 扩展帧,tx_header.ExtId = idtx_header.RTR = CAN_RTR_DATA;         // 数据帧// CAN_RTR_DATA: 数据帧// CAN_RTR_REMOTE: 远程帧HAL_CAN_AddTxMessage(&can_handle, &tx_header, buf, &tx_mail); // 发送数据// 等待所有邮箱空闲(发送完毕再返回)while(HAL_CAN_GetTxMailboxesFreeLevel(&can_handle) != 3);// 打印调试信息uint8_t i = 0;printf("发送数据:\r\n");for(i = 0; i < len; i++)printf("%X ", buf[i]);printf("\r\n");
}// ===========================
// 接收CAN数据帧
// 参数:
//   buf —— 用于存放接收到的数据
// 返回值:
//   实际接收到的数据字节数(0表示无数据)
// ===========================
uint8_t can_receive_data(uint8_t *buf)
{CAN_RxHeaderTypeDef rx_header = {0}; // 接收报文头结构体// 查询FIFO0接收缓冲区是否有数据if(HAL_CAN_GetRxFifoFillLevel(&can_handle, CAN_RX_FIFO0) == 0)return 0;HAL_CAN_GetRxMessage(&can_handle, CAN_RX_FIFO0, &rx_header, buf); // 读取数据uint8_t i = 0;printf("接收数据:\r\n");for(i = 0; i < rx_header.DLC; i++)printf("%X ", buf[i]);printf("\r\n");return rx_header.DLC;   // 返回数据长度
}

can.h

#ifndef __CAN_H__     
#define __CAN_H__#include "sys.h"      // ========================
// 初始化CAN硬件与滤波器
// ========================
void can_init(void);// ========================
// 发送一帧CAN数据
// 参数:
//   id  —— 帧ID(标准帧/扩展帧均可)
//   buf —— 数据缓存区
//   len —— 数据长度(0~8字节)
// ========================
void can_send_data(uint32_t id, uint8_t *buf, uint8_t len);// ========================
// 接收一帧CAN数据
// 参数:
//   buf —— 存放接收数据的缓存区
// 返回:
//   实际接收到的数据长度
// ========================
uint8_t can_receive_data(uint8_t *buf);#endif

回环模式:使用回环模式实现自发自收;

只用修改模式即可

can_handle.Init.Mode = CAN_MODE_LOOPBACK; // 设置CAN模式

http://www.xdnf.cn/news/17116.html

相关文章:

  • 解决Node.js v12在Apple Silicon(M1/M2)上的安装问题
  • 使用R将nc文件转换为asc文件或者tif文件
  • 下载Android studio
  • try catch throw的本质
  • Linux《进程间通信(上)》
  • WARN:get Topic [TBW102] RouteInfoFromNameServer is not exist value
  • 使用MatterJs物理2D引擎实现重力和鼠标交互等功能,有点击事件(盒子堆叠效果)
  • [Oracle] NVL()函数
  • 测试单节点elasticsearch配置存储压缩后的比率
  • 河南萌新联赛2025第(四)场【补题】
  • 8月6日星期三今日早报简报微语报早读
  • JAVA无人系统台球茶室棋牌室系统支持H5小程序APP公众号源码
  • 基于跨境电商场景的智能商品管理系统,采用Bootstrap+Django+MySQL技术架构,实现用户行为追踪、智能推荐、多维度商品展示等核心功能
  • 8、项目管理
  • JAVA 程序员cursor 和idea 结合编程
  • Solidity 编程进阶
  • 8.6 JavaWeb(请求响应 P67-P74)
  • PyTorch入门引导
  • Go语言“fmt”包详解
  • 【Linux内核系列】:信号(上)
  • Docker的安装,服务器与客户端之间的通信
  • LeetCode每日一题,8-6
  • springboot项目justAuth扩展第二个小程序
  • Unity轻量观察相机
  • 功能安全和网络安全的综合保障流程
  • 云端软件工程智能代理:任务委托与自动化实践全解
  • CDP集群中通过Hive外部表迁移HBase数据的操作记录
  • 昇思+昇腾开发板+DeepSeek模型推理和性能优化
  • 自己本地搭建的服务器怎么接公网?公网IP直连服务器方法,和只有内网IP直接映射到互联网
  • 线性代数中矩阵的基本运算运算