本文主要讲解 CANFD 的发送模式,CANFD 支持的发送模式有 3 种:TXBUFF, TXQUEUE, TXFIFO,另外发送模式还支持发送事件。对 CANFD 的接收模式有兴趣的可以参看我之前的文章《一文读懂CANFD的接收模式》。本文以
STM32H7 为为例,将对以下的关键词进行详细的讲解。
TXBUFF
TXQUEUE
TXFIFO
发送事件
事件戳
一, CANFD 的三种发送模式
CANFD 支持三种发送模式,再配置过程中可以选择其中一种或者两种(TXQUEUE 和 TXFIFO 不能同时使用),使用两种发送方式的的组合可以是:TXBUFF + TXQUEUE 和 TXBUFF + TXFIFO。
三者的区别如下:
1. TXBUFF 发送模式
TXBUFF 的发送方式为专用发送发送缓存区,每个换缓存区仅能保存一帧报文,根据对 Message RAM 的配置可以选择将报文放入指定编号的发送缓存区, 最大支持 32 个专用发送缓存区。
使用 TXBUFF 发送模式时,需要 Message RAM 的配置方式为:
hfdcan1.Init.TxBuffersNbr = 10; // 设置 TXBUFF 的数量为 10
使用 TXBUFF 的发送方式可以使能发送完成中断,开启方法为:
/* 开启 TXBUFF0 发送完成中断,注意第三个参数使用宏的方式 */
HAL_FDCAN_Ac
tivateNotification(&hfdcan1, FDCAN_IT_TX_COMPLETE , FDCAN_TX_BUFFER0);
/* 发送完成中断回调函数 */
void HAL_FDCAN_TxBufferCompleteCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndexes)
{
if (hfdcan == &hfdcan1)
{
/* TODO */
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_TX_COMPLETE, BufferIndexes);
}
}
使用 TXBUFF 发送一帧报文的示例:
FDCAN1_TxHeader.Identifier = 0x103;
FDCAN1_TxHeader.IdType = FDCAN_STANDARD_ID;
FDCAN1_TxHeader.TxFrameType = FDCAN_DATA_FRAME;
FDCAN1_TxHeader.DataLength = FDCAN_DLC_BYTES_8;
FDCAN1_TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
FDCAN1_TxHeader.BitRateSwitch = FDCAN_BRS_ON;
FDCAN1_TxHeader.FDFormat = FDCAN_FD_CAN;
FDCAN1_TxHeader.TxEventFifoControl = FDCAN_STORE_TX_EVENTS;
FDCAN1_TxHeader.MessageMarker = 22;
/* 将要发送的报文放入发送缓存区 */
HAL_FDCAN_AddMessageToTxBuffer(&hfdcan1, &FDCAN1_TxHeader, rxdata1, FDCAN_TX_BUFFER0);
FDCAN1_TxHeader.Identifier = 0x102;
HAL_FDCAN_AddMessageToTxBuffer(&hfdcan1, &FDCAN1_TxHeader, rxdata1, FDCAN_TX_BUFFER1);
FDCAN1_TxHeader.Identifier = 0x101;
HAL_FDCAN_AddMessageToTxBuffer(&hfdcan1, &FDCAN1_TxHeader, rxdata1, FDCAN_TX_BUFFER2);
FDCAN1_TxHeader.Identifier = 0x100;
HAL_FDCAN_AddMessageToTxBuffer(&hfdcan1, &FDCAN1_TxHeader, rxdata1, FDCAN_TX_BUFFER3);
HAL_FDCAN_EnableTxBufferRequest(&hfdcan1, FDCAN_TX_BUFFER0 | FDCAN_TX_BUFFER1 | FDCAN_TX_BUFFER2 | FDCAN_TX_BUFFER3);
将发送消息的请求给到 CANFD 控制之后,CANFD 会自动将 TXBUFF 的消息发送,会根据保存在 TXBUFF 中的消息的帧 ID 的大小来发送 ,帧 ID 数值越小,优先级越高。
2. TXQUEUE 发送模式
TXQUEUE 的发送方式为专用发送队列,最大支持 32 个深度的发送队列,将需要放的发送的数据放入发送队列之后,硬件会根据报文的帧 ID 不同,按照帧 ID 数值越小的先发送,例如压了2帧数据,帧 ID 分别是 0x101 和 0X102,先压入 0x102 在压入 0x101 ,那么先发出去的消息是 0x101 的报文。
使用 TXQUEUE 发送模式时,需要 Message RAM 的配置方式为:
hfdcan1.Init.TxFifoQueueElmtsNbr = 10;
/* 使能 QUEUE 模式 */
hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_QUEUE_OPERATION;
使用 TXBUFF 发送报文的示例:
FDCAN1_TxHeader.Identifier = 0x102;
FDCAN1_TxHeader.IdType = FDCAN_STANDARD_ID;
FDCAN1_TxHeader.TxFrameType = FDCAN_DATA_FRAME;
FDCAN1_TxHeader.DataLength = FDCAN_DLC_BYTES_8;
FDCAN1_TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
FDCAN1_TxHeader.BitRateSwitch = FDCAN_BRS_ON;
FDCAN1_TxHeader.FDFormat = FDCAN_FD_CAN;
FDCAN1_TxHeader.TxEventFifoControl = FDCAN_STORE_TX_EVENTS;
FDCAN1_TxHeader.MessageMarker = 0;
FDCAN1_TxHeader1.Identifier = 0x101;
FDCAN1_TxHeader1.IdType = FDCAN_STANDARD_ID;
FDCAN1_TxHeader1.TxFrameType = FDCAN_DATA_FRAME;
FDCAN1_TxHeader1.DataLength = FDCAN_DLC_BYTES_8;
FDCAN1_TxHeader1.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
FDCAN1_TxHeader1.BitRateSwitch = FDCAN_BRS_ON;
FDCAN1_TxHeader1.FDFormat = FDCAN_FD_CAN;
FDCAN1_TxHeader1.TxEventFifoControl = FDCAN_STORE_TX_EVENTS;
FDCAN1_TxHeader1.MessageMarker = 1;
/* 压入 QUEUE 的帧 ID 顺序为:0x102, 0x102, 0x102, 0x101 */
HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &FDCAN1_TxHeader, rxdata1);
HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &FDCAN1_TxHeader, rxdata1);
HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &FDCAN1_TxHeader, rxdata1);
HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &FDCAN1_TxHeader1, rxdata1);
依次收到的报文的顺序是:
在发送第二帧的报文的时候检测到有多个帧 ID 不同的报文,QUEUE 模式会优先发送帧 ID 较小(优先级更高的消息)。
3. TXFIFO 发送模式
TXFIFO 的发送方式为先入先出,最大支持 32 个深度的发送 FIFO,将需要放的发送的数据放入发送 FIFO 之后,硬件会根据报文的放入的顺序,依次发送。
使用 TXFIFO 发送模式时,需要 Message RAM 的配置方式为:
hfdcan1.Init.TxFifoQueueElmtsNbr = 10;
/* 使能 QUEUE 模式 */
hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
使用 TXFIFO 的发送方式可以使能 FIFO 空中断,开启方法为:
/* 开启 TXFIFO 空中断 */
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_TX_FIFO_EMPTY , 0);
/* FIFO 空中断回调函数 */
void HAL_FDCAN_TxFifoEmptyCallback(FDCAN_HandleTypeDef *hfdcan)
{
if (hfdcan == &hfdcan1)
{
/* TODO */
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_TX_FIFO_EMPTY , 0);
}
}
将发送模式修改为 TXFIFO 之后,使用 TXQUEUE 的发送代码,接收到报文的顺序为:
按照压入 FIFO 的顺序,发送了报文。
4. TXBUFF + TXQUEUE 的混合发送模式
使用 TXBUFF + TXQUEUE 模式,准备好的报文如下图所示的顺序放入了 Message RAM.
在混合模式下,会依次检索 TXBUFF 和 TXQUEUE 中的优先级最高的消息进行发送,发送的顺序为:
TXBUFF3 (ID = 1)
TXQUEU8 (ID = 2)
TXBUFF0 (ID = 3)
TXQUEUE7 (ID = 4)
TXBUFF4 (ID = 8)
TXBUFF2 (ID = 12)
TXBUFF1 (ID = 15)
TXBUFF5 (ID = 24)
5. TXBUFF + TXFIFO 的混合发送模式
使用 TXBUFF + TXFIFO 模式,准备好的报文如下图所示的顺序放入了 Message RAM.
在混合模式下,会依次检索 TXBUFF 中优先级最高的消息与 TXFIFO 中第一条的消息进行比较,从他们二者中选择优先级更高的报文进行发送,发送的顺序为:
TXBUFF3 (ID = 1),TXBUFF 中优先级最高的是 ID =1 ,TXFIFO 中第一条消息的优先级 ID = 4;
TXBUFF0 (ID = 3),TXBUFF 中优先级最高的是 ID =3 ,TXFIFO 中第一条消息的优先级 ID = 4;
TXFIFO7 (ID = 4), 前两条消息发送之后,TXBUFF 中优先级最高的 ID = 8, TXFIFO 第一条消息 ID = 4;
TXFIFO8 (ID = 2), 前三条消息发送之后,TXBUFF 中优先级最高的 ID = 8, 剩余 TXFIFO 第一条消息 ID = 2;
TXBUFF4 (ID = 8)
TXBUFF2 (ID = 12)
TXBUFF1 (ID = 15)
TXBUFF5 (ID = 24)
二, CANFD 的发送事件
CANFD 中增加了发送事件的功能,即在发送成功之后可以记录下发送的信息,发送事件的使用场景可以是,发送某个紧急报文时,可以将其记录下来。开启发送事件记录功能需要在 Message RAM 中开启, CANFD 中支持最大记录事件的个数为 32.
/* 设置记录发送事件的个数为 10 */
hfdcan1.Init.TxEventsNbr = 10;
根据 Message RAM 的配置,可以设置新事件到来的中断回调以及事件的水位中断,用来通知到用户发送事件的存储已经快满了。
/* 设置发送事件的水位为 4 */
HAL_FDCAN_ConfigFifoWatermark(&hfdcan1, FDCAN_CFG_TX_EVENT_FIFO, 4);
/* 设置响应的中断类型 */
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_TX_EVT_FIFO_WATERMARK |
FDCAN_IT_TX_EVT_FIFO_NEW_DATA |, 0);
设置好中断之后,可以在中断中捕获对应的事件
/* 发送事件的回调函数 */
void HAL_FDCAN_TxEventFifoCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t TxEventFifoITs)
{
if (hfdcan == &hfdcan1)
{
if ((TxEventFifoITs & FDCAN_IT_TX_EVT_FIFO_NEW_DATA) != RESET)
{
/* TODO */
HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_TX_EVT_FIFO_NEW_DATA, 0);
}
if ((TxEventFifoITs & FDCAN_IT_TX_EVT_FIFO_WATERMARK) != RESET)
{
/* TODO */
HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_TX_EVT_FIFO_WATERMARK, 0);
}
}
}
当捕获到对应的时间后,用户需要使用获取事件的 API 来获取事件,该 API 在裸机下可以直接在中断中读取,在 RTOS 中可以使用发送一个信号量的方式在线程中读取。
HAL_FDCAN_GetTxEvent(&hfdcan1, &tx_event);
以下是发送一帧报文,然后保存这个发送事件:
FDCAN1_TxHeader.Identifier = 0x102;
FDCAN1_TxHeader.IdType = FDCAN_STANDARD_ID;
FDCAN1_TxHeader.TxFrameType = FDCAN_DATA_FRAME;
FDCAN1_TxHeader.DataLength = FDCAN_DLC_BYTES_8;
FDCAN1_TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
FDCAN1_TxHeader.BitRateSwitch = FDCAN_BRS_ON;
FDCAN1_TxHeader.FDFormat = FDCAN_FD_CAN;
/* 设置保存发送事件 */
FDCAN1_TxHeader.TxEventFifoControl = FDCAN_STORE_TX_EVENTS;
FDCAN1_TxHeader.MessageMarker = 0;
HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &FDCAN1_TxHeader, rxdata);
MessageMarker 是消息标签,可以在发送消息的时候给每个报文打上这个标签,用来在获取事件的时候方便用户知道是发送的哪个标签的报文。
获取事件可以得到的信息,用结构体 FDCAN_TxEventFifoTypeDef 来记录,换句话说能获取到的信息,都在这个结构体里面
typedef struct
{
uint32_t Identifier; // 帧 ID
uint32_t IdType; // 帧 ID 类型
uint32_t TxFrameType; // 发送帧类型
uint32_t DataLength; // 发送数据长度
uint32_t ErrorStateIndicator; // 错误状态描述
uint32_t BitRateSwitch; // 波特率切换
uint32_t FDFormat; // 是不是 CANFD
uint32_t TxTimestamp; // 时间戳
uint32_t MessageMarker; // 消息编号
uint32_t EventType; // 事件类型
} FDCAN_TxEventFifoTypeDef;
大部分都可以发送报文的结构体一样。
TxTimestamp : CANFD 外设有一个 16 位的定时器,用来记录发送的时间
EventType : 事件类型,可以是发送事件,也可以取消了但仍发送的事件。
三,CANFD 时间戳
CANFD 提供一个 16 位回卷计数器来生成时间戳。预分频器 TSCC.TCP 可配置为以 CAN位时间的倍数 (1…16) 为计数器提供时钟。计数器值可通过 TSCV[TCV] 读取。对寄存器 TSCV 进行写访问会将计数器复位为 0。时间戳计数器回卷时,中断标志 IR[TSW] 会置 1。 开始接收/发送帧时,会捕获计数器值,并将该值存储在接收缓冲区/接收 FIFO (RXTS[15:0])或发送事件 FIFO (TXTS[15:0]) 元素的时间戳部分。
开启时间戳功能
/* 设置分频系数为 1 */
HAL_FDCAN_ConfigTimestampCounter(&hfdcan1, FDCAN_TIMESTAMP_PRESC_1);
/* 使用内部时钟源 */
HAL_FDCAN_EnableTimestampCounter(&hfdcan1, FDCAN_TIMESTAMP_INTERNAL);
在开启时间戳功能之后,就能在 获取发送事件的时候知道发送报文的时间戳。
四, 总结
CANFD 是在 CAN 的基础上改进而来,增加了很多新的特性,这些新的特性赋予了 CANFD 更大的功能,根据这些特性来适配项目的需求,有利于设计出更加健壮的程序,对一些未提到的细节做以补充:
TXBUFF 和 TXFIFO/TXQUEUE 总是从 TXBUFF 开始查询
CANFD 的发送模式非常灵活,没有特别需求直接选用 TXFIFO 的方式,最简便
注意激活多种中断的时候,使用或的方式
激活 TXBUFF 时,建议使用宏的方式,32个 TXBUFF 每个占用一个 bit,根据这个特性用户自己去设计需要关系的 TXBUFF 的标号。
参考文档《RM0433》《AN5348》。
调试设备 ZLG-USBCANFD-200U
按照压入 FIFO 的顺序,发送了报文。
4. TXBUFF + TXQUEUE 的混合发送模式
使用 TXBUFF + TXQUEUE 模式,准备好的报文如下图所示的顺序放入了 Message RAM.
在混合模式下,会依次检索 TXBUFF 和 TXQUEUE 中的优先级最高的消息进行发送,发送的顺序为:
TXBUFF3 (ID = 1)
TXQUEU8 (ID = 2)
TXBUFF0 (ID = 3)
TXQUEUE7 (ID = 4)
TXBUFF4 (ID = 8)
TXBUFF2 (ID = 12)
TXBUFF1 (ID = 15)
TXBUFF5 (ID = 24)
5. TXBUFF + TXFIFO 的混合发送模式
使用 TXBUFF + TXFIFO 模式,准备好的报文如下图所示的顺序放入了 Message RAM.
在混合模式下,会依次检索 TXBUFF 中优先级最高的消息与 TXFIFO 中第一条的消息进行比较,从他们二者中选择优先级更高的报文进行发送,发送的顺序为:
TXBUFF3 (ID = 1),TXBUFF 中优先级最高的是 ID =1 ,TXFIFO 中第一条消息的优先级 ID = 4;
TXBUFF0 (ID = 3),TXBUFF 中优先级最高的是 ID =3 ,TXFIFO 中第一条消息的优先级 ID = 4;
TXFIFO7 (ID = 4), 前两条消息发送之后,TXBUFF 中优先级最高的 ID = 8, TXFIFO 第一条消息 ID = 4;
TXFIFO8 (ID = 2), 前三条消息发送之后,TXBUFF 中优先级最高的 ID = 8, 剩余 TXFIFO 第一条消息 ID = 2;
TXBUFF4 (ID = 8)
TXBUFF2 (ID = 12)
TXBUFF1 (ID = 15)
TXBUFF5 (ID = 24)
二, CANFD 的发送事件
CANFD 中增加了发送事件的功能,即在发送成功之后可以记录下发送的信息,发送事件的使用场景可以是,发送某个紧急报文时,可以将其记录下来。开启发送事件记录功能需要在 Message RAM 中开启, CANFD 中支持最大记录事件的个数为 32.