/** * @file ecat_motor_ota.h * @brief EtherCAT(CoE 邮箱)电机 OTA 进料通路 —— 主站把电机固件灌进 DCU 本地暂存区, * 再由本模块泵进 MotorOtaRelay 现有的 4KB 环缓。 * * 为什么要有它(不是重复造轮子):电机 OTA 的引擎一直是 MotorOtaRelay * (AES 握手 + CAN 停等 + 单发模式),只有一条进料路:ESP32 经 SPI 0xD1 喂。 * 纯 EtherCAT 场合(只插网线、ESP32 WiFi 关断)那条 HTTP 打不通, * 于是前端把能力标成 supportsOta=false。本模块只换**进料口**,不换引擎: * - 分块写入仍走 MotorOtaRelay::feedAt(),offset/幂等/环缓满的判据只有一份; * - 加密、停等、重发、终态判定全部沿用原状态机; * - 通道独占锁同样沿用(EtherCAT 自己的 PDO 运动帧也被它挡在 OTA 之外)。 * * 与 BOOT 的 0x2500/0x2501(刷 DCU 自身)的区别:那边落 flash,这边只落 SRAM 暂存区, * 全程不碰 fmc_*,因此不需要关中断擦写那套优雅关停。 * * 对象(APP 字典,表项与钩子包装在 comm/ssc/port/dc_app.c): * 0x2510 会话控制(REC of UINT32,读写钩子返回实时值) * 0x2511 数据窗(VAR OCTETSTRING,4096B,载荷 [offset:u32 LE][crc32:u32 LE][data...]) * * 本头刻意只用 类型:dc_app.c 是 C 且靠相对路径取 ecat_def.h, * 而 comm/ 下的 C++ 拿不到那套 SSC 类型。SDO abort 码的映射留在 dc_app.c 那一层。 */ #ifndef COMM_ECAT_MOTOR_OTA_H #define COMM_ECAT_MOTOR_OTA_H #include /** * ── 尺寸常量(宏:C 与 C++ 两侧共用同一真源) ── * 0x2511 对象字节数上限,含 8 字节自描述头。 * 单条目 BitSize 是 UINT16 ⇒ 8191 字节是天花板,但**真正的上限是 APP 的堆**: * SSC 的分段下载缓冲是 `ALLOCMEM(ROUNDUPBYTE2WORD(size))` 即 malloc(size) * (comm/ssc/src/sdoserv.c:751,池不够就回 abort 0x05040005 Out of memory), * 而 APP 的 `__HEAP_SIZE` 只有 0xC00=3072B(BOOT 是 0x4000,所以 BOOT 的 * 0x2501 能用 4096 —— 照抄块长不照抄堆就会当场失败,2026-10-11 实测)。 * 取 1024:48KB 镜像 = 47 块,每块 1 initiate + 4 段 ≈ 5 个邮箱帧, * 总邮箱流量仍在亚秒级,且给 malloc 留出 3 倍余量。 * 改这里必须同步 tools/ecat/ecat_native.c 的 MFO_CHUNK_OBJ(两边各自断言不了对方)。 */ #define ECAT_MOTA_CHUNK_OBJ_BYTES 1024U #define ECAT_MOTA_CHUNK_HDR_BYTES 8U /** 电机固件上限:必须与 MotorOtaRelay::kMaxFwSize(48KB)一致,C++ 侧有 static_assert 守着。 */ #define ECAT_MOTA_IMG_MAX_BYTES 0xC000U /** 0x2510 的子索引个数(sub1..sub11)。 */ #define ECAT_MOTA_CTL_SUBS 11U /** * ── 会话结果码 ── * 与 MotorOtaRelay 的协议错误码(0x01..0x3x,来自电机 BOOT)刻意分段: * 0x8x 永远是「DCU 本端把料挡了」,不是电机报错。看到 0x8x 查链路与参数, * 看到 0x0x/0x1x/0x3x 查电机与 CAN。同一份码既写进 0x2510:10 高字节(主站可读), * 也作为钩子的返回值(dc_app.c 再换成 SDO abort 码)。 */ #define ECAT_MOTA_OK 0x00U #define ECAT_MOTA_E_BADLEN 0x81U /* 声明总长或块长非法 */ #define ECAT_MOTA_E_CHUNKCRC 0x82U /* 块 CRC32 不符:邮箱搬坏了,主站重发该块 */ #define ECAT_MOTA_E_GAP 0x83U /* 块偏移越界/留洞:主站发料顺序错 */ #define ECAT_MOTA_E_IMGCRC 0x84U /* 整镜像 CRC32 不符:拒绝进 BOOT,一个字节都没发 */ #define ECAT_MOTA_E_BUSY 0x85U /* relay 已在跑(另一条链路或上一会话未结束) */ #define ECAT_MOTA_E_NOTARMED 0x86U /* 没声明总长就写数据窗 */ #define ECAT_MOTA_E_STARTED 0x87U /* 已 start,暂存区冻结,不再收块 */ #define ECAT_MOTA_E_PARAM 0x88U /* 通道/节点参数非法 */ #define ECAT_MOTA_E_BOOTPEND 0x89U /* DCU 自己正在交接回 BOOT,此时不许刷电机 */ #ifdef __cplusplus namespace EcatMotorOta { /** 主循环每圈调用:把暂存区泵进环缓,并在会话结束时收标志。 */ void process(void); /** 本模块的会话是否活跃(已 start 且 relay 未进终态)。 */ bool sessionActive(void); } /* namespace EcatMotorOta */ #define ECAT_MOTA_LINKAGE extern "C" #else #define ECAT_MOTA_LINKAGE extern #endif /* __cplusplus */ /*--------------------------------------------------------------------------- * SDO 钩子(dc_app.c 的对象表引用这三个,实现在 ecat_motor_ota.cpp) * * 返回 ECAT_MOTA_OK 或上面的 ECAT_MOTA_E_*;dc_app.c 把非 0 换成 SDO abort 码, * 主站既拿得到 abort,也能从 0x2510:10 读到具体是哪一条,不用猜。 *---------------------------------------------------------------------------*/ /** @brief 0x2510 写入:sub1=cmd(1=start / 3=abort)、sub2=总长、sub3=通道、sub4=node、sub5=整镜像 CRC32。 */ ECAT_MOTA_LINKAGE uint8_t EcatMotorOtaCtlWrite(uint8_t subindex, uint32_t dataSize, const uint8_t *bytes); /** @brief 0x2510 读取:返回该 sub 的实时值(进度不落地缓存,避免读到旧数)。 */ ECAT_MOTA_LINKAGE uint32_t EcatMotorOtaCtlRead(uint8_t subindex); /** @brief 0x2511 写入:一块镜像(自描述 offset + 块 CRC32),失败即 SDO abort。 */ ECAT_MOTA_LINKAGE uint8_t EcatMotorOtaChunkWrite(uint32_t dataSize, const uint8_t *bytes); #endif /* COMM_ECAT_MOTOR_OTA_H */