利用STM32的GPIO对CAN收发器链路进行硬件电平检测
1. 概述
本文档介绍如何使用STM32的GPIO对CAN收发器链路进行硬件电平检测,确保从MCU的TX/RX引脚到CAN总线的整个信号通路正常。硬件链路为:
-
STM32 TX (PB13) → 数字隔离器 NSI8021N1 → CAN收发器 NCA1051N → CAN总线
-
STM32 RX (PB12) ← 数字隔离器 NSI8021N1 ← CAN收发器 NCA1051N ← CAN总线
通过将TX/RX引脚临时切换为普通GPIO,手动控制TX输出电平,经过隔离器和收发器回环后读取RX引脚电平,验证通路是否正常。
2. 硬件连接
| 组件 | 引脚 | 连接对象 | 说明 |
|---|---|---|---|
| STM32 | PB13 (TX) | NSI8021N1输入 | CAN发送信号 |
| STM32 | PB12 (RX) | NSI8021N1输出 | CAN接收信号 |
| NSI8021N1 | 输出 | NCA1051N TXD | 隔离后送收发器 |
| NSI8021N1 | 输入 | NCA1051N RXD | 收发器输出经隔离回MCU |
| NCA1051N | CANH, CANL | CAN总线 | 差分总线 |
注意:NSI8021N1为数字隔离器,默认输出高电平(对应CAN隐性态),输入悬空时输出为高。
3. 测试原理
利用CAN收发器的逻辑特性:
-
TX = HIGH(隐性) → 收发器不驱动总线 → CANH ≈ CANL ≈ 2.5V → RXD = HIGH → RX = HIGH
-
TX = LOW(显性) → 收发器驱动总线 → CANH = 3.5V, CANL = 1.5V → RXD = LOW → RX = LOW
因此,通过控制TX输出高/低,并检查RX是否跟随,即可判断整个链路是否正常。
4. 关键参数:NCA1051N的TXD Dominant Timeout (DTO)
NCA1051N具有TXD显性超时保护功能,当TXD引脚持续为低超过一定时间后,收发器会自动关闭驱动器,使总线恢复隐性,RXD回到高电平。数据手册参数:
-
t_TXD_DTO: Min = 800μs, Typ = 2200μs, Max = 4500μs
测试时必须确保TX拉低的时间 < 800μs(取最小值作为安全上限),否则会导致收发器触发保护,RX误判为高。本代码使用 100μs 延时,远小于800μs,留有充足裕量。
5. 实现步骤
5.1 停止CAN外设
CAN外设运行时控制TX/RX引脚,必须停止外设再修改GPIO配置,避免干扰。
HAL_FDCAN_Stop(&hfdcan);
OS_TASK_Delay(10);
5.2 配置TX为推挽输出
-
模式:
GPIO_MODE_OUTPUT(手动控制电平) -
输出类型:
GPIO_OTYPE_PP(推挽,主动驱动高/低) -
速度:
GPIO_SPEED_FREQ_HIGH(与CAN通信配置一致) -
上下拉:
GPIO_NOPULL(推挽输出无需上下拉) -
清除AFR:确保引脚完全由GPIO控制
5.3 配置RX为输入
-
模式:
GPIO_MODE_INPUT(读取电平) -
上下拉:
GPIO_NOPULL(隔离器主动驱动,不需要内部上下拉) -
清除AFR:同TX
5.4 等待配置稳定
延时2μs,确保GPIO寄存器写入生效及信号链路稳定(考虑隔离器+收发器传播延迟约480ns)。
5.5 测试TX=HIGH(隐性)
-
TX置高,延时100μs,读取RX。
-
预期RX=HIGH,否则故障(可能电源异常、短路等)。
5.6 测试TX=LOW(显性)
-
TX置低,延时100μs,读取RX。
-
预期RX=LOW,否则故障(注意不能超过DTO时间)。
-
测试完成后立即恢复TX=HIGH,避免长时间占用总线。
5.7 恢复引脚为CAN复用功能
-
重新配置TX/RX为复用模式(
GPIO_MODE_AF),设置AF编号(如AF9对应FDCAN)。 -
配置参数与CAN初始化时一致(推挽、高速、无上下拉)。
5.8 重新初始化CAN外设
-
调用
HAL_FDCAN_DeInit复位外设,再通过CAN_HardInitial重新初始化,确保外设与GPIO同步。
6. 代码关键片段解析
6.1 GPIO配置函数
GPIO_Set(端口, 引脚, 模式, 输出类型, 速度, 上下拉)
GPIO_Set(CAN_TX_PORT, CAN_TX_PIN,
GPIO_MODE_OUTPUT, /* Mode: 输出模式 —— 手动控制电平 */
GPIO_OTYPE_PP, /* OType: 推挽输出 —— 主动驱动高低电平 */
GPIO_SPEED_FREQ_HIGH, /* Speed: 高速 —— 与 CAN 通信时的配置保持一致 */
GPIO_NOPULL); /* PUPD: 无上下拉 —— 推挽输出的引脚由驱动器主动驱动,不需要上下拉电阻 */
各参数选择理由:
[GPIO_MODE_OUTPUT] 输出模式
(1)我们需要手动控制 TX 引脚输出高/低电平
(2)不能用 AF 模式(那是给 FDCAN 外设用的)
[GPIO_OTYPE_PP] 推挽输出
(1)推挽(Push-Pull): 能主动驱动 HIGH 和 LOW,驱动能力强
(2)开漏(Open-Drain): 只能主动拉低,高电平靠外部上拉,驱动弱
(3)这里必须用推挽,因为:
(a)需要主动输出 HIGH 给隔离器 NSI8021N1 的输入端,也需要主动输出 LOW 给隔离器
(b)开漏模式下 HIGH 电平由上拉电阻提供,上升沿慢,且如果没有外部上拉则完全无法输 出 HIGH。
[GPIO_SPEED_FREQ_HIGH] 高速
(1)速度(OSPEEDR)控制输出驱动器的压摆率(slew rate)
(2)对于本测试(静态电平),速度其实不影响功能,LOW 也可以。选择 HIGH 是为了与 CAN 通信时的配置保持一致 (CAN 波特率 50kbps~1Mbps 需要较快的边沿)
注意:VERY_HIGH 会增加 EMI,对本应用无必要
[GPIO_NOPULL] 无上下拉
(1)推挽输出模式下,引脚由驱动器主动驱动,不需要上下拉电阻。加上下拉反而可能与驱动器产生微小冲突,增加功耗。
(2)CAN 的 TX 空闲态为 HIGH(隐性),但这由我们的代码控制,不需要靠上拉来维持
6.2 清除AFR
CAN_TX_PORT->AFR[CAN_TX_BIT >> 3] &= ~(0x0F << ((CAN_TX_BIT & 0x07) * 4));
虽然GPIO模式下AFR不起作用,但清除是良好实践,避免潜在硬件行为不一致。
6.3 控制TX电平
使用BSRR寄存器进行原子操作:
-
置高:
CAN_TX_PORT->BSRR = CAN_TX_PIN; -
置低:
CAN_TX_PORT->BSRR = (CAN_TX_PIN << 16);
6.4 延时选择
#define CAN_GPIO_TEST_DELAY_US 100
OS_TASK_Delayus(CAN_GPIO_TEST_DELAY_US);
必须远小于800μs,100μs是经验值,兼顾稳定性和安全裕量。
6.5 读取RX电平
rx_state = (CAN_RX_PORT->IDR & CAN_RX_PIN) ? GPIO_PIN_SET : GPIO_PIN_RESET;
7. 注意事项
(1)DTO超时风险:任何TX拉低的时间超过800μs(芯片个体最小值)都会导致误判。必须使用微秒级延时,不可使用HAL_Delay(毫秒级)。
(2)恢复TX为HIGH:测试完成后立即置高,避免总线长时间显性,影响其他节点通信。
(3)上下拉配置:RX必须为NOPULL,否则可能掩盖断线故障或导致电平不稳。
(4)外设复位:测试后需DeInit再Init,确保CAN外设状态干净。
(5)调试选项:代码中DEBUG_DTO_MEASUREMENT可用于实测DTO时间,便于验证芯片特性。
8. 故障排查
-
TX=HIGH时RX=LOW:可能电源异常、CANH/CANL对地短路、隔离器/收发器损坏。
-
TX=LOW时RX=HIGH:可能总线断路、收发器未驱动、DTO超时(检查延时是否过大)、收发器供电异常。
9. 结论
通过GPIO电平检测,可快速定位CAN硬件链路故障。本方法不依赖CAN协议,仅需基本GPIO操作,适用于产线测试或板级调试。关键在于理解收发器DTO特性并控制测试时长,正确配置GPIO上下拉,以及测试后恢复外设状态。
10.完整代码
/* 引脚定义 */
#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;
/**
* @brief CAN GPIO 电平检测
* @details 硬件链路:
* STM32 PB13(TX) → NSI8021N1(隔离器) → NCA1051N(CAN收发器) → CAN Bus
* STM32 PB12(RX) ← NSI8021N1(隔离器) ← NCA1051N(CAN收发器) ← CAN Bus
*
* 测试原理: 将 TX/RX 从 CAN 复用功能切换为普通 GPIO,
* 手动控制 TX 输出高/低电平,信号经过 隔离器→收发器→总线→收发器→隔离器
* 回环后,在 RX 上读取电平,验证整条通路是否正常。
*
* CAN 电平逻辑:
* TX=HIGH(隐性) → 收发器不驱动总线 → CANH≈CANL → RX=HIGH
* TX=LOW (显性) → 收发器驱动总线 → CANH>CANL → RX=LOW
*
* @warning NCA1051N 存在 TXD Dominant Timeout (DTO) 保护机制
* 数据手册参数 t_TXD_DTO: Min=800μs, Typ=2200μs, Max=4500μs
* 实测值: ≈2130μs (DWT 精确测量)
* TX 持续拉低超过 DTO 后,收发器自动释放总线,RX 回到隐性(HIGH)
* 因此测试延时必须 < 800μs (DTO Min值),本代码使用 100μs
*
* @retval 1: 正常, 0: 异常
*/
uint8_t AutoTest_CheckCAN_GPIO(void)
{
AUTO_DBG("\n=== CAN GPIO Level Check ===\n");
uint8_t result = 1;
GPIO_PinState rx_state;
/* 第1步: 停止 CAN 外设 */
if (hfdcan.Instance != NULL)
{
HAL_FDCAN_Stop(&hfdcan);
OS_TASK_Delay(10);
}
/* 第2步: 配置 TX 为推挽输出 */
/* GPIO_Set(GPIOx, Pinx, Mode, OType, OSpeed, PUPD) */
GPIO_Set(CAN_TX_PORT, CAN_TX_PIN,
GPIO_MODE_OUTPUT, /* Mode: 输出模式 —— 手动控制电平 */
GPIO_OTYPE_PP, /* OType: 推挽输出 —— 主动驱动高低电平 */
GPIO_SPEED_FREQ_HIGH, /* Speed: 高速 —— 与 CAN 通信时的配置保持一致 */
GPIO_NOPULL); /* PUPD: 无上下拉 —— 推挽输出的引脚由驱动器主动驱动,不需要上下拉电阻 */
/* 清除 TX 引脚的 AFR (Alternate Function Register) 设置 */
CAN_TX_PORT->AFR[CAN_TX_BIT >> 3] &= ~(0x0F << ((CAN_TX_BIT & 0x07) * 4));
/* 第3步: 配置 RX 为输入 */
GPIO_Set(CAN_RX_PORT, CAN_RX_PIN,
GPIO_MODE_INPUT, /* 输入模式: 读取电平 */
GPIO_OTYPE_PP, /* (输入模式下无效,传PP保持统一) */
GPIO_SPEED_FREQ_HIGH, /* (输入模式下无效,传HIGH保持统一) */
GPIO_NOPULL); /* 无上下拉: 隔离器主动驱动,不需要 */
/* 清除 RX 引脚的 AFR 设置 (理由同 TX) */
CAN_RX_PORT->AFR[CAN_RX_BIT >> 3] &= ~(0x0F << ((CAN_RX_BIT & 0x07) * 4));
#if DEBUG_DTO_MEASUREMENT
AUTO_DBG("\n=== CAN DTO Precise Measurement ===\n");
/* 使能 DWT 计数器(如果还没使能) */
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
uint32_t sys_clk = SystemCoreClock; /* e.g. 280MHz for H7B0 */
AUTO_DBG("System Clock: %lu MHz\n", sys_clk / 1000000);
/* 先恢复 TX=HIGH */
CAN_TX_PORT->BSRR = CAN_TX_PIN;
OS_TASK_Delay(50);
/* TX 拉低,开始计时 */
__disable_irq(); /* 关中断,确保采样不被打断 */
DWT->CYCCNT = 0;
CAN_TX_PORT->BSRR = (CAN_TX_PIN << 16); /* TX = LOW */
uint32_t start_cycle = DWT->CYCCNT;
uint32_t flip_cycle = 0;
uint8_t found = 0;
/* 持续采样 20ms(足够长) */
uint32_t timeout_cycles = sys_clk / 50; /* 20ms */
while ((DWT->CYCCNT - start_cycle) < timeout_cycles)
{
if (CAN_RX_PORT->IDR & CAN_RX_PIN) /* RX 变高了! */
{
flip_cycle = DWT->CYCCNT - start_cycle;
found = 1;
break;
}
}
/* 恢复 TX */
CAN_TX_PORT->BSRR = CAN_TX_PIN;
__enable_irq();
if (found)
{
uint32_t dto_us = flip_cycle / (sys_clk / 1000000);
AUTO_DBG("[DTO] RX flipped after %lu cycles = %lu us\n", flip_cycle, dto_us);
AUTO_DBG("[DTO] This is the Dominant Timeout of your CAN transceiver!\n");
}
else
{
AUTO_DBG("[DTO] RX never flipped in 20ms — transceiver may not have DTO\n");
AUTO_DBG("[DTO] Or there's a hardware issue\n");
}
#endif
/* 等待 GPIO 配置生效 */
OS_TASK_Delayus(2);
/* 打印配置状态用于调试 */
AUTO_DBG("[CAN] TX_MODER=0x%X, RX_MODER=0x%X\n",
(CAN_TX_PORT->MODER >> 26) & 0x3,
(CAN_RX_PORT->MODER >> 24) & 0x3);
/* 第4步: 测试 TX=HIGH (隐性态) */
CAN_TX_PORT->BSRR = CAN_TX_PIN; /* BSRR 低16位置1 = 置高, 原子操作 */
// OS_TASK_Delay(5); /* 会造成 DTO 超时,让电平重新变成隐性 */
OS_TASK_Delayus(CAN_GPIO_TEST_DELAY_US); /* 100μs: 等待信号通过整条链路 */
rx_state = (CAN_RX_PORT->IDR & CAN_RX_PIN) ? GPIO_PIN_SET : GPIO_PIN_RESET;
if (rx_state == GPIO_PIN_SET)
{
AUTO_DBG("[CAN] TX=HIGH, RX=HIGH -> PASS\n");
}
else
{
AUTO_DBG("[CAN] TX=HIGH, RX=LOW -> FAIL\n");
AUTO_DBG("Possible causes: Abnormal power supply to the isolator/transceiver, short circuit to ground in CANH or CANL.\n");
result = 0;
}
/* 第5步: 测试 TX=LOW (显性态) */
CAN_TX_PORT->BSRR = (CAN_TX_PIN << 16); /* BSRR 低16位置1 = 置高, 原子操作 */
// OS_TASK_Delay(5); /* 会造成 DTO 超时,让电平重新变成隐性 */
OS_TASK_Delayus(CAN_GPIO_TEST_DELAY_US); /* 100μs: 必须远小于 DTO(800μs Min) */
rx_state = (CAN_RX_PORT->IDR & CAN_RX_PIN) ? GPIO_PIN_SET : GPIO_PIN_RESET;
if (rx_state == GPIO_PIN_RESET)
{
AUTO_DBG("[CAN] TX=LOW, RX=LOW -> PASS\n");
}
else
{
AUTO_DBG("[CAN] TX=LOW, RX=HIGH -> FAIL\n");
result = 0;
}
/* 立即恢复 TX=HIGH (隐性态) */
CAN_TX_PORT->BSRR = CAN_TX_PIN;
/* 第6步: 恢复 TX 引脚为 FDCAN 复用功能 (AF9) */
GPIO_Set(CAN_TX_PORT, CAN_TX_PIN,
GPIO_MODE_AF, /* Mode: 复用模式 —— 交给 FDCAN 外设控制 */
GPIO_OTYPE_PP, /* OType: 推挽输出 —— CAN TX 需主动驱动 */
GPIO_SPEED_FREQ_HIGH, /* Speed: 高速 —— 匹配 CAN 通信波特率需求 */
GPIO_NOPULL); /* PUPD: 无上下拉 —— 外设控制,不需要 */
/* 设置 TX 引脚的复用功能为 AF9 (FDCAN2) */
GPIO_AF_Set(CAN_TX_PORT, CAN_TX_BIT, CAN_AF_NUM); /* PB13 -> AF9 (FDCAN2_TX) */
/* 第7步: 恢复 RX 引脚为 FDCAN 复用功能 (AF9) */
GPIO_Set(CAN_RX_PORT, CAN_RX_PIN,
GPIO_MODE_AF, /* Mode: 复用模式 —— 交给 FDCAN 外设读取 */
GPIO_OTYPE_PP, /* OType: (RX方向无效,保持与初始化一致) */
GPIO_SPEED_FREQ_HIGH, /* Speed: (RX方向无效,保持与初始化一致) */
GPIO_NOPULL); /* PUPD: 无上下拉 —— 隔离器主动驱动 */
GPIO_AF_Set(CAN_RX_PORT, CAN_RX_BIT, CAN_AF_NUM); /* PB12 -> AF9 (FDCAN2_RX) */
/* 第8步: 重新初始化并启动 CAN 外设 */
if (hfdcan.Instance != NULL)
{
HAL_FDCAN_DeInit(&hfdcan); /* 完全复位 FDCAN 外设 */
CAN_HardInitial(CRT_CAN, 50000); /* 重新初始化 (50kbps) */
// HAL_FDCAN_Start(&hfdcan);
}
AUTO_DBG("[CAN-GPIO] Result: %s\n", result ? "PASS" : "FAIL");
return result;
}
更多推荐

所有评论(0)