feat(ethercat): LAN9252 bring-up完成,EEPROM烧录改主站侧并增强DC/AL可观测性

- 新增ESC硬件复位序列(1.5.25):PB10驱动LAN9252 RST#,拉低30ms+释放后300ms轮询HBI就绪,
  满足tpurstd/trstia时序;此前RST#仅靠R44上拉自然释放
- 修正EXMC 16-bit地址左移(1.5.27):HBI DATA偏移0x20000/0x30000→0x40000/0x60000;
  ADDSET 2→4 HCLK;片选更名NE1→NE0(LQFP100仅Region0)
- 按LAN9252手册/AN1907修正EEPROM镜像(1.5.28):word1高字节0x00→0xCC(SYNC0/1 Map+Output,
  否则SYNC0留LATCH态DC过程数据死)、word2=5000(脉宽50µs)、尾部补真实0xFF;
  gen_eeprom.py按ETG.1000.6重写;DCU_ESI.xml PDI 0x05→0x8D、RxPdo/TxPdo互换改4×64bit对齐
- 删除GD32侧EEPROM烧录路径(1.5.29):移除EscEeprom/esc_eeprom、eeprom_image.h及主循环
  烧录块,ESI镜像改由主站ecat_flash_eeprom.py经ESC 0x0500刷写(162字全校验通过);
  HBI互斥补屏蔽EXTI2(SYNC1),防Sync1_Isr两步事务打断主循环HBI访问致错址
- DC SYNC0通路可观测(1.5.30/31):EthercatSync新增sync0/sync1/escirq三个volatile计数器,
  task_runner 5s按增量打印速率;SSC OD新增只读0x2000 SYNC Diag,主站SDO轮询网页显示
- AL通路诊断(1.5.32):ECAT_Main/SetALStatus打印主站AL请求与写回状态+错误码,
  task_runner新增5s [ECAT][HB]心跳(仅同步ISR激活时);dc_app.c导出EcatDbg_ReadAlDiag
- 删除死代码EthercatSlave::periodicIsr三缓冲PDO桥,PDO交换由SSC完成(4×64bit=32B);
  EthercatSlave::init()恢复调用并以isBusReady()(BYTE_TEST)门控
- CANFD修复同通道突发只发3帧:transmitFrame新增allow_abort区分突发/软件队列路径,
  邮箱满落入软件TX队列(深度13)由TX完成中断drainTxQueueIsr回灌;BRS重新启用(0→1)
- motor_state新增CONFIG帧缓存(0x00000101, DLC 24)记录目标值回报,条目44B→76B、每页5→3
- SPI 0xA2诊断扩展action 20~24(ECAT_NRST拉低/拉高/浮空/回读);新增ESP32 EN自检打印
- 版本号更新至1.5.32
This commit is contained in:
cbb
2026-09-01 03:07:48 +08:00
parent a2d0b28b3d
commit cfe7725b26
32 changed files with 22382 additions and 1349 deletions
+8 -111
View File
@@ -1,141 +1,38 @@
/**
* @file ethercat_slave.h
* @brief EtherCAT 从站周期处理接口与 PDO 三缓冲(triple buffer)结构。
* @note 周期 ISR 调用 periodicIsr() 完成输入 PDO 读取 + 输出 PDO 写入;
* 应用层通过 getLatestInputs()/setOutputs() 与周期路径无锁交换数据。
*
* 通知路径分离:
* 周期路径(periodicIsr)用 s_periodicSeq 序列号通知主循环;
* 事件路径(ESC IRQ)用 TASK_ETHERCAT_PROCESS 标志。
* 两者独立,避免 preempt=0 ISR 与 BASEPRI 临界区的 RMW 竞态。
* @brief EtherCAT 从站 init 探针与运行状态查询接口。
* @note 1.5.27:原先的 periodicIsr()/三缓冲 PDO 桥是死代码(无任何调用点),
* 运行期 PDO 交换实际由 SSC 协议栈完成 —— 周期路径为 Sync0_Isr →
* ECAT_Application → APPL_InputMapping/OutputMapping(comm/ssc/port/dc_app.c),
* 主循环路径为 MainLoop → ECAT_Main。本类仅保留 init 探针与诊断查询。
*/
#ifndef COMM_ETHERCAT_SLAVE_H
#define COMM_ETHERCAT_SLAVE_H
#include <cstdint>
#include <cstddef>
#include "sys/config.h" /* CONFIG_ETHERCAT_PDO_SIZE */
/** @brief EtherCAT AL 状态码(EtherCAT 协议定义,ESC AL Status 寄存器低 4 位)。 */
constexpr uint8_t AL_STATE_SAFEOP = 0x04U; /**< SAFEOP 状态(SYNC0 可触发下限)。 */
/**
* @brief PDO 数据缓冲(输入/输出共用结构)。
* @note 周期 ISR 与应用层通过三缓冲无锁交换。
*/
struct PdoBuffer {
uint8_t data[CONFIG_ETHERCAT_PDO_SIZE] = {0}; /**< 数据载荷。 */
size_t len = 0U; /**< 有效数据长度。 */
};
/**
* @brief EtherCAT 从站周期处理。
* @note 周期路径(periodicIsr)必须极简:读输入→写输出→模 3 轮转指针→递增序列号。
* @brief EtherCAT 从站 init 探针与状态查询。
*/
class EthercatSlave {
public:
/** @brief 初始化 ESC 与三缓冲。 */
/** @brief 初始化 ESC(HBI 探活 + 芯片 ID + AL 状态),供 init 汇总诊断。 */
static void init();
/**
* @brief 周期中断处理(由 SYNC0 EXTI 触发)。
* @note 放入 RAM 执行段(.ramfunc)以降低 Flash 等待带来的抖动。
* 流程:读输入 PDO → 写输出 PDO → 模 3 轮转索引 → 递增 s_periodicSeq。
* 不再调用 TaskManager::set,避免 preempt=0 与 BASEPRI 临界区的 RMW 竞态。
*/
static void periodicIsr();
/**
* @brief 获取最新输入 PDO(主循环调用,无锁)。
* @return const PdoBuffer* 最新输入缓冲只读指针;返回与 ISR 当前写入位置隔一个的缓冲。
* @note 三缓冲容忍主循环跨 1 个周期,主循环处理必须 < 2 × 周期时间。
*/
static const PdoBuffer* getLatestInputs();
/**
* @brief 设置下次周期要发送的输出 PDO(主循环调用)。
* @param[in] outputs 输出数据指针(内容会被拷贝到内部输出缓冲)。
* @note 写入与 ISR 当前读取位置隔一个的缓冲,无需任何临界区。
*/
static void setOutputs(const PdoBuffer *outputs);
/**
* @brief 应用层 PDO 映射入口(非实时,由 TASK_ETHERCAT_PROCESS 或周期序列号触发)。
* @note 留接口,具体映射逻辑由协议栈后续实现。
*/
static void process();
/**
* @brief 获取应用层输入 PDO 快照(主循环调用)。
* @return const PdoBuffer* 最近一次 process() 拷贝的输入快照只读指针。
* @note 返回的指针在下次 process() 调用前有效;应用层应在 process() 后立即读取。
* @note TODO(app-layer): 应用层 PDO 字典映射 spec 启用前无外部调用方;
* 保留接口契约,待未来 spec 实现后由应用层调用。 */
static const PdoBuffer* getApplicationInputs();
/**
* @brief 设置应用层输出 PDO 快照(主循环调用)。
* @param[in] outputs 输出数据指针(内容会被拷贝到内部快照)。
* @note 下次 process() 调用时,内部快照通过 setOutputs() 推入三缓冲。
* @note TODO(app-layer): 应用层 PDO 字典映射 spec 启用前无外部调用方;
* 保留接口契约,待未来 spec 实现后由应用层调用。 */
static void setApplicationOutputs(const PdoBuffer *outputs);
/**
* @brief 检查是否有新的周期数据待主循环处理。
* @return bool true 表示 s_periodicSeq 与已处理序号不同。
* @note 主循环 runPending() 优先调用此接口,替代原 TASK_ETHERCAT_PROCESS 周期路径。
*/
static bool hasPendingPeriodic();
/**
* @brief 标记当前周期数据已被主循环处理。
* @note 调用此接口前应已调用 taskEthercatProcess() 完成本次 PDO 映射。
*/
static void markPeriodicProcessed();
/** @brief 读取 ISR 执行时间诊断(max/avg 单位:CPU 周期数,除以 180 得 µs)。 */
static void getIsrStats(uint32_t *maxCycles, uint32_t *avgCycles, uint32_t *count);
/** @brief ESC 自检是否通过(init 后可查询,用于决定是否使能同步中断)。 */
static bool isEscReady() { return s_escReady; }
/** @brief 读取 ESC 芯片 ID(供诊断打印;未就绪时为上次读到的值)。 */
static uint32_t getChipId() { return s_chipId; }
/** @brief 读取 ESC AL 状态码(供诊断打印与 SSC 集成;未就绪时为上次读到的值)。
* @note TODO(app-layer): 当前仅 init 内部用作 SAFEOP 守卫,外部诊断未启用;
* SSC 集成后由应用层查询。 */
/** @brief 读取 ESC AL 状态码(供诊断打印;未就绪时为上次读到的值)。 */
static uint8_t getAlState() { return s_alState; }
private:
/**
* @brief 输入三缓冲。
* ISR 按 s_inputWriteIdx 模 3 轮转写入;getLatestInputs() 读取 (writeIdx + 2) % 3。
*/
static PdoBuffer s_inputs[3];
/**
* @brief 输出三缓冲。
* ISR 按 s_outputReadIdx 模 3 轮转读取;setOutputs() 写入 (readIdx + 2) % 3,
* 与 ISR 当前读取位置隔 2 个缓冲,提供 2 周期宽限消除拷贝竞态。
*/
static PdoBuffer s_outputs[3];
/** @brief 应用层输入快照(主循环访问,process() 从三缓冲拷贝;无需三缓冲)。 */
static PdoBuffer s_appInputs;
/** @brief 应用层输出快照(主循环访问,process() 推入三缓冲;无需三缓冲)。 */
static PdoBuffer s_appOutputs;
static volatile uint8_t s_inputWriteIdx; /**< ISR 当前写入索引;模 3 轮转。 */
static volatile uint8_t s_outputReadIdx; /**< ISR 当前读取索引;模 3 轮转。 */
/** @brief 周期路径通知序列号(ISR 单字原子递增,主循环比对;uint16_t 提供 6.55s 回绕周期)。 */
static volatile uint16_t s_periodicSeq;
/** @brief 主循环已处理序号(由 hasPendingPeriodic/markPeriodicProcessed 维护)。 */
static uint16_t s_processedSeq;
static volatile uint32_t s_isrMaxCycles; /**< ISR 最大执行周期数(180MHz 基准)。 */
static volatile uint32_t s_isrAvgCycles; /**< ISR 平均执行周期数(滑动累计)。 */
static volatile uint32_t s_isrCount; /**< ISR 执行次数计数。 */
static bool s_escReady; /**< ESC 自检结果(init 后置位)。 */
static uint32_t s_chipId; /**< ESC 芯片 ID(诊断用)。 */
static uint8_t s_alState; /**< ESC AL 状态码(init 后可查询,供 isEscReady 守卫)。 */