Files
DCU_APP/GD32E508_DCU_APP/comm/esc_exmc_bus.cpp
T
cbb cfe7725b26 feat(ethercat): LAN9252 bring-up完成,EEPROM烧录改主站侧并增强DC/AL可观测性
- 新增ESC硬件复位序列(1.5.25):PB10驱动LAN9252 RST#,拉低30ms+释放后300ms轮询HBI就绪,
  满足tpurstd/trstia时序;此前RST#仅靠R44上拉自然释放
- 修正EXMC 16-bit地址左移(1.5.27):HBI DATA偏移0x20000/0x30000→0x40000/0x60000;
  ADDSET 2→4 HCLK;片选更名NE1→NE0(LQFP100仅Region0)
- 按LAN9252手册/AN1907修正EEPROM镜像(1.5.28):word1高字节0x00→0xCC(SYNC0/1 Map+Output,
  否则SYNC0留LATCH态DC过程数据死)、word2=5000(脉宽50µs)、尾部补真实0xFF;
  gen_eeprom.py按ETG.1000.6重写;DCU_ESI.xml PDI 0x05→0x8D、RxPdo/TxPdo互换改4×64bit对齐
- 删除GD32侧EEPROM烧录路径(1.5.29):移除EscEeprom/esc_eeprom、eeprom_image.h及主循环
  烧录块,ESI镜像改由主站ecat_flash_eeprom.py经ESC 0x0500刷写(162字全校验通过);
  HBI互斥补屏蔽EXTI2(SYNC1),防Sync1_Isr两步事务打断主循环HBI访问致错址
- DC SYNC0通路可观测(1.5.30/31):EthercatSync新增sync0/sync1/escirq三个volatile计数器,
  task_runner 5s按增量打印速率;SSC OD新增只读0x2000 SYNC Diag,主站SDO轮询网页显示
- AL通路诊断(1.5.32):ECAT_Main/SetALStatus打印主站AL请求与写回状态+错误码,
  task_runner新增5s [ECAT][HB]心跳(仅同步ISR激活时);dc_app.c导出EcatDbg_ReadAlDiag
- 删除死代码EthercatSlave::periodicIsr三缓冲PDO桥,PDO交换由SSC完成(4×64bit=32B);
  EthercatSlave::init()恢复调用并以isBusReady()(BYTE_TEST)门控
- CANFD修复同通道突发只发3帧:transmitFrame新增allow_abort区分突发/软件队列路径,
  邮箱满落入软件TX队列(深度13)由TX完成中断drainTxQueueIsr回灌;BRS重新启用(0→1)
- motor_state新增CONFIG帧缓存(0x00000101, DLC 24)记录目标值回报,条目44B→76B、每页5→3
- SPI 0xA2诊断扩展action 20~24(ECAT_NRST拉低/拉高/浮空/回读);新增ESP32 EN自检打印
- 版本号更新至1.5.32
2026-09-01 03:07:48 +08:00

556 lines
24 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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 <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 */
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<<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;
}
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 打断而错址(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);
}
}