一、CAN总线简介
1.1 CAN简介
CAN 是 Controller Area Network 的缩写(以下称为 CAN),是 ISO 国际标准化的串行通信
协议。异步半双工。
ISO11898:123kbps~1Mbps。
ISO11519:125kbps
特点:
- 多主控制
- 没有地址信息
- 速度快,距离远
- 具有错误检测、错误通知、错误恢复功能
- 故障封闭功能
- 多节点连接
1.1.1 ISO11898、ISO11519
帮助记忆:显"0"。多点线与机制,一个是0全是0,所有1才为1。类似于上拉。
差分信号:抗干扰,双绞线(相互抵消电磁干扰),电压差值不变。
1.2 CAN协议帧
- 数据帧:发送单元向接收单元传送数据。
- 遥控帧:接收单元向具有相同ID的发送单元请求数据的帧。
- 错误帧:检测出错误,向其他单元通知错误。
- 过载帧:接收单元通知为做好准备。
- 间隔帧:将数据帧及遥控帧与前面的帧分隔开。
1.2.1 数据帧
1.2.1.1 帧起始(1bit)
1bit,显性电平。
1.2.1.2 仲裁段(12bit)
- 显性电平获取优先。
- 禁止高7位都为隐性电平(1111111XXXX)。标准:ID10~ID0。扩展:基本ID28~ID18,扩展ID17~ID0。
- RTR:0数据帧,1远程帧
- IDE:0标准,1扩展
- SRR:代替RTR,隐性电平。
1.2.1.3 控制段(6bit)
- r0,r1:保留,显性电平。
- DLC:4bit,数据长度,MSB,0~8。
1.2.1.4 数据段(0~8bytes)
- MSB
1.2.1.5 CRC段(16bit)
- 计算:帧起始、仲裁段、控制段、数据段。
1.2.1.6 ACK段(2bit)
- 发送单元:隐性,隐性。
- 接收单元:显性,隐性。
1.2.1.7 帧结束(7bit隐性)
1.2.2 位时序
1位,分为4个段,每个段分为若干个Tq。
- 同步段(SS):1Tq,
- 传播时间段(PTS):1~8Tq,
- 相位缓冲段1(PBS1):1~8Tq,
- 相位缓冲段2(PBS2):2~8Tq,
- 再同步补偿宽度(SJW):1~4T
1.2.3 位填充
当同样的电平持续 5 位时则添加一个位的反型数据。
发送单元的工作:
在发送数据帧和遥控帧时,SOF~CRC 段间的数据,相同电平如果持续 5 位,在下一个位(第 6 个位)则要插入 1 位与前 5 位反型的电平。
接收单元的工作:
在接收数据帧和遥控帧时,SOF~CRC 段间的数据,相同电平如果持续 5 位,需要删除下一个位(第 6 个位)再接收。如果这个第 6 个位的电平与前 5 位相同,将被视为错误并发送错误帧。
1.2.4 同步
硬件同步(帧起始同步);
再同步(发慢收快,延长相位缓冲段1;发快收慢,缩短相缓2)。
同步规则:
- 每位只同步一次。
- 任何一个隐性到显性的跳变都可以用于同步。
- 硬同步发生在SOF,其他接受节点调整各自的同步段。
- 重同步发生在帧除SOF之外的位置,当跳变沿落在了同步段之外的情况下。
1.2.5 总线优先级
- 在总线空闲态,最先开始发送消息的单元获得发送权。
- 多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送。
- 具有相同 ID 的数据帧和遥控帧在总线上竞争时,仲裁段的最后一位(RTR)为显性位的数据帧具有优先权 ,可继续发送。(数据大于遥控)
- 标准格式 ID 与具有相同 ID 的遥控帧或者扩展格式的数据帧在总线上竞争时,标准格式的 RTR 位为显性位的具有优先权 ,可继续发送。(标准大于扩展)
扩展知识:
STM32F1 自带的是 bxCAN,即基本扩展 CAN。它支持 CAN 协议 2.0A 和 2.0B。 CAN2.0A
只能处理标准数据帧,扩展帧的内容会识别错误; CAN2.0B Active 可以处理标准数据帧和扩展
数据帧;而 CAN2.0B passive 只能处理标准数据帧,扩展帧的内容会忽略。 它的设计目标是,
以最小的 CPU 负荷来高效处理大量收到的报文。它也支持报文发送的优先级要求(优先级特性
可软件配置)。
STM32F1 的 bxCAN 的主要特点有:
- 支持 CAN 协议 2.0A 和 2.0B 主动模式
- 波特率最高达 1Mbps
- 支持时间触发通信
- 具有 3 个发送邮箱
- 具有 3 级深度的 2 个接收 FIFO
- 可变的过滤器组(最多 28 个)
过滤器组的位宽和工作模式
为了过滤出一组 标识符,应该设置过滤器组工作在屏蔽位模式。
为了过滤出一个标识符,应该设置过滤器组工作在标识符列表模式。应用程序不用的过滤器组,应该保持在禁用状态。
优先级:32位>16位,标识符列表>屏蔽位,过滤号小的优先级高。
举例说明:过滤器组0工作在:1个32位过滤器-标识符屏蔽模式,CAN_F0R1=0XFFFF0000,CAN_F0R2=0XFF00FF00,
1.2.6 CAN收发流程
CAN发送
程序选择 1 个空置的邮箱(TME=1) →设置标识符(ID),数据长度和发送数据→设置 CAN_TIxR 的 TXRQ 位为 1,请求发送→邮箱挂号(等待成为最高优先级) →预定发送(等待总线空闲) →发送→邮箱空置。
CAN接收
FIFO 空→收到有效报文→挂号_1(存入 FIFO 的一个邮箱,这个由硬件控制,我们不需要理会) →收到有效报文→挂号_2→收到有效报文→挂号_3→收到有效报文溢出。
通过查询 CAN_RFxR 的 FMP 寄存器来得到,只要 FMP不为 0,我们就可以从 FIFO 读出收到的报文。
STM32的CAN位时间特性:
1.3 CAN寄存器
- CAN_MCR:INRQ位,先设置为1,初始化,设置为0,正常工作。
- CAN_BTR:设置分频,Tbs1,Tbs2及Tsjw等参数,进而设置波特率。可以设置环回模式。
- CAN_TIxR:设置标识符,帧类型。TXRQ请求邮箱发送。x=0~3。
- CAN_TDTxR:设置数据长度。
- CAN_TDLxR、CAN_TDHxR:数据。
- CAN_RIxR:设置接收标识符。
- CAN_FM1R:设置28个过滤器的模式。
- CAN_FS1R:设置各过滤器组的位宽。
- CAN_FFA1R:设置报文存放的FIFO。
- CAN_FA1R:过滤器激活寄存器。
- CAN_FiRx:过滤器位。i=0~13,x=1或2。
二、程序设计
2.1 CAN的HAL驱动
HAL_CAN_Init函数
cpp
HAL_StatusTypeDef HAL_CAN_Init(CAN_HandleTypeDef *hcan);
typedef struct __CAN_HandleTypeDef
{
CAN_TypeDef *Instance; /* CAN 控制寄存器基地址 */
CAN_InitTypeDef Init; /* 初始化参数结构体 */
__IO HAL_CAN_StateTypeDef State; /* CAN 通讯状态 */
__IO uint32_t ErrorCode; /* CAN 通讯结果编码 */
} CAN_HandleTypeDef;
typedef struct
{
uint32_t Prescaler; /* 分频值, 可以配置为 1~1024 间的任意整数 */
uint32_t Mode; /* can 操作模式,有效值参考 CAN_operating_mode 的描述 */
uint32_t SyncJumpWidth; /* CAN 硬件的最大超时时间 */
uint32_t TimeSeg1; /* CAN_time_quantum_in_bit_segment_1 */
uint32_t TimeSeg2; /* CAN_time_quantum_in_bit_segment_2 */
FunctionalState TimeTriggeredMode; /* 启用或禁用时间触发模式 */
FunctionalState AutoBusOff; /* 禁止/使能软件自动断开总线的功能 */
FunctionalState AutoWakeUp; /* 禁止/使能 CAN 的自动唤醒功能 */
FunctionalState AutoRetransmission; /* 禁止/使能 CAN 的自动传输模式 */
FunctionalState ReceiveFifoLocked; /* 禁止/使能 CAN 的接收 FIFO */
FunctionalState TransmitFifoPriority; /* 禁止/使能 CAN 的发送 FIFO */
} CAN_InitTypeDef;
HAL_CAN_ConfigFilter 函数
cpp
HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan,
CAN_FilterTypeDef *sFilterConfig)
typedef struct
{
uint32_t FilterIdHigh; /* 过滤器标识符高位 */
uint32_t FilterIdLow; /* 过滤器标识符低位 */
uint32_t FilterMaskIdHigh; /* 过滤器掩码号高位(列表模式下,也是属于标识符) */
uint32_t FilterMaskIdLow; /* 过滤器掩码号低位(列表模式下,也是属于标识符) */
uint32_t FilterFIFOAssignment; /* 与过滤器组管理的 FIFO */
uint32_t FilterBank; /* 指定过滤器组, 单 CAN 为 0~13, 双 CAN 可为 0~27 */
uint32_t FilterMode; /* 过滤器的模式 标识符屏蔽位模式/标识符列表模式 */
uint32_t FilterScale; /* 过滤器的位宽 32 位/16 位 */
uint32_t FilterActivation; /* 禁用或者使能过滤器 */
uint32_t SlaveStartFilterBank; /* 双 CAN 模式下,规定 CAN 的主从模式的过滤器分配 */
} CAN_FilterTypeDef;
HAL_CAN_Start 函数
cpp
HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan);
HAL_CAN_ActivateNotification 函数
cpp
HAL_StatusTypeDef HAL_CAN_ActivateNotification(CAN_HandleTypeDef *hcan,
uint32_t ActiveITs);
HAL_CAN_AddTxMessage 函数
cpp
HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan,
CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox)
typedef struct
{
uint32_t StdId; /* 标准标识符 11 位 范围:0~0x7FF */
uint32_t ExtId; /* 扩展标识符 29 位 范围:0~0x1FFFFFFF */
uint32_t IDE; /* 标识符类型 CAN_ID_STD / CAN_ID_EXT */
uint32_t RTR; /* 帧类型 CAN_RTR_DATA / CAN_RTR_REMOTE */
uint32_t DLC; /* 帧长度 范围:0~8byte */
FunctionalState TransmitGlobalTime; /* 时间戳是否在开始时捕获 */
} CAN_TxHeaderTypeDef;
HAL_CAN_GetRxMessage 函数
cpp
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan,
uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[])
typedef struct
{
uint32_t StdId; /* 标准标识符 11 位 范围:0~0x7FF */
uint32_t ExtId; /* 扩展标识符 29 位 范围:0~0x1FFFFFFF */
uint32_t IDE; /* 标识符类型 CAN_ID_STD / CAN_ID_EXT */
uint32_t RTR; /* 帧类型 CAN_RTR_DATA / CAN_RTR_REMOTE */
uint32_t DLC; /* 帧长度 范围:0~8byte */
uint32_t Timestamp; /* 在帧接收开始时开始捕获的时间戳 */
uint32_t FilterMatchIndex; /* 过滤器匹配序号 */
} CAN_RxHeaderTypeDef;
2.2 CAN初始化配置步骤
1.CAN参数初始化
cpp
can_init(CAN_SJW_1TQ, CAN_BS2_8TQ, CAN_BS1_9TQ, 4, CAN_MODE_LOOPBACK);//500Kbps
2.开启CAN和对应管脚时钟,配置CAN_TX和CAN_RX的复用功能输出
3.设置过滤器
4.CAN数据接收和发送
cpp
/**
* @brief CAN 发送一组数据
* @note 发送格式固定为: 标准 ID, 数据帧
* @param id : 标准 ID(11 位)
* @retval 发送状态 0, 成功; 1, 失败;
*/
uint8_t can_send_msg(uint32_t id, uint8_t *msg, uint8_t len)
{
uint32_t TxMailbox = CAN_TX_MAILBOX0;
g_canx_txheader.StdId = id; /* 标准标识符 */
g_canx_txheader.ExtId = id; /* 扩展标识符(29 位) */
g_canx_txheader.IDE = CAN_ID_STD; /* 使用标准帧 */
g_canx_txheader.RTR = CAN_RTR_DATA; /* 数据帧 */
g_canx_txheader.DLC = len;
if (HAL_CAN_AddTxMessage(&g_canx_handler, &g_canx_txheader,
msg, &TxMailbox) != HAL_OK) /* 发送消息 */
{
return 1;
}
/* 等待发送完成,所有邮箱为空(3 个邮箱) */
while (HAL_CAN_GetTxMailboxesFreeLevel(&g_canx_handler) != 3);
return 0;
}
/**
* @brief CAN 接收数据查询
* @note 接收数据格式固定为: 标准 ID, 数据帧
* @param id : 要查询的 标准 ID(11 位)
* @param buf : 数据缓存区
* @retval 接收结果
* @arg 0 , 无数据被接收到;
* @arg 其他, 接收的数据长度
*/
uint8_t can_receive_msg(uint32_t id, uint8_t *buf)
{
if (HAL_CAN_GetRxFifoFillLevel(&g_canx_handler, CAN_RX_FIFO0) != 1)
{
return 0;
}
if (HAL_CAN_GetRxMessage(&g_canx_handler, CAN_RX_FIFO0, &g_canx_rxheader,
buf) != HAL_OK)
{
return 0;
}
/* 接收到的 ID 不对 / 不是标准帧 / 不是数据帧 */
if (g_canx_rxheader.StdId!= id || g_canx_rxheader.IDE != CAN_ID_STD ||
g_canx_rxheader.RTR != CAN_RTR_DATA)
{
return 0;
}
return g_canx_rxheader.DLC;
}
三、常见问题
1、为什么用CAN?
汽车的电子控制单元逐渐增多。各电控单元之间的信号交换导致汽车线束的级数增加。大大减少了汽车的线束。可靠性下降。增加了重量。
2、如何减少波特率的误差带来的通信错误?
CAN总线规定信号的跳变沿时刻进行同步,将误差累计限制在两个跳变沿。 当发送数据出现都为0或者1的时候,同样不跳变造成误差累计怎么办? 采用填充位在连续5个相同位后插入一个相反位,产生跳变沿,用于同步。
3、终端电阻的作用
- 提高抗干扰能力,让高频低能量信号快速走掉。
- 确保总线快速进入隐性状态,让寄生电容的能量快速走掉。
- 提高信号质量,放置在总线两端,让反射能量降低。
4、需要在接收数据的时候用一个标志来判断当前发送的数据是真正的命令还是干扰,用一个特殊的值来做为接收指令的开始,这里选用0XA5做为数据的开始,为什么选0xA5呢?
因为0xA5的 二进制数位 1010 0101 刚好是一个1一个0,间隔开,用这个数字做为开始可以很大程度上的避免干扰信号,因为干扰信号一般不会是这种高低高低很有规律的信号。于是协议就改为 0xA5 为第一个数据,做为上位机发送命令的标志。
5、CAN物理层常见故障(CAN示波器)
(1)CAN节点供电正常,CAN工具上就是接收不到CAN报文数据或者报错,可能是什么原因?
首先确定CAN器件程序有bug;终端电阻不匹配;CAN收发器的影响。
(2)测试CAN物理波形,发现报文出现严重的振铃和反射现象?
缩短引线长度;加粗到线、印制铜箔的宽度;减小信号的传输距离;采用引线电感小的元器件;阻抗匹配
(3)通信错误会不会是CAN收发器故障引脚,如何判断收发器的好坏?
可以在ECU上电的CAN总线空闲情况下,测量CANH和CANL对地的电压是否在2.5V左右,如果出现0V或5V之类的,可考虑收发器故障问题。
(4)来自想用厂家的两个CAN节点又可以正常通信,为什么两个不同厂家的CAN节点连接通信时好时坏,甚至不能正常通信?
不同的厂家使用不用的采样点,也有可能造成通信不良。
(5)所有在测ECU节点CAN功能单独测试正常,装车后CAN功能失常,CAN错误频发,特别是新能源车上?
电磁干扰:选择性能好的隔离收发器;增加CAN双绞程度;布线尽量远离;感性防护器件。