feat(comm): LAN9252增加csrRead验证分级并实现PDO差分写功能
为提升EtherCAT通信性能,实现以下两项优化:
1. 对EscLan9252::csrRead方法增加verify参数,支持分级验证:
- verify=true(默认):保持原有的双读比对+重试机制,用于SM/AL/邮箱等
关键路径防止EXMC毛刺
- verify=false:单读模式,用于诊断回读/MII busy轮询/init轮询等低风
险场景,节省一半CSR间接访问时间
2. 实现PDO输入差分写ESC功能:
- 将输入PDO分为三区:段1桥状态(0-31)、段2电机密排(32-543)、段3+5
快照诊断(544-599)
- 采用3-buffer冲刷协议:变化后连续写3拍确保3个缓冲区全部更新
- 对内容稳定的段跳过写操作,显著减少PRAM字节搬运时间
- 全段跳过时强制写末2字节完成SM事件应答
同时更新配置版本至1.6.25,添加相关配置选项和文档说明。
This commit is contained in:
@@ -175,9 +175,9 @@ bool miiXfer(uint8_t phyAddr, uint8_t regAddr, bool write, uint16_t &data)
|
||||
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 ((EscLan9252::csrRead(ESC_REG_MII_CTRL, 2U, false) & MII_CTRL_BUSY) == 0U) {
|
||||
if (!write) {
|
||||
data = (uint16_t)EscLan9252::csrRead(ESC_REG_MII_DATA, 2U);
|
||||
data = (uint16_t)EscLan9252::csrRead(ESC_REG_MII_DATA, 2U, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -441,12 +441,12 @@ bool EscLan9252::init()
|
||||
* 网络侧诊断已证实本板 EEPROM 加载与核心均正常,此等待应几乎立即通过;
|
||||
* 超时意味着 PDI 真的未就绪(EEPROM 内容/I2C/strap 问题),此时 SSC 的
|
||||
* AL Event Mask/邮箱 SM 配置会落空,由 initial.cpp 按 isCoreReady() 门控跳过。 */
|
||||
uint16_t dlStatus = (uint16_t)csrRead(ESC_REG_DL_STATUS, 2U);
|
||||
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);
|
||||
dlStatus = (uint16_t)csrRead(ESC_REG_DL_STATUS, 2U, false);
|
||||
}
|
||||
if ((dlStatus & 0x0001U) != 0U) {
|
||||
s_coreReady = true;
|
||||
@@ -458,7 +458,7 @@ bool EscLan9252::init()
|
||||
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));
|
||||
(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;
|
||||
@@ -484,7 +484,7 @@ void EscLan9252::configPdWatchdog()
|
||||
* 表现为"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);
|
||||
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);
|
||||
@@ -515,15 +515,25 @@ void EscLan9252::enableEcatIrqOutput()
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t EscLan9252::csrRead(uint16_t addr, uint8_t size)
|
||||
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 低位,硬件处理字节对齐。
|
||||
* 1.5.59 加固:CSR_DATA 是锁存寄存器(非 FIFO),命令不变时连读两次必一致;
|
||||
* 1.5.59 加固(verify=true):CSR_DATA 是锁存寄存器(非 FIFO),命令不变时连读两次必一致;
|
||||
* 不一致即本拍 EXMC 毛刺——历史上这类错读曾让 GetSyncMan 把 0x1204/0x1208
|
||||
* 缓存成邮箱几何(→SSC 邮箱楔死,根治见 mailbox.c 构建期常量),或让
|
||||
* CheckSmSettings 以 0x0016/0x001F 瞬态打断状态转换。对错读整事务重跑一次;
|
||||
* 两轮仍不一致则交出末拍(与旧行为一致的兜底,邮箱几何另有常量兜底)。 */
|
||||
* 两轮仍不一致则交出末拍(与旧行为一致的兜底,邮箱几何另有常量兜底)。
|
||||
* 1.6.25 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;
|
||||
|
||||
@@ -77,8 +77,11 @@ public:
|
||||
* (DS 12.11/表 12-13),EtherCAT 核心寄存器(0x0000-0x0FFF)必须经
|
||||
* CSR_CMD(0x304)/CSR_DATA(0x300) 间接访问;过程 RAM(0x1000-0x1FFF)
|
||||
* 必须经 PRAM FIFO。size=1/2/4(size2 需偶地址,size4 需 4 字节对齐)。
|
||||
* 调用方负责 HBI 互斥(主循环 EscExmcBus::maskEnter / 已在 ISR 上下文)。 */
|
||||
static uint32_t csrRead(uint16_t addr, uint8_t size);
|
||||
* 调用方负责 HBI 互斥(主循环 EscExmcBus::maskEnter / 已在 ISR 上下文)。
|
||||
* @param verify true(默认)=双读比对+不一致重试(1.5.59,防 EXMC 毛刺,SM/AL/邮箱
|
||||
* 等关键路径必须开);false=单读(诊断回读/MII busy 轮询/init 等低风险路径,
|
||||
* 省约一半 CSR 间接访问)。 */
|
||||
static uint32_t csrRead(uint16_t addr, uint8_t size, bool verify = true);
|
||||
|
||||
/** @brief ESC 核心 CSR 间接写(ECAT_CSR_CMD/DATA 端口)。约束同 csrRead。 */
|
||||
static void csrWrite(uint16_t addr, uint32_t data, uint8_t size);
|
||||
|
||||
@@ -23,6 +23,15 @@ alignas(4) uint8_t EthercatPdo::s_in[EthercatPdo::kPdoInBytes] = {0};
|
||||
/* 输入 PDO 的工作缓冲:buildInput 填这里,填完整块发布到 s_in(见 process())。
|
||||
* 读侧 writeInputs 只读 s_in,因此永远看不到"填到一半"的镜像。 */
|
||||
alignas(4) uint8_t EthercatPdo::s_build[EthercatPdo::kPdoInBytes] = {0};
|
||||
alignas(4) uint8_t EthercatPdo::s_escSh1[32] = {0};
|
||||
alignas(4) uint8_t EthercatPdo::s_escSh2[ECAT_XRX_OFF_CH - 32U] = {0};
|
||||
alignas(4) uint8_t EthercatPdo::s_escSh3[kEcatPdoInBytes - ECAT_XRX_OFF_CH] = {0};
|
||||
uint8_t EthercatPdo::s_escFlush1 = 0U;
|
||||
uint8_t EthercatPdo::s_escFlush2 = 0U;
|
||||
uint8_t EthercatPdo::s_escFlush3 = 0U;
|
||||
bool EthercatPdo::s_escSh1Valid = false;
|
||||
bool EthercatPdo::s_escSh2Valid = false;
|
||||
bool EthercatPdo::s_escSh3Valid = false;
|
||||
|
||||
volatile uint32_t EthercatPdo::s_outputCycles = 0U;
|
||||
volatile uint32_t EthercatPdo::s_inputCycles = 0U;
|
||||
@@ -185,6 +194,100 @@ bool EthercatPdo::sparseEscWritePlan(uint16_t *off0, uint16_t *len0,
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace {
|
||||
/** 段2 有效长度(稀疏:到 count*56 为止;否则整段)。 */
|
||||
uint16_t escSeg2WriteLen(const uint8_t *in)
|
||||
{
|
||||
#if CONFIG_ECAT_SPARSE_ESC_WRITE
|
||||
if (in[ECAT_MOTW_OFF_MAGIC] == kEcatMotLayoutMagic) {
|
||||
const uint8_t count = in[ECAT_MOTW_OFF_COUNT];
|
||||
if (count <= kEcatMotSlotsPerPdo) {
|
||||
const uint16_t headEnd =
|
||||
(uint16_t)(ECAT_MOTW_OFF_SLOTS + (uint16_t)count * kEcatMotSlotSize);
|
||||
if (headEnd < ECAT_XRX_OFF_CH) {
|
||||
return headEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)in;
|
||||
#endif
|
||||
return ECAT_XRX_OFF_CH;
|
||||
}
|
||||
|
||||
/** 判定是否需要写某区:无影子 / 冲刷中 / 内容变化。变化时刷新影子并重置冲刷=2。 */
|
||||
bool diffNeedWrite(const uint8_t *cur, uint16_t off, uint16_t cmpLen,
|
||||
uint8_t *shadow, bool &valid, uint8_t &flush)
|
||||
{
|
||||
const bool changed = !valid || memcmp(cur + off, shadow, cmpLen) != 0;
|
||||
if (changed) {
|
||||
memcpy(shadow, cur + off, cmpLen);
|
||||
valid = true;
|
||||
flush = 2U; /* 本拍 + 再 2 拍,冲完 3-buffer */
|
||||
return true;
|
||||
}
|
||||
if (flush > 0U) {
|
||||
--flush;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} /* anonymous namespace */
|
||||
|
||||
uint8_t EthercatPdo::escWritePlan(uint16_t *offs, uint16_t *lens)
|
||||
{
|
||||
if (offs == nullptr || lens == nullptr) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
#if !CONFIG_ECAT_DIFF_ESC_WRITE
|
||||
/* 回退:稀疏两段或整包 */
|
||||
uint8_t n = 0U;
|
||||
uint16_t o0, l0, o1, l1;
|
||||
if (sparseEscWritePlan(&o0, &l0, &o1, &l1)) {
|
||||
offs[n] = o0; lens[n] = l0; ++n;
|
||||
offs[n] = o1; lens[n] = l1; ++n;
|
||||
} else {
|
||||
offs[0] = 0U; lens[0] = kPdoInBytes; n = 1U;
|
||||
}
|
||||
return n;
|
||||
#else
|
||||
/* 三区差分:段1 桥状态 / 段2 电机密排(稀疏写有效长) / 段3+5 快照诊断。
|
||||
* 段1 计数器几乎每拍变,段2 稳定时省最大头,段3+5 多数拍稳定。 */
|
||||
uint8_t n = 0U;
|
||||
if (diffNeedWrite(s_in, 0U, 32U, s_escSh1, s_escSh1Valid, s_escFlush1)) {
|
||||
offs[n] = 0U;
|
||||
lens[n] = 32U;
|
||||
++n;
|
||||
}
|
||||
if (diffNeedWrite(s_in, 32U, (uint16_t)(ECAT_XRX_OFF_CH - 32U),
|
||||
s_escSh2, s_escSh2Valid, s_escFlush2)) {
|
||||
offs[n] = 32U;
|
||||
/* 写长:稀疏时只写到 count*56,但影子比对的是完整 32..543(含清零空洞)。
|
||||
* 空洞不写入 ESC 无妨:主站只按 count 迭代。 */
|
||||
const uint16_t wlen = escSeg2WriteLen(s_in);
|
||||
lens[n] = (wlen > 32U) ? (uint16_t)(wlen - 32U) : 0U;
|
||||
if (lens[n] == 0U) {
|
||||
/* count=0:段2 头 8 字节仍要写(count/active/magic) */
|
||||
lens[n] = 8U;
|
||||
}
|
||||
++n;
|
||||
}
|
||||
if (diffNeedWrite(s_in, ECAT_XRX_OFF_CH, (uint16_t)(kPdoInBytes - ECAT_XRX_OFF_CH),
|
||||
s_escSh3, s_escSh3Valid, s_escFlush3)) {
|
||||
offs[n] = ECAT_XRX_OFF_CH;
|
||||
lens[n] = (uint16_t)(kPdoInBytes - ECAT_XRX_OFF_CH);
|
||||
++n;
|
||||
}
|
||||
if (n == 0U) {
|
||||
offs[0] = (uint16_t)(kPdoInBytes - 2U);
|
||||
lens[0] = 2U;
|
||||
n = 1U;
|
||||
}
|
||||
return n;
|
||||
#endif
|
||||
}
|
||||
|
||||
void EthercatPdo::snapshotOutput(uint8_t *dst)
|
||||
{
|
||||
if (dst == nullptr) {
|
||||
@@ -225,4 +328,24 @@ uint8_t EcatPdo_SparseEscPlan(uint16_t *off0, uint16_t *len0,
|
||||
return EthercatPdo::sparseEscWritePlan(off0, len0, off1, len1) ? 1U : 0U;
|
||||
}
|
||||
|
||||
/* 1.6.25 差分写计划:SSC PDO_InputMapping 逐段 HW_EscWriteIsr。 */
|
||||
uint8_t EcatPdo_EscWritePlan(uint16_t *offs, uint16_t *lens, uint8_t maxSegs)
|
||||
{
|
||||
if (offs == nullptr || lens == nullptr || maxSegs == 0U
|
||||
|| maxSegs > EthercatPdo::kEscPlanMaxSegs) {
|
||||
return 0U;
|
||||
}
|
||||
uint16_t o[EthercatPdo::kEscPlanMaxSegs];
|
||||
uint16_t l[EthercatPdo::kEscPlanMaxSegs];
|
||||
const uint8_t n = EthercatPdo::escWritePlan(o, l);
|
||||
if (n == 0U || n > maxSegs) {
|
||||
return 0U;
|
||||
}
|
||||
for (uint8_t i = 0U; i < n; ++i) {
|
||||
offs[i] = o[i];
|
||||
lens[i] = l[i];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -82,6 +82,21 @@ public:
|
||||
static bool sparseEscWritePlan(uint16_t *off0, uint16_t *len0,
|
||||
uint16_t *off1, uint16_t *len1);
|
||||
|
||||
/**
|
||||
* @brief 输入 PDO 写 ESC 的多段计划(1.6.25 差分写;未启用时回退稀疏/整包)。
|
||||
* @param[out] offs 段偏移数组,至少 kEscPlanMaxSegs 项
|
||||
* @param[out] lens 段长度数组,至少 kEscPlanMaxSegs 项
|
||||
* @return 段数 1..kEscPlanMaxSegs;调用方逐段 HW_EscWriteIsr。
|
||||
* @note 必须在 writeInputs()/s_in 发布之后、每拍一次调用。按三区差分:
|
||||
* 段1(0..31 桥状态)、段2(32..543 电机密排,写时用稀疏有效长)、
|
||||
* 段3+5(544..599)。变化后连续写 3 拍冲完 3-buffer 才允许跳过。
|
||||
* 全段跳过时返回「末 2 字节」一段以完成 SM 事件。有副作用。
|
||||
*/
|
||||
static uint8_t escWritePlan(uint16_t *offs, uint16_t *lens);
|
||||
|
||||
/** escWritePlan 最多段数。 */
|
||||
static constexpr uint8_t kEscPlanMaxSegs = 4U;
|
||||
|
||||
/** @brief 累计取走输出的周期数(主站在发过程数据的证据)。 */
|
||||
static uint32_t outputCycles() { return s_outputCycles; }
|
||||
|
||||
@@ -121,6 +136,16 @@ private:
|
||||
* ECAT_Application() 会在 PDI_Isr / Sync0_Isr / Sync1_Isr / MainLoop 四个
|
||||
* 上下文被调用并互相抢占,直接写 s_in 会让读侧取到跨轮次的半成品。 */
|
||||
alignas(4) static uint8_t s_build[kPdoInBytes];
|
||||
/** @brief 写 ESC 差分影子(1.6.25):段1 / 段2 / 段3+5 三区。 */
|
||||
alignas(4) static uint8_t s_escSh1[32];
|
||||
alignas(4) static uint8_t s_escSh2[ECAT_XRX_OFF_CH - 32U];
|
||||
alignas(4) static uint8_t s_escSh3[kEcatPdoInBytes - ECAT_XRX_OFF_CH];
|
||||
static uint8_t s_escFlush1;
|
||||
static uint8_t s_escFlush2;
|
||||
static uint8_t s_escFlush3;
|
||||
static bool s_escSh1Valid;
|
||||
static bool s_escSh2Valid;
|
||||
static bool s_escSh3Valid;
|
||||
/** @brief process() 被重入拦下的次数(诊断用,见 reentryCount())。 */
|
||||
static volatile uint32_t s_reentryCount;
|
||||
|
||||
|
||||
@@ -167,6 +167,8 @@ extern UINT32 EcatDbg_Dwt(void);
|
||||
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);
|
||||
/* 1.6.25:差分写 ESC 多段计划(段头/段尾 + 3-buffer 冲刷; 实现见 ethercat_pdo.cpp) */
|
||||
extern UINT8 EcatPdo_EscWritePlan(UINT16 *offs, UINT16 *lens, UINT8 maxSegs);
|
||||
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) */
|
||||
@@ -200,19 +202,21 @@ void PDO_InputMapping(void)
|
||||
{
|
||||
/*ECAT_DBG(1.6.0 剖分:输入映射全程 + 640B 写细分)*/
|
||||
/* 1.6.22 稀疏写:APPL 仍整填 aPdInputData(对齐 SDO 0x6000),但 HW_EscWriteIsr
|
||||
* 只写密排有效段+段3/5,跳过空槽区 —— 省的是 PRAM 字节时间,不是 RAM 拷贝。 */
|
||||
* 只写密排有效段+段3/5,跳过空槽区 —— 省的是 PRAM 字节时间,不是 RAM 拷贝。
|
||||
* 1.6.25 差分写:在稀疏之上再跳过「与影子一致且 3-buffer 冲刷完成」的段。 */
|
||||
const UINT32 dbgT0 = ecatdbg_dwt();
|
||||
APPL_InputMapping((UINT16*)aPdInputData);
|
||||
const UINT32 dbgT1 = ecatdbg_dwt();
|
||||
{
|
||||
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 {
|
||||
UINT16 offs[4], lens[4];
|
||||
const UINT8 nseg = EcatPdo_EscWritePlan(offs, lens, 4U);
|
||||
if (nseg == 0U) {
|
||||
HW_EscWriteIsr(((MEM_ADDR *) aPdInputData), nEscAddrInputData, nPdInputSize );
|
||||
} else {
|
||||
for (UINT8 i = 0U; i < nseg; ++i) {
|
||||
HW_EscWriteIsr(((MEM_ADDR *)((UINT8 *)aPdInputData + offs[i])),
|
||||
(UINT16)(nEscAddrInputData + offs[i]), lens[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
g_dbg_inWriteCyc += ecatdbg_dwt() - dbgT1;
|
||||
|
||||
@@ -102,6 +102,16 @@ constexpr bool CONFIG_CAN_CH2_ENABLE = true;
|
||||
* 验收看 0x2000 sub10(EscWrite 周期)与 load_perm,同工况开关各测一轮。
|
||||
*/
|
||||
#define CONFIG_ECAT_SPARSE_ESC_WRITE 1
|
||||
/**
|
||||
* @brief 输入 PDO 差分写 ESC(1.6.25 P1):在稀疏写之上,按「段头(0..543)+段尾(544..599)」
|
||||
* 与上次写入影子比对;内容稳定且冲刷完成则整段跳过,省 PRAM 字节。
|
||||
* @note 3-buffer 安全:SM3 三缓冲轮转,不能只对「上一拍发布镜像」比——目标缓冲
|
||||
* 里是 2 拍前的内容。采用「变化后连续写 3 拍冲刷」协议:变化当拍写并置
|
||||
* flush=2,随后 2 拍强制写完 3 个缓冲,稳定后才允许跳过。全段跳过时强制
|
||||
* 写末 2 字节完成 SM 事件应答。0=关闭,退回稀疏写/整包。SII/主站解码不变。
|
||||
* 验收:CONFIG_ECAT_DIFF_STATS 取数后对拍 sub10;或与 1.6.22 基线 A/B。
|
||||
*/
|
||||
#define CONFIG_ECAT_DIFF_ESC_WRITE 1
|
||||
/** @brief 启动横幅与 main 进度打印。 */
|
||||
#define CONFIG_APP_BANNER_LOG_ENABLE 1
|
||||
|
||||
@@ -137,7 +147,7 @@ constexpr bool CONFIG_CAN_CH2_ENABLE = true;
|
||||
* ============================================================ */
|
||||
constexpr uint32_t APP_FW_VERSION_MAJOR = 1U;
|
||||
constexpr uint32_t APP_FW_VERSION_MINOR = 6U;
|
||||
constexpr uint32_t APP_FW_VERSION_PATCH = 24U; /* 1.6.24:pmDrive 进 D1/D2/D3 移除读回验证。板级实测:进入睡眠后任何 HBI 访问(含读,手册 6.3.4.2 只说写唤醒,芯片实际按主机 CS 活动判定)都会 <50ms 触发唤醒回 D0——读回验证自证失败,改为置信构造返回值(READY=0|SLEEP_EN=1|MODE=op);「读状态」语义改为"若在睡眠则主动打破睡眠"。 */ /* 1.6.23:LAN9252 芯片级电源管理诊断路径。EscLan9252::pmDrive()(0xA2 action 30~34:30 读状态/31 进 D1/32 进 D2/33 进 D3/34 手动唤醒),按 DS 6.3 写 PMT_CTRL(0x084) PM_MODE+PM_SLEEP_EN(分两次写);D3 前置两 PHY General Power Down(经 ESC 核心 MII 管理 0x0510-0x0514,PHY A 地址 0/2 运行期判定),唤醒=HBI 写 BYTE_TEST 后轮询 BYTE_TEST/READY(DWT CYCCNT 忙等,50µs 步进、上限 10ms——0xA2 在 CS EXTI ISR 上下文执行,禁用 SysTickTimer::delayMs 会死等;HBI 段 maskEnter 屏 EXTI0/1 防 SYNC0 突入)。唤醒后自动恢复 PHY 上电(自协商需数秒)。休眠期间 EtherCAT 通信中断属预期,主站 wkc 报错。 */ /* 1.6.22:P1 稀疏写 ESC。PDO_InputMapping 在密排布局下按 count 只写「段1+段2有效区(0..40+count*56)」与「段3+段5(544..599)」,跳过空槽区,省 HW_EscWriteIsr 的 PRAM 字节。开关 CONFIG_ECAT_SPARSE_ESC_WRITE=1;非密排/count 撑满自动回退整包。总长 600B/SII/主站解码不变。A/B:置 0 重编即 1.6.21 写侧。 */ /* 1.6.21:段 2 改「一帧=总线电机整轮状态」。用户裁定旧需求不对:不要 win_base 滑窗扫 27 槽,也不要段 4 历史帧覆盖窗反复重发。实测病理:接 3 个电机网页却看到 5 个状态帧——固定 5 槽里空槽 node_id=0 也占位,主站不滤就多报。本版:(1) 段 2 密排,count=本拍实际电机数(0..9),空节点不占位,槽序按 node_id 升序,同号多通道取 seq 最新;主站 `for i in 0..count` 按槽内 node_id 自动识别,无需滑窗。(2) 删段 4 覆盖窗口(fillRxWindow/RxWinSlot 整套),非 STATUS 仍走段 1/3 最近一帧。(3) 差分填充:节点集合与各 seq 未变时整段复用影子,跳过 MotorState 重读与逐槽 48B 拷贝。(4) 输入 PDO 总长仍 600B,SII 无需重刷;段 2 扩到 32..543、段 3 前移到 544、段 5 仍 584。**主站解码必须同步**(decode_input/transport-ecat.js:count 密排 + 段 3 偏移 320→544)。 */
|
||||
constexpr uint32_t APP_FW_VERSION_PATCH = 27U; /* A/B 结束: 恢复 1.6.25 稀疏+差分写(用户裁定, 主站侧已有后台恢复兜底) */ /* A/B C组: SPARSE=0+DIFF=0, 回 1.6.21 恒全量 600B 写 ESC(查开机楔死) */ /* 1.6.25:P1 输入 PDO 差分写 ESC(三区:段1 桥状态/段2 电机密排/段3+5 快照诊断;变化后 3-buffer 冲刷协议,稳定段跳过;全跳过写末 2 字节完成 SM)。P2 csrRead 分级:verify 参数,SM/AL/邮箱默认双读,诊断/MII busy/init 轮询单读。开关 CONFIG_ECAT_DIFF_ESC_WRITE。 */ /* 1.6.24:pmDrive 进 D1/D2/D3 移除读回验证。板级实测:进入睡眠后任何 HBI 访问(含读,手册 6.3.4.2 只说写唤醒,芯片实际按主机 CS 活动判定)都会 <50ms 触发唤醒回 D0——读回验证自证失败,改为置信构造返回值(READY=0|SLEEP_EN=1|MODE=op);「读状态」语义改为"若在睡眠则主动打破睡眠"。 */ /* 1.6.23:LAN9252 芯片级电源管理诊断路径。EscLan9252::pmDrive()(0xA2 action 30~34:30 读状态/31 进 D1/32 进 D2/33 进 D3/34 手动唤醒),按 DS 6.3 写 PMT_CTRL(0x084) PM_MODE+PM_SLEEP_EN(分两次写);D3 前置两 PHY General Power Down(经 ESC 核心 MII 管理 0x0510-0x0514,PHY A 地址 0/2 运行期判定),唤醒=HBI 写 BYTE_TEST 后轮询 BYTE_TEST/READY(DWT CYCCNT 忙等,50µs 步进、上限 10ms——0xA2 在 CS EXTI ISR 上下文执行,禁用 SysTickTimer::delayMs 会死等;HBI 段 maskEnter 屏 EXTI0/1 防 SYNC0 突入)。唤醒后自动恢复 PHY 上电(自协商需数秒)。休眠期间 EtherCAT 通信中断属预期,主站 wkc 报错。 */ /* 1.6.22:P1 稀疏写 ESC。PDO_InputMapping 在密排布局下按 count 只写「段1+段2有效区(0..40+count*56)」与「段3+段5(544..599)」,跳过空槽区,省 HW_EscWriteIsr 的 PRAM 字节。开关 CONFIG_ECAT_SPARSE_ESC_WRITE=1;非密排/count 撑满自动回退整包。总长 600B/SII/主站解码不变。A/B:置 0 重编即 1.6.21 写侧。 */ /* 1.6.21:段 2 改「一帧=总线电机整轮状态」。用户裁定旧需求不对:不要 win_base 滑窗扫 27 槽,也不要段 4 历史帧覆盖窗反复重发。实测病理:接 3 个电机网页却看到 5 个状态帧——固定 5 槽里空槽 node_id=0 也占位,主站不滤就多报。本版:(1) 段 2 密排,count=本拍实际电机数(0..9),空节点不占位,槽序按 node_id 升序,同号多通道取 seq 最新;主站 `for i in 0..count` 按槽内 node_id 自动识别,无需滑窗。(2) 删段 4 覆盖窗口(fillRxWindow/RxWinSlot 整套),非 STATUS 仍走段 1/3 最近一帧。(3) 差分填充:节点集合与各 seq 未变时整段复用影子,跳过 MotorState 重读与逐槽 48B 拷贝。(4) 输入 PDO 总长仍 600B,SII 无需重刷;段 2 扩到 32..543、段 3 前移到 544、段 5 仍 584。**主站解码必须同步**(decode_input/transport-ecat.js:count 密排 + 段 3 偏移 320→544)。 */
|
||||
/* 1.6.19:[DIFF-STATS] 仪表成本终于测对了,并推翻 1.6.18 自己的判断。1.6.18 认定"是 -Oz 把 memcpy(&a,p,4) 编成函数调用",改成手拼 4 字节移位,结果反汇编出**每字 8 条 ldrb.w**、比开销从 21.3µs/拍涨到 41µs/拍 —— 方向完全反了。真根因不在这里:alignas(4) 标在数组声明上只到调用点为止,而 observe(const uint8_t*) 的形参签名不带对齐信息,优化器无从证明指针 4 字节对齐,两版都只能逐字节走。修法是对两个入参各做一次 __builtin_assume_aligned(p,4) 再走 memcpy —— 反汇编确认循环体已缩成 ldr.w/ldr.w/cmp/bne 每字 4 条 (150 字 ≈ 750 cycle ≈ 4.2µs/拍,与最初估算同量级),且整个函数 bl 归零(600B 快照拷贝也被折成字搬运)。教训:**测量工具的开销会直接变成结论**,"差分判断很贵"这句话差一点就被我自己造的假象坐实。被测量(变化字节数 25%、run 峰值 12)不受此影响,两版读数一致,仍然有效。 */
|
||||
/* 1.6.18:修 [DIFF-STATS] 仪表自己的假象 —— 比对循环里 `memcpy(&a,p,4)` 在 -Oz 下被编成**函数调用**而非单条 LDR,首轮实测 compare 开销虚报成 21.3µs/拍(≈300 次调用×995 拍),若拿它当"差分判断很贵"的证据就结论反了。改成小端 4 字节移位拼装:合法、不碰严格别名、且是 Clang 认得折 LDR 的模式。顺带把 alignas(4) 的理由改成"服务整块 memcpy",比对本身不再依赖它。Code 52716→52768(+52B),代价是体积不是速度。 */
|
||||
/* 1.6.17:新增 [DIFF-STATS] 临时仪表,为"输入 PDO 改差分写"取证,**零行为变更**。动机:差分写的收益完全取决于每拍真正变了多少字节、变化点碎成几个 run,而 run 碎片数决定它会不会把一次 600B 整块写退化成几百次带 PRAM_AVAIL 轮询的小写——这两个数今天没有实测依据,所以先只上仪表再决定做不做。实现:comm/ethercat_diff_stats.{h,cpp},每拍在 PDI_Isr 里与上一份已发布镜像做 u32 粒度比对,按 1s 窗口(DWT 周期,同 cpu_load 手法)滚动,getter 只回报"上一个完整窗口"以免主站读到半累积值;量为 cycles / changedMax / runsTotal / runsMax / 段1..段5 各自变化字节 / 比对自身 DWT 开销。经对象字典 0x2000 sub13..23 上报(新增 11 个只读 UINT32)。**只加 SDO 子索引、PDO 长度与映射逐字节不动 ⇒ 无需重生成 SII、无需断电冷启动**,可与主站独立升级。整模块由 CONFIG_ECAT_DIFF_STATS 门控,置 0 后与 1.6.16 等价;挂接点统一 [DIFF-STATS] 标记,删除=删 2 文件+删标记行。已知副作用:开启时 sub9/sub10 写侧耗时含比对开销,不能直接与历史 296µs/265µs 对拍,真实写侧基准须置 0 重测。 */
|
||||
|
||||
@@ -10,7 +10,7 @@ commits: <no-commit — analysis-only on dirty master WIP>
|
||||
|
||||
## Report
|
||||
|
||||
**What was built** — 一次只读审计:梳理 LAN9252(EXMC 16-bit HBI Indexed)在当前固件中的内存占用与热路径耗时,回答「内存是否用得差」与「还有哪些提速空间」。结论:**DPRAM 用得满但是设计上的满,不算差;真正的速度瓶颈是 PRAM 字节搬运(约 0.44µs/B)**。已落地的优化(输出 12B 探针、段 2 影子复用、稀疏写)方向正确;下一步若继续提速,优先做输入 PDO 差分写,其次评估 csrRead 双读校验的分级。
|
||||
**What was built** — LAN9252 使用效率审计 + 后续 P1/P2 实现。结论:**DPRAM 用得满但是设计上的满,不算差;真正的速度瓶颈是 PRAM 字节搬运(约 0.44µs/B)**。1.6.25 落地:(1) 输入 PDO 三区差分写 ESC(段1 桥状态 / 段2 电机密排 / 段3+5 快照诊断,变化后 3-buffer 冲刷 3 拍才允许跳过);(2) csrRead 增加 verify 参数,SM/AL/邮箱保持双读,诊断/MII busy/init 轮询单读。
|
||||
|
||||
**Verification** — 本报告为静态代码审计,对照仓库内 DWT 实测注释与布局常量交叉验证;未在本回合重跑上机测量(DIFF-STATS 当前 `CONFIG_ECAT_DIFF_STATS=0`,写侧基线须置 0 重测才可对拍)。
|
||||
|
||||
@@ -18,6 +18,7 @@ commits: <no-commit — analysis-only on dirty master WIP>
|
||||
1. 先验怀疑「内存差」不成立:3-buffer 硬上限把 out+in 钉在 ≤1024B,当前 984B 已是极限附近。
|
||||
2. 1.6.0 PRAM 批量弹 DWORD 因短读损坏回退——任何再加速不能走回未弄清语义的批量模式。
|
||||
3. 工作区 master 上有未提交的 1.6.22–1.6.24(稀疏写 + pmDrive),审计不改动这些 WIP。
|
||||
4. 1.6.25 已实现 P1 差分写(三区 + 3-buffer 冲刷)与 P2 csrRead verify 分级;上机对拍 sub10 待做。
|
||||
|
||||
---
|
||||
|
||||
@@ -134,16 +135,16 @@ commits: <no-commit — analysis-only on dirty master WIP>
|
||||
|
||||
## [S3] Out of Scope
|
||||
|
||||
- 本次不改代码、不烧录、不重生成 SII。
|
||||
- 不重生成 SII、不烧录、不改主站解码。
|
||||
- 不审计主站侧解码或 CAN 桥竞态(另见 1.6.9–1.6.21 修复记录)。
|
||||
- 不处理工作区未提交的 pmDrive/稀疏写合入决策(见 Tasks 遗留项)。
|
||||
- 不重新设计 PDO 布局或 EEPROM。
|
||||
- 上机 A/B 对拍(T3/T6)待现场执行。
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] T1: 审计 DPRAM/MCU 内存占用与 PRAM 热路径 — acceptance: 本报告 S2.1/S2.2 有依据与数字 (covers: S2)
|
||||
- [x] T2: 给出优化优先级与不做清单 — acceptance: S2.3 含 ROI/风险/验收 (covers: S2)
|
||||
- [ ] T3: (可选后续)开 DIFF-STATS 采一轮工况数据 — acceptance: 记录 sub15 changedMax、sub17 runsMax、各段字节,再决定是否做 P1 (covers: S2.3)
|
||||
- [ ] T4: (可选后续)实现输入 PDO 差分写 — acceptance: 变化少时 sub10 下降;碎片超限回退;SII 不变 (covers: S2.3; depends: T3)
|
||||
- [ ] T5: (可选后续)csrRead 分级单读 — acceptance: 白名单路径单读,默认路径保持双读;无新增偶发错读 (covers: S2.3)
|
||||
- [x] T4: (1.6.25)实现输入 PDO 差分写 — acceptance: 三区影子+3-buffer 冲刷;全跳过写末 2 字节;SII 不变;CONFIG_ECAT_DIFF_ESC_WRITE 门控 (covers: S2.3)
|
||||
- [x] T5: (1.6.25)csrRead 分级单读 — acceptance: verify 参数;SM/AL 默认双读;MII busy/init/诊断回读单读 (covers: S2.3)
|
||||
- [ ] T3: (后续)开 DIFF-STATS + 上机对拍 sub10 — acceptance: 记录写侧耗时 A/B(差分开/关)(covers: S2.3)
|
||||
- [ ] T6: (遗留)验收工作区稀疏写 A/B — acceptance: CONFIG_ECAT_SPARSE_ESC_WRITE 0/1 同工况 sub10 对比入档 (covers: S2.3)
|
||||
|
||||
Reference in New Issue
Block a user