进料口从「只有 ESP32 SPI 一条」扩成两条,中继引擎不变:主站经 CoE 0x2511 数据窗 把电机固件明文灌进 GD32 的 48KB SRAM 暂存区,再由 comm/ecat_motor_ota 泵进原有 4KB 环缓;AES 握手、CAN 单发停等、重发、0AAC 运行确认全部沿用 MotorOtaRelay。 纯网线(ESP32 WiFi 关断、/motor/ota 打不通)场合因此也能刷电机。 偏移/幂等判据仍只有一份:feedData 拆出 feedAt,两个进料口共用。 同时把审计报告 VULN-12/13/14 落地(此前只存在于文档,一行未实现):start() 第一条 语句在 PRIMASK 内原子置位 s_channel_lock_mask,EtherCAT 桥接 sendBatch/processOutput 与 SPI 0xC0/0xC7 对锁定通道回新码 0xE7=「被电机 OTA 独占」,不冒充「邮箱满」0xE4。 反向互斥一并补上:电机 OTA 活跃时 requestEnterBoot() 直接拒绝——SPI 0xB0、CoE 0x2502、 CAN 0x320 三条交接入口共用这一个判据,避免复位把刷写腰斩在 erase 之后。 完整性口径按链路分开,不互相冒充:块级 CRC32 + 整镜像 CRC32 都在从站核,且核过才 让电机进 BOOT——失败时电机仍跑旧 APP、擦除没开始,不存在「先擦再验」的变砖路径。 CRC 为 CRC-32/B(poly 0x04C11DB7,初值 0xFFFFFFFF,无反射无末尾异或),与 v2_crc32 同算法(用公开向量 123456789=0x0376E6E7 对过)。 块长取 1024 而不是 BOOT 用的 4096:SSC 的分段下载缓冲是全工程唯一的 malloc 用户, 而 APP 的 __HEAP_SIZE 只有 3KB,4096 会被从站当场回 abort 0x05040005 Out of memory (BOOT 的堆是 16KB 才撑得住 4096——块长能抄,堆抄不了)。这是跨仓常量,须与 tools/ecat 的 MFO_CHUNK_OBJ 同步。 APP 1.7.4(RAM ZI 97156)。已实测:0x2510 参数写与一块 1016B 数据写均被从站接受 (staged=1016、phase bit4);端到端电机刷写待复测(native 块长修复后未跑完整一轮)。
98 lines
5.4 KiB
C++
98 lines
5.4 KiB
C++
/**
|
||
* @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...])
|
||
*
|
||
* 本头刻意只用 <stdint.h> 类型: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 <stdint.h>
|
||
|
||
/**
|
||
* ── 尺寸常量(宏: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 */
|