1. 项目背景与需求解析
在嵌入式硬件开发中,CAN总线通信的可靠性至关重要。但在实际项目中,我们经常会遇到CAN通信异常的情况,这时候如何快速定位问题是硬件链路故障还是软件配置问题,就成了工程师们头疼的难题。本文将详细介绍一种利用STM32 GPIO对CAN收发器链路进行硬件电平检测的实用方法。
这个方法的核心价值在于:
- 不依赖CAN协议栈,仅使用基本GPIO功能
- 可以验证从MCU引脚到CAN总线的整个信号通路
- 适用于产线测试和现场故障排查
- 实现简单,不需要额外测试设备
2. 硬件架构与信号通路
2.1 硬件连接拓扑
整个硬件链路包含三个关键组件:
- STM32微控制器
- NSI8021N1数字隔离器
- NCA1051N CAN收发器
具体连接方式如下:
| 组件 | 引脚 | 连接对象 | 信号方向 | 备注 |
|---|---|---|---|---|
| STM32 | PB13 | NSI8021N1输入 | TX → | CAN发送信号 |
| STM32 | PB12 | NSI8021N1输出 | ← RX | CAN接收信号 |
| NSI8021N1 | 输出 | NCA1051N TXD | → | 隔离后送收发器 |
| NSI8021N1 | 输入 | NCA1051N RXD | ← | 收发器输出经隔离回MCU |
| NCA1051N | CANH | CAN总线 | → | 差分总线高电平 |
| NCA1051N | CANL | CAN总线 | → | 差分总线低电平 |
2.2 关键器件特性
数字隔离器NSI8021N1:
- 默认输出高电平(对应CAN隐性态)
- 输入悬空时输出为高
- 传播延迟约200ns
CAN收发器NCA1051N:
- 支持5Mbps高速CAN
- 具有TXD显性超时(DTO)保护功能
- 典型传播延迟280ns
3. 检测原理与技术细节
3.1 CAN总线电平逻辑
CAN总线采用差分信号,逻辑状态分为显性(Dominant)和隐性(Recessive):
-
隐性态(逻辑1):
- TX = HIGH
- 收发器不驱动总线
- CANH ≈ CANL ≈ 2.5V
- RXD = HIGH
- RX = HIGH
-
显性态(逻辑0):
- TX = LOW
- 收发器驱动总线
- CANH = 3.5V, CANL = 1.5V
- RXD = LOW
- RX = LOW
3.2 检测方法设计
基于上述特性,我们可以:
- 将CAN TX/RX引脚临时配置为普通GPIO
- 手动控制TX输出高/低电平
- 通过RX读取返回的电平
- 验证信号是否完整通过整个链路
3.3 关键参数:TXD Dominant Timeout (DTO)
NCA1051N收发器具有DTO保护功能,当TXD持续为低超过一定时间后,收发器会自动关闭驱动器,使总线恢复隐性。这是为了防止某个节点故障导致总线长时间被占用。
数据手册参数:
- t_TXD_DTO最小值:800μs
- 典型值:2200μs
- 最大值:4500μs
重要提示:测试时必须确保TX拉低的时间小于800μs(取最小值作为安全上限),否则会导致收发器触发保护,RX误判为高。
4. 实现步骤详解
4.1 停止CAN外设
在修改GPIO配置前,必须先停止CAN外设:
c复制HAL_FDCAN_Stop(&hfdcan);
OS_TASK_Delay(10); // 确保外设完全停止
4.2 配置TX为推挽输出
关键配置参数及选择理由:
| 参数 | 值 | 理由 |
|---|---|---|
| 模式 | GPIO_MODE_OUTPUT | 需要手动控制电平 |
| 输出类型 | GPIO_OTYPE_PP | 必须推挽才能可靠驱动高低电平 |
| 速度 | GPIO_SPEED_FREQ_HIGH | 与CAN通信配置一致 |
| 上下拉 | GPIO_NOPULL | 推挽输出不需要上下拉 |
配置代码示例:
c复制GPIO_Set(CAN_TX_PORT, CAN_TX_PIN,
GPIO_MODE_OUTPUT,
GPIO_OTYPE_PP,
GPIO_SPEED_FREQ_HIGH,
GPIO_NOPULL);
4.3 配置RX为输入
RX配置要点:
- 模式:GPIO_MODE_INPUT
- 上下拉:GPIO_NOPULL(隔离器会主动驱动)
- 清除AFR设置
4.4 延时等待稳定
建议延时2μs,考虑因素:
- GPIO寄存器写入生效时间
- 隔离器传播延迟(200ns)
- 收发器传播延迟(280ns)
- 信号在PCB上的传输延迟
4.5 电平测试流程
-
TX=HIGH测试:
- 置高TX引脚
- 延时100μs
- 读取RX电平
- 预期:RX=HIGH
-
TX=LOW测试:
- 置低TX引脚
- 延时100μs(必须<800μs)
- 读取RX电平
- 预期:RX=LOW
- 立即恢复TX=HIGH
4.6 恢复CAN功能
测试完成后需要:
- 重新配置TX/RX为复用模式
- 设置正确的AF编号(如AF9对应FDCAN)
- 重新初始化CAN外设
5. 关键代码解析
5.1 GPIO控制实现
使用BSRR寄存器进行原子操作:
c复制// 置高TX
CAN_TX_PORT->BSRR = CAN_TX_PIN;
// 置低TX
CAN_TX_PORT->BSRR = (CAN_TX_PIN << 16);
5.2 延时选择
定义测试延时为100μs:
c复制#define CAN_GPIO_TEST_DELAY_US 100
OS_TASK_Delayus(CAN_GPIO_TEST_DELAY_US);
选择理由:
- 远小于800μs的安全阈值
- 足够让信号稳定通过整个链路
- 实测在280MHz主频下精度可靠
5.3 RX电平读取
直接读取IDR寄存器:
c复制rx_state = (CAN_RX_PORT->IDR & CAN_RX_PIN) ? GPIO_PIN_SET : GPIO_PIN_RESET;
6. 注意事项与经验分享
6.1 DTO超时风险
- 必须使用微秒级延时,不能使用HAL_Delay(毫秒级)
- 建议实际测量所用收发器的DTO时间(可通过DWT计数器)
- 保持至少20%的安全裕量
6.2 恢复时序
- 测试完成后必须立即将TX恢复为HIGH
- 重新初始化CAN外设前先DeInit
- 检查CAN配置参数是否与之前一致
6.3 调试技巧
- 增加调试输出:
c复制AUTO_DBG("[CAN] TX_MODER=0x%X, RX_MODER=0x%X\n",
(CAN_TX_PORT->MODER >> 26) & 0x3,
(CAN_RX_PORT->MODER >> 24) & 0x3);
- 实测DTO时间(使用DWT计数器):
c复制CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
7. 故障排查指南
7.1 常见故障现象
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| TX=HIGH时RX=LOW | 电源异常 CANH/CANL对地短路 隔离器/收发器损坏 |
检查各节点供电 测量CANH-CANL电阻 分段测试 |
| TX=LOW时RX=HIGH | 总线断路 收发器未驱动 DTO超时 |
检查连接器接触 测量TXD引脚波形 缩短测试时间 |
7.2 测量点建议
- MCU的TX/RX引脚
- 隔离器输入/输出
- 收发器TXD/RXD
- CANH/CANL差分信号
8. 完整实现代码
c复制/* 引脚定义 */
#define CAN_TX_PORT GPIOB
#define CAN_TX_PIN GPIO_PIN_13
#define CAN_TX_BIT 13
#define CAN_RX_PORT GPIOB
#define CAN_RX_PIN GPIO_PIN_12
#define CAN_RX_BIT 12
#define CAN_AF_NUM 9 /* GPIO_AF9_FDCAN */
#define CAN_GPIO_TEST_DELAY_US 100 // 测试延时 (微秒)
#define DEBUG_DTO_MEASUREMENT 0 // 测试用,打印 DTO 测量值
extern FDCAN_HandleTypeDef hfdcan;
uint8_t AutoTest_CheckCAN_GPIO(void)
{
uint8_t result = 1;
GPIO_PinState rx_state;
/* 停止CAN外设 */
if (hfdcan.Instance != NULL) {
[HAL](https://taotoken.net/?utm_source=hardware)_FDCAN_Stop(&hfdcan);
OS_TASK_Delay(10);
}
/* 配置TX为推挽输出 */
GPIO_Set(CAN_TX_PORT, CAN_TX_PIN,
GPIO_MODE_OUTPUT,
GPIO_OTYPE_PP,
GPIO_SPEED_FREQ_HIGH,
GPIO_NOPULL);
CAN_TX_PORT->AFR[CAN_TX_BIT >> 3] &= ~(0x0F << ((CAN_TX_BIT & 0x07) * 4));
/* 配置RX为输入 */
GPIO_Set(CAN_RX_PORT, CAN_RX_PIN,
GPIO_MODE_INPUT,
GPIO_OTYPE_PP,
GPIO_SPEED_FREQ_HIGH,
GPIO_NOPULL);
CAN_RX_PORT->AFR[CAN_RX_BIT >> 3] &= ~(0x0F << ((CAN_RX_BIT & 0x07) * 4));
/* 等待稳定 */
OS_TASK_Delayus(2);
/* 测试TX=HIGH */
CAN_TX_PORT->BSRR = CAN_TX_PIN;
OS_TASK_Delayus(CAN_GPIO_TEST_DELAY_US);
rx_state = (CAN_RX_PORT->IDR & CAN_RX_PIN) ? GPIO_PIN_SET : GPIO_PIN_RESET;
if (rx_state != GPIO_PIN_SET) {
result = 0;
}
/* 测试TX=LOW */
CAN_TX_PORT->BSRR = (CAN_TX_PIN << 16);
OS_TASK_Delayus(CAN_GPIO_TEST_DELAY_US);
rx_state = (CAN_RX_PORT->IDR & CAN_RX_PIN) ? GPIO_PIN_SET : GPIO_PIN_RESET;
if (rx_state != GPIO_PIN_RESET) {
result = 0;
}
/* 恢复TX=HIGH */
CAN_TX_PORT->BSRR = CAN_TX_PIN;
/* 恢复CAN功能 */
GPIO_Set(CAN_TX_PORT, CAN_TX_PIN,
GPIO_MODE_AF,
GPIO_OTYPE_PP,
GPIO_SPEED_FREQ_HIGH,
GPIO_NOPULL);
GPIO_AF_Set(CAN_TX_PORT, CAN_TX_BIT, CAN_AF_NUM);
GPIO_Set(CAN_RX_PORT, CAN_RX_PIN,
GPIO_MODE_AF,
GPIO_OTYPE_PP,
GPIO_SPEED_FREQ_HIGH,
GPIO_NOPULL);
GPIO_AF_Set(CAN_RX_PORT, CAN_RX_BIT, CAN_AF_NUM);
if (hfdcan.Instance != NULL) {
HAL_FDCAN_DeInit(&hfdcan);
CAN_HardInitial(CRT_CAN, 50000);
}
return result;
}
9. 实际应用建议
-
产线测试:
- 可以作为PCBA出厂测试项
- 配合自动化测试设备实现
- 记录测试结果用于质量追溯
-
现场诊断:
- 在CAN通信异常时作为诊断工具
- 可以集成到设备自检功能中
- 结合LED指示或串口输出结果
-
扩展应用:
- 适配不同型号的CAN收发器
- 扩展支持CAN FD
- 增加更多测试项(如终端电阻检测)
通过这种方法,我们可以在不使用昂贵测试设备的情况下,快速验证CAN硬件链路的完整性,大幅提高开发和维护效率。在实际项目中,这个方法已经帮助我快速定位了多起硬件连接问题和器件故障。
