Files
DCU_APP/GD32E508_DCU_APP/comm/esc_lan9252.cpp
T
cbb f3d7fc0c30 feat(canfd): 添加收发器静默模式支持和SPI命令接口
添加了CAN FD收发器静默模式功能,允许通过硬件引脚控制发送使能:
- 新增setTxSilent/isTxSilent接口控制TCAN1057收发器Silent模式
- 静默时驱动器关断但接收机继续工作,支持只听不发场景
- 添加SPI命令0xC8用于ESP32远程控制静默模式切换
- 在总线状态位图中增加bit5表示静默状态
- 新增canBusyWaitUs函数用于ISR安全的微秒延时

fix(esc_lan9252): 改进warm jump后LAN9252探测逻辑

修复warm jump导致的LAN9252初始化问题:
- 识别warm jump后芯片非POR状态(idx0!=0x1234)的情况
- 自动补发硬件复位对齐到POR状态后再进行正常探测
- 改进调试信息输出,区分POR状态和warm jump残值情况
- 修正失败路径的复位标志初始化逻辑

refactor(spi): 整理SPI命令文件结构

调整SPI命令相关文件的组织结构:
- 移动诊断相关文件到更合适的位置
- 重新整理SPI命令处理文件的分组
2026-10-10 02:25:56 +08:00

719 lines
35 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_lan9252.cpp
* @brief LAN9252 EtherCAT ESC 驱动实现。
*
* 硬件使用 16-bit HBI Indexed Address Mode:
* - A2=0,A1=0 (偏移 0x00000) -> HBI_IDX_0
* - A2=1,A1=0 (偏移 0x40000) -> HBI_DATA_0 低 16 位
* - A2=1,A1=1 (偏移 0x60000) -> HBI_DATA_0 高 16 位
*
* 地址线换算(16-bit 数据宽度时 EXMC 内部将 HADDR[25:1] 接到 EXMC_A[24:0]):
* 板线:LAN9252 A1(Pin25)→PD11=EXMC_A16,A2(Pin29)→PD12=EXMC_A17。
* GD32E50x 用户手册 p869:16-bit 数据宽度时 EXMC 内部将 HADDR[25:1] 接到
* EXMC_A[24:0],即 EXMC_A16=CPU 偏移 bit17(0x20000)、EXMC_A17=bit18(0x40000)。
* LAN9252 手册 Table 9-3:A2=1 选 HBI_DATA_0,A1 选低/高半字。
*
* 访问模型(DS 12.11/表 12-13):
* HBI 索引直接寻址的只有 LAN9252 系统 CSR(BYTE_TEST 0x064 等)与访问端口;
* EtherCAT 核心 CSR(0x0000-0x0FFF)经 ECAT_CSR_CMD(0x304)/ECAT_CSR_DATA(0x300)
* 间接访问,过程 RAM(0x1000-0x1FFF)经 PRAM FIFO(0x000-0x03C + 0x308-0x314) 访问。
* 核心 CSR 不在 HBI 直接空间,直读恒 0、写丢弃。
*/
#include "comm/esc_lan9252.h"
#include "comm/esc_exmc_bus.h"
#include "sys/config.h"
#include "sys/systick.h"
#include <stdio.h>
#include "RTE_Components.h"
#include CMSIS_device_header
#include "gd32e50x_gpio.h"
#include "gd32e50x_rcu.h"
namespace {
/* HBI Indexed Mode 下 EXMC 偏移(A2=LAN9252 Pin29←PD12=EXMC_A17, A1=Pin25←PD11=EXMC_A16)
* EXMC 16-bit 模式地址左移 1 位:A16 引脚=CPU 偏移 0x20000,A17 引脚=CPU 偏移 0x40000。 */
constexpr uint32_t HBI_IDX0_OFFSET = 0x00000U; /* A2=0, A1=0 */
constexpr uint32_t HBI_DATA0_LOW_OFF = 0x40000U; /* A2=1(EXMC_A17), A1=0(EXMC_A16) */
constexpr uint32_t HBI_DATA0_HIGH_OFF = 0x60000U; /* A2=1(EXMC_A17), A1=1(EXMC_A16) */
/* ESC 关键寄存器字节地址。
* 注: LAN9252 的 HBI 在 EEPROM 空白/无效时依然工作(数据手册 9.5.4.2:
* 先轮询 HBI_IDX_0 默认值 0x1234,再轮询 BYTE_TEST(0x064)=0x87654321)。
* 芯片身份探针用 BYTE_TEST 寄存器(0x064),不是 ESC 0x0000(那是类型寄存器=0xC0)。 */
constexpr uint16_t LAN9252_REG_BYTE_TEST = 0x0064U; /**< 字节顺序测试寄存器(默认 0x87654321,LAN9252 手册 16.1.2)。 */
constexpr uint32_t LAN9252_BYTE_TEST_VALUE = 0x87654321U; /**< BYTE_TEST 默认值(小端序)。 */
constexpr uint16_t ESC_REG_DL_STATUS = 0x0110U; /**< DL Status:bit0 = PDI operational / EEPROM loaded。 */
constexpr uint16_t ESC_REG_EEP_CSR = 0x0502U; /**< EEPROM Control/Status:bit11=校验错,bit12=加载错(0=OK)。 */
/* LAN9252 系统中断寄存器(HBI 索引直接可达,DS 8.3 表 0.1)。
* IRQ 引脚上电默认全关:IRQ_CFG.IRQ_EN=0 且 INT_EN.ECAT_INT_EN=0,
* 不配这两个寄存器则 PB1 永远收不到中断,SSC 的 PDI_Isr 从不执行。 */
constexpr uint16_t LAN9252_REG_IRQ_CFG = 0x0054U; /**< 中断配置:bit8=IRQ_EN,bit4=IRQ_POL,bit0=IRQ_TYPE。 */
constexpr uint16_t LAN9252_REG_INT_STS = 0x0058U; /**< 中断状态(R/WC):bit0=ECAT_INT。 */
constexpr uint16_t LAN9252_REG_INT_EN = 0x005CU; /**< 中断使能:bit0=ECAT_INT_EN。 */
constexpr uint32_t IRQ_CFG_IRQ_EN = 1UL << 8; /**< IRQ 引脚输出总闸。 */
constexpr uint32_t IRQ_CFG_IRQ_POL = 1UL << 4; /**< 1=高有效,0=低有效(开漏时忽略本位)。 */
constexpr uint32_t IRQ_CFG_IRQ_TYPE = 1UL << 0; /**< 1=推挽,0=开漏(开漏强制低有效)。 */
constexpr uint32_t INT_EN_ECAT_INT = 1UL << 0; /**< 允许 EtherCAT 事件驱动 IRQ 引脚。 */
/* ESC 过程数据看门狗(DS 12.14.41-43)。SII 不携带看门狗字段,芯片复位默认
* divider=0x09C2(100µs)/WD Time PD=0x03E8(1000) 即 100ms——等于
* 100ms 手动调试周期,SM2 每 100ms 被判定超时,SSC 以 0x001B 拖回 SAFEOP。 */
constexpr uint16_t ESC_REG_WD_DIVIDER = 0x0400U; /**< 看门狗分频(100µs 基准,复位默认 0x09C2)。 */
constexpr uint16_t ESC_REG_WD_TIME_PD = 0x0420U; /**< 过程数据看门狗步数(复位默认 0x03E8=100ms)。 */
constexpr uint16_t ESC_WD_TIME_PD_STEPS = 10000U; /**< 10000 × 100µs = 1s。 */
/* LAN9252 直接映射的 ESC 访问端口(DS 表 12-13;经 HBI 索引直接读写) */
constexpr uint16_t ECAT_PRAM_RD_DATA = 0x0000U; /**< PRAM 读 FIFO(0x00-0x1C 别名,功能相同,突发用) */
constexpr uint16_t ECAT_PRAM_WR_DATA = 0x0020U; /**< PRAM 写 FIFO(0x20-0x3C 别名) */
constexpr uint16_t ECAT_CSR_DATA_REG = 0x0300U; /**< ESC 核心 CSR 间接访问数据寄存器 */
constexpr uint16_t ECAT_CSR_CMD_REG = 0x0304U; /**< ESC 核心 CSR 间接访问命令寄存器 */
constexpr uint16_t ECAT_PRAM_RD_ADDR_LEN = 0x0308U; /**< PRAM 读地址/长度 */
constexpr uint16_t ECAT_PRAM_RD_CMD = 0x030CU; /**< PRAM 读命令/状态 */
constexpr uint16_t ECAT_PRAM_WR_ADDR_LEN = 0x0310U; /**< PRAM 写地址/长度 */
constexpr uint16_t ECAT_PRAM_WR_CMD = 0x0314U; /**< PRAM 写命令/状态 */
constexpr uint32_t CSR_BUSY = 1UL << 31; /**< CSR_CMD:写 1 启动,完成自清 */
constexpr uint32_t CSR_RNW = 1UL << 30; /**< CSR_CMD:1=读 0=写 */
constexpr uint32_t PRAM_BUSY = 1UL << 31; /**< PRAM_*_CMD:写 1 启动,完成自清 */
constexpr uint32_t PRAM_AVAIL = 1UL << 0; /**< PRAM_*_CMD:数据可用(读)/空间可用(写) */
constexpr uint32_t POLL_MAX = 2000UL; /**< busy/avail 轮询上限(~ms 级,防芯片异常时死等) */
/* 电源管理(DS 6.3/表 5-1):芯片级睡眠 D1-D3。
* READY=0 期间仅 HW_CFG/PMT_CTRL/BYTE_TEST/RESET_CTL 四个寄存器可访问(DS 6.2.3),
* 恰好覆盖睡眠进入与唤醒流程所需;PMT_CTRL 可在睡眠态安全读(DS 6.3.5.1 注)。 */
constexpr uint16_t LAN9252_REG_HW_CFG = 0x0074U; /**< 硬件配置:bit27=READY(与 PMT_CTRL bit0 同源,DS 6.3.5.1)。 */
constexpr uint16_t LAN9252_REG_PMT_CTRL = 0x0084U; /**< 功耗管理控制:bit0=READY,bit28=PM_SLEEP_EN,bit31:29=PM_MODE。 */
constexpr uint32_t PMT_CTRL_READY = 1UL << 0;
constexpr uint32_t PMT_CTRL_PM_MODE_MSK = 7UL << 29; /**< 001=D1 010=D2 011=D3。 */
constexpr uint32_t PMT_CTRL_PM_SLEEP_EN = 1UL << 28; /**< 置 1 进入 PM_MODE 指定的睡眠档(须与 PM_MODE 分两次写)。 */
/* ESC 核心 MII 管理(ETG.1000.4 ESC 寄存器 0x0510-0x0514,经 csrRead/csrWrite 间接访问;
* 表 5-1 证实 PHY 寄存器不在 HBI 直接空间)。用于 D3 前置的 PHY General Power Down
* (DS 6.3.4:D3 要求两 PHY 先 General Power Down)与唤醒后恢复。 */
constexpr uint16_t ESC_REG_MII_CTRL = 0x0510U; /**< bit0=CMD(写 1 启动自清) bit1=WRITE bit8=BUSY。 */
constexpr uint16_t ESC_REG_MII_ADDR = 0x0512U; /**< bit4:0=PHY_ADDR bit15:5=REG_ADDR。 */
constexpr uint16_t ESC_REG_MII_DATA = 0x0514U; /**< bit15:0=MII 数据。 */
constexpr uint16_t MII_CTRL_CMD = 1U << 0;
constexpr uint16_t MII_CTRL_WRITE = 1U << 1;
constexpr uint16_t MII_CTRL_BUSY = 1U << 8;
constexpr uint8_t MII_REG_BMCR = 0U; /**< IEEE 802.3 Basic Control 寄存器 0。 */
constexpr uint16_t MII_BMCR_POWER_DOWN = 1U << 11; /**< PHY_PWR_DWN(DS 11.2.8)。 */
constexpr uint8_t MII_PHY_ADDR_B = 1U; /**< PHY B 地址恒 1(DS 11.1.1);A 模式相关 0/2,运行期判定。 */
__attribute__((section(".ramfunc"), noinline))
void setHbiIndex(uint16_t addr)
{
EscExmcBus::write16(HBI_IDX0_OFFSET, addr);
}
__attribute__((section(".ramfunc"), noinline))
uint16_t readDataLow()
{
return EscExmcBus::read16(HBI_DATA0_LOW_OFF);
}
__attribute__((section(".ramfunc"), noinline))
uint16_t readDataHigh()
{
return EscExmcBus::read16(HBI_DATA0_HIGH_OFF);
}
__attribute__((section(".ramfunc"), noinline))
void writeDataLow(uint16_t v)
{
EscExmcBus::write16(HBI_DATA0_LOW_OFF, v);
}
__attribute__((section(".ramfunc"), noinline))
void writeDataHigh(uint16_t v)
{
EscExmcBus::write16(HBI_DATA0_HIGH_OFF, v);
}
__attribute__((section(".ramfunc"), noinline))
uint32_t readReg32(uint16_t addr)
{
setHbiIndex(addr);
uint16_t lo = readDataLow();
uint16_t hi = readDataHigh();
return ((uint32_t)hi << 16) | lo;
}
__attribute__((section(".ramfunc"), noinline))
void writeReg32(uint16_t addr, uint32_t v)
{
setHbiIndex(addr);
writeDataLow((uint16_t)(v & 0xFFFFU));
writeDataHigh((uint16_t)(v >> 16));
}
/* ---- 电源管理 helper(仅 pmDrive 诊断路径使用) ---- */
/** @brief DWT CYCCNT 忙等微秒。
* @note EXTI ISR 上下文(0xA2 在 AppSpiCommand::onCsRelease 内同步执行)禁止用
* SysTickTimer::delayMs:其基于 SysTick 中断递增 s_ms,中断被 CS EXTI
* 压住时计数冻结会死等。DWT CYCCNT 已由 init() 使能,纯寄存器读,ISR 安全。 */
__attribute__((section(".ramfunc"), noinline))
void pmBusyWaitUs(uint32_t us)
{
const uint32_t start = DWT->CYCCNT;
const uint32_t cycles = us * (SystemCoreClock / 1000000U);
while ((DWT->CYCCNT - start) < cycles) { }
}
/** @brief ESC 核心 MII 管理事务(读/写一个 PHY 寄存器)。
* @note 调用方必须已持 HBI 互斥(pmDrive 的 maskEnter 段内)。MII 事务典型 ~25µs
* (2.5MHz MDIO × 32bit 帧结构),BUSY 轮询上限 POLL_MAX 防芯片异常死等。 */
bool miiXfer(uint8_t phyAddr, uint8_t regAddr, bool write, uint16_t &data)
{
EscLan9252::csrWrite(ESC_REG_MII_ADDR, ((uint32_t)regAddr << 5) | phyAddr, 2U);
if (write) {
EscLan9252::csrWrite(ESC_REG_MII_DATA, data, 2U);
}
EscLan9252::csrWrite(ESC_REG_MII_CTRL,
write ? (MII_CTRL_CMD | MII_CTRL_WRITE) : MII_CTRL_CMD, 2U);
for (uint32_t i = 0U; i < POLL_MAX; i++) {
if ((EscLan9252::csrRead(ESC_REG_MII_CTRL, 2U, false) & MII_CTRL_BUSY) == 0U) {
if (!write) {
data = (uint16_t)EscLan9252::csrRead(ESC_REG_MII_DATA, 2U, false);
}
return true;
}
}
return false;
}
/** @brief 置/清两 PHY 的 BMCR.PowerDown(D3 进入前置要求/唤醒后恢复)。
* @return 全部事务成功 true。PHY A 地址模式相关(DS 11.1.1:0 或 2,随 chip_mode_strap),
* 以 BMCR 读回有效性判定(0x0000/0xFFFF=无响应),结果缓存在静态变量。 */
bool phySetPowerDown(bool down)
{
static uint8_t addrA = 0xFFU; /* 0xFF=未判定 */
if (addrA == 0xFFU) {
addrA = 0U;
uint16_t probe = 0xFFFFU;
if (!miiXfer(0U, MII_REG_BMCR, false, probe) ||
probe == 0x0000U || probe == 0xFFFFU) {
addrA = 2U;
}
}
bool ok = true;
const uint8_t addrs[2] = { addrA, MII_PHY_ADDR_B };
for (uint8_t i = 0U; i < 2U; i++) {
uint16_t bmcr = 0U;
ok = miiXfer(addrs[i], MII_REG_BMCR, false, bmcr) && ok;
if (down) {
bmcr |= MII_BMCR_POWER_DOWN;
} else {
bmcr &= (uint16_t)~MII_BMCR_POWER_DOWN;
}
ok = miiXfer(addrs[i], MII_REG_BMCR, true, bmcr) && ok;
}
return ok;
}
/** @brief 把 PMT_CTRL 值打包成单字节状态:bit0=READY bit1=PM_SLEEP_EN bit3:2=PM_MODE。 */
uint8_t pmPackStatus(uint32_t pmt)
{
return (uint8_t)(((pmt & PMT_CTRL_READY) ? 0x01U : 0U) |
((pmt & PMT_CTRL_PM_SLEEP_EN) ? 0x02U : 0U) |
(((pmt >> 29) & 3U) << 2));
}
} /* anonymous namespace */
bool EscLan9252::s_busReady = false;
bool EscLan9252::s_coreReady = false;
bool EscLan9252::s_resetDone = false;
bool EscLan9252::s_pmPhyDown = false;
bool EscLan9252::s_pmSuspended = false;
void EscLan9252::hardwareReset()
{
/* 受控复位 ECAT_NRST(PB10 → LAN9252 Pin11 RST#,SCH 第1页 Pin47)。
* 手册 18.6.3 要求 tpurstd(电源稳定→RST#释放)≥25ms、trstia(低脉宽)≥200µs,
* 且释放前时钟输入已稳定。固件执行到此点时上电早已完成、晶振已稳定,
* 统一 30ms 低脉宽同时覆盖两个下限。RST# 释放瞬间重锁 CHIP_MODE0/1 strap
* (LED_LINK1/2 经 R35/R36 10k 下拉 → 00b = 2 端口 HBI 模式)。
* 释放后保持推挽输出高:空闲电平确定,并保留运行期软件复位手段;
* GD32 自身复位期间引脚回浮空,由 R44 上拉维持高,不会误复位 ESC。 */
rcu_periph_clock_enable(RCU_GPIOB);
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
CONFIG_ESC_NRST_PIN);
gpio_bit_reset(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN); /* RST# 拉低 */
SysTickTimer::delayMs(CONFIG_ESC_NRST_ASSERT_MS);
gpio_bit_set(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN); /* 释放复位 */
/* 释放后等 50ms:warm jump 后芯片不是冷上电,内部状态机/PHY
* 重锁比 POR 慢;等太短时随后的 HBI 探针会读到启动中垃圾值。
* 50ms 仍远小于 300ms 轮询窗,且 init 单次成本可接受。 */
SysTickTimer::delayMs(50U);
/* 回读实际电平:GD32 输入通路常连引脚,输出态也能读到真实电压。
* 驱动高却读回低 => ECAT_NRST 网络被外部短路到地(或引脚损伤),
* LAN9252 被按死在复位态 —— 直接解释 HBI 全浮空 + 晶振照振。 */
const bool pinHigh = (gpio_input_bit_get(CONFIG_ESC_NRST_PORT,
CONFIG_ESC_NRST_PIN) == SET);
printf("[ECAT][ESC] hardwareReset: RST# low %ums -> released, pin reads %s\r\n",
(unsigned)CONFIG_ESC_NRST_ASSERT_MS,
pinHigh ? "HIGH" : "*** LOW (net shorted to GND / pin damaged) ***");
/* RST# 复位清掉 PM 睡眠态(芯片回 D0),挂起门控随之解除 */
s_pmSuspended = false;
}
uint8_t EscLan9252::nrstDrive(uint8_t action)
{
/* SPI 0xA2 action 20+ 的 NRST 手动诊断入口。
* 返回值 = 实际引脚电平(0/1),非 OK 码;0xEE = 无效 action。 */
switch (action) {
case 0: /* 恢复自动态:推挽输出高(与 hardwareReset 释放后一致) */
case 2: /* 持续拉高 */
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
CONFIG_ESC_NRST_PIN);
gpio_bit_set(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN);
/* 复位释放后芯片重走上电流程回 D0,睡眠挂起随之解除 */
s_pmSuspended = false;
break;
case 1: /* 持续拉低(手动保持复位) */
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
CONFIG_ESC_NRST_PIN);
gpio_bit_reset(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN);
break;
case 3: /* 释放:浮空输入,网络交给 R44 10k 上拉 */
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_IN_FLOATING, 0,
CONFIG_ESC_NRST_PIN);
s_pmSuspended = false;
break;
case 4: /* 只读回,不改驱动 */
break;
default:
return 0xEE;
}
const bool level = (gpio_input_bit_get(CONFIG_ESC_NRST_PORT,
CONFIG_ESC_NRST_PIN) == SET);
static const char *const kName[5] = {
"auto(high)", "drive LOW", "drive HIGH", "float", "read-only"};
printf("[ECAT][NRST] %s -> pin reads %s\r\n", kName[action],
level ? "HIGH" : "LOW");
return level ? 1U : 0U;
}
uint8_t EscLan9252::pmDrive(uint8_t op)
{
if (op > 4U) {
return 0xEEU;
}
if (!s_busReady) {
printf("[ECAT][PM] op%u rejected: HBI bus not ready\r\n", op);
return 0xE1U;
}
/* 0xA2 在 CS EXTI ISR 上下文执行(AppSpiCommand::onCsRelease 同步调用)。
* HBI Indexed 是两步事务(写 IDX→读写 DATA),而 SYNC0(EXTI0,preempt=0)是
* 全系统最高优先级、BASEPRI 挡不住,与主循环 HW_* 路径相同必须关 NVIC:
* maskEnter 屏蔽 EXTI0/EXTI1 并返回先前状态,ISR 里调用同样正确(纯 NVIC
* 寄存器保存/恢复,挂起的中断在 maskExit 后补执行)。
* 残余竞态窗口:CS EXTI 优先级更高,可抢占主循环持锁中的 HBI 事务半截——
* 人工低频诊断可接受(continuityDrive 直接切走 EXMC 引脚破坏性更大)。 */
const uint32_t mask = EscExmcBus::maskEnter();
uint8_t status;
if (op == 0U) {
/* 状态查询。注意:HBI 读事务本身就会唤醒睡眠中的芯片,
* 本操作的语义 = "读状态(若在睡眠则主动打破睡眠)",非无侵入观测。 */
const uint32_t pmt = readReg32(LAN9252_REG_PMT_CTRL);
status = pmPackStatus(pmt);
/* 读到 D0(SLEEP_EN=0):芯片不在睡眠,若门控仍挂起(如 op4 唤醒超时后
* 芯片延迟自醒)则自愈解除,恢复主循环/ISR 的 HBI 访问。 */
if ((status & 0x02U) == 0U && s_pmSuspended) {
s_pmSuspended = false;
printf("[ECAT][PM] status: chip in D0, pm suspend gate released\r\n");
}
printf("[ECAT][PM] status: PMT_CTRL=0x%08lX READY=%u SLEEP_EN=%u MODE=D%u\r\n",
(unsigned long)pmt,
(unsigned)((status >> 0) & 1U), (unsigned)((status >> 1) & 1U),
(unsigned)((status >> 2) & 3U));
} else if (op <= 3U) {
/* 进入 D1/D2/D3。D3 最深(晶振/PLL 全关,仅手动唤醒),手册要求先把两个 PHY
* 置 General Power Down,否则 PLL 在 D2 不真关/D3 直接不满足前置。 */
if (op == 3U) {
s_pmPhyDown = phySetPowerDown(true);
printf("[ECAT][PM] D3 pre: both PHY power-down %s\r\n",
s_pmPhyDown ? "OK" : "FAIL (entering anyway)");
}
uint32_t v = readReg32(LAN9252_REG_PMT_CTRL);
v = (v & ~PMT_CTRL_PM_MODE_MSK) | ((uint32_t)op << 29);
/* PM_MODE 与 PM_SLEEP_EN 必须分两次写(DS 6.3.5.1 注:同一写中改 MODE 置 EN 无效)。 */
writeReg32(LAN9252_REG_PMT_CTRL, v);
writeReg32(LAN9252_REG_PMT_CTRL, v | PMT_CTRL_PM_SLEEP_EN);
/* 立即挂起全部 HBI 消费者(task_runner 停 MainLoop/守护/心跳,
* ethercat_sync 三 ISR 清 pending 后直接返回)——本函数运行于 CS EXTI ISR,
* 退出后主循环第一圈 ECAT_Main 读 0x220 就会把刚睡着的芯片叫醒;
* 此前置位窗口内本 ISR 是唯一运行者(maskEnter 已屏 EXTI0/1),无竞态。 */
s_pmSuspended = true;
/* 进入睡眠后任何 HBI 访问(含读!)都会立即触发唤醒——
* 手册 6.3.4.2 只说 HBI "write" 唤醒,但芯片实际按主机 CS 活动判定,
* 写入后的读回验证会把芯片叫回 D0。
* 故此处置信构造返回值,不再读回;睡眠态观测交给"读状态"(读即唤醒,
* 语义=主动打破睡眠),页面轮询须在睡眠态暂停。 */
status = (uint8_t)(0x02U | ((uint32_t)op << 2)); /* READY=0 | SLEEP_EN=1 | MODE=op */
static const char *const kMode[4] = {"D0", "D1", "D2", "D3"};
printf("[ECAT][PM] enter %s: entered, no read-back (HBI read wakes chip)\r\n",
kMode[op]);
printf("[ECAT][PM] WARNING: EtherCAT comms halted, wake with 0xA2 action 34\r\n");
} else {
/* 手动唤醒(DS 6.3.4.2):对 BYTE_TEST 做一次 HBI 写触发,随后轮询
* BYTE_TEST=0x87654321(接口恢复)→ PMT_CTRL.READY=1(回 D0 且 PLL 稳定)。
* 手册标称 ≤2ms,轮询 50µs 步进、上限 10ms——EXTI ISR 内不做长阻塞。 */
writeReg32(LAN9252_REG_BYTE_TEST, LAN9252_BYTE_TEST_VALUE);
bool awake = false;
for (uint32_t i = 0U; i < 200U; i++) {
pmBusyWaitUs(50U);
if (readReg32(LAN9252_REG_BYTE_TEST) == LAN9252_BYTE_TEST_VALUE) {
awake = true;
break;
}
}
if (awake) {
for (uint32_t i = 0U; i < 200U; i++) {
pmBusyWaitUs(50U);
if ((readReg32(LAN9252_REG_PMT_CTRL) & PMT_CTRL_READY) != 0U) {
break;
}
}
awake = (readReg32(LAN9252_REG_PMT_CTRL) & PMT_CTRL_READY) != 0U;
}
if (awake && s_pmPhyDown) {
/* D3 唤醒后恢复 PHY 上电,链路重新自协商(数秒后 LINK 恢复)。 */
const bool restored = phySetPowerDown(false);
s_pmPhyDown = !restored;
printf("[ECAT][PM] wake: PHY power-up %s (autoneg re-negotiating)\r\n",
restored ? "OK" : "FAIL (retry action 34)");
}
if (awake) {
/* 唤醒成功:解除 HBI 挂起,主循环/ISR 恢复运行。
* 超时则保持挂起——芯片大概率仍睡着;若实际已醒,下一次 op0
* 状态查询读到 D0 会自愈解除。 */
s_pmSuspended = false;
}
status = awake ? pmPackStatus(readReg32(LAN9252_REG_PMT_CTRL)) : 0U;
printf("[ECAT][PM] wake: %s -> status=0x%02X (READY=%u SLEEP_EN=%u)\r\n",
awake ? "OK" : "TIMEOUT (10ms, poll action 30 / retry 34)",
status, (unsigned)(status & 1U), (unsigned)((status >> 1) & 1U));
}
EscExmcBus::maskExit(mask);
return status;
}
bool EscLan9252::init()
{
s_busReady = false; /* EXMC+BYTE_TEST; warm 先探再定是否 RST# */
s_coreReady = false; /* s_coreReady 语义 = EEPROM 加载完成、EtherCAT 核心使能 */
/* 使能 DWT 周期计数器,供 hw_gd32exmc/SSC 执行时间测量 */
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0U;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
/* 先起 EXMC 再谈复位。
* warm jump:BOOT 刚用过 HBI,芯片活着;此时若先 RST# 再起 EXMC,等于
* 把一条本来能通的总线按进复位重协商,一旦 RST# 释放时序不稳就整段假死。
* cold:POR 后芯片也需要 EXMC 才谈得上探活。
* 顺序固定为:EXMC → 无复位探 BYTE_TEST → 不通才 RST# 重来。 */
if (!EscExmcBus::init()) {
printf("[ECAT][ESC] init: FAIL, EXMC bus not ready (see [ECAT][EXMC] log)\r\n");
return false;
}
uint16_t idx0 = 0U;
uint32_t byteTest = 0U;
/* 第 0 轮:不复位直接探。warm 残值下 BOOT 刚写过 HBI,BYTE_TEST 应立刻可读。
* BYTE_TEST 只证明通路;idx0==0x1234 才证明「芯片处于 POR 默认态」。
* warm jump 后 idx0 常为 0x0054(BOOT 索引事务残值)——与刚上电不同。
* 为了「跳转 = 上电」,残值时补一发 RST# 回到 POR,再走与 cold 相同的探活。 */
idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET);
byteTest = readReg32(LAN9252_REG_BYTE_TEST);
const bool porLike = (byteTest == LAN9252_BYTE_TEST_VALUE) && (idx0 == 0x1234U);
if (porLike) {
printf("[ECAT][ESC] probe: BYTE_TEST ok without RST# (idx0=0x%04X) — POR 态,跳过复位\r\n",
(unsigned)idx0);
s_resetDone = true;
} else if (byteTest == LAN9252_BYTE_TEST_VALUE) {
/* 通路活但不是 POR(典型 warm jump:idx0=0x0054)。补 RST# 对齐上电。 */
printf("[ECAT][ESC] probe: BYTE_TEST ok but idx0=0x%04X != 0x1234 — warm 残值,"
"补 RST# 对齐上电 POR\r\n", (unsigned)idx0);
hardwareReset();
s_resetDone = true;
if (!EscExmcBus::init()) {
printf("[ECAT][ESC] init: FAIL, EXMC re-init after RST# (warm residual)\r\n");
return false;
}
idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET);
byteTest = readReg32(LAN9252_REG_BYTE_TEST);
if (byteTest != LAN9252_BYTE_TEST_VALUE) {
printf("[ECAT][ESC] probe: BYTE_TEST=0x%08lX after warm RST#, fall back to multi-round\r\n",
(unsigned long)byteTest);
} else {
printf("[ECAT][ESC] probe: warm RST# ok (idx0=0x%04X, BYTE_TEST=0x87654321) — 与上电同态\r\n",
(unsigned)idx0);
}
}
if (byteTest != LAN9252_BYTE_TEST_VALUE) {
printf("[ECAT][ESC] probe: BYTE_TEST=0x%08lX idx0=0x%04X, 补 RST# 最多 3 轮\r\n",
(unsigned long)byteTest, (unsigned)idx0);
s_resetDone = false; /* 失败路径首轮强制 RST#,与上电后探不通时一致 */
bool alive = false;
for (uint8_t attempt = 0U; attempt < 3U; ++attempt) {
if (!s_resetDone || attempt > 0U) {
hardwareReset();
s_resetDone = true;
}
/* 复位后重配 EXMC:BOOT 关时钟时 PD/PE 停在 AF,MCU 侧寄存器也可能残留 */
if (!EscExmcBus::init()) {
continue;
}
idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET);
/* 仅在「像复位中/未驱动」时轮询 0x1234;warm 残值直接测 BYTE_TEST */
const bool idxLooksIdle = (idx0 == 0x0000U) || (idx0 == 0xFFFFU);
if (idxLooksIdle || idx0 == 0x1234U) {
for (uint32_t waited = 0U;
idx0 != 0x1234U && waited < CONFIG_ESC_HBI_POLL_TIMEOUT_MS;
waited += CONFIG_ESC_HBI_POLL_STEP_MS) {
SysTickTimer::delayMs(CONFIG_ESC_HBI_POLL_STEP_MS);
idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET);
}
}
byteTest = readReg32(LAN9252_REG_BYTE_TEST);
if (byteTest == LAN9252_BYTE_TEST_VALUE) {
alive = true;
printf("[ECAT][ESC] probe#%u: BYTE_TEST ok (idx0=0x%04X)\r\n",
(unsigned)(attempt + 1U), (unsigned)idx0);
break;
}
printf("[ECAT][ESC] probe#%u fail (idx0=0x%04X byte_test=0x%08lX)\r\n",
(unsigned)(attempt + 1U), (unsigned)idx0, (unsigned long)byteTest);
}
if (!alive) {
printf("[ECAT][ESC] init: FAIL, BYTE_TEST=0x%08lX (expect 0x87654321), idx0=0x%04X -> LAN not driving bus\r\n",
(unsigned long)byteTest, (unsigned)idx0);
printf("[ECAT][ESC] hint: check CS(PD7) pulse, CHIP_MODE0/1 straps=00, RST#/power\r\n");
return false;
}
}
if (idx0 != 0x1234U) {
printf("[ECAT][ESC] init: WARN idx0=0x%04X != 0x1234, BYTE_TEST ok -> 复位态未知但通路可用,放行\r\n",
(unsigned)idx0);
}
printf("[ECAT][ESC] init: OK, HBI alive (idx0=0x%04X, BYTE_TEST=0x87654321)\r\n",
(unsigned)idx0);
s_busReady = true;
/* 等待 PDI 就绪、EtherCAT 核心可交互。
* DL_STATUS 属 EtherCAT 核心寄存器,不在 HBI 直接空间,必须经 CSR 间接端口读。
* 判据:DL_STATUS(0x0110) bit0 = PDI operational / EEPROM loaded。
* 超时意味着 PDI 真的未就绪(EEPROM 内容/I2C/strap 问题),此时 SSC 的
* AL Event Mask/邮箱 SM 配置会落空,由 initial.cpp 按 isCoreReady() 门控跳过。 */
uint16_t dlStatus = (uint16_t)csrRead(ESC_REG_DL_STATUS, 2U, false);
uint32_t waited = 0U;
while ((dlStatus & 0x0001U) == 0U && waited < CONFIG_ESC_HBI_POLL_TIMEOUT_MS) {
SysTickTimer::delayMs(CONFIG_ESC_HBI_POLL_STEP_MS);
waited += CONFIG_ESC_HBI_POLL_STEP_MS;
dlStatus = (uint16_t)csrRead(ESC_REG_DL_STATUS, 2U, false);
}
if ((dlStatus & 0x0001U) != 0U) {
s_coreReady = true;
printf("[ECAT][ESC] PDI operational, EtherCAT core ready (DL_STATUS=0x%04X, waited %ums)\r\n",
(unsigned)dlStatus, (unsigned)waited);
enableEcatIrqOutput();
configPdWatchdog();
} else {
printf("[ECAT][ESC] init: FAIL, PDI not operational after %ums "
"(DL_STATUS=0x%04X EEP_CSR=0x%04X)\r\n",
(unsigned)CONFIG_ESC_HBI_POLL_TIMEOUT_MS, (unsigned)dlStatus,
(unsigned)csrRead(ESC_REG_EEP_CSR, 2U, false));
printf("[ECAT][ESC] hint: 用 tools/ecat/ecat_esc_regs.py 从网络侧读 0x0502 错误位区分 EEPROM/I2C/内容问题\r\n");
}
return true;
}
/**
* @brief 使能 LAN9252 的 IRQ 引脚输出,让 EtherCAT 事件真正到达 GD32 的 PB1。
* @note IRQ_CFG(0x054)/INT_EN(0x05C) 都是 LAN9252 系统 CSR
* (HBI 索引直接可达,不走 CSR 间接端口),复位默认值均为 0,即
* IRQ_EN=0(引脚永久去断言) 且 ECAT_INT_EN=0(EtherCAT 事件不上报)。
* 不配这两个寄存器,PDI_Isr 不会触发,SSC 永远等不到 PROCESS_OUTPUT_EVENT,
* SAFEOP->OP 被无限挂起(表现为主站 "进 OP 失败 code=0x0000")。
* 极性必须与 ethercat_sync.cpp 的 EXTI 配置一致:那里是上拉输入 +
* 下降沿触发,故此处选推挽(IRQ_TYPE=1) + 低有效(IRQ_POL=0)。
* 注意 AL Event Mask(0x204) 由 SSC 在状态迁移时自行维护,不在此处设置。
*/
void EscLan9252::configPdWatchdog()
{
/* 过程数据看门狗 100ms(复位默认)→1s。
* 主站侧手动单步/100ms 调试周期会触发 SM2 看门狗:SSC 在 CheckIfEcatError()
* 读 0x0440 发现已过期,调 AL_ControlInd(STATE_SAFEOP, 0x001B) 把 OP 拖回 SAFEOP,
* 表现为"100ms 周期全 WKC 错、10/50ms 正常"。分频保持默认 100µs,只延长步数;
* 正常周期(≤50ms)远小于 1s,行为不变。 */
csrWrite(ESC_REG_WD_TIME_PD, ESC_WD_TIME_PD_STEPS, 2U);
const uint32_t rd = csrRead(ESC_REG_WD_TIME_PD, 2U, false);
if ((rd & 0xFFFFU) == ESC_WD_TIME_PD_STEPS) {
printf("[ECAT][ESC] PD watchdog = 1s (0x0420=%u x 100us, default was 100ms)\r\n",
(unsigned)ESC_WD_TIME_PD_STEPS);
} else {
printf("[ECAT][ESC] WARN: PD watchdog writeback 0x%04lX != %u, 100ms 调试周期会被 0x001B 拖回 SAFEOP\r\n",
(unsigned long)(rd & 0xFFFFU), (unsigned)ESC_WD_TIME_PD_STEPS);
}
}
void EscLan9252::enableEcatIrqOutput()
{
/* 先清掉可能悬留的状态位(R/WC:写 1 清零),避免使能瞬间就有一个陈旧电平。 */
writeReg32(LAN9252_REG_INT_STS, INT_EN_ECAT_INT);
/* 允许 EtherCAT 事件汇入 IRQ。 */
writeReg32(LAN9252_REG_INT_EN, INT_EN_ECAT_INT);
/* 打开引脚总闸:推挽 + 低有效(IRQ_POL=0 即不置该位),去断言间隔保持 0=禁用。 */
writeReg32(LAN9252_REG_IRQ_CFG, IRQ_CFG_IRQ_EN | IRQ_CFG_IRQ_TYPE);
const uint32_t cfg = readReg32(LAN9252_REG_IRQ_CFG);
const uint32_t en = readReg32(LAN9252_REG_INT_EN);
if ((cfg & IRQ_CFG_IRQ_EN) != 0U && (en & INT_EN_ECAT_INT) != 0U) {
printf("[ECAT][ESC] IRQ output enabled (IRQ_CFG=0x%08lX INT_EN=0x%08lX, push-pull active-low)\r\n",
(unsigned long)cfg, (unsigned long)en);
} else {
printf("[ECAT][ESC] WARN: IRQ enable readback failed (IRQ_CFG=0x%08lX INT_EN=0x%08lX)"
" -> PDI_Isr 不会触发,SAFEOP->OP 会卡住\r\n",
(unsigned long)cfg, (unsigned long)en);
}
}
uint32_t EscLan9252::csrRead(uint16_t addr, uint8_t size, bool verify)
{
/* DS 12.11.1:写 CSR_CMD(BUSY|R_nW|SIZE|ADDR) 启动,busy 自清后读 CSR_DATA;
* 有效数据总在 CSR_DATA 低位,硬件处理字节对齐。
* verify=true(默认):CSR_DATA 是锁存寄存器(非 FIFO),命令不变时连读两次必一致;
* 不一致即本拍 EXMC 毛刺——错读可能让 GetSyncMan 把错误值缓存成邮箱几何
* (→SSC 邮箱楔死,邮箱几何另有 mailbox.c 构建期常量兜底),或让
* CheckSmSettings 以瞬态值打断状态转换。对错读整事务重跑一次;
* 两轮仍不一致则交出末拍。
* verify=false:单读路径,供诊断回读/MII busy 轮询/init 轮询等低风险
* 调用方使用;SM/AL/邮箱等状态机关键寄存器必须保持默认双读。 */
if (!verify) {
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
writeReg32(ECAT_CSR_CMD_REG, CSR_BUSY | CSR_RNW | ((uint32_t)size << 16) | addr);
n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
return readReg32(ECAT_CSR_DATA_REG);
}
for (uint8_t attempt = 0U; attempt < 2U; ++attempt)
{
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
writeReg32(ECAT_CSR_CMD_REG, CSR_BUSY | CSR_RNW | ((uint32_t)size << 16) | addr);
n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
const uint32_t d1 = readReg32(ECAT_CSR_DATA_REG);
const uint32_t d2 = readReg32(ECAT_CSR_DATA_REG);
if (d1 == d2)
{
return d1;
}
}
return readReg32(ECAT_CSR_DATA_REG);
}
void EscLan9252::csrWrite(uint16_t addr, uint32_t data, uint8_t size)
{
/* DS 12.11.2:先写 CSR_DATA(数据在低位),再写 CSR_CMD(BUSY|SIZE|ADDR,R_nW=0)。 */
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
writeReg32(ECAT_CSR_DATA_REG, data);
writeReg32(ECAT_CSR_CMD_REG, CSR_BUSY | ((uint32_t)size << 16) | addr);
n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
}
void EscLan9252::pramRead(uint16_t addr, uint8_t *buf, uint16_t len)
{
/* DS 12.11.3:写 ADDR_LEN 再置 BUSY 启动;引擎按 DWORD 向下对齐填充读 FIFO,
* 首 DWORD 有效字节从 (addr&3) 起(表 12-7),末 DWORD 尾部无效(表 12-8)。
* 别名口 0x00-0x1C 功能相同,轮换寻址即官方突发用法;每读满一个 DWORD 弹一项。
*
* 必须逐 DWORD 轮询并弹前查 AVAIL 位。CMD 的 AVAIL_CNT(bits[12:8])批量弹模式
* 对短读不可靠:首 DWORD 新鲜、后续 DWORD 可能取到 FIFO 陈旧项。邮箱短读/探针
* 类小传输只验证过"逐 DWORD 查 AVAIL"模式,不要改回批量弹 DWORD。 */
if (len == 0U) {
return;
}
writeReg32(ECAT_PRAM_RD_ADDR_LEN, ((uint32_t)len << 16) | addr);
writeReg32(ECAT_PRAM_RD_CMD, PRAM_BUSY);
uint16_t done = 0U;
uint16_t alias = 0U;
while (done < len) {
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_PRAM_RD_CMD) & PRAM_AVAIL) == 0U && --n != 0U) {}
if (n == 0U) {
break; /* 芯片异常:避免死等,交出残缺数据 */
}
const uint32_t dw = readReg32((uint16_t)(ECAT_PRAM_RD_DATA + alias));
alias = (uint16_t)((alias + 4U) & 0x1FU);
const uint8_t lead = (done == 0U) ? (uint8_t)(addr & 3U) : 0U;
const uint8_t take = (uint8_t)((len - done) < (4U - lead) ? (len - done) : (4U - lead));
for (uint8_t i = 0U; i < take; ++i) {
buf[done + i] = (uint8_t)(dw >> (8U * (lead + i)));
}
done = (uint16_t)(done + take);
}
}
void EscLan9252::pramWrite(uint16_t addr, const uint8_t *buf, uint16_t len)
{
/* DS 12.11.4:写 ADDR_LEN 再置 BUSY 启动;首 DWORD 有效字节从 (addr&3) 起,
* 引擎只取有效字节;别名口 0x20-0x3C 轮换;写满一个 DWORD 推一项。
* 与 pramRead 同样逐 DWORD 轮询(推前必查 AVAIL 位),邮箱小 PDU 写同类风险。 */
if (len == 0U) {
return;
}
writeReg32(ECAT_PRAM_WR_ADDR_LEN, ((uint32_t)len << 16) | addr);
writeReg32(ECAT_PRAM_WR_CMD, PRAM_BUSY);
uint16_t done = 0U;
uint16_t alias = 0U;
while (done < len) {
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_PRAM_WR_CMD) & PRAM_AVAIL) == 0U && --n != 0U) {}
if (n == 0U) {
break; /* 芯片异常:放弃剩余字节 */
}
const uint8_t lead = (done == 0U) ? (uint8_t)((uint16_t)(addr + done) & 3U) : 0U;
const uint8_t take = (uint8_t)((len - done) < (4U - lead) ? (len - done) : (4U - lead));
uint32_t dw = 0U;
for (uint8_t i = 0U; i < take; ++i) {
dw |= (uint32_t)buf[done + i] << (8U * (lead + i));
}
writeReg32((uint16_t)(ECAT_PRAM_WR_DATA + alias), dw);
alias = (uint16_t)((alias + 4U) & 0x1FU);
done = (uint16_t)(done + take);
}
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_PRAM_WR_CMD) & PRAM_BUSY) != 0U && --n != 0U) {}
}
uint32_t EscLan9252::readChipId()
{
/* INIT-ONLY:init 阶段中断未使能,直接读取 BYTE_TEST(32-bit) */
return readReg32(LAN9252_REG_BYTE_TEST);
}
uint16_t EscLan9252::initOnlyReadReg(uint16_t addr)
{
/* ESC 核心 CSR 经间接端口读(核心寄存器不在 HBI 直接空间,直读恒 0) */
return (uint16_t)csrRead(addr, 2U);
}
void EscLan9252::initOnlyWriteReg(uint16_t addr, uint16_t v)
{
csrWrite(addr, v, 2U);
}