FDCAN应用说明
概述
最近用到了FDCAN,下面对FDCAN的一些使用注意事项进行分析。
一些FDCAN的基本概念可以看这些文章。一文搞懂CAN FD总线协议帧格式 - 知乎、STM32H743使用FDCAN+TJA1042T配合CubeMX及HAL库实现仲裁段1M速率,数据段5M速率的FDCAN总线传输记录_stm32h743 can cubemx-CSDN博客
假设作为软件工程师,不想关注具体里面的东西,可以简单理解为每个CAN交互报文都是如下格式。ID和CRC是协议必须带的,Data0-Datan是实际要发送的内容。
对于FDCAN和CAN的差异,从软件侧来看,主要包括如下。由于ID和CRC要占不少东西,而且还有抢占的问题,所以一般软件能用FDCAN就用FDCAN。
CAN | FDCAN | |
---|---|---|
Data最大长度(Bytes) | 8 | 64 |
Data段最大速度 | 1Mbps | 5Mbps(其他段最大还是1Mbps) |
PHY芯片 | 普通的TJA1050就行 | 如果要大于1Mbps,用TJA1042 |
ST芯片时钟配置
在STM32H5上面使用时,里面有2个FDCAN控制器,可独立使用。
但是FDCAN2的时钟配置好像必须一样,不然行为就很奇怪。其他配置没细的尝试,应该主要和红框有关,其他影响不大。
某种情况下,IO有限,只能使用FDCAN2时,分频系数必须配置为DIV1
。
ST芯片FDCAN重置
FDCAN有个比较麻烦的地方,就是其是有ACK机制的,也就是发送的数据包如果没ACK(或者没接PHY转换芯片,其会监控TX和RX信号),数据包会一直在队列中。
但是应用某些情况下需要把FIFO中的数据包都清空。可以先调用HAL_FDCAN_Stop
接口,再调用HAL_FDCAN_Start
接口,即可重置。
void FDCAN_DeConfig(FDCAN_HandleTypeDef *hfdcan)
{
if (HAL_FDCAN_Stop(hfdcan) != HAL_OK)
{
while(1);
}
}
void FDCAN_Config(FDCAN_HandleTypeDef *hfdcan)
{
...
/* Start the FDCAN module */
if (HAL_FDCAN_Start(hfdcan) != HAL_OK)
{
while(1);
}
}
void fdcan_control_restart(FDCAN_HandleTypeDef *hfdcan)
{
FDCAN_DeConfig(hfdcan);
FDCAN_Config(hfdcan);
}
FDCAN的TX和RX
需要注意,当有PHY芯片时,如果用逻辑分析仪看MCU的TX和RX信号,会发现TX发送任何数据,RX就会收到与之对应的数据。
这也是为什么MCU可以做到冲突规避的原理。
注意,可以把总线所有设备都拿掉,直接看TX,RX的信号。
FDCAN的ID唯一性
注意,注意,注意
,这里每个设备最好都用一个唯一的ID来总线上交互,不然会丢包。
由于总线上每个设备都可以随时发起数据包请求,当然应用协议上做好限制,交互是响应式(Request+Response)的请求模式的话, 允许所有设备用一个ID进行交互。
因为当设备的ID相同时,假设同时发起来FDCAN请求,那FDCAN的硬件仲裁机制将无法正常工作,这时总线上很可能有多个设备同时工作,有些设备的数据包可能没发送出去,但是硬件却认为数据包已经发送成功了。这个在大规模数据压力测试时出现会恶心死。
FDCAN的拓扑结构
标准的CAN网络要求有一个总线,所有设备都接入到总线中,总线的起点和终点需要放置120欧的电阻。
但是实际项目中基本都不是这种情况,一般都是星形网络,支持节点的热插拔。
在这种网络中,并没有像汽车那种一根长长的总线。总线可能就是一个PCB板,留了很多接入点,设备可以用线缆连入,这个情况下,总线长度可能不到10cm
,支线的长度却有2-5m
的长度,这个情况下,因为不确定什么时候有设备加入,也无法决定哪个子节点需要加120欧电阻。
那这个情况下,就直接所有子节点都不加120欧电阻。通过示波器查看电压,可以看看各个节点的压降都还不错。
此外,既然已经非标了,就别使用太高速的CAN频率,使用250kbps
或者500kbps
即可。
FDCAN测试设备
协议分析怎么能少了仪器,虽然示波器已经能看很多东西了,但是业务问题还是有这种抓包仪器会好分析一些。200-300就可以抓包看了。多通道USB转CAN分析仪调试器CAN盒CANFD总线CANopen J1939OBD ZLG-淘宝网。
个人买了一个2通道的就足够用了。
安装上位机软件可以抓到交互的数据包。
FDCAN总线示波器波形
可以看看大佬写的文章,写得很好了。用示波器排查CAN的各种错误帧(1) - 知乎
最好在每个终端节点旁边都测测其看到的差分电平压降,只要不衰减太厉害,所设计的电路和can网络拓扑就没什么问题。下面借鉴大佬的一个图,上面的相对干净了。
确保上升沿和下降沿迅速切换,别有斜坡(别用太大的电容)。
一个简易FDCAN控制器代码
#include <stdint.h>
#include <string.h>
FDCAN_HandleTypeDef hfdcan1;
FDCAN_HandleTypeDef hfdcan2;
/**
* @brief This function handles FDCAN1 interrupt 0.
*/
void FDCAN1_IT0_IRQHandler(void)
{
/* USER CODE BEGIN FDCAN1_IT0_IRQn 0 */
/* USER CODE END FDCAN1_IT0_IRQn 0 */
HAL_FDCAN_IRQHandler(&hfdcan1);
/* USER CODE BEGIN FDCAN1_IT0_IRQn 1 */
/* USER CODE END FDCAN1_IT0_IRQn 1 */
}
/**
* @brief This function handles FDCAN2 interrupt 0.
*/
void FDCAN2_IT0_IRQHandler(void)
{
/* USER CODE BEGIN FDCAN2_IT0_IRQn 0 */
/* USER CODE END FDCAN2_IT0_IRQn 0 */
HAL_FDCAN_IRQHandler(&hfdcan2);
/* USER CODE BEGIN FDCAN2_IT0_IRQn 1 */
/* USER CODE END FDCAN2_IT0_IRQn 1 */
}
/**
* @brief FDCAN1 Initialization Function
* @param None
* @retval None
*/
static void MX_FDCAN1_Init(void)
{
/* USER CODE BEGIN FDCAN1_Init 0 */
/* USER CODE END FDCAN1_Init 0 */
/* USER CODE BEGIN FDCAN1_Init 1 */
/* USER CODE END FDCAN1_Init 1 */
hfdcan1.Instance = FDCAN1;
hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV2;
hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_NO_BRS;
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan1.Init.AutoRetransmission = ENABLE;
hfdcan1.Init.TransmitPause = DISABLE;
hfdcan1.Init.ProtocolException = DISABLE;
hfdcan1.Init.NominalPrescaler = 1;
hfdcan1.Init.NominalSyncJumpWidth = 62;
hfdcan1.Init.NominalTimeSeg1 = 187;
hfdcan1.Init.NominalTimeSeg2 = 62;
hfdcan1.Init.DataPrescaler = 2;
hfdcan1.Init.DataSyncJumpWidth = 5;
hfdcan1.Init.DataTimeSeg1 = 19;
hfdcan1.Init.DataTimeSeg2 = 5;
hfdcan1.Init.StdFiltersNbr = 1;
hfdcan1.Init.ExtFiltersNbr = 0;
hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK)
{
while(1);
}
/* USER CODE BEGIN FDCAN1_Init 2 */
/* USER CODE END FDCAN1_Init 2 */
}
/**
* @brief FDCAN2 Initialization Function
* @param None
* @retval None
*/
static void MX_FDCAN2_Init(void)
{
/* USER CODE BEGIN FDCAN2_Init 0 */
/* USER CODE END FDCAN2_Init 0 */
/* USER CODE BEGIN FDCAN2_Init 1 */
/* USER CODE END FDCAN2_Init 1 */
hfdcan2.Instance = FDCAN2;
hfdcan2.Init.ClockDivider = FDCAN_CLOCK_DIV2;
hfdcan2.Init.FrameFormat = FDCAN_FRAME_FD_NO_BRS;
hfdcan2.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan2.Init.AutoRetransmission = ENABLE;
hfdcan2.Init.TransmitPause = DISABLE;
hfdcan2.Init.ProtocolException = DISABLE;
hfdcan2.Init.NominalPrescaler = 1;
hfdcan2.Init.NominalSyncJumpWidth = 62;
hfdcan2.Init.NominalTimeSeg1 = 187;
hfdcan2.Init.NominalTimeSeg2 = 62;
hfdcan2.Init.DataPrescaler = 2;
hfdcan2.Init.DataSyncJumpWidth = 5;
hfdcan2.Init.DataTimeSeg1 = 19;
hfdcan2.Init.DataTimeSeg2 = 5;
hfdcan2.Init.StdFiltersNbr = 1;
hfdcan2.Init.ExtFiltersNbr = 0;
hfdcan2.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
if (HAL_FDCAN_Init(&hfdcan2) != HAL_OK)
{
while(1);
}
/* USER CODE BEGIN FDCAN2_Init 2 */
/* USER CODE END FDCAN2_Init 2 */
}
void FDCAN_DeConfig(FDCAN_HandleTypeDef *hfdcan)
{
if (HAL_FDCAN_Stop(hfdcan) != HAL_OK)
{
while(1);
}
}
void FDCAN_Config(FDCAN_HandleTypeDef *hfdcan)
{
FDCAN_FilterTypeDef sFilterConfig;
/* Configure Rx filter */
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
if(hfdcan->Instance == FDCAN1)
{
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
// sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1;
}
else if(hfdcan->Instance == FDCAN2)
{
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1;
}
sFilterConfig.FilterID1 = 0x00000000;
sFilterConfig.FilterID2 = 0x000007FF;
if (HAL_FDCAN_ConfigFilter(hfdcan, &sFilterConfig) != HAL_OK)
{
while(1);
}
/* Configure global filter:
Filter all remote frames with STD and EXT ID
Reject non matching frames with STD ID and EXT ID */
if (HAL_FDCAN_ConfigGlobalFilter(hfdcan, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
{
while(1);
}
/* Activate Rx FIFO 0 new message notification on both FDCAN instances */
// if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE |
// FDCAN_IT_RAM_ACCESS_FAILURE | FDCAN_IT_ERROR_LOGGING_OVERFLOW | FDCAN_IT_RAM_WATCHDOG | FDCAN_IT_ARB_PROTOCOL_ERROR | FDCAN_IT_DATA_PROTOCOL_ERROR | FDCAN_IT_RESERVED_ADDRESS_ACCESS
// | FDCAN_IT_ERROR_PASSIVE | FDCAN_IT_ERROR_WARNING | FDCAN_IT_BUS_OFF
// , 0) != HAL_OK)
// {
// while(1);
// }
if(hfdcan->Instance == FDCAN1)
{
if (HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_RX_FIFO0_MESSAGE_LOST | FDCAN_IT_RX_FIFO0_FULL, 0) != HAL_OK)
{
while(1);
}
}
else
{
if (HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO1_NEW_MESSAGE | FDCAN_IT_RX_FIFO1_MESSAGE_LOST | FDCAN_IT_RX_FIFO1_FULL, 0) != HAL_OK)
{
while(1);
}
}
// if (HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_BUS_OFF, 0) != HAL_OK)
// {
// while(1);
// }
/* Configure and enable Tx Delay Compensation, required for BRS mode.
TdcOffset default recommended value: DataTimeSeg1 * DataPrescaler
TdcFilter default recommended value: 0 */
HAL_FDCAN_ConfigTxDelayCompensation(hfdcan, hfdcan->Init.DataPrescaler * hfdcan->Init.DataTimeSeg1, 0);
HAL_FDCAN_EnableTxDelayCompensation(hfdcan);
/* Start the FDCAN module */
if (HAL_FDCAN_Start(hfdcan) != HAL_OK)
{
while(1);
}
}
// void HAL_FDCAN_ErrorCallback(FDCAN_HandleTypeDef *hfdcan)
// {
// printf("HAL_FDCAN_ErrorCallback, Error Code = 0x%x, State = 0x%x\r\n", hfdcan->ErrorCode, hfdcan->State);
// }
// void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs)
// {
// printf("HAL_FDCAN_ErrorStatusCallback, Error Code = 0x%x, State = 0x%x, ErrorStatusITs = 0x%x\r\n", hfdcan->ErrorCode, hfdcan->State, ErrorStatusITs);
// }
int fdcan_control_get_tx_len_by_real_len(uint32_t real_len)
{
if(real_len <= 0)
{
return FDCAN_DLC_BYTES_0;
}
else if(real_len <= 1)
{
return FDCAN_DLC_BYTES_1;
}
else if(real_len <= 2)
{
return FDCAN_DLC_BYTES_2;
}
else if(real_len <= 3)
{
return FDCAN_DLC_BYTES_3;
}
else if(real_len <= 4)
{
return FDCAN_DLC_BYTES_4;
}
else if(real_len <= 5)
{
return FDCAN_DLC_BYTES_5;
}
else if(real_len <= 6)
{
return FDCAN_DLC_BYTES_6;
}
else if(real_len <= 7)
{
return FDCAN_DLC_BYTES_7;
}
else if(real_len <= 8)
{
return FDCAN_DLC_BYTES_8;
}
else if(real_len <= 12)
{
return FDCAN_DLC_BYTES_12;
}
else if(real_len <= 16)
{
return FDCAN_DLC_BYTES_16;
}
else if(real_len <= 20)
{
return FDCAN_DLC_BYTES_20;
}
else if(real_len <= 24)
{
return FDCAN_DLC_BYTES_24;
}
else if(real_len <= 32)
{
return FDCAN_DLC_BYTES_32;
}
else if(real_len <= 48)
{
return FDCAN_DLC_BYTES_48;
}
return FDCAN_DLC_BYTES_64;
}
void fdcan_control_restart(FDCAN_HandleTypeDef *hfdcan)
{
int index = hfdcan->Instance == FDCAN1 ? 0 : 1;
EASY_LOG_ERR("fdcan_control_restart, index: %d\r\n", index);
// reconfig fdcan
FDCAN_DeConfig(hfdcan);
FDCAN_Config(hfdcan);
}
int fdcan_control_tx_packet(FDCAN_HandleTypeDef *hfdcan, uint16_t id, const uint8_t *data, uint32_t len)
{
uint8_t data_buf[64] = {0};
int index = hfdcan->Instance == FDCAN1 ? 0 : 1;
if(len > 64)
{
len = 64;
}
memcpy(data_buf, data, len);
FDCAN_TxHeaderTypeDef TxHeader;
TxHeader.Identifier = id; // CAN ID
TxHeader.IdType = FDCAN_STANDARD_ID; // 标准ID
TxHeader.TxFrameType = FDCAN_DATA_FRAME;
TxHeader.DataLength = fdcan_control_get_tx_len_by_real_len(len); // 发送长度:64byte
TxHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
TxHeader.BitRateSwitch = FDCAN_BRS_OFF;
TxHeader.FDFormat = FDCAN_FD_CAN; // CANFD
TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
TxHeader.MessageMarker = 0;
int ret = 0;
ret = HAL_FDCAN_AddMessageToTxFifoQ(hfdcan, &TxHeader, data_buf);
if(ret != HAL_OK)
{
return 0;
}
else
{
}
return len;
}
void HAL_FDCAN_TxBufferCompleteCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndexes)
{
EASY_LOG_DBG("Tx complete\r\n");
}
__EASY_WEAK__ void fdcan1_control_rx_packet(uint32_t can_id, uint8_t *data, uint32_t len)
{
}
__EASY_WEAK__ void fdcan2_control_rx_packet(uint32_t can_id, uint8_t *data, uint32_t len)
{
}
static const uint8_t DLCtoBytes[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64};
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
FDCAN_RxHeaderTypeDef RxHeader;
uint8_t data_buf[64] = {0};
uint16_t data_len = 0;
if((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != RESET)
{
// int work_cnt = 0;
/* Retrieve Rx messages from RX FIFO0 */
/* Check if FIFO 0 receive at least one message */
while (HAL_FDCAN_GetRxFifoFillLevel(hfdcan, FDCAN_RX_FIFO0))
{
// work_cnt ++;
// if(work_cnt > 1)
// {
// printf("!!!!!!!work_cnt = %d\r\n", work_cnt);
// }
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, data_buf) != HAL_OK)
{
while(1);
}
data_len = DLCtoBytes[RxHeader.DataLength];
// printf("Identifier = 0x%x\r\n", RxHeader.Identifier);
// printf("DataLength = %d(0x%x)\r\n", data_len, RxHeader.DataLength);
// printf("FDFormat = 0x%x\r\n", RxHeader.FDFormat);
// printf("BitRateSwitch = %d\r\n", RxHeader.BitRateSwitch);
// printf("ErrorStateIndicator = 0x%x\r\n", RxHeader.ErrorStateIndicator);
// for(int i = 0; i < data_len; i++)
// {
// printf("Data[%d] = 0x%x\r\n", i, can1_rxbuf[i]);
// }
if(hfdcan->Instance == FDCAN1)
{
fdcan1_control_rx_packet(RxHeader.Identifier, data_buf, data_len);
}
else if(hfdcan->Instance == FDCAN2)
{
fdcan2_control_rx_packet(RxHeader.Identifier, data_buf, data_len);
}
}
/* Retrieve Rx messages from RX FIFO0 */
// status = HAL_FDCAN_GetRxMessage(&hfdcan, FDCAN_RX_FIFO0, &RxHeader, RxData);
// if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, data_buf) != HAL_OK)
// {
// while(1);
// }
}
if((RxFifo0ITs & FDCAN_IT_RX_FIFO0_MESSAGE_LOST) != RESET)
{
EASY_LOG_DBG("Rx FIFO0 message lost\r\n");
}
if((RxFifo0ITs & FDCAN_IT_RX_FIFO0_FULL) != RESET)
{
EASY_LOG_DBG("Rx FIFO0 message full\r\n");
}
}
void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs)
{
FDCAN_RxHeaderTypeDef RxHeader;
uint8_t data_buf[64] = {0};
uint16_t data_len = 0;
if((RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE) != RESET)
{
// int work_cnt = 0;
/* Retrieve Rx messages from RX FIFO1 */
/* Check if FIFO 0 receive at least one message */
while (HAL_FDCAN_GetRxFifoFillLevel(hfdcan, FDCAN_RX_FIFO1))
{
// work_cnt ++;
// if(work_cnt > 1)
// {
// printf("!!!!!!!work_cnt = %d\r\n", work_cnt);
// }
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO1, &RxHeader, data_buf) != HAL_OK)
{
while(1);
}
data_len = DLCtoBytes[RxHeader.DataLength];
// printf("Identifier = 0x%x\r\n", RxHeader.Identifier);
// printf("DataLength = %d(0x%x)\r\n", data_len, RxHeader.DataLength);
// printf("FDFormat = 0x%x\r\n", RxHeader.FDFormat);
// printf("BitRateSwitch = %d\r\n", RxHeader.BitRateSwitch);
// printf("ErrorStateIndicator = 0x%x\r\n", RxHeader.ErrorStateIndicator);
// for(int i = 0; i < data_len; i++)
// {
// printf("Data[%d] = 0x%x\r\n", i, can1_rxbuf[i]);
// }
if(hfdcan->Instance == FDCAN1)
{
fdcan1_control_rx_packet(RxHeader.Identifier, data_buf, data_len);
}
else if(hfdcan->Instance == FDCAN2)
{
fdcan2_control_rx_packet(RxHeader.Identifier, data_buf, data_len);
}
}
/* Retrieve Rx messages from RX FIFO1 */
// status = HAL_FDCAN_GetRxMessage(&hfdcan, FDCAN_RX_FIFO1, &RxHeader, RxData);
// if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO1, &RxHeader, data_buf) != HAL_OK)
// {
// while(1);
// }
}
if((RxFifo1ITs & FDCAN_IT_RX_FIFO1_MESSAGE_LOST) != RESET)
{
EASY_LOG_DBG("Rx FIFO1 message lost\r\n");
}
if((RxFifo1ITs & FDCAN_IT_RX_FIFO1_FULL) != RESET)
{
EASY_LOG_DBG("Rx FIFO1 message full\r\n");
}
}
int fdcan_control_init(void)
{
EASY_LOG_DBG("fdcan_control_init\n");
MX_FDCAN1_Init();
MX_FDCAN2_Init();
return 0;
}