/** * @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 "sys/config.h" #include "device/watchdog.h" #include extern "C" { #include "gd32e50x_exmc.h" /* RTE/Device/GD32E508VE 或 -I 同目录 */ #include "gd32e50x_gpio.h" #include "gd32e50x_rcu.h" } /* 内存映射访问宏:基址 + 偏移(按 16-bit 半字对齐,LAN9252 HBI 地址左移 1 位)。 */ #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 */ /* LAN9252 MODE0/1 strap 引脚:临时用 PB8/PB9 模拟拉高/拉低测试。 * 实际硬件 MODE0(Pin48)=PB8, MODE1(Pin46)=PB9(若未接 PB8/PB9 则此测试无效)。 */ constexpr uint32_t kPinMode0 = 8U; /* PB8 -> LAN9252 Pin48 MODE0 */ constexpr uint32_t kPinMode1 = 9U; /* PB9 -> LAN9252 Pin46 MODE1 */ 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 / NE1 * A17(PD12) 用于 HBI Indexed Mode 访问 HBI_DATA_0,已将 LAN9252 A2 (Pin 29) * 接到 PD12。 */ 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_NE1_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_NE1_PIN); ECAT_LOGI( "[ECAT][EXMC] init: GPIO configured (D0-D15/A16/A17/NOE/NWE/NE1)\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) */ 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 = 2U; /* ~11ns */ rw_timing.asyn_address_holdtime = 2U; /* min 2 */ 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() { /* 通断测试模式活跃时跳过诊断,避免 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"); /* 保存原 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); } /* 恢复 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 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); /* 全部数据线 + 控制线设为 OUT_PP */ gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, kDataMaskD | (1U< OUT_PP)\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; s_continuityActive = false; printf("[CONT] test mode exited (GPIO -> AF_PP restored)\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<