九、STM32——CAN
1 概述
-
CAN只需要两根通信线(CAN_H、CAN_L),线路少,无需共地。
-
差分信号通信(利用两个信号的电压差值传递信息),抗干扰能力强。无需时钟线,通信速率由设备各自约定。
-
11位/29位报文ID,用于区分消息的功能,同时决定消息发送的优先级(ID号小的优先发送)。11位是标准帧、29位是扩展帧。
-
一个数据帧可配置 1 ~ 8字节的有效载荷(每次只能传输1~8字节数据)。
-
CAN总线的两种标准:
-
高速CAN(ISO11898):125K~1Mbps,<40m
-
低速CAN(ISO11519):10K~125Kbps,<1Km
-
1.1 CAN协议硬件电路
CAN协议硬件电路有闭环CAN总线和开环CAN总线两种接法
高速CAN使用闭环网络,CAN_H和CAN_L两端添加120Ω的终端电阻,终端电阻可以防止回波反射,并在没有设备操作总线的情况下将两根通信线的电压维持在同一水平。(下面都以介绍高速CAN为主,除非有特殊说明)
低速CAN使用开环网络,CAN_H和CAN_L其中一端添加2.2kΩ的终端电阻,它们同样有防止回波反射的作用。
1.2 CAN协议帧格式
CAN协议规定了以下5种类型的帧:
1.2.1 数据帧
-
SOF:首先开始的是SOF(逻辑电平显性0,表示数据帧的开始),总线被拉开。
-
ID:然后紧接着就是11位的ID位,是用来区分当前发送数据帧设备的ID,范围就是 0x000 ~ 0x7FF
-
RTR:用来区分数据帧和遥控帧,数据帧就是显性0,遥控帧就是显性1
-
IDE:是扩展位,如果是显性0就表示标准帧格式,隐性1表示扩展格式
-
r0:表示保留位
-
DLC:表示的是后面数据的长度,DLC是4位,范围是0000 ~ 1111,最大就是64位,也就是8字节。
-
CRC:校验段,当出现错误的时候这里基本上都会被发现的,然后驳回这个帧。
-
CRC界定符:在进入ACK之前刚开始有一个CRC界定符,这时候发送方释放CAN总线,发送一个显性1(CRC界)作为起始标志,然后如果有接收方的话,接收方就会拿到总线,这时候接收方就会把总线给拉开使得为显性0(ACK槽),当此时发送方再次拿到总线的时候就会发现总线上变为显性1(ACK界)了,那么就说明有人接收了,所以发送完成。
-
EOF:最后就是结束位EOF,长度为7位,均为隐性1。
数据帧扩展格式
CAN 2.0 时期, ID 不够用,出现了扩展格式,增加了 ID 的位数,为了区分标准格式与扩展格式,协议将标准格式中的 r1 赋予了新功能 IDE
1.2.2 遥控帧(远程帧)
遥控帧无数据段, RTR 为隐性电平 1 ,其他部分与数据帧相同
1.2.3 错误帧
总线上所有设备都会监督总线的数据,一旦发现“位错误”或“填充错误”或“ CRC 错误”或“格式错误”或“应答错误” ,这些设备便会发出错误帧来破坏数据,同时终止当前的发送设备
1.2.4 过载帧
当接收方收到大量数据而无法处理时,其可以发出过载帧,延缓发送方的数据发送,以平衡总线负载,避免数据丢失
1.2.5 帧间隔
将数据帧和遥控帧 与前面的帧分离开
2 CAN如何实现位时序与位同步
2.1 通过约定的波特率确定位时长
我们知道CAN总线是没有时钟线的,总线上的所有设备通过约定的波特率的方式确定每一个数据位的时长。
2.2 如何在一个位时间内正确采样获取数据
发送方以约定的位时长每隔固定时间输出一个数据位;接收方以约定的位时长每隔固定时间采样总线的电平,输入一个数据位。
理想状态下
,接收方能依次采样到发送方发出的每个数据位,且采样点位于数据位中心附近。
接收方刚开始采样正确,但是时钟有误差,随着误差积累,采样点逐渐偏离。
为了灵活调整每个采样点的位置,使采样点对齐数据位中心附近,CAN总线对每一个数据位的时长进行了更细的划分(即位时序),分为同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每个段又由若干个最小时间单位(Tq)构成,其中PBS1和PBS2的交界处即为采样点。
主要通过硬同步和再同步解决以上采样问题
2 STM32设置CAN波特率
2.1 波特率的设定
-
最小时间单位(Tq,Time Quantum)
-
同步段(SS,Synchronization Segment)1Tq
-
传播时间段(PTS,Propagation Time Segment)1-8Tq
-
相位缓冲段1(PBS1,Phase Buffer Segment1)1-8Tq
-
相位缓冲段2(PBS2,Phase Buffer Segment2)2-8Tq
-
再同步补偿宽度(SJW,reSynchronization Jump Width)1-4Tq
-
波特率分频器(BRP,Baud Rate Prescaler)
2.2 如何计算100K波特率
STM32中把传播时间段PTS和PBS1合并到了一起,形成了新的时间段1(BS1)总结:关于波特率设置
https://blog.csdn.net/piaolingyekong/article/details/124276670
3 STM32过滤器
-
CAN的数据发送,需要先存储在3个邮箱了的某一个
-
CAN数据的接收,需要先接收到FIFO0或者FIFO1中的三个邮箱中
CAN接收数据存储到邮箱中之前,有个接收过滤器,用于过滤ID,是否接收指定的ID发出的数据。
每组筛选器包含2个32位的寄存器,分别为 CAN_FxR1
和 CAN_FxR2
,它们用来存储要筛选的ID或掩码。
3.1 过滤的两种方式
过滤的方法分为以下两种模式:
-
标识符列表模式(CAN_FILTERMODE_IDLIST):它把要接收报文的ID列成一个表,要求报文ID与列表中的某一个标识符完全相同才可以接收。接收报文ID要完全匹配列表中的ID。
-
掩码模式(CAN_FILTERMODE_IDMASK):ID是指定某几位必须匹配才会接收,某几位是通过掩码来确定的,掩码就是指定ID中的那几位必须匹配,那几位不需要匹配,这样就会有个ID的范围。
3.2 过滤器如何配置
3.2.1 标识符列表模式
-
32位标识符模式:CAN_FxR1和CAN_FxR2各存储1个ID, 2个寄存器表示2个筛选的ID
-
16位标识符模式:CAN_FxR1和CAN_FxR2各存储2个ID, 2个寄存器表示4个筛选的ID
3.2.2 掩码模式
-
32位掩码模式:CAN_FxR1存储ID, CAN_FxR2存储哪个位必须要与CAN_FxR1中的ID一致 , 2个寄存器表示1组掩码。
-
16位掩码模式:CAN_FxR1高16位存储ID, 低16位存储哪个位必须要与高16位的ID一致; CAN_FxR2高16位存储ID, 低16位存储哪个位必须要与高16位的ID一致 2个寄存器表示2组掩码。
示例中的每个位,要对应看CAN_FxR1和CAN_FxR2寄存器每个位的功能。比如:
-
16列表或者掩码模式,都需要左移5个位,是因为,从左到右存储ID。右边空余5个位,分别是RTR、IDE(扩展帧)、EXID(扩展帧部分ID)
-
32列表或者掩码模式,都需要左移3位,是因为,从左到右存储ID。
分析如何接收0x200 - 0x2FF
4 STM32配置CAN通信程序
4.1 位时间变量设置
4.2 基本变量设置
-
时间触发模式(Time Triggered Communication Mode):会自动为报文生成时间戳,没有研究过,暂时默认关闭。
-
自动总线关闭管理(Automatic Bus-Off Management):没有研究过,暂时默认关闭。
-
自动唤醒模式(Automatic Wake-Up Mode):使能后,当CAN外设在休眠状态时如果CAN总线有数据,则自动唤醒CAN外设。建议使能。
-
自动重发(Automatic Retransmission):使能后,如果因为仲裁失败(总线冲突)或是其他原因导致发送失败,会自动重发。建议使能。
-
接收FIFO锁定模式(Receive Fifo Locked Mode):如果使能,当接收FIFO满时,下一条数据会被丢失。如果不使能,则覆盖前面的数据。
-
发送FIFO优先级(Transmit Fifo Priority):当发送邮箱中同时有多个帧,是按照先进先出的顺序发送还是按照ID的优先级发送。如果不使能,则按照ID优先级发送。如果使能,则按照先进先出优先级发送。
4.3 操作模式
-
正常模式:CAN外设正常地向CAN总线发送数据并从CAN总线上接收数据。
-
回环模式:CAN外设正常向CAN总线发送数据,同时接收自己发送的数据,但不从CAN总线上接收数据。
-
静默模式:CAN外设不向CAN总线发送数据,仅从CAN总线上接收数据,但不会应答。一般用于检测CAN总线的流量。
-
静默回环模式:CAN外设不会往CAN总线收发数据,仅给自己发送。一般用于自检。
4.4 筛选器设置
-
初始化后要设置筛选器。
-
启动CAN
-
激动CAN对应的FIFO0的待处理接收中断。
4.5 CAN相关中断
系统内核的CAN中断,也就是上面四种,通过 startup_stm32l431xx.s
文件可以看到:
STM32 HAL库,针对中断有进行了详细的划分:
为了能够使用中断处理函数,必须的先激活中断,也就是因为默认HAL库中断处理函数,对需要处理的中断类型做了判断。
需要用中断启动函数启动中断,启动函数如下:
//启动CAN中断函数
HAL_StatusTypeDef HAL_CAN_ActivateNotification(CAN_HandleTypeDef *hcan, uint32_t ActiveITs);
//禁用CAN中断函数
HAL_StatusTypeDef HAL_CAN_DeactivateNotification(CAN_HandleTypeDef *hcan, uint32_t InactiveITs);
启动中断具体如下:
//启动CAN发送中断
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_TX_MAILBOX_EMPTY);
//启动CAN接收中断-FIFO0接收新消息
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
//启动CAN发送中断-FIFO0接收满
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_FULL);
//启动CAN发送中断-FIFO0接收上溢
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_OVERRUN);
//启动CAN接收中断-FIFO1接收新消息
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING);
//启动CAN发送中断-FIFO1接收满
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_FULL);
//启动CAN发送中断-FIFO1接收上溢
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_OVERRUN);
//启动CAN-唤醒中断
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_WAKEUP);
//启动CAN-睡眠中断
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_SLEEP_ACK);
//启动CAN-错误告警中断
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_ERROR_WARNING);
//启动CAN-错误被动中断
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_ERROR_PASSIVE);
//启动CAN-总线关闭中断
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_BUSOFF);
//启动CAN-上一个错误代码中断
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_LAST_ERROR_CODE);
//启动CAN-错误中断
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_ERROR);
当产生对应的中断,就会调用对应的中断回调函数。
//CAN通信-发送完成回调函数
void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan);
void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan);
void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan);
//CAN通信-发送取消回调函数
void HAL_CAN_TxMailbox0AbortCallback(CAN_HandleTypeDef *hcan);
void HAL_CAN_TxMailbox1AbortCallback(CAN_HandleTypeDef *hcan);
void HAL_CAN_TxMailbox2AbortCallback(CAN_HandleTypeDef *hcan);
//CAN通信-FIFO0接收新消息回调函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan);
//CAN通信-FIFO0接收满回调函数
void HAL_CAN_RxFifo0FullCallback(CAN_HandleTypeDef *hcan);
//CAN通信-FIFO1接收新消息回调函数
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan);
//CAN通信-FIFO1接收满回调函数
void HAL_CAN_RxFifo1FullCallback(CAN_HandleTypeDef *hcan);
//CAN通信-休眠回调函数
void HAL_CAN_SleepCallback(CAN_HandleTypeDef *hcan);
//CAN通信-唤醒回调函数
void HAL_CAN_WakeUpFromRxMsgCallback(CAN_HandleTypeDef *hcan);
//CAN通信-错误回调函数
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan);
4.6 HAL库CAN接收数据
typedef struct
{
uint32_t StdId; /* 标准帧ID,0到0x7FF之间 */
uint32_t ExtId; /* 扩展帧ID,0到0x1FFFFFFF之间 */
uint32_t IDE; /* 指定是标准帧格式还是扩展帧格式 CAN_ID_STD 或者CAN_ID_EXT */
uint32_t RTR; /* 指定是标准帧,还是远程遥控帧 CAN_RTR_DATA 或者 CAN_RTR_REMOTE */
uint32_t DLC; /* 一帧的传输长度 */
/**
* 指定接收帧时捕获的时间戳计数器值。
* @note: 必须启用时间触发通信模式。
此参数必须是一个介于 Min_Data = 0 和 Max_Data = 0xFFFF 之间的数字。
*/
uint32_t Timestamp;
/**
* 指定匹配的接收筛选器元素的索引
* 此参数必须是一个介于 Min_Data = 0 和 Max_Data = 0xFF 之间的数字
*/
uint32_t FilterMatchIndex;
} CAN_RxHeaderTypeDef;
接收数据的代码如下:
void
HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CAN_RxHeaderTypeDef Header;
if(hcan->Instance == CAN1)
{
if(HAL_CAN_GetRxFifoFillLevel(hcan, CAN_RX_FIFO0) == 0)
return 0;//没有接收到数据
/* 如果没有 HAL_CAN_GetRxMessage 函数,接收到第一条数据之后,就会一直卡死在这里 */
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &Header, CAN_Receive_Data);
HAL_UART_Transmit_DMA (&huart1, CAN_Receive_Data, Header.DLC);
}
}
4.7 HAL库CAN发送数据
typedef struct
{
uint32_t StdId; /* 标准帧ID,0到0x7FF之间 */
uint32_t ExtId; /* 扩展帧ID,0到0x1FFFFFFF之间 */
uint32_t IDE; /* 指定是标准帧格式还是扩展帧格式 CAN_ID_STD 或者CAN_ID_EXT */
uint32_t RTR; /* 指定是标准帧,还是远程遥控帧 CAN_RTR_DATA 或者 CAN_RTR_REMOTE */
uint32_t DLC; /* 一帧的传输长度 */
/**
* 指定是否将开始帧传输时捕获的时间戳计数器值,发送在 DATA6 和 DATA7 中,替代 pData[6] 和 pData[7]。
* @note: 必须启用时间触发通信模式。
* @note: DLC 必须设置为 8 字节,以便发送这 2 字节。
此参数可以设置为 ENABLE 或 DISABLE。
*/
FunctionalState TransmitGlobalTime;
} CAN_TxHeaderTypeDef;
发送数据的代码如下:
CAN_TxHeaderTypeDef TxHeader;
uint32_t TxMailbox;
uint8_t aTxData[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
TxHeader.DLC = 8;
TxHeader.IDE = CAN_ID_STD;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.StdId = 0x321;
TxHeader.TransmitGlobalTime = DISABLE;
HAL_CAN_AddTxMessage (&hcan1, &TxHeader, aTxData, &TxMailbox);
/* 等待三个邮箱都空 */
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) != 3);