添加了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命令处理文件的分组
719 lines
35 KiB
C++
719 lines
35 KiB
C++
/**
|
||
* @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);
|
||
}
|