0 工具准备
1.STM32CubeMX
1 前言
本文介绍基于STM32CubeMX,修改基于STM32CubeMX生成的FDCAN初始化代码,成为我们能够正常使用的状态。
2 初始化代码修改
2.1 FDCAN初始化代码修改
typedef enum
{
FDCAN_100K = 0,
FDCAN_250K,
FDCAN_500K,
FDCAN_1M,
FDCAN_2_5M,
FDCAN_5M,
} FDCAN_BAUD_CFG_Type;
typedef enum
{
FDCAN_NORMAL = FDCAN_MODE_NORMAL, /* 正常模式 */
FDCAN_RESTRICTED_OPERATIO = FDCAN_MODE_RESTRICTED_OPERATION, /* 受限模式 */
FDCAN_BUS_MONITORING = FDCAN_MODE_BUS_MONITORING, /* 监控模式 */
FDCAN_INTERNAL_LOOPBACK = FDCAN_MODE_INTERNAL_LOOPBACK, /* 内部环回模式 */
FDCAN_EXTERNAL_LOOPBACK = FDCAN_MODE_EXTERNAL_LOOPBACK, /* 外部环回模式 */
} FDCAN_MODE_Type;
const fdcan_cfg_t fdCANCfg[] =
{
/* 输入时钟100MHz,测试100K、250K、500K、1M、2M、5M收发CAN2.0正常 */
{100000, 17, 2}, /* 采样点 :90% */
{250000, 17, 2}, /* 采样点 :90% */
{500000, 17, 2}, /* 采样点 :90% */
{1000000, 17, 2}, /* 采样点 :90% */
{2500000, 17, 2}, /* 采样点 :90% */
{5000000, 17, 2}, /* 采样点 :90% */
};
/**
* @brief 设置FDCAN标准ID过滤器
*
* @param id ID(0-0x7ff)
* @param mask 掩码(0-0x7ff)
*/
void set_fdcan_std_filter(u32 id, u32 mask)
{
hfdCan2StdFilter.IdType = FDCAN_STANDARD_ID; /* 设置标准 ID */
/* 用于过滤索引,如果是标准 ID,范围 0 到 127。如果是扩展 ID,范围 0 到 64 */
/* 过滤索引和前面配置的过滤器个数对应,如果个数为n,则索引为0 - n-1 */
hfdCan2StdFilter.FilterIndex = 0;
hfdCan2StdFilter.FilterType = FDCAN_FILTER_MASK; /* 过滤器采样屏蔽位模式 */
hfdCan2StdFilter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; /* 如果过滤匹配,将数据保存到 Rx FIFO 0 */
hfdCan2StdFilter.FilterID1 = id; /* 屏蔽位模式下,FilterID1 是消息 ID */
hfdCan2StdFilter.FilterID2 = mask; /* 屏蔽位模式下,FilterID2 是消息屏蔽位 */
}
/**
* @brief 设置FDCAN扩展ID过滤器
*
* @param id ID(0-0x1fffffff)
* @param mask 掩码(0-0x1fffffff)
*/
void set_fdcan_ext_filter(u32 id, u32 mask)
{
hfdCan2ExtFilter.IdType = FDCAN_EXTENDED_ID; /* 设置扩展 ID */
/* 用于过滤索引,如果是标准 ID,范围 0 到 127。如果是扩展 ID,范围 0 到 64 */
/* 过滤索引和前面配置的过滤器个数对应,如果个数为n,则索引为0 - n-1 */
hfdCan2ExtFilter.FilterIndex = 0;
hfdCan2ExtFilter.FilterType = FDCAN_FILTER_MASK; /* 过滤器采样屏蔽位模式 */
hfdCan2ExtFilter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; /* 如果过滤匹配,将数据保存到 Rx FIFO 0 */
hfdCan2ExtFilter.FilterID1 = id; /* 屏蔽位模式下,FilterID1 是消息 ID */
hfdCan2ExtFilter.FilterID2 = mask; /* 屏蔽位模式下,FilterID2 是消息屏蔽位 */
}
/**
* @brief FDCAN初始化
*
* @param mode 模式 环回、正常等
* @param fdCANBaud FDCAN波特率
* @return int 0:成功 -1:失败
*/
int MX_FDCAN_Init(FDCAN_MODE_Type mode, FDCAN_BAUD_CFG_Type fdCANBaud)
{
HAL_StatusTypeDef ret;
/* 选择FDCAN2 */
hfdcan2.Instance = FDCAN2;
/* 帧格式选择 */
/* FDCAN_FRAME_CLASSIC:经典CAN格式(CAN2.0),最高支持1M
仅支持收发CAN2.0报文
*/
/* FDCAN_FRAME_FD_NO_BRS:不可变波特率FDCAN格式(仲裁段和数据段波特率一致),不兼容CAN2.0
仲裁段和数据段的波特率一致
支持收发FDCAN报文和CAN2.0报文
*/
/* FDCAN_FRAME_FD_BRS:可变波特率FDCAN格式(仲裁段和数据段波特率可以一致),兼容CAN2.0
在仲裁段使用较低波特率如500K,在数据段使用较高波特率如2M
支持收发FDCAN报文和CAN2.0报文
*/
/* 帧格式:不可变波特率 */
hfdcan2.Init.FrameFormat = FDCAN_FRAME_FD_NO_BRS;
/* 工作模式:自环、正常工作模式等 */
/* 实测使用自环有bug,自己收不到自己发出的CAN报文 */
hfdcan2.Init.Mode = mode;
/* 关闭重传、发送暂停、协议错误处理 */
hfdcan2.Init.AutoRetransmission = DISABLE;
hfdcan2.Init.TransmitPause = DISABLE;
hfdcan2.Init.ProtocolException = DISABLE;
/* FDCAN分频系数 */
/* CAN/FDCAN 1bit = 1Tq同步段 + 1-8Tq传播时间段 + 1-8相位缓冲段1 + 1-8相位缓冲段2 */
/* CAN/FDCAN 实际使用中:1bit = 1Tq同步段 + TimeSeg1/BS1(传播时间段+相位缓冲段)+ TimeSeg2/BS2(相位缓冲段2) */
/* 采样点推荐位置:85-90% 满足 (1Tq + BS1) / (1Tq + BS1 + BS2) , 建议87.5 */
/* 经典CAN只需要关注仲裁阶段波特率设置,FDCAN需要设置仲裁段和数据段波特率且允许二者不一致(波特率可变)*/
/*
选择CAN2.0或FDCAN不可变波特率,仲裁段设置的波特率就是整个CAN报文波特率
选择FDCAN可变波特率,仲裁段设置的波特率是仲裁段的CAN报文波特率
*/
hfdcan2.Init.NominalPrescaler = FDCAN2_INPUT_CLOCK / (fdCANCfg[fdCANBaud].baud * (1 + fdCANCfg[fdCANBaud].bs1 + fdCANCfg[fdCANBaud].bs2));
/* 用于动态调节Phase_Seg1和Phase_Seg2,所以不可以比Phase_Seg2和Phase_Seg2大,固定为1 */
hfdcan2.Init.NominalSyncJumpWidth = 1;
/* 设置位段1、位段2 */
hfdcan2.Init.NominalTimeSeg1 = fdCANCfg[fdCANBaud].bs1;
hfdcan2.Init.NominalTimeSeg2 = fdCANCfg[fdCANBaud].bs2;
/* 只有选择FDCAN可变波特率帧格式这里的配置才作为数据段波特率 */
hfdcan2.Init.DataPrescaler = FDCAN2_INPUT_CLOCK / (fdCANCfg[fdCANBaud].baud * (1 + fdCANCfg[fdCANBaud].bs1 + fdCANCfg[fdCANBaud].bs2));
/* 用于动态调节Phase_Seg1和Phase_Seg2,所以不可以比Phase_Seg2和Phase_Seg2大,固定为1 */
hfdcan2.Init.DataSyncJumpWidth = 1;
/* 设置位段1、位段2 */
hfdcan2.Init.DataTimeSeg1 = fdCANCfg[fdCANBaud].bs1;
hfdcan2.Init.DataTimeSeg2 = fdCANCfg[fdCANBaud].bs2;
/*
FDCAN RAM偏移地址,只有一个FDCAN,偏移地址为0
FDCAN1和FDCAN2共享2560个字(4Byte)
*/
hfdcan2.Init.MessageRAMOffset = 0;
/* 标准ID过滤器个数,范围0到128 */
hfdcan2.Init.StdFiltersNbr = 1;
/* 标准ID过滤器个数,范围0到64 */
hfdcan2.Init.ExtFiltersNbr = 1;
/* RXFIFO0元素个数,范围0到64 */
hfdcan2.Init.RxFifo0ElmtsNbr = 64;
/* RXFIFO0每个元素数据大小 */
hfdcan2.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_64;
/* RXFIFO1元素个数,范围0到64 */
hfdcan2.Init.RxFifo1ElmtsNbr = 0;
/* RXFIFO1每个元素数据大小 */
hfdcan2.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_64;
/* 设置Rx Buffer元素个数,范围0-64 */
hfdcan2.Init.RxBuffersNbr = 0;
/* 设置RxBuffer元素中每个数据大小,范围0-64 */
hfdcan2.Init.RxBufferSize = FDCAN_DATA_BYTES_64;
/* Tx Event FIFO元素个数,范围0到32 */
hfdcan2.Init.TxEventsNbr = 0;
/* 设置专用的 Tx Buffer 元素个数,范围 0 到 32*/
hfdcan2.Init.TxBuffersNbr = 0;
/* 设置用于Tx FIFO/Queue 的 Tx Buffers 个数。范围 0 到 32*/
hfdcan2.Init.TxFifoQueueElmtsNbr = 32;
/* 设置 FIFO 模式或者 QUEUE 队列模式 */
hfdcan2.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
/* 设置 Tx Element 中的数据域大小 */
hfdcan2.Init.TxElmtSize = FDCAN_DATA_BYTES_8;
/* 初始化FDCAN2 */
if (HAL_FDCAN_Init(&hfdcan2) != HAL_OK)
{
return -1;
}
/* 配置标准ID过滤器 */
if (HAL_FDCAN_ConfigFilter(&hfdcan2, &hfdCan2StdFilter) != HAL_OK)
{
return -1;
}
/* 配置扩展ID过滤器 */
if (HAL_FDCAN_ConfigFilter(&hfdcan2, &hfdCan2ExtFilter) != HAL_OK)
{
return -1;
}
/* 配置全局过滤器,配置后过滤器配置才会生效 */
ret = HAL_FDCAN_ConfigGlobalFilter(&hfdcan2, FDCAN_REJECT, FDCAN_REJECT,
ENABLE, ENABLE);
if (ret != HAL_OK)
{
return -1;
}
/* 设置RxFIFO0的水印为1,接收到1个CAN报文就触发中断 */
ret = HAL_FDCAN_ConfigFifoWatermark(&hfdcan2, FDCAN_CFG_RX_FIFO0, 1);
if (ret != HAL_OK)
{
return -1;
}
/* 激活RXFIFO0的水印通知中断 */
ret = HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO0_WATERMARK, 0);
if (ret != HAL_OK)
{
return -1;
}
/* 启动 FDCAN */
HAL_FDCAN_Start(&hfdcan2);
return 0;
}
为了便于使用,这里为FDCAN初始化函数增加了模式、波特率2个形参。所有的配置全部有详细注释,可以参考注释进行配置。
2.2 增加FDCAN的RXFIFO0接收回调函数
/**
* @brief FDCAN的RXFIFO0接收回调
*
* @param hfdcan FDCAN句柄
* @param RxFifo0ITs RXFIFO0状态
*/
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
FDCAN_RxHeaderTypeDef rxHeader;
uint8_t rxData[64];
if (hfdcan == &hfdcan2)
{
if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_WATERMARK) != RESET)
{
/* 轮询RX FIFO,直到无数据可读 */
for (;;)
{
if (HAL_FDCAN_GetRxFifoFillLevel(hfdcan, FDCAN_RX_FIFO0) > 0)
{
/* 从 RX FIFO0 读取数据 */
HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &rxHeader, rxData);
add_fdcan_recv_msg(&rxHeader, rxData);
}
else
{
break;
}
}
}
}
}
2.3 增加FDCAN的发送函数
/**
* @brief 发送CAN/FDCAN报文
*
* @param buff 数据
* @param len 长度
* @return int 0-成功 -1-失败
*/
int fdcan_send(u8 *buff, u8 len)
{
int timeout = 0;
HAL_StatusTypeDef ret;
/* 初始化FDCAN发送参数 */
fdCANSendCfg.Identifier = 0x147; /* 设置消息的ID */
fdCANSendCfg.IdType = FDCAN_STANDARD_ID; /* 标准ID */
fdCANSendCfg.TxFrameType = FDCAN_DATA_FRAME; /* 数据帧 */
fdCANSendCfg.ErrorStateIndicator = FDCAN_ESI_ACTIVE; /* 设置错误状态指示 */
fdCANSendCfg.BitRateSwitch = FDCAN_BRS_OFF; /* 关闭可变波特率 */
fdCANSendCfg.FDFormat = FDCAN_CLASSIC_CAN; /* FDCAN格式 */
fdCANSendCfg.TxEventFifoControl = FDCAN_NO_TX_EVENTS; /* 用于发送事件 FIFO 控制, 不存储 */
fdCANSendCfg.MessageMarker = 0; /* 用于复制到 TX EVENT FIFO 的消息 Maker 来识别消息状态(检查消息是否发送成功等),范围0到0xFF */
fdCANSendCfg.DataLength = (uint32_t)len << 16; /* 发送数据长度 */
/* 等待TX FIFO可用 */
while (HAL_FDCAN_GetTxFifoFreeLevel(&hfdcan2) == 0)
{
HAL_Delay(1);
timeout++;
if (timeout > 100)
{
return -1;
}
}
/* 添加数据到 TX FIFO */
ret = HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan2, &fdCANSendCfg, buff);
if (ret != HAL_OK)
{
return -1;
}
add_fdcan_send_msg(&fdCANSendCfg, buff);
return 0;
}
以上代码和CAN调试器在100K、250K、500K、1M下测试100万次收发正常。在内部回环、外部回环下无法收到FDCAN自己发出的报文,怀疑是HAL库bug,本文使用的HAL库版本如下:
STM32Cube FW_H7 V1.11.0
此外,发现在高速接收CAN报文时,如果stm32H743主频被设置为480MHz则会丢掉几乎所有包,而主频设置为400MHz则能够正常接收。
如果你也遇到了和我一样的问题,欢迎留言交流!
问题确认:
stm32H743主频设置为480MHz时FDCAN外设在回环模式工作不正常。需要修改2个地方:
(1)将主频修改为400MHz。
(2)LDO稳压器输出的电压选择VOS1(480MHz时是VOS0),语句如下:
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
stm32H743即使是v版本,最好也不要将主频设置到480MHz,否则会有各种头疼和意想不到(在实际使用过程中,将主频提高到480MHz,温度上升将近10℃,一些外设也出现了令人头脑眩晕的问题)的问题,为了稳定和省事起见,还是老老实实将主频设置为400MHz。