Files
DCU_APP/GD32E508_DCU_APP/comm/esc_exmc_bus.cpp
T

601 lines
27 KiB
C++
Raw Normal View History

/**
* @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"
2026-07-20 02:36:56 +08:00
#include <stdio.h>
#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 */
/* bbDelay/bbReadData 仅被 diagDump 使用,随之一起门控,避免默认关闭时
* 触发 -Wunused-function;bbDriveData 被 continuityDrive 复用,保持常开。 */
#if CONFIG_ESC_EXMC_DIAG
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;
}
#endif /* CONFIG_ESC_EXMC_DIAG (bbDelay + bbReadData) */
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 */
2026-07-20 02:36:56 +08:00
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);
/* 先 deinit 再 init。BOOT 跳转前 exmc_norsram_disable + 关时钟,
* SNCTL/SNTCFG 仍留着 BOOT 的配置;APP 若只 enable+init 可能叠在残留位上。
* deinit 把 Region0 打回复位默认,再按下面参数重铺。 */
exmc_norsram_deinit(CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION0);
/* 配置 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);
2026-07-20 02:36:56 +08:00
/* 控制线: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");
2026-07-20 02:36:56 +08:00
/*
* 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)。时序余量按 DS 表 9-4/9-5 独立核算,见下方 BUSLAT。 */
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;
/* BUSLAT: 4。DS 表 9-4/9-5 要求 trdrd/twrwr/twrrd ≥ 13ns,
* 该间隔由 EXMC 在两次访问之间插入的总线空闲期承担 = (1 固有 + BUSLAT) HCLK。
* BUSLAT=1 → 2 HCLK ≈ 11.1ns,低于 13ns 下限,仅靠 AHB 取指间隙偶然兜底:
* 常温刚好可用,温度升高(传播延迟增大)/电压下降(驱动减弱)即越界,表现为
* 偶发读到 0xFFFF 或错值。
* BUSLAT=4 → 5 HCLK ≈ 27.8ns,余量 2.1 倍,覆盖全温区与电压波动。
* 代价:每次 HBI 访问多 3 HCLK(16.7ns)。满载 1024B 过程数据按每 DWORD
* 两次 16-bit 访问计,增加约 256×2×16.7ns ≈ 8.6us,相对原 218us 增幅 3.9%,
* 4ms 周期占用从 5.4% 升至 5.7%,无实质影响。 */
rw_timing.bus_latency = 4U;
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, "
"ADDSET=%u AHLD=%u DSET=%u BUSLAT=%u HCLK)\r\n",
(unsigned)rw_timing.asyn_address_setuptime,
(unsigned)rw_timing.asyn_address_holdtime,
(unsigned)rw_timing.asyn_data_setuptime,
(unsigned)rw_timing.bus_latency);
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);
/* 时序寄存器回读:确认写入的 BUSLAT 真正落到硬件。
* SNTCFG0 位域: [3:0]=ASET-1 [7:4]=AHLD-1 [15:8]=DSET-1 [19:16]=BUSLAT-1。
* 期望 ASET=4/AHLD=2/DSET=20/BUSLAT=4 → 0x0313_0000|... 逐字段解码打印,
* 避免只看整字导致的位域误读;BUSLAT 实际值 <4 说明库或硬件截断,须立即排查。 */
{
const uint32_t tcfg = EXMC_SNTCFG0;
const unsigned aset = (unsigned)((tcfg & EXMC_SNTCFG_ASET) >> 0) + 1U;
const unsigned ahld = (unsigned)((tcfg & EXMC_SNTCFG_AHLD) >> 4) + 1U;
const unsigned dset = (unsigned)((tcfg & EXMC_SNTCFG_DSET) >> 8) + 1U;
const unsigned buslat = (unsigned)((tcfg & EXMC_SNTCFG_BUSLAT) >> 16) + 1U;
ECAT_LOGI("[ECAT][EXMC] init: SNTCFG0=0x%08lX -> ASET=%u AHLD=%u DSET=%u BUSLAT=%u\r\n",
(unsigned long)tcfg, aset, ahld, dset, buslat);
/* 5 HCLK @180MHz = 27.8ns,DS trdrd 下限 13ns → 需 BUSLAT>=2 才达标,取 4 留余量 */
if (buslat < 4U) {
ECAT_LOGI("[ECAT][EXMC] WARN: BUSLAT=%u < 4, trdrd 余量不足,"
" 温漂/压降时会偶发读到 0xFFFF 或错值\r\n", buslat);
}
}
/* 总线探活: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");
2026-07-20 02:36:56 +08:00
/* ---- 硬件诊断: 转储关键寄存器 + GPIO 手动输出测试 ----
* 默认关闭。硬件层已确认全通,探针仅换板/补焊排查浮空时经
* CONFIG_ESC_EXMC_DIAG 临时开启(会拆 EXMC 引脚成 GPIO,正常运行不该跑)。 */
#if CONFIG_ESC_EXMC_DIAG
diagDump();
#endif
2026-07-20 02:36:56 +08:00
s_ok = true;
ECAT_LOGI("[ECAT][EXMC] init: OK, bus enabled\r\n");
return true;
}
2026-07-20 02:36:56 +08:00
/* ========================================================================
* 硬件诊断: 精简版 —— 只保留核心探活 + bit-bang + 位级回显
* 整段由 CONFIG_ESC_EXMC_DIAG 门控,默认不编译。bbDelay/bbReadData/
* bbDriveData 等位拍辅助函数在上方匿名 namespace 中,continuityDrive 仍复用,保留。
* ======================================================================== */
#if CONFIG_ESC_EXMC_DIAG
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 实际电平回读:输入通路常连引脚,输出态亦读真实电压。
* 驱动高却 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);
2026-07-20 02:36:56 +08:00
}
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");
}
}
#endif /* CONFIG_ESC_EXMC_DIAG */
/* ========================================================================
* 通断测试: 网页逐个 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 状态可能被污染。 */
s_maskPrev = EscExmcBus::maskEnter();
/* 全部数据线 + 控制线设为 OUT_PP */
gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
kDataMaskD | (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinWr) |
(1U<<kPinA1) | (1U<<kPinA2));
gpio_init(GPIOE, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, kDataMaskE);
/* 控制线空闲态: CS/RD/WR=高, A1=A2=低 */
GPIO_BOP(GPIOD) = (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinWr);
GPIO_BC(GPIOD) = (1U<<kPinA1) | (1U<<kPinA2);
s_continuityActive = true;
printf("[CONT] test mode entered (GPIO -> 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<<kPinCs) | (1U<<kPinRd) | (1U<<kPinWr);
GPIO_BC(GPIOD) = (1U<<kPinA1) | (1U<<kPinA2);
printf("[CONT] pattern 0x%04X driven\r\n", (unsigned)pat);
return 0;
}
case 5: /* CS low */
case 6: /* RD low */
case 7: /* WR low */
case 8: /* A1 high */
case 9: { /* A2 high */
continuityEnterTestMode();
/* 数据线恢复空闲(避免上次数据 pattern 残留) */
bbDriveData(0x0000);
uint32_t pin;
bool drive_high;
switch (action) {
case 5: pin = kPinCs; drive_high = false; break; /* CS low */
case 6: pin = kPinRd; drive_high = false; break; /* RD low */
case 7: pin = kPinWr; drive_high = false; break; /* WR low */
case 8: pin = kPinA1; drive_high = true; break; /* A1 high */
case 9: pin = kPinA2; drive_high = true; break; /* A2 high */
default: return 0xEE;
}
/* 先把所有控制线恢复空闲,再驱动目标线 */
GPIO_BOP(GPIOD) = (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinWr);
GPIO_BC(GPIOD) = (1U<<kPinA1) | (1U<<kPinA2);
if (drive_high) {
GPIO_BOP(GPIOD) = (1U << pin);
} else {
GPIO_BC(GPIOD) = (1U << pin);
}
printf("[CONT] control line PD%lu driven %s\r\n",
(unsigned long)pin, drive_high ? "HIGH" : "LOW");
return 0;
2026-07-20 02:36:56 +08:00
}
case 10: { /* READ HOLD: CS#+RD# 持续拉低, 数据线浮空输入
* 用途: 万用表在 LAN9252 芯片侧引脚测量读周期数据电平。
* 判据: 芯片 HBI 活着 => 驱动 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<<kPinWr);
GPIO_BC(GPIOD) = (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinA1) | (1U<<kPinA2);
printf("[CONT] READ-HOLD: CS#+RD# held low, data Hi-Z\r\n");
printf("[CONT] measure at chip: D2(P12)/D4(P49)/D5(P50)/D9(P19)/D12(P21)=3.3V & others 0V => 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 打断而错址。 */
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);
}
}