Files
DCU_APP/GD32E508_DCU_APP/comm/esc_exmc_bus.cpp
T
cbb 5fd986299c feat(comm): 1.6.51 CAN 空通道自动静默 + PDO 诊断 + OTA 布局共用头
变更点:
1. CAN-FD(device/CANFD.cpp、sys/config.h)
   - 发送统一 FD+BRS(DLC≤8 不再回退经典 CAN);OTA 期仍按通道压制 BRS
   - 空通道自动识别:连续 5 个 1Hz 窗口无有效帧且有错误 → 静默上报;有帧即恢复
   - 三路 CAN 默认全开;RX parse budget 48→64
2. EtherCAT 桥 / PDO
   - 段1 增 txtxid/rx00ff;版本应答 0x00FF 粘滞发布,避免被 CONFIG 冲掉
   - STATUS 禁止写入 xrx 快照;processSeq 新鲜度救援
3. OTA / 配置
   - sys/config.h include 仓库内 shared/ota_layout.h(与 BOOT 共用,不再依赖仓外路径)
   - ota_boot_request 对齐配置页 word 布局
4. docs/audit_ecat_spi_wifi_ota_compatibility.md:ECAT/SPI/WiFi/OTA 兼容性审计
5. .gitignore:忽略 .trae/、docs/traework-import、评审过程稿
2026-09-20 22:58:09 +08:00

601 lines
27 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 */
/* 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 */
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);
/* 控制线: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)。时序余量按 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");
/* ---- 硬件诊断: 转储关键寄存器 + GPIO 手动输出测试 ----
* 默认关闭。硬件层已确认全通,探针仅换板/补焊排查浮空时经
* CONFIG_ESC_EXMC_DIAG 临时开启(会拆 EXMC 引脚成 GPIO,正常运行不该跑)。 */
#if CONFIG_ESC_EXMC_DIAG
diagDump();
#endif
s_ok = true;
ECAT_LOGI("[ECAT][EXMC] init: OK, bus enabled\r\n");
return true;
}
/* ========================================================================
* 硬件诊断: 精简版 —— 只保留核心探活 + 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);
}
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;
}
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);
}
}