feat(comm): 1.6.22 稀疏写 ESC + 1.6.23~24 pmDrive 电源管理; LAN9252 性能审计报告

This commit is contained in:
cbb
2026-09-12 01:59:42 +08:00
parent 1742d9a759
commit 9ca3db26f8
8 changed files with 434 additions and 5 deletions
+189
View File
@@ -80,6 +80,28 @@ 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)
{
@@ -126,11 +148,86 @@ void writeReg32(uint16_t addr, uint32_t v)
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) & MII_CTRL_BUSY) == 0U) {
if (!write) {
data = (uint16_t)EscLan9252::csrRead(ESC_REG_MII_DATA, 2U);
}
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;
void EscLan9252::hardwareReset()
{
@@ -193,6 +290,98 @@ uint8_t EscLan9252::nrstDrive(uint8_t action)
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) {
/* 状态查询。注意:1.6.24 实测 HBI 读事务本身就会唤醒睡眠中的芯片,
* 本操作的语义 = "读状态(若在睡眠则主动打破睡眠)",非无侵入观测。 */
const uint32_t pmt = readReg32(LAN9252_REG_PMT_CTRL);
status = pmPackStatus(pmt);
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);
/* 1.6.24 实测:进入睡眠后任何 HBI 访问(含读!)都会立即触发唤醒——
* 手册 6.3.4.2 只说 HBI "write" 唤醒,但芯片实际按主机 CS 活动判定,
* 写入后的读回验证会在 <50ms 内把芯片叫回 D0(板级复现:进 D1 后
* 3s/6s 静默仍自动回 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)");
}
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; /* 1.5.27:s_busReady 语义 = init 完整成功(EXMC+探针+BYTE_TEST) */
+13
View File
@@ -46,6 +46,18 @@ public:
* 驱动高读回低 => ECAT_NRST 被按死在复位,LAS9252 永在复位态。 */
static uint8_t nrstDrive(uint8_t action);
/** @brief 诊断:LAN9252 芯片级电源管理(休眠/唤醒,SPI 0xA2 action 30+ 转发到此)。
* @param op 0=读状态 1=进 D1 2=进 D2 3=进 D3(先两 PHY General Power Down) 4=手动唤醒
* @return 打包状态字节 bit0=READY bit1=PM_SLEEP_EN bit3:2=PM_MODE;
* 唤醒 op 另有 1=成功 0=超时;0xEE=无效 op;0xE1=HBI 总线未就绪。
* @note DS 6.3:PMT_CTRL(0x084) PM_MODE[31:29] 选档 + PM_SLEEP_EN(bit28) 进入,
* 两字段必须分两次写。D1/D2/D3 下主机接口停摆、EtherCAT 通信中断(主站
* wkc 报错),唤醒后 PM_MODE/PM_SLEEP_EN 自清、寄存器配置保留、READY 回高
* (正常 ≤2ms)。D3 最深(晶振/PLL 全关)且手册要求先置两 PHY BMCR.PowerDown
* (经 ESC 核心 MII 管理 0x0510 间接访问,PHY A 地址模式相关 0/2 运行期判定),
* 唤醒后本函数自动恢复 PHY 上电,链路重新自协商需数秒。 */
static uint8_t pmDrive(uint8_t op);
/**
* @brief 读取 ESC 芯片 ID(INIT-ONLY,供 init 打印诊断用)。
* @return uint32_t 32-bit 芯片 ID(期望值 0x87654321)。
@@ -102,6 +114,7 @@ private:
static bool s_busReady; /**< init 完整成功(EXMC+IDX 探针+BYTE_TEST)后置位。 */
static bool s_coreReady; /**< EEPROM 加载完成、EtherCAT 核心使能后置位(1.5.39)。 */
static bool s_resetDone; /**< hardwareReset 已执行门控(防二次复位清掉 SSC 配置)。 */
static bool s_pmPhyDown; /**< pmDrive 进 D3 时两 PHY 已被置 PowerDown,唤醒后需恢复。 */
};
#endif /* COMM_ESC_LAN9252_H */
+39
View File
@@ -154,6 +154,37 @@ void EthercatPdo::writeInputs(uint8_t *esc)
++s_inputCycles;
}
bool EthercatPdo::sparseEscWritePlan(uint16_t *off0, uint16_t *len0,
uint16_t *off1, uint16_t *len1)
{
if (off0 == nullptr || len0 == nullptr || off1 == nullptr || len1 == nullptr) {
return false;
}
#if !CONFIG_ECAT_SPARSE_ESC_WRITE
return false;
#else
/* 仅 1.6.21 密排布局:count 有效且两段之间确有空洞才分段。
* magic 不对 / count 越界 / count 撑满到与段3相接 → 整包,避免陈旧空洞被误读。 */
if (s_in[ECAT_MOTW_OFF_MAGIC] != kEcatMotLayoutMagic) {
return false;
}
const uint8_t count = s_in[ECAT_MOTW_OFF_COUNT];
if (count > kEcatMotSlotsPerPdo) {
return false;
}
const uint16_t headEnd =
(uint16_t)(ECAT_MOTW_OFF_SLOTS + (uint16_t)count * kEcatMotSlotSize);
if (headEnd >= ECAT_XRX_OFF_CH) {
return false; /* 9 电机满配:A+B 无缝,整包更省一次 EXMC 事务 */
}
*off0 = 0U;
*len0 = headEnd;
*off1 = ECAT_XRX_OFF_CH;
*len1 = (uint16_t)(kPdoInBytes - ECAT_XRX_OFF_CH);
return true;
#endif
}
void EthercatPdo::snapshotOutput(uint8_t *dst)
{
if (dst == nullptr) {
@@ -186,4 +217,12 @@ void EcatPdo_WriteInputs(uint8_t *esc)
EthercatPdo::writeInputs(esc);
}
/* 1.6.22 稀疏写计划:SSC PDO_InputMapping 据此决定 HW_EscWriteIsr 写几段。
* 返回 1=两段有效;0=回退整包。 */
uint8_t EcatPdo_SparseEscPlan(uint16_t *off0, uint16_t *len0,
uint16_t *off1, uint16_t *len1)
{
return EthercatPdo::sparseEscWritePlan(off0, len0, off1, len1) ? 1U : 0U;
}
} /* extern "C" */
+12 -1
View File
@@ -67,10 +67,21 @@ public:
/**
* @brief 交回输入过程数据(从站→主站)。
* @param[out] esc 指向 ESC 输入缓冲的 kPdoInBytes 字节。
* @note 由 APPL_InputMapping() 每周期调用。
* @note 由 APPL_InputMapping() 每周期调用。只填 RAM/对象存储;
* 真正写 ESC 的字节量由 PDO_InputMapping 经 sparseEscWritePlan 决定。
*/
static void writeInputs(uint8_t *esc);
/**
* @brief 稀疏写 ESC 的两段计划(1.6.22):段1+段2有效区、段3+段5;跳过空槽区。
* @param[out] off0/len0 第一段相对 PDO 起点的偏移与长度
* @param[out] off1/len1 第二段
* @return true=可按两段写;false=应回退整包 600B(开关关/非密排/count 越界/两段相接)
* @note 必须在 writeInputs() 之后调用,读的是已发布的 s_in。
*/
static bool sparseEscWritePlan(uint16_t *off0, uint16_t *len0,
uint16_t *off1, uint16_t *len1);
/** @brief 累计取走输出的周期数(主站在发过程数据的证据)。 */
static uint32_t outputCycles() { return s_outputCycles; }
+15 -1
View File
@@ -165,6 +165,8 @@ extern UINT32 EcatDbg_Dwt(void);
#define ecatdbg_dwt() EcatDbg_Dwt()
/* 1.6.0:输出探针(实现见 comm/ethercat_pdo.cpp,C 链接) */
extern UINT8 EcatPdo_OutputProbe(UINT8 const *head);
/* 1.6.22:稀疏写 ESC 计划(密排 count 只写有效段; 实现见 ethercat_pdo.cpp) */
extern UINT8 EcatPdo_SparseEscPlan(UINT16 *off0, UINT16 *len0, UINT16 *off1, UINT16 *len1);
volatile UINT32 g_dbg_pdiCyc = 0; /* sub5: PDI_Isr 全函数累计周期 */
volatile UINT32 g_dbg_outCyc = 0; /* sub6: PDO_OutputMapping(输出探针/全量读) */
volatile UINT32 g_dbg_freerunCyc = 0; /* sub7: MainLoop free-run 分支(SM 同步下恒 0) */
@@ -197,10 +199,22 @@ BOOL bInitFinished = FALSE; /** < \brief indicates if the initialization is fini
void PDO_InputMapping(void)
{
/*ECAT_DBG(1.6.0 剖分:输入映射全程 + 640B 写细分)*/
/* 1.6.22 稀疏写:APPL 仍整填 aPdInputData(对齐 SDO 0x6000),但 HW_EscWriteIsr
* 只写密排有效段+段3/5,跳过空槽区 —— 省的是 PRAM 字节时间,不是 RAM 拷贝。 */
const UINT32 dbgT0 = ecatdbg_dwt();
APPL_InputMapping((UINT16*)aPdInputData);
const UINT32 dbgT1 = ecatdbg_dwt();
HW_EscWriteIsr(((MEM_ADDR *) aPdInputData), nEscAddrInputData, nPdInputSize );
{
UINT16 o0, l0, o1, l1;
if (EcatPdo_SparseEscPlan(&o0, &l0, &o1, &l1)) {
HW_EscWriteIsr(((MEM_ADDR *)((UINT8 *)aPdInputData + o0)),
(UINT16)(nEscAddrInputData + o0), l0);
HW_EscWriteIsr(((MEM_ADDR *)((UINT8 *)aPdInputData + o1)),
(UINT16)(nEscAddrInputData + o1), l1);
} else {
HW_EscWriteIsr(((MEM_ADDR *) aPdInputData), nEscAddrInputData, nPdInputSize );
}
}
g_dbg_inWriteCyc += ecatdbg_dwt() - dbgT1;
g_dbg_inCyc += ecatdbg_dwt() - dbgT0;
}