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)外设复位:测试后需DeInitInit,确保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;
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐