使用CUBEMX配置CANFD
3. 配置CANFD
3.1 波特率计算
- 波特率
波特率 = FDCAN时钟频率 / (NominalPrescaler × (1 + NominalTimeSeg1 + NominalTimeSeg2))
- 时间量子(TQ)
时间量子也就是一个Tick的时间
时间量子 = FDCAN时钟频率 / NominalPrescaler
- 位时间
这里的1就是同步段NominalSyncJumpWidth,同步段一般设置为固定的1TQ;
NominalTimeSeg1决定了采样点的位置,采样点在NominalTimeSeg1段的结束处;
NominalTimeSeg2是采样点到位结束的距离,越大对抖动的容忍度越高。
位时间 = 1 + NominalTimeSeg1 + NominalTimeSeg2 个时间量子
- 采样点
采样点一般设置在75%左右。
采样点位置 = (1 + NominalTimeSeg1) / (1 + NominalTimeSeg1 + NominalTimeSeg2)
- 波特率设置注意事项
1. NominalPrescaler应该尽可能小,以提高采样精度,减少误差;
2. CANFD推荐仲裁域NominalPrescaler <= 数据域NominalPrescaler;
3. SJW应尽量大,尽量保持与TSEG2一致,以提高位宽容忍度;
4. 波特率大于800K,推荐采样点在75%;波特率大于500K,推荐采样点在80%;波特率小于500K,推荐采样点在87.5%;
5. 尽量保证总线上所有节点的采样点一致,CANFD的仲裁域和数据域采样点不要求一致;
6. CANFD仲裁域与数据域的波特率之比应该大于等于1/8.
3.2 配置参数
在这里我们目标是使用CANFD的加速模式,仲裁域波特率为1M,数据域波特率为2M。
Frame Format
- Classic mode : 标准的can模式,非CANFD,单帧数据最多8个字节。
- FD mode without BitRate Switching:CANFD模式,仲裁域和数据域波特率相同,CANFD单帧数据可扩展至64个字节。
- FD mode with BitRate Switching:CANFD加速模式,数据域波特率可与仲裁域波特率不同,数据域波特率大于仲裁域波特率。
Data Prescaler 这里是数据域的波特率设置,在CANFD加速模式下有效。
波特率 = FDCAN时钟频率 / (NominalPrescaler × (1 + NominalTimeSeg1 + NominalTimeSeg2))
------
2000 Kbits/s = 50MHZ / (1 x (1 + 23 + 1))
数据域和仲裁域的波特率不建议差距太大,会导致时钟同步出错,通讯稳定性下降
Message Ram Offset: 地址偏移,如果设置两路CANFD的话,第二路CANFD的Message Ram Offset地址需要偏移,避免冲突。具体的偏移值为
Std Filters Nbr * 1 + Ext Filters Nbr*2 + Rx Fifo0 Elmts Nbr * 18 + Rx Fifo1 Elmts Nbr* 18 + Rx Buffers Nbr * 18
,需要注意的是 STM32H7的Message RAM最大为10KB,为2560个words(在这里一个word为32个Bits)。Std Filters Nbr: 设置标准帧滤波器的个数,每个滤波器占据1个word;
Ext Filters Nbr: 设置扩展帧滤波器的个数,每个滤波器占据2个word;
Rx Fifo0 Elmts Nbr:数据接收区域0的个数,遵从先入先出原则,在代码中可以设置哪些帧落入接收区域0;
Rx Fifo0 Elmts Size:接收区域0中数据的长度,最大可以接收64个bytes;
Rx Fifo1 Elmts Nbr:数据接收区域1的个数,遵从先入先出原则,在代码中可以设置哪些帧落入接收区域1;
Rx Fifo1 Elmts Size:接收区域1中数据的长度,最大可以接收64个bytes;
# 滤波设置参考函数
void ConfigureFDCANFilters(void)
{
FDCAN_FilterTypeDef sFilterConfig;
// 配置1: 标准帧路由到FIFO0 (高优先级)
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_MASK;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
// 拒绝特定ID的消息
//sFilterConfig.FilterConfig = FDCAN_FILTER_REJECT; // 拒绝匹配的消息
sFilterConfig.FilterID1 = 0x100; // 发动机控制
sFilterConfig.FilterID2 = 0x7FF; // 掩码
HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig);
// 配置2: 扩展帧路由到FIFO1 (低优先级)
sFilterConfig.IdType = FDCAN_EXTENDED_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_MASK;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1;
sFilterConfig.FilterID1 = 0x18FF1000; // 诊断数据
sFilterConfig.FilterID2 = 0x1FFFFFFF; // 掩码
HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig);
// 配置3: 紧急消息路由到Buffer0
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 1;
sFilterConfig.FilterType = FDCAN_FILTER_MASK;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXBUFFER;
sFilterConfig.FilterID1 = 0x300; // 紧急停止
sFilterConfig.FilterID2 = 0x7FF; // 掩码
sFilterConfig.RxBufferIndex = 0; // Buffer0
HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig);
// 配置4: 安全消息路由到Buffer1
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 2;
sFilterConfig.FilterType = FDCAN_FILTER_MASK;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXBUFFER;
sFilterConfig.FilterID1 = 0x400; // 安全气囊
sFilterConfig.FilterID2 = 0x7FF; // 掩码
sFilterConfig.RxBufferIndex = 1; // Buffer1
HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig);
}
- Rx Buffers Nbr: 专用数据缓存区,实时性较高,在代码中可以设置哪些帧落入Buffers区域;
- Rx Buffers Size: 专用数据缓存区域中数据的长度,最大可以接收64个bytes;
- Tx Fifo Queue Elmts Nbr:发送区域FIFO区域数据长度,这里设置为8,意味着我们可以一次性发送8帧数据,而不需要增加延时以等待发送完成;
- Tx Fifo Queue Mode
- FIFO mode:先入先出原则;
- Queue mode:优先级原则;
- Bit Timings Parameters :仲裁域波特率设置
波特率 = FDCAN时钟频率 / (NominalPrescaler × (1 + NominalTimeSeg1 + NominalTimeSeg2))
------
1000 Kbits/s = 50MHZ / (10 x (1 + 3 + 1))
3.2.1 配置中断线
每路CANFD有两个中断线可以选择使用fdcan1 interrupt 0
和fdcan1 interrupt 1
.项目中根据需求将不同类型的中断分配到不同的中断线上。默认情况下,所有中断都指向了中断线0,fdcan1 interrupt 0
.
配置中断线需要在代码中手动输入。
// 将接收中断分配到中断线0
HAL_FDCAN_ConfigInterruptLines(&hfdcan1,
FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_RX_FIFO1_NEW_MESSAGE,
FDCAN_INTERRUPT_LINE0);
// 将错误中断分配到中断线1
HAL_FDCAN_ConfigInterruptLines(&hfdcan1,
FDCAN_IT_ERROR_LOGGING_OVERFLOW | FDCAN_IT_RAM_ACCESS_FAILURE,
FDCAN_INTERRUPT_LINE1);
可供使用配置的中断:
接收相关中断:
FDCAN_IT_RX_FIFO0_NEW_MESSAGE - Rx FIFO 0新消息
FDCAN_IT_RX_FIFO0_WATERMARK - Rx FIFO 0水位标记
FDCAN_IT_RX_FIFO0_FULL - Rx FIFO 0满
FDCAN_IT_RX_FIFO0_MESSAGE_LOST - Rx FIFO 0消息丢失
FDCAN_IT_RX_FIFO1_NEW_MESSAGE - Rx FIFO 1新消息
FDCAN_IT_RX_FIFO1_WATERMARK - Rx FIFO 1水位标记
FDCAN_IT_RX_FIFO1_FULL - Rx FIFO 1满
FDCAN_IT_RX_FIFO1_MESSAGE_LOST - Rx FIFO 1消息丢失
FDCAN_IT_RX_BUFFER_NEW_MESSAGE - Rx缓冲区新消息
FDCAN_IT_RX_HIGH_PRIORITY_MSG - 高优先级消息
发送相关中断:
FDCAN_IT_TX_COMPLETE - 发送完成
FDCAN_IT_TX_ABORT_COMPLETE - 发送中止完成
FDCAN_IT_TX_FIFO_EMPTY - 发送FIFO空
错误相关中断:
FDCAN_IT_RAM_ACCESS_FAILURE - RAM访问失败
FDCAN_IT_ERROR_LOGGING_OVERFLOW - 错误日志溢出
FDCAN_IT_RAM_WATCHDOG - RAM看门狗
FDCAN_IT_ARB_PROTOCOL_ERROR - 仲裁协议错误
FDCAN_IT_DATA_PROTOCOL_ERROR - 数据协议错误
FDCAN_IT_RESERVED_ADDRESS_ACCESS - 保留地址访问
3.2.2 端口设置
3.3 函数使用
3.3.1 接收
- FIFO0回调函数
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
FDCAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[64];
// 数据存储在FIFO0中
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK)
{
// 处理FIFO0中的数据
printf("收到FIFO0数据: ID=0x%X, 长度=%d\n",
RxHeader.Identifier, RxHeader.DataLength);
// 处理数据内容
for (int i = 0; i < RxHeader.DataLength; i++) {
printf("数据[%d] = 0x%02X\n", i, RxData[i]);
}
}
}
- FIFO1回调函数
void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs)
{
FDCAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[64];
// 数据存储在FIFO1中
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO1, &RxHeader, RxData) == HAL_OK)
{
// 处理FIFO1中的数据
printf("收到FIFO1数据: ID=0x%X, 长度=%d\n",
RxHeader.Identifier, RxHeader.DataLength);
// 处理数据内容
for (int i = 0; i < RxHeader.DataLength; i++) {
printf("数据[%d] = 0x%02X\n", i, RxData[i]);
}
}
}
- Buffer回调函数
void HAL_FDCAN_RxBufferNewMessageCallback(FDCAN_HandleTypeDef *hfdcan)
{
FDCAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[64];
// 检查哪个Buffer有数据
for (int i = 0; i < hfdcan->Init.RxBuffersNbr; i++)
{
if (HAL_FDCAN_GetRxMessage(hfdcan, i, &RxHeader, RxData) == HAL_OK)
{
// 数据存储在Buffer i中
printf("收到Buffer%d数据: ID=0x%X, 长度=%d\n",
i, RxHeader.Identifier, RxHeader.DataLength);
// 处理数据内容
for (int j = 0; j < RxHeader.DataLength; j++) {
printf("数据[%d] = 0x%02X\n", j, RxData[j]);
}
}
}
}
- 批量处理FIFO数据
void ProcessFIFOData(FDCAN_HandleTypeDef *hfdcan, uint32_t FifoLocation)
{
FDCAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[64];
uint32_t messageCount = 0;
// 获取FIFO中的消息数量
uint32_t fifoCount = HAL_FDCAN_GetRxFifoFillLevel(hfdcan, FifoLocation);
printf("FIFO中有 %d 条消息\n", fifoCount);
// 批量处理所有消息
while (fifoCount > 0)
{
if (HAL_FDCAN_GetRxMessage(hfdcan, FifoLocation, &RxHeader, RxData) == HAL_OK)
{
messageCount++;
// 根据FIFO位置处理数据
if (FifoLocation == FDCAN_RX_FIFO0)
{
printf("FIFO0消息%d: ID=0x%X\n", messageCount, RxHeader.Identifier);
// 处理高优先级数据
ProcessHighPriorityData(&RxHeader, RxData);
}
else if (FifoLocation == FDCAN_RX_FIFO1)
{
printf("FIFO1消息%d: ID=0x%X\n", messageCount, RxHeader.Identifier);
// 处理低优先级数据
ProcessLowPriorityData(&RxHeader, RxData);
}
}
fifoCount = HAL_FDCAN_GetRxFifoFillLevel(hfdcan, FifoLocation);
}
}
- 处理BUFFER数据
void ProcessBufferData(FDCAN_HandleTypeDef *hfdcan)
{
FDCAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[64];
// 检查所有Buffer
for (int i = 0; i < hfdcan->Init.RxBuffersNbr; i++)
{
if (HAL_FDCAN_GetRxMessage(hfdcan, i, &RxHeader, RxData) == HAL_OK)
{
printf("Buffer%d: ID=0x%X, 长度=%d\n",
i, RxHeader.Identifier, RxHeader.DataLength);
// 根据Buffer索引处理数据
switch (i)
{
case 0:
ProcessEmergencyData(&RxHeader, RxData); // 紧急数据
break;
case 1:
ProcessSafetyData(&RxHeader, RxData); // 安全数据
break;
default:
ProcessGeneralData(&RxHeader, RxData); // 一般数据
break;
}
}
}
}
3.3.2 发送
- 模板
hfdcan1为can的实例;id为待发送的CANID;data指向待发送的数据指针;发送的数据长度是len。
#define hcan_t FDCAN_HandleTypeDef
uint8_t fdcanx_send_data(hcan_t *hfdcan, uint16_t id, uint8_t *data, uint32_t len)
{
FDCAN_TxHeaderTypeDef pTxHeader;
pTxHeader.Identifier=id;
pTxHeader.IdType=FDCAN_STANDARD_ID;
pTxHeader.TxFrameType=FDCAN_DATA_FRAME;
if(len<=8)
pTxHeader.DataLength = len;
if(len==12)
pTxHeader.DataLength = FDCAN_DLC_BYTES_12;
if(len==16)
pTxHeader.DataLength = FDCAN_DLC_BYTES_16;
if(len==20)
pTxHeader.DataLength = FDCAN_DLC_BYTES_20;
if(len==24)
pTxHeader.DataLength = FDCAN_DLC_BYTES_24;
if(len==32)
pTxHeader.DataLength = FDCAN_DLC_BYTES_32;
if(len==48)
pTxHeader.DataLength = FDCAN_DLC_BYTES_48;
if(len==64)
pTxHeader.DataLength = FDCAN_DLC_BYTES_64;
pTxHeader.ErrorStateIndicator=FDCAN_ESI_ACTIVE;
pTxHeader.BitRateSwitch=FDCAN_BRS_ON;
pTxHeader.FDFormat=FDCAN_FD_CAN;
pTxHeader.TxEventFifoControl=FDCAN_NO_TX_EVENTS;
pTxHeader.MessageMarker=0;
if(HAL_FDCAN_AddMessageToTxFifoQ(hfdcan, &pTxHeader, data)!=HAL_OK)
return 1;//·¢ËÍ
return 0;
}
- 使用
hfdcan1为can的实例;0x201为待发送的CANID;tx_data1指向待发送的数据指针;发送的数据长度是8。
uint8_t tx_data1[] = {0,1,2,3,4,5,6,7};
fdcanx_send_data(&hfdcan1, 0x201, tx_data1, 8);
3.3.3 常用的函数
- 中断触发机制设置
// 每当有新消息进入FIFO时触发中断
// 触发时机:
// 消息1进入 → 中断1
// 消息2进入 → 中断2
// 消息3进入 → 中断3
// ...
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);
// 当FIFO中消息数量达到水位线时触发中断
// 触发时机:
// 消息1-15进入 → 无中断
// 消息16进入 → 中断1
// 消息17-31进入 → 无中断
// 消息32进入 → 中断2
HAL_FDCAN_ConfigFifoWatermark(&hfdcan1, FDCAN_RX_FIFO0, 16); // 设置水位线为16
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_WATERMARK, 0);
// 当FIFO满时触发中断
// 触发时机:
// 消息1-63进入 → 无中断
// 消息64进入 → 中断1(FIFO满)
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_FULL, 0);