/** * @file motor_ota_relay.h * @brief 电机 OTA relay 状态机 — GD32 本地完成与电机 Bootloader 的全部交互。 * * 职责: * - AES-128 密钥协商(K_master 存本地 flash) * - 固件数据 AES-ECB 加密 + keyed-CRC32 * - CAN 上逐包 stop-and-wait(发 0x101 → 等 0x102 ACK → 按包号继续) * - 进度/完成/错误通过 SPI ring push 报 ESP32 * * ESP32 通过 SPI 0xD0(start) / 0xD1(data) / 0xD3(cancel) 喂数据, * GD32 通过 0xD2 ring push 报进度。 */ #ifndef SYS_MOTOR_OTA_RELAY_H #define SYS_MOTOR_OTA_RELAY_H #include namespace MotorOtaRelay { /* 电机 Bootloader CAN ID(固定,非节点相关) */ static constexpr uint32_t kOtaReqId = 0x00000100U; /* Host→Motor 握手 */ static constexpr uint32_t kOtaDataId = 0x00000101U; /* Host→Motor 数据 */ static constexpr uint32_t kOtaAckId = 0x00000102U; /* Motor→Host ACK */ static constexpr uint32_t kOtaWaitId = 0x00000103U; /* Motor→Host IDLE心跳 */ static constexpr uint32_t kOtaCancelId = 0x00000000U; /* Host→Motor 取消 */ static constexpr uint32_t kPacketPayload = 48U; /* 每包有效载荷 */ static constexpr uint32_t kFrameDlc = 64U; /* CAN FD DLC */ static constexpr uint32_t kMaxFwSize = 0xC000U; /* 48 KB */ static constexpr uint32_t kAckTimeoutMs = 5000U; /* 单包 ACK 超时 */ static constexpr uint32_t kHandshakeTimeoutMs = 3000U; enum class State : uint8_t { kIdle = 0, kHandshakeReq, /* 已发 [00 AC],等 ACK */ kHandshakeNonce, /* 已发 nonce,等 36B ACK */ kHandshakeConfirm, /* 已发 confirm,等 [BA C0] */ kErase, /* 已发 size,等擦除完成 ACK */ kTransfer, /* 逐包传输中 */ kFinishOta, /* 收到 0xF0F0F0F0,已发 [0A AC],等 FINISH ACK */ kFinished, /* 完成 */ kError, /* 错误 */ kPreCancel = 9, /* 等待电机 BOOT 心跳(0x103)确认已进入 Bootloader,然后进 kHandshakeReq */ kWaitAppRun = 10 /* 尾包 ACK 后电机已跳转,等新 APP 上电自主发的 [0A AC] 运行确认,收到才进 kFinished */ }; struct Status { State state; uint8_t error_code; uint16_t packets_acked; uint16_t total_packets; uint32_t fw_size; }; /* 初始化(注册 CAN RX 回调等) */ void init(); /* 主循环推进(从 task_runner 周期调用,建议 1~5ms 一次) */ void process(); /* 数据发送推进:由主循环 TaskRunner 每圈调用,按 SysTick 钟表控制每包间隔 * (不再走 ISR 计时 + TASK 标志,消除跨上下文计数器竞争)。 */ void onTxTick(); /* CAN RX 分发入口:匹配 0x102 ACK 帧时消费,返回 true 表示已处理 */ bool onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len); /* SPI 命令接口 */ bool start(uint8_t can_channel, uint32_t fw_size); /** * @brief 喂入固件明文。payload 布局:[offset:u32 LE][data...] * offset 为固件绝对字节偏移;重复/重叠区间幂等 ACK,缺口拒绝(→SPI WAIT)。 * 无 offset 的旧纯流会在 SPI 超时重传时把同一 chunk 追加进环缓(BLF 见前 236B 循环)。 */ bool feedData(const uint8_t *data, uint16_t len); void cancel(); /* 状态查询 */ Status getStatus(); bool isActive(); } /* namespace MotorOtaRelay */ #endif /* SYS_MOTOR_OTA_RELAY_H */