/** * @file esc_exmc_bus.cpp * @brief LAN9252 ESC 的 EXMC 16-bit 并行总线驱动实现。 * * 使用 GigaDevice gd32e50x_exmc 标准库。 * 注意:DFP 只把 FMC 注册为 CMSIS 组件,EXMC 未进组件列表; * 源文件放在 RTE/Device/... 并由 cproject group device_exmc 本地编译。 */ #include "comm/esc_exmc_bus.h" #include "comm/ethercat_sync.h" #include "sys/config.h" #include "device/watchdog.h" #include #include "RTE_Components.h" #include CMSIS_device_header extern "C" { #include "gd32e50x_exmc.h" /* RTE/Device/GD32E508VE 或 -I 同目录 */ #include "gd32e50x_gpio.h" #include "gd32e50x_rcu.h" } /* 内存映射访问宏:基址 + CPU 字节偏移。 * 注意 EXMC 16-bit 模式地址左移 1 位(用户手册 p869:HADDR[25:1]→EXMC_A[24:0]): * EXMC_A16(PD11) 引脚对应偏移 bit17=0x20000,EXMC_A17(PD12) 对应 bit18=0x40000, * 即 1<<16 的 CPU 偏移驱动的是 A15 引脚而非 A16。 */ #define ESC_EXMC_REG16(offset) \ (*(volatile uint16_t *)(CONFIG_ESC_EXMC_BASE_ADDR + (offset))) /* ======================================================================== * GPIO 位拍(bit-bang)辅助函数 —— 完全绕过 EXMC 外设, * 用普通 GPIO 手动产生 LAN9252 HBI 时序,用于判定"芯片是否活着"。 * 数据线物理映射:D0=PD14 D1=PD15 D2=PD0 D3=PD1 D4..D12=PE7..PE15 * D13=PD8 D14=PD9 D15=PD10 * 控制线:CS#=PD7(NE0) RD#=PD4(NOE) WR#=PD5(NWE) A1=PD11 A2=PD12 * ======================================================================== */ namespace { /* 通断测试模式状态(提前声明供 diagDump() 检查) */ static bool s_continuityActive = false; /* GPIOD/GPIOE 上属于数据总线的引脚掩码 */ constexpr uint32_t kDataMaskD = (1U << 0) | (1U << 1) | (1U << 8) | (1U << 9) | (1U << 10) | (1U << 14) | (1U << 15); constexpr uint32_t kDataMaskE = 0xFF80U; /* PE7..PE15 */ constexpr uint32_t kPinCs = 7U; /* PD7 */ constexpr uint32_t kPinRd = 4U; /* PD4 */ constexpr uint32_t kPinWr = 5U; /* PD5 */ constexpr uint32_t kPinA1 = 11U; /* PD11 */ constexpr uint32_t kPinA2 = 12U; /* PD12 */ void bbDelay() { for (volatile uint32_t i = 0U; i < 3000U; i++) { } /* ~40-80us @180MHz,远大于 HBI 时序要求 */ } uint16_t bbReadData() { uint32_t d = GPIO_ISTAT(GPIOD); uint32_t e = GPIO_ISTAT(GPIOE); uint16_t v = 0U; if (d & (1U << 14)) v |= (uint16_t)(1U << 0); /* D0 */ if (d & (1U << 15)) v |= (uint16_t)(1U << 1); /* D1 */ if (d & (1U << 0)) v |= (uint16_t)(1U << 2); /* D2 */ if (d & (1U << 1)) v |= (uint16_t)(1U << 3); /* D3 */ if (e & (1U << 7)) v |= (uint16_t)(1U << 4); /* D4 */ if (e & (1U << 8)) v |= (uint16_t)(1U << 5); /* D5 */ if (e & (1U << 9)) v |= (uint16_t)(1U << 6); /* D6 */ if (e & (1U << 10)) v |= (uint16_t)(1U << 7); /* D7 */ if (e & (1U << 11)) v |= (uint16_t)(1U << 8); /* D8 */ if (e & (1U << 12)) v |= (uint16_t)(1U << 9); /* D9 */ if (e & (1U << 13)) v |= (uint16_t)(1U << 10); /* D10 */ if (e & (1U << 14)) v |= (uint16_t)(1U << 11); /* D11 */ if (e & (1U << 15)) v |= (uint16_t)(1U << 12); /* D12 */ if (d & (1U << 8)) v |= (uint16_t)(1U << 13); /* D13 */ if (d & (1U << 9)) v |= (uint16_t)(1U << 14); /* D14 */ if (d & (1U << 10)) v |= (uint16_t)(1U << 15); /* D15 */ return v; } void bbDriveData(uint16_t v) { uint32_t dset = 0U, dclr = 0U, eset = 0U, eclr = 0U; /* GPIOD 数据位 */ const uint32_t dpins[7] = {14U, 15U, 0U, 1U, 8U, 9U, 10U}; const int dbits[7] = {0, 1, 2, 3, 13, 14, 15}; for (int i = 0; i < 7; ++i) { if (v & (1U << dbits[i])) dset |= (1U << dpins[i]); else dclr |= (1U << dpins[i]); } /* GPIOE 数据位:D4..D12 = PE7..PE15 = 数据位 4..12 */ for (int bit = 4; bit <= 12; ++bit) { uint32_t pin = (uint32_t)(bit + 3); /* bit4->PE7 ... bit12->PE15 */ if (v & (1U << bit)) eset |= (1U << pin); else eclr |= (1U << pin); } if (dset) GPIO_BOP(GPIOD) = dset; if (dclr) GPIO_BC(GPIOD) = dclr; if (eset) GPIO_BOP(GPIOE) = eset; if (eclr) GPIO_BC(GPIOE) = eclr; } } /* anonymous namespace */ bool EscExmcBus::s_ok = false; bool EscExmcBus::init() { s_ok = false; ECAT_LOGI("[ECAT][EXMC] init: enable RCU clocks\r\n"); rcu_periph_clock_enable(RCU_GPIOD); rcu_periph_clock_enable(RCU_GPIOE); rcu_periph_clock_enable(RCU_AF); rcu_periph_clock_enable(RCU_EXMC); /* 配置 16-bit 数据线 D0-D15 为 AF 推挽 */ gpio_init(CONFIG_EXMC_D0_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D0_PIN); gpio_init(CONFIG_EXMC_D1_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D1_PIN); gpio_init(CONFIG_EXMC_D2_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D2_PIN); gpio_init(CONFIG_EXMC_D3_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D3_PIN); gpio_init(CONFIG_EXMC_D4_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D4_PIN); gpio_init(CONFIG_EXMC_D5_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D5_PIN); gpio_init(CONFIG_EXMC_D6_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D6_PIN); gpio_init(CONFIG_EXMC_D7_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D7_PIN); gpio_init(CONFIG_EXMC_D8_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D8_PIN); gpio_init(CONFIG_EXMC_D9_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D9_PIN); gpio_init(CONFIG_EXMC_D10_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D10_PIN); gpio_init(CONFIG_EXMC_D11_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D11_PIN); gpio_init(CONFIG_EXMC_D12_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D12_PIN); gpio_init(CONFIG_EXMC_D13_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D13_PIN); gpio_init(CONFIG_EXMC_D14_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D14_PIN); gpio_init(CONFIG_EXMC_D15_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D15_PIN); /* 控制线:A16 / A17 / NOE / NWE / NE0 * A17(PD12) → LAN9252 A2 (Pin 29,IDX/DATA 选择);A16(PD11) → LAN9252 A1 (Pin 25)。 * 注意 EXMC 16-bit 模式地址左移 1 位:HBI_DATA_0 的 CPU 偏移是 0x40000/0x60000, * 见 esc_lan9252.cpp 头注释。 */ gpio_init(CONFIG_EXMC_A16_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_A16_PIN); gpio_init(CONFIG_EXMC_A17_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_A17_PIN); gpio_init(CONFIG_EXMC_NOE_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_NOE_PIN); gpio_init(CONFIG_EXMC_NWE_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_NWE_PIN); gpio_init(CONFIG_EXMC_NE0_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_NE0_PIN); ECAT_LOGI( "[ECAT][EXMC] init: GPIO configured (D0-D15/A16/A17/NOE/NWE/NE0)\r\n"); /* * EXMC NOR/SRAM Region0:16-bit、非复用、异步写。 * 注意:GD32E508Vx (LQFP100) 只有 NE0 (PD7) 一个 NOR/SRAM 片选引脚, * NE0 归属 Region0 (0x60000000, SNCTL0),必须用 Region0。 * 时序(AHB=180MHz,1 HCLK ≈ 5.56ns): * LAN9252 HBI 最小读/写周期约 100ns → data_setup ≥ 18 HCLK。 * asyn_* 字段单位为 HCLK 计数(库文档范围 * address_setup 1..16,data_setup 2..256)。 */ exmc_norsram_parameter_struct norsram_init; exmc_norsram_timing_parameter_struct rw_timing; exmc_norsram_timing_parameter_struct wr_timing; norsram_init.read_write_timing = &rw_timing; norsram_init.write_timing = &wr_timing; exmc_norsram_struct_para_init(&norsram_init); norsram_init.norsram_region = CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION0; norsram_init.write_mode = EXMC_ASYN_WRITE; norsram_init.extended_mode = DISABLE; norsram_init.asyn_wait = DISABLE; norsram_init.nwait_signal = DISABLE; norsram_init.memory_write = ENABLE; norsram_init.nwait_config = EXMC_NWAIT_CONFIG_BEFORE; norsram_init.wrap_burst_mode = DISABLE; norsram_init.nwait_polarity = EXMC_NWAIT_POLARITY_LOW; norsram_init.burst_mode = DISABLE; norsram_init.databus_width = EXMC_NOR_DATABUS_WIDTH_16B; norsram_init.memory_type = EXMC_MEMORY_TYPE_SRAM; norsram_init.address_data_mux = DISABLE; /* 读/写共用时序(非 extended mode) * 2026-08-20 慢档测试(16/16/256 HCLK)无效,已还原:慢档时序未带来任何 * 读数变化,速度因素彻底排除(与 bit-bang 40-80us 仍失败一致)。 */ rw_timing.asyn_access_mode = EXMC_ACCESS_MODE_A; rw_timing.syn_data_latency = EXMC_DATALAT_2_CLK; rw_timing.syn_clk_division = EXMC_SYN_CLOCK_RATIO_DISABLE; rw_timing.bus_latency = 1U; rw_timing.asyn_address_setuptime = 4U; /* ~22ns。DS 表9-4/9-5: trdrd/twrwr/twrrd ≥13ns——选通间隔=ADDSET 相,2 HCLK≈11.1ns 不达标(仅靠 AHB 间隙兜底),4 HCLK 稳过 */ rw_timing.asyn_address_holdtime = 2U; /* min 2,DS tah≥0 */ rw_timing.asyn_data_setuptime = 20U; /* ~111ns ≥ 100ns HBI */ wr_timing = rw_timing; ECAT_LOGI("[ECAT][EXMC] init: applying NOR/SRAM Region0 params (16b, async, " "t_data=%u HCLK)\r\n", (unsigned)rw_timing.asyn_data_setuptime); exmc_norsram_init(&norsram_init); exmc_norsram_enable(CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION0); /* === 清除 reserved bit 7 === * 现象:exmc_norsram_init 后 EXMC_SNCTL0=0x00001091,bit 7(reserved)=1。 * DFP 复位值 0x000030DB 的 bit 7=1,可能被硬件保留或导致未定义行为。 * NRW(bits 4-5)=01 已经是 16-bit,无需修改。 * 修复:清除 bit 7,保持其他位不变。 */ uint32_t snctl_before = EXMC_SNCTL0; EXMC_SNCTL0 = snctl_before & ~BIT(7); uint32_t snctl_after = EXMC_SNCTL0; ECAT_LOGI("[ECAT][EXMC] init: SNCTL0 fix: before=0x%08lX after=0x%08lX (bit7 reserved cleared)\r\n", (unsigned long)snctl_before, (unsigned long)snctl_after); /* 总线探活:LAN9252 处于 16-bit HBI Indexed Mode,无法通过固定偏移直接读取。 * 真实的芯片 ID / AL 状态校验由 EscLan9252::init() 通过 HBI_IDX_0/HBI_DATA_0 * 间接访问完成。 注意:LAN9252 上电后第一次 Host Bus * 操作必须是读周期,否则后续写操作会被忽略; EscLan9252 在 init 中首先读取 * CHIP_ID,满足该约束。 */ ECAT_LOGI("[ECAT][EXMC] init: Region0 enabled, chip ID will be verified by " "ESC driver\r\n"); /* ---- 硬件诊断: 转储关键寄存器 + GPIO 手动输出测试 ---- */ diagDump(); s_ok = true; ECAT_LOGI("[ECAT][EXMC] init: OK, bus enabled\r\n"); return true; } /* ======================================================================== * 硬件诊断: 精简版 —— 只保留核心探活 + bit-bang + 位级回显 * ======================================================================== */ void EscExmcBus::diagDump() { /* EtherCAT 同步 ISR 激活期间禁止诊断:本函数会把 EXMC 数据/控制线切成 GPIO, * 并大量 printf,会周期性拆毁运行中的 EtherCAT 总线。 */ if (EthercatSync::isActive()) { printf("[EXMC-DIAG] skipped: EtherCAT sync ISR active (bus in use)\r\n"); return; } /* 通断测试模式活跃时跳过诊断,避免 20s 周期 probe 覆盖用户设置的电平 */ if (s_continuityActive) { printf("[EXMC-DIAG] skipped: continuity test mode active\r\n"); return; } printf("[EXMC-DIAG] --- bit-bang probe (bypass EXMC) ---\r\n"); /* NRST 实际电平回读(1.5.26):输入通路常连引脚,输出态亦读真实电压。 * 驱动高却 LOW => ECAT_NRST 被外部按低 => LAN9252 一直在复位态。 */ printf("[EXMC-DIAG] NRST(PB10) reads %s\r\n", (gpio_input_bit_get(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN) == SET) ? "HIGH" : "LOW"); /* 保存原 GPIO 配置 */ uint32_t save_ctl0_d = GPIO_CTL0(GPIOD); uint32_t save_ctl1_d = GPIO_CTL1(GPIOD); uint32_t save_ctl0_e = GPIO_CTL0(GPIOE); uint32_t save_ctl1_e = GPIO_CTL1(GPIOE); /* 控制线 OUT_PP, 数据线 IN */ gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_7|GPIO_PIN_11|GPIO_PIN_12); gpio_init(GPIOD, GPIO_MODE_IN_FLOATING, 0, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_8| GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_14|GPIO_PIN_15); gpio_init(GPIOE, GPIO_MODE_IN_FLOATING, 0, GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9| GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15); /* 空闲:CS/RD/WR 高, A1=A2=0 */ GPIO_BOP(GPIOD) = (1U<<7)|(1U<<4)|(1U<<5); GPIO_BC(GPIOD) = (1U<<11)|(1U<<12); bbDelay(); /* 读 IDX_0 (A2=0) —— 复位默认值 0x1234 */ GPIO_BC(GPIOD) = (1U<<7); /* CS=低 */ bbDelay(); GPIO_BC(GPIOD) = (1U<<4); /* RD=低 */ bbDelay(); uint16_t bb_idx0 = bbReadData(); GPIO_BOP(GPIOD) = (1U<<4); /* RD=高 */ bbDelay(); GPIO_BOP(GPIOD) = (1U<<7); /* CS=高 */ bbDelay(); printf("[EXMC-DIAG] bit-bang IDX_0 = 0x%04X (expect 0x1234)\r\n", (unsigned)bb_idx0); /* 判读 */ if (bb_idx0 == 0x1234U) { printf("[EXMC-DIAG] *** LAN9252 ALIVE ***\r\n"); } else if (bb_idx0 == 0x0000U || bb_idx0 == 0xFFFFU) { printf("[EXMC-DIAG] FAIL: bus floating (0x%04X) -> data line broken or chip not driving\r\n", (unsigned)bb_idx0); } else { printf("[EXMC-DIAG] UNEXPECTED: 0x%04X -> partial data line issue?\r\n", (unsigned)bb_idx0); } /* --- 预驱动/释放/读周期三段采样: 区分"被动拉低"与"芯片主动驱动" --- * 背景: bit-bang 读到稳定 0x0000, 而纯浮空输入应保持上次残留值或漂移。 * 两种解释: (a) 板上下拉/对地漏电(被动) (b) 芯片在 RD# 有效期驱动 0(主动)。 * 序列: 驱动 pattern -> 切输入快采样(电容应保持) -> 慢采样(检测下拉) * -> 拉低 CS#+RD# 采样(芯片若响应应驱动总线, 例如 0x1234)。 */ printf("[EXMC-DIAG] --- predrive/release/read probe ---\r\n"); static const uint16_t probe_pats[2] = {0xAAAAU, 0x5555U}; for (int p = 0; p < 2; ++p) { uint16_t pat = probe_pats[p]; /* 1) 数据线推挽驱动 pattern(控制线保持空闲: CS/RD/WR 高, A1/A2 低) */ gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, kDataMaskD); gpio_init(GPIOE, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, kDataMaskE); bbDriveData(pat); bbDelay(); /* 2) 切浮空输入, 快采样(自身电容应保持 pattern) */ gpio_init(GPIOD, GPIO_MODE_IN_FLOATING, 0, kDataMaskD); gpio_init(GPIOE, GPIO_MODE_IN_FLOATING, 0, kDataMaskE); for (volatile int d = 0; d < 50; d++) {} /* ~微秒级 */ uint16_t s_fast = bbReadData(); /* 3) 慢采样(约 40-80us 后: 若板上有下拉会漏向 0) */ bbDelay(); uint16_t s_slow = bbReadData(); /* 4) 读周期采样(CS#+RD# 低: 芯片若驱动会在总线上给出真实数据) */ GPIO_BC(GPIOD) = (1U << 7) | (1U << 4); bbDelay(); uint16_t s_rd = bbReadData(); GPIO_BOP(GPIOD) = (1U << 4) | (1U << 7); bbDelay(); printf("[EXMC-DIAG] pat=0x%04X fast=0x%04X slow=0x%04X rd=0x%04X\r\n", (unsigned)pat, (unsigned)s_fast, (unsigned)s_slow, (unsigned)s_rd); } /* 恢复 GPIO */ GPIO_CTL0(GPIOD) = save_ctl0_d; GPIO_CTL1(GPIOD) = save_ctl1_d; GPIO_CTL0(GPIOE) = save_ctl0_e; GPIO_CTL1(GPIOE) = save_ctl1_e; /* EXMC 寄存器回读(一行) */ printf("[EXMC-DIAG] SNCTL0=0x%08lX SNTCFG0=0x%08lX\r\n", (unsigned long)EXMC_SNCTL0, (unsigned long)EXMC_SNTCFG0); /* EXMC raw 读 + 位级回显(仅在未响应时) */ uint16_t idx0_raw = ESC_EXMC_REG16(0x00000); printf("[EXMC-DIAG] EXMC raw IDX_0 = 0x%04X\r\n", (unsigned)idx0_raw); if (idx0_raw != 0x1234U) { /* 位级回显: 写 4 个 pattern 看哪些位回显(浮空)哪些位被驱动 */ const uint16_t patterns[4] = {0x0000U, 0xFFFFU, 0xAAAAU, 0x5555U}; uint16_t readbacks[4] = {0}; for (int i = 0; i < 4; ++i) { ESC_EXMC_REG16(0x00000) = patterns[i]; for (volatile int d = 0; d < 50; d++) {} readbacks[i] = ESC_EXMC_REG16(0x00000); } uint16_t echo_bits = 0U, driven_bits = 0U; for (int b = 0; b < 16; ++b) { uint16_t mask = (uint16_t)(1U << b); bool all_echo = true; for (int i = 0; i < 4; ++i) { if ((readbacks[i] & mask) != (patterns[i] & mask)) { all_echo = false; break; } } if (all_echo) echo_bits |= mask; else driven_bits |= mask; } printf("[EXMC-DIAG] echo bits(disconnected): 0x%04X driven bits(connected): 0x%04X\r\n", (unsigned)echo_bits, (unsigned)driven_bits); if (echo_bits == 0xFFFFU) { printf("[EXMC-DIAG] ALL 16 bits floating -> CS not reaching LAN9252 or chip dead\r\n"); printf("[EXMC-DIAG] -> run continuityTest() to check data lines with multimeter\r\n"); } else if (driven_bits == 0xFFFFU) { printf("[EXMC-DIAG] ALL 16 bits driven -> HBI alive but IDX_0!=0x1234 (unexpected)\r\n"); } else { printf("[EXMC-DIAG] PARTIAL echo -> broken data lines: "); for (int b = 0; b < 16; ++b) if (echo_bits & (1U << b)) printf("D%d ", b); printf("\r\n"); } } else { printf("[EXMC-DIAG] === HBI ALIVE ===\r\n"); } } /* ======================================================================== * 通断测试: 网页逐个 pattern 触发,GD32 驱动数据线/控制线输出已知电平, * 用户用万用表在 LAN9252 端测量对应引脚电压。 * 设计: * - continuityDrive(action) 不延时,驱动完立即返回,电平保持到下次调用 * - action=0(stop) 恢复 GPIO 为 EXMC AF_PP,回到正常工作模式 * - 不点按钮时引脚是高阻输入态(安全),手碰到也不短路 * 判据: * - GD32 输出高(3.3V)但 LAN9252 端测到 0V/浮空 → 断线 * - GD32 输出低(0V)但 LAN9252 端测到 3.3V → 断线/短路 * - 电压跟随 → 连线正常 * ======================================================================== */ static uint32_t s_save_ctl0_d = 0, s_save_ctl1_d = 0; static uint32_t s_save_ctl0_e = 0, s_save_ctl1_e = 0; static uint32_t s_maskPrev = 0U; static void continuityEnterTestMode() { if (s_continuityActive) return; s_save_ctl0_d = GPIO_CTL0(GPIOD); s_save_ctl1_d = GPIO_CTL1(GPIOD); s_save_ctl0_e = GPIO_CTL0(GPIOE); s_save_ctl1_e = GPIO_CTL1(GPIOE); /* 测试期屏蔽 SYNC0/ESC IRQ/SYNC1:ISR 的 HBI 访问落在已被切成 GPIO 态的 * 总线上只会读回垃圾并错写 IDX,SSC 状态可能被污染(1.5.29)。 */ s_maskPrev = EscExmcBus::maskEnter(); /* 全部数据线 + 控制线设为 OUT_PP */ gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, kDataMaskD | (1U< OUT_PP, ECAT ISRs masked)\r\n"); } static void continuityExitTestMode() { if (!s_continuityActive) return; GPIO_CTL0(GPIOD) = s_save_ctl0_d; GPIO_CTL1(GPIOD) = s_save_ctl1_d; GPIO_CTL0(GPIOE) = s_save_ctl0_e; GPIO_CTL1(GPIOE) = s_save_ctl1_e; EscExmcBus::maskExit(s_maskPrev); s_continuityActive = false; printf("[CONT] test mode exited (GPIO -> AF_PP restored, ISRs unmasked)\r\n"); } uint8_t EscExmcBus::continuityDrive(uint8_t action) { switch (action) { case 0: /* stop */ continuityExitTestMode(); return 0; case 1: /* 0xFFFF ALL HIGH */ case 2: /* 0x0000 ALL LOW */ case 3: /* 0xAAAA ODD HIGH */ case 4: { /* 0x5555 EVEN HIGH */ continuityEnterTestMode(); static const uint16_t pats[4] = {0xFFFF, 0x0000, 0xAAAA, 0x5555}; uint16_t pat = pats[action - 1]; bbDriveData(pat); /* 控制线恢复空闲态(避免上次控制线测试残留) */ GPIO_BOP(GPIOD) = (1U< 驱动 IDX_0=0x1234 (0001 0010 0011 0100b) => * 置位 bit2/4/5/9/12 => D2(Pin12)/D4(Pin49)/D5(Pin50)/D9(Pin19)/ * D12(Pin21)=3.3V, 其余数据脚=0V(全部为确定电平)。 * 全部中电平/漂移 => 芯片未驱动(区分"芯片不驱动"与"数据线断线": * GD32 侧 rd 采样无法区分两者, 芯片侧测量可以)。 */ continuityEnterTestMode(); /* 幂等:已活跃则保持;内部已屏蔽 ECAT ISR */ /* 数据线浮空输入(交给芯片驱动),控制线保持 OUT_PP */ gpio_init(GPIOD, GPIO_MODE_IN_FLOATING, 0, kDataMaskD); gpio_init(GPIOE, GPIO_MODE_IN_FLOATING, 0, kDataMaskE); /* 持续读周期: WR 高, CS#+RD# 低, A1=A2=0 -> 读 IDX_0 */ GPIO_BOP(GPIOD) = (1U< alive(0x1234); all floating => not driving\r\n"); return 0; } default: return 0xEE; } } __attribute__((section(".ramfunc"), noinline)) uint16_t EscExmcBus::read16(uint32_t offset) { return ESC_EXMC_REG16(offset); } __attribute__((section(".ramfunc"), noinline)) void EscExmcBus::write16(uint32_t offset, uint16_t data) { ESC_EXMC_REG16(offset) = data; } uint32_t EscExmcBus::maskEnter() { uint32_t prev = 0U; /* EXTI0=SYNC0(preempt=0) 无法被 BASEPRI 屏蔽,只能关 NVIC; * EXTI1=ESC IRQ / EXTI2=SYNC1 (均 preempt=1) 的 PDI_Isr/Sync1_Isr 同样做 * HBI 两步事务访问,一并屏蔽——否则主循环事务会被 Sync1_Isr 的 * PDO_InputMapping→HW_EscWriteIsr 打断而错址(1.5.29 修复)。 */ if (NVIC_GetEnableIRQ(EXTI0_IRQn) != 0U) { prev |= 1U; NVIC_DisableIRQ(EXTI0_IRQn); } if (NVIC_GetEnableIRQ(EXTI1_IRQn) != 0U) { prev |= 2U; NVIC_DisableIRQ(EXTI1_IRQn); } if (NVIC_GetEnableIRQ(EXTI2_IRQn) != 0U) { prev |= 4U; NVIC_DisableIRQ(EXTI2_IRQn); } return prev; } void EscExmcBus::maskExit(uint32_t prev) { /* 按保存的状态恢复:外层已屏蔽的(如 SSC DISABLE_ESC_INT)不会被误开 */ if ((prev & 4U) != 0U) { NVIC_EnableIRQ(EXTI2_IRQn); } if ((prev & 2U) != 0U) { NVIC_EnableIRQ(EXTI1_IRQn); } if ((prev & 1U) != 0U) { NVIC_EnableIRQ(EXTI0_IRQn); } }