FDCAN(Flexible Data-Rate CAN)是CAN总线的升级版本,兼容传统CAN 2.0,同时支持更高的传输速率(数据段最高8Mbps)和更大的单帧数据量(64字节,传统CAN仅8字节)。STM32H743内置2个独立的FDCAN外设(FDCAN1/FDCAN2),支持CAN FD协议,是工业控制、汽车电子、物联网等场景中实现高速、可靠通信的核心外设。
一、STM32H743 FDCAN 核心硬件特性
STM32H743的FDCAN模块属于APB1/APB2总线外设(FDCAN1挂APB2,FDCAN2挂APB1),核心特性如下:
特性 | 说明 |
协议兼容 | 支持CAN FD(ISO 11898-1:2015)、传统CAN 2.0A/B,可配置为CAN FD或CAN 2.0模式 |
传输速率 | 仲裁段(Arbitration Phase):≤1Mbps(兼容传统CAN);数据段(Data Phase):≤8Mbps |
单帧数据长度 | 传统CAN:08字节;CAN FD:064字节 |
通信模式 | 正常模式、环回模式(自测)、静默模式(仅收不发) |
滤波机制 | 32个可配置滤波器(掩码/列表模式),支持标准ID(11位)/扩展ID(29位)过滤 |
中断支持 | 接收完成、发送完成、错误中断、总线离线中断等 |
数据传输方式 | 邮箱发送(3个发送邮箱)、FIFO接收(2个接收FIFO) |
总线容错 | 支持总线错误自动恢复、离线自动重连 |
典型应用 | 工业现场总线通信、汽车ECU通信、智能设备组网、多节点数据交互 |
FDCAN 核心信号
信号名称 | 功能 | 典型引脚(STM32H743) | 备注 |
FDCAN_TX | 发送线 | PB9(FDCAN1)/ PB13(FDCAN2) | 需外接CAN收发器(如TJA1050)转差分信号 |
FDCAN_RX | 接收线 | PB8(FDCAN1)/ PB12(FDCAN2) | 同上 |
关键:FDCAN_TX/RX为TTL电平,必须外接CAN收发器(如TJA1050、SN65HVD230)转换为CAN总线的差分电平(CAN_H/CAN_L),否则无法通信。
二、FDCAN 通信原理
FDCAN的通信本质是“多节点通过CAN总线以广播方式收发数据帧”,核心概念和流程如下:
1. 核心概念
- 仲裁段/数据段:
- 仲裁段:传输帧ID,用于总线仲裁(优先级),速率≤1Mbps(兼容传统CAN);
- 数据段:传输实际数据,CAN FD模式下速率可提升至8Mbps;
- 帧类型:
- 传统CAN帧:ID(11/29位)+ 数据(0~8字节);
- CAN FD帧:ID + 数据(0~64字节)+ BRS位(速率切换)+ ESI位(错误状态);
- 滤波器:STM32通过滤波器筛选需要接收的帧(如仅接收ID为0x123的帧),无关帧直接丢弃;
- 邮箱/FIFO:
- 发送邮箱:存储待发送的帧,最多3个,发送完成后清空;
- 接收FIFO:存储接收到的帧,最多2个,读取后需释放。
2. 通信流程
- 初始化:
- 配置FDCAN时钟、引脚、波特率(仲裁段/数据段);
- 配置滤波器(如接收ID为0x123的帧);
- 启用FDCAN外设和中断(可选)。
- 发送数据:
- 填充帧结构体(ID、数据长度、数据内容、帧类型);
- 将帧写入发送邮箱,硬件自动发送;
- 发送完成后触发中断(可选)。
- 接收数据:
- 总线上的帧经滤波器筛选后,存入接收FIFO;
- 触发接收中断(可选),读取FIFO中的帧数据;
- 释放接收FIFO,准备接收下一帧。
波特率计算(关键)
FDCAN波特率由“预分频器+时间段”决定,公式:
波特率 = FDCAN时钟频率 / (预分频器 × (时间段1 + 时间段2 + 1))示例(常用配置):
- 仲裁段(1Mbps):FDCAN时钟=80MHz,预分频器=8,时间段1=6,时间段2=3 → 80/(8×(6+3+1))=1Mbps;
- 数据段(4Mbps):预分频器=2,时间段1=6,时间段2=3 → 80/(2×(6+3+1))=4Mbps。
三、STM32H743 FDCAN 配置步骤
时钟配置:
步骤3:HAL库核心API(新手必记)
函数 | 功能 |
HAL_FDCAN_Init() | 初始化FDCAN外设(核心函数) |
HAL_FDCAN_ConfigFilter() | 配置滤波器(筛选接收帧) |
HAL_FDCAN_AddMessageToTxFifo() | 将帧写入发送FIFO(发送数据) |
HAL_FDCAN_GetRxMessage() | 从接收FIFO读取帧(接收数据) |
HAL_FDCAN_ActivateNotification() | 启用中断通知(接收/发送完成) |
HAL_FDCAN_GetState() | 获取FDCAN状态(就绪/忙/错误) |
HAL_FDCAN_GetError() | 获取FDCAN错误码 |
常用帧结构体:
FDCAN_TxHeaderTypeDef tx_header; // 发送帧头 FDCAN_RxHeaderTypeDef rx_header; // 接收帧头 uint8_t tx_data[64]; // 发送数据缓冲区 uint8_t rx_data[64]; // 接收数据缓冲区四、实战代码示例
示例1:FDCAN初始化+环回模式自测(无外设也能测)
环回模式下,发送的帧会被自身接收,验证FDCAN配置正确性:
#include "stm32h7xx_hal.h" #include <stdio.h> #include <string.h> /* FDCAN句柄(CubeMX自动生成) */ FDCAN_HandleTypeDef hfdcan1; /* USART1句柄(串口输出,115200 8N1) */ UART_HandleTypeDef huart1; /* 帧结构体 */ FDCAN_TxHeaderTypeDef fdcan_tx_header; FDCAN_RxHeaderTypeDef fdcan_rx_header; uint8_t fdcan_tx_buf[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; uint8_t fdcan_rx_buf[8]; /* 错误处理函数 */ void Error_Handler(void) { while(1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // LED快闪表示错误 HAL_Delay(200); } } /** * @brief 重定向printf到串口 */ int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 10); return ch; } /** * @brief FDCAN中断回调函数(接收完成) */ void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { if((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != RESET) { // 读取接收FIFO中的帧 if(HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &fdcan_rx_header, fdcan_rx_buf) == HAL_OK) { printf("Received Frame: ID=0x%X, Len=%d, Data=", fdcan_rx_header.Identifier, fdcan_rx_header.DataLength); for(uint8_t i=0; i<fdcan_rx_header.DataLength; i++) { printf("%02X ", fdcan_rx_buf[i]); } printf("\r\n"); HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // LED闪一下表示接收完成 } // 重新启用接收通知 HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); } } int main(void) { /* 初始化HAL库、系统时钟、GPIO(LED)、FDCAN1、USART1 */ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); // PA5为LED(推挽输出) MX_FDCAN1_Init(); // FDCAN1初始化(环回模式) MX_USART1_UART_Init(); // 串口初始化 HAL_StatusTypeDef status; printf("STM32H743 FDCAN Loopback Test\r\n"); // 1. 配置发送帧头 fdcan_tx_header.Identifier = 0x123; // 帧ID fdcan_tx_header.IdType = FDCAN_STANDARD_ID; // 标准ID(11位) fdcan_tx_header.TxFrameType = FDCAN_DATA_FRAME; // 数据帧 fdcan_tx_header.DataLength = FDCAN_DLC_BYTES_8; // 数据长度8字节 fdcan_tx_header.ErrorStateIndicator = FDCAN_ESI_ACTIVE; fdcan_tx_header.BitRateSwitch = FDCAN_BRS_ON; // 启用速率切换(CAN FD) fdcan_tx_header.FDFormat = FDCAN_FD_CAN; // CAN FD模式 fdcan_tx_header.TxEventFifoControl = FDCAN_NO_TX_EVENTS; fdcan_tx_header.MessageMarker = 0; // 2. 启用接收中断通知 if(HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK) { Error_Handler(); } // 3. 发送帧(环回模式下会被自身接收) printf("Sending Frame: ID=0x123, Len=8, Data=01 02 03 04 05 06 07 08\r\n"); status = HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &fdcan_tx_header, fdcan_tx_buf); if(status != HAL_OK) { printf("Send Frame Error: %d\r\n", status); Error_Handler(); } while (1) { // 每隔1秒重发一次 HAL_Delay(1000); HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &fdcan_tx_header, fdcan_tx_buf); } }示例2:FDCAN正常模式通信(双节点)
两个STM32H743节点通过CAN总线通信,节点1发送数据,节点2接收数据:
节点1(发送端)代码
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_FDCAN1_Init(); // 正常模式(非环回) MX_USART1_UART_Init(); printf("FDCAN Transmitter Node\r\n"); // 配置发送帧头(同示例1) fdcan_tx_header.Identifier = 0x123; fdcan_tx_header.IdType = FDCAN_STANDARD_ID; fdcan_tx_header.TxFrameType = FDCAN_DATA_FRAME; fdcan_tx_header.DataLength = FDCAN_DLC_BYTES_8; fdcan_tx_header.BitRateSwitch = FDCAN_BRS_ON; fdcan_tx_header.FDFormat = FDCAN_FD_CAN; while (1) { // 填充发送数据(递增) for(uint8_t i=0; i<8; i++) { fdcan_tx_buf[i]++; } // 发送帧 printf("Send Data: "); for(uint8_t i=0; i<8; i++) { printf("%02X ", fdcan_tx_buf[i]); } printf("\r\n"); HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &fdcan_tx_header, fdcan_tx_buf); HAL_Delay(1000); } }节点2(接收端)代码
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_FDCAN1_Init(); // 正常模式 MX_USART1_UART_Init(); printf("FDCAN Receiver Node\r\n"); // 启用接收中断 HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); while (1) { // 主循环无需操作,中断处理接收 HAL_Delay(100); } } // 接收中断回调函数(同示例1) void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { if((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != RESET) { HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &fdcan_rx_header, fdcan_rx_buf); printf("Received Data: "); for(uint8_t i=0; i<fdcan_rx_header.DataLength; i++) { printf("%02X ", fdcan_rx_buf[i]); } printf("\r\n"); HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); } }示例3:FDCAN 64字节大数据传输(CAN FD特性)
利用CAN FD的64字节数据长度,传输大尺寸数据:
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_FDCAN1_Init(); MX_USART1_UART_Init(); uint8_t big_tx_data[64]; // 填充64字节测试数据 for(uint8_t i=0; i<64; i++) { big_tx_data[i] = i; } // 配置发送帧头(64字节) FDCAN_TxHeaderTypeDef big_tx_header; big_tx_header.Identifier = 0x456; big_tx_header.IdType = FDCAN_STANDARD_ID; big_tx_header.TxFrameType = FDCAN_DATA_FRAME; big_tx_header.DataLength = FDCAN_DLC_BYTES_64; // 64字节 big_tx_header.BitRateSwitch = FDCAN_BRS_ON; big_tx_header.FDFormat = FDCAN_FD_CAN; printf("FDCAN 64 Bytes Data Test\r\n"); while (1) { // 发送64字节数据 HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &big_tx_header, big_tx_data); printf("Sent 64 Bytes Data: 00~3F\r\n"); HAL_Delay(2000); } }五、常见问题与避坑指南
1. FDCAN初始化失败(HAL_FDCAN_Init返回错误)
- 原因1:时钟配置错误:
FDCAN时钟源未配置(如PCLK2=0),或预分频器设置为0;
→ 解决:CubeMX中确保FDCAN时钟源为PCLK2(80MHz),预分频器≥1; - 原因2:引脚配置错误:
FDCAN_TX/RX引脚未设为Alternate Function模式,或引脚号错误;
→ 解决:核对引脚(FDCAN1=PB8/RX、PB9/TX),确保模式为AF9(FDCAN); - 原因3:总线未初始化:
FDCAN外设未进入正常模式,或仲裁段/数据段参数冲突;
→ 解决:初始化后调用HAL_FDCAN_Start(&hfdcan1)启动FDCAN。
2. 发送帧无响应,接收不到数据
- 原因1:未接CAN收发器/终端电阻:
FDCAN_TX/RX直接接CAN总线,无TJA1050转换差分电平,或总线未接120Ω终端电阻;
→ 解决:必须外接CAN收发器,且CAN_H/CAN_L之间接120Ω电阻; - 原因2:波特率不匹配:
发送端和接收端波特率配置不同(如一端1Mbps,一端500kbps);
→ 解决:双节点波特率参数(预分频器、时间段1/2)必须完全一致; - 原因3:滤波器配置错误:
接收端滤波器ID/掩码设置错误,过滤掉了目标帧;
→ 解决:测试时可禁用滤波器(接收所有帧),或确保掩码为全1(全匹配); - 原因4:CAN FD模式未启用:
帧头的FDFormat设为FDCAN_CLASSIC_CAN,无法传输64字节数据;
→ 解决:CAN FD模式需设FDFormat=FDCAN_FD_CAN,BRS=FDCAN_BRS_ON。
3. 中断不触发/回调函数无响应
- 原因1:未启用中断通知:
未调用HAL_FDCAN_ActivateNotification()启用接收/发送中断;
→ 解决:发送前启用对应中断(如FDCAN_IT_RX_FIFO0_NEW_MESSAGE); - 原因2:NVIC中断未开启:
CubeMX中未勾选FDCAN中断,或优先级过低;
→ 解决:NVIC Settings中启用FDCAN中断,优先级设为High; - 原因3:回调函数名错误:
未实现HAL_FDCAN_RxFifo0Callback(),或函数参数错误;
→ 解决:确保回调函数名与HAL库一致,参数为FDCAN_HandleTypeDef *hfdcan。
4. 仅能发送8字节,无法发送64字节
- 原因1:帧头数据长度配置错误:
DataLength设为FDCAN_DLC_BYTES_8(8字节),而非FDCAN_DLC_BYTES_64;
→ 解决:CAN FD模式下,数据长度可设为FDCAN_DLC_BYTES_16/32/64; - 原因2:未启用BRS位:
BitRateSwitch设为FDCAN_BRS_OFF,无法切换到高速数据段;
→ 解决:启用BRS位(FDCAN_BRS_ON),才能使用CAN FD的高速和64字节特性; - 原因3:对方节点不支持CAN FD:
接收端为传统CAN节点(仅支持8字节),无法接收64字节帧;
→ 解决:双节点均配置为CAN FD模式。
5. 总线错误/离线(Error State)
- 原因1:总线短路/断路:
CAN_H/CAN_L短路,或总线未接电源;
→ 解决:用万用表测CAN_H(≈3.5V)、CAN_L(≈1.5V),检查接线; - 原因2:发送频率过高:
连续发送帧导致总线负载率>80%,触发错误;
→ 解决:降低发送频率(如≥10ms/帧),或优化通信协议; - 原因3:节点地址冲突:
多个节点发送相同ID的帧,总线仲裁失败;
→ 解决:为每个节点分配唯一的帧ID,或采用应答机制。
六、调试顺序
1. 入门步骤
- 第一步:环回模式自测,验证FDCAN配置和代码正确性(无需外设);
- 第二步:外接CAN收发器,实现单节点发送+PC CAN分析仪接收(验证硬件);
- 第三步:双STM32节点通信,实现8字节数据收发;
- 第四步:拓展到64字节CAN FD数据传输;
- 第五步:添加滤波器、错误处理、总线重连机制。
2. 调试技巧
- CAN分析仪调试:
用USB-CAN分析仪(如周立功USBCAN)连接CAN总线,抓取帧数据,验证发送/接收是否正确; - 示波器调试:
测CAN_H/CAN_L电平(正常:CAN_H≈3.5V,CAN_L≈1.5V),发送帧时应有电平跳变; - 分步测试:
- 先测试传统CAN 8字节通信,再切换到CAN FD 64字节;
- 先禁用滤波器,接收所有帧,再逐步配置滤波器;
- 错误日志:
打印HAL_FDCAN_GetError()的错误码,定位总线错误原因(如位错误、填充错误)。
3. 重点注意事项
- 终端电阻:CAN总线两端必须接120Ω终端电阻(靠近总线首尾节点),否则信号反射导致通信失败;
- 波特率一致性:所有节点的仲裁段/数据段波特率必须完全一致,否则无法通信;
- 数据对齐:64字节数据缓冲区需按32位对齐(用
__attribute__((aligned(4)))修饰); - 容错处理:添加总线错误检测和自动重连机制(如离线后重新初始化FDCAN)。
4. 实际应用练习
- 工业通信:实现多节点传感器数据采集(温度、压力),通过FDCAN上传到主节点;
- 汽车电子:模拟ECU通信,发送/接收汽车CAN总线帧(如车速、转速);
- 容错通信:添加帧重传、校验和机制,确保数据传输可靠性;
- 协议适配:适配CANopen协议(工业常用),实现标准化通信。
七、实际调试
使用can分析仪:
此CAN转USB使用的是自定义USB类。需要安装驱动:
安装过程略。
安装完毕后:
配置上位机软件:
这里使用正点原子H7开发板:
使用自带的例程即可。按下wakeup键,切换到normal模式。注意此开发板的CAN和USB复用,所以需要将跳线帽跳到CAN模式下。
按下KEY0后,上位机软件监控到CAN数据。