变更点: 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、评审过程稿
601 lines
27 KiB
C++
601 lines
27 KiB
C++
/**
|
||
* @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);
|
||
}
|
||
} |