Files
DCU_APP/GD32E508_DCU_APP/sys/motor_ota_relay.cpp
T

1056 lines
50 KiB
C++
Raw Normal View History

/**
* @file motor_ota_relay.cpp
* @brief 电机 OTA relay 状态机实现。
*/
#include "sys/motor_ota_relay.h"
#include "sys/aes128.h"
#include "sys/config.h"
#include "sys/systick.h"
#include "sys/task_manager.h"
#include "main.h"
#include "device/canfd.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
#include <cstring>
#include <cstdio>
namespace MotorOtaRelay {
/* ═══════════════════ 内部常量 ═══════════════════ */
/* 与电机 BOOT 工程一致的 K_master(预置主密钥) */
static const uint8_t K_MASTER[16] = {
0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF,
0xFE,0xDC,0xBA,0x98,0x76,0x54,0x32,0x10
};
/* 固件环形缓冲:ESP32 喂入明文,状态机消耗加密后发 CAN。
* 2KB≈42 包:CAN 5ms/包时约 210ms 余量;ESP32 SPI 欠载时会出现 ~500ms CAN 空洞。
* 提到 4KB≈85 包(~420ms)给 WiFi/HTTP 抖动更大缓冲。 */
static constexpr uint16_t kBufSize = 4096U;
/* ═══════════════════ 内部状态 ═══════════════════ */
static State s_state = State::kIdle;
static uint8_t s_error = 0;
static uint8_t s_channel = 0;
static uint32_t s_fw_size = 0;
static uint32_t s_fw_fed = 0; /* 已接受的固件字节数(绝对流位置,防 0xD1 重传重复追加) */
static uint16_t s_total_packets = 0;
static uint16_t s_packets_acked = 0;
static uint32_t s_session_num = 0;
static bool s_boot_heartbeat_seen = false; /* kPreCancel 期间是否收到 0x103 心跳 */
/* AES */
static aes128_context s_aes_ctx;
static uint8_t s_k_session[16];
static uint8_t s_nonce[16];
/* ACK 捕获(由 onCanFrame 在 taskCanRxParse 上下文写入) */
static volatile bool s_ack_pending = false;
static uint8_t s_ack_data[64];
static uint8_t s_ack_len = 0;
/* 超时 / 发送节奏 */
static uint32_t s_state_enter_ms = 0;
/* 上次发送时刻;0 = 当前步骤尚未首发(enterState 置 0,process() 负责首发)。 */
static uint32_t s_last_send_ms = 0;
/* 当前步骤已补发次数(进入新步骤 / 弹出新数据包时清零)。 */
static uint8_t s_retry_count = 0;
/* CRC 错误后待重发标志:置位后由 onTxTick() 按 kTransferGapMs 间隔重发当前包(不立即重发)。 */
static bool s_crc_resend_pending = false;
/* 数据发送节奏完全由主循环 onTxTick() + SysTick 钟表(s_last_send_ms)驱动,
* 不再用跨 ISR/主循环的计数器(消除 s_gap_div_100us 竞争)。 */
/* 固件环形缓冲 */
static uint8_t s_buf[kBufSize];
static volatile uint16_t s_buf_head = 0; /* 写入位置(ESP32 feed) */
static volatile uint16_t s_buf_tail = 0; /* 读取位置(状态机消耗) */
/* 当前待发包的明文暂存 + "当前包是否已从缓冲弹出"标志。
* CRC 错误时 BOOT 要求重发同一包,不能再 bufPop 新数据。
* s_pkt_pending=false 表示当前没有待发包数据(需弹新包);
* s_pkt_pending=true 表示 s_pkt_plain 已含待发数据,可重发。 */
static uint8_t s_pkt_plain[kPacketPayload];
static bool s_pkt_pending = false;
/* 末包 padding 诊断:sendNextPacket 弹末包时记录有效字节数(0xFFFF=非末包/已打印),
* 供 sendCurrentPacket 在 TX 日志前一次性打印,避免 padding 日志被紧接的 TX 行吞掉。 */
static uint16_t s_last_pad_valid = 0xFFFFU;
/* ═══════════════════ 工具函数 ═══════════════════ */
static inline uint16_t bufUsed() {
return static_cast<uint16_t>((s_buf_head - s_buf_tail + kBufSize) % kBufSize);
}
static inline uint16_t bufFree() {
return static_cast<uint16_t>(kBufSize - 1U - bufUsed());
}
static void bufPush(const uint8_t *data, uint16_t len) {
for (uint16_t i = 0; i < len; i++) {
s_buf[s_buf_head] = data[i];
s_buf_head = static_cast<uint16_t>((s_buf_head + 1U) % kBufSize);
}
}
static bool bufPop(uint8_t *out, uint16_t len) {
if (bufUsed() < len) return false;
for (uint16_t i = 0; i < len; i++) {
out[i] = s_buf[s_buf_tail];
s_buf_tail = static_cast<uint16_t>((s_buf_tail + 1U) % kBufSize);
}
return true;
}
/* 简单伪随机 nonce(SysTick + 未初始化 ADC 噪声足够区分会话) */
static void generateNonce(uint8_t *out) {
uint32_t seed = SysTickTimer::getMs();
for (uint8_t i = 0; i < 16; i++) {
seed = seed * 1103515245U + 12345U;
out[i] = static_cast<uint8_t>((seed >> 16) & 0xFF);
}
}
/* keyed-CRC32:与电机 BOOT calculate_crc32_with_key 查表实现完全一致。
* ★ 必须用查表法:BOOT 侧 (crc ^ key[i]) & 0xFF(8位 XOR 再查表),
* bit-by-bit 用 crc ^= key[i](32位 XOR),两者不等价,会导致每包 CRC 校验失败。 */
/* CRC32 查找表(标准多项式 0xEDB88320)。 */
static const uint32_t kCrcTable[256] = {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
};
static void keyedCrc32(const uint8_t *key, const uint8_t *data, uint8_t *output) {
uint32_t crc = 0xFFFFFFFFU;
/* 16 字节会话密钥先喂入(与 BOOT 完全一致:查表法) */
for (int i = 0; i < 16; ++i) {
crc = kCrcTable[(crc ^ key[i]) & 0xFFU] ^ (crc >> 8);
}
/* 48 字节有效数据再喂入 */
for (int i = 0; i < (int)kPacketPayload; ++i) {
crc = kCrcTable[(crc ^ data[i]) & 0xFFU] ^ (crc >> 8);
}
/* 取反得到最终结果 */
crc = ~crc;
/* 小端格式输出 */
output[0] = static_cast<uint8_t>(crc & 0xFFU);
output[1] = static_cast<uint8_t>((crc >> 8) & 0xFFU);
output[2] = static_cast<uint8_t>((crc >> 16) & 0xFFU);
output[3] = static_cast<uint8_t>((crc >> 24) & 0xFFU);
}
/* ═══════════════════ CAN 发送辅助 ═══════════════════ */
/* OTA 帧发送:periodic 控制是否允许硬件自动重传。
* 所有帧统一用 periodic=false:发一次即止,不靠 CAN 硬件重传。
* - 握手/控制帧:重传由 relay 软件 process() 按 kStepTimeoutMs 超时补发。
* - 数据帧:靠 relay 软件 stop-and-wait(onTxTick)补发。
* 否则电机跳 BOOT 期间 CAN 短暂断开时硬件持续重传,恢复后重复帧到达 BOOT
* 被判为非法握手 → INVALID_HANDSHAKE(0x05),或数据帧重复 → CRC 错误(0x06)。 */
static bool canSend(uint32_t id, const uint8_t *data, uint8_t dlc, bool periodic = false) {
/* 诊断:kTransfer 期间只应发 0x101,若出现 0x100 则为幽灵帧 */
if (s_state == State::kTransfer && id == kOtaReqId) {
printf("[MOTOR-OTA] BUG! canSend(0x100) in kTransfer! dlc=%u\r\n", (unsigned)dlc);
}
const CanFd::CanTxResult r = CanFd::transmitFrame(s_channel, id, data, dlc, periodic);
return (r == CanFd::CanTxResult::kOk);
}
static void enterState(State st) {
const State prev = s_state;
s_state = st;
s_state_enter_ms = SysTickTimer::getMs();
/* 进入新步骤:清补发计数;s_last_send_ms=0 令 process() 先“首发”再进入等待/补发节奏。 */
s_retry_count = 0;
s_last_send_ms = 0;
s_crc_resend_pending = false;
/* auto_retrans 切换:OTA 是应用层停等协议,硬件自动重发会打穿电机单缓冲 RX
* (重发的前一帧被当成新帧处理 → receive_packet_num 错位 → CRC 错)。
* 进入 kHandshakeReq(start() 唯一入口)即关 ARD 切单发;
* 回到 kIdle/kFinished/kError 即恢复 ARD,避免异常路径漏恢复导致全局卡在单发。
* 注:setChannelAutoRetrans 内部按 init-mode 改 ARD 位,开销在几十 µs 量级。 */
const bool otaActive = (st == State::kPreCancel || st == State::kHandshakeReq
|| st == State::kHandshakeNonce || st == State::kHandshakeConfirm
|| st == State::kErase || st == State::kTransfer
|| st == State::kFinishOta);
const bool wasActive = (prev == State::kPreCancel || prev == State::kHandshakeReq
|| prev == State::kHandshakeNonce || prev == State::kHandshakeConfirm
|| prev == State::kErase || prev == State::kTransfer
|| prev == State::kFinishOta);
if (otaActive && !wasActive) {
CanFd::setChannelAutoRetrans(s_channel, false); /* OTA 开始:单发 */
} else if (!otaActive && wasActive) {
CanFd::setChannelAutoRetrans(s_channel, true); /* OTA 结束:恢复重发 */
}
}
static bool stateTimedOut(uint32_t timeout_ms) {
return (SysTickTimer::getMs() - s_state_enter_ms) >= timeout_ms;
}
static void pushStatus(); /* 前置声明:setError 里要用,定义在下方 */
static void setError(uint8_t code) {
s_error = code;
enterState(State::kError);
/* 任何失败都立即把 kError 推给 ESP32:否则 ESP32 feedTask 看不到错误态,
* 会一直喂到 120s 超时才退出。"失败了就报错退出"要求即时上报。 */
pushStatus();
}
/* 进度推送给 ESP32(0xD2 ring push)。
* 传输期 ESP32 suppress ring:push 只会占 PB6/m_txBuf 50ms,干扰 0xD1 喂数。
* kTransfer 内禁止 push;终态/错误/握手阶段仍推。 */
static void pushStatus() {
if (s_state == State::kTransfer) {
return;
}
uint8_t payload[12];
payload[0] = static_cast<uint8_t>(s_state);
payload[1] = s_error;
payload[2] = static_cast<uint8_t>(s_packets_acked & 0xFF);
payload[3] = static_cast<uint8_t>(s_packets_acked >> 8);
payload[4] = static_cast<uint8_t>(s_total_packets & 0xFF);
payload[5] = static_cast<uint8_t>(s_total_packets >> 8);
payload[6] = static_cast<uint8_t>(s_fw_size & 0xFF);
payload[7] = static_cast<uint8_t>((s_fw_size >> 8) & 0xFF);
payload[8] = static_cast<uint8_t>((s_fw_size >> 16) & 0xFF);
payload[9] = static_cast<uint8_t>((s_fw_size >> 24) & 0xFF);
payload[10] = static_cast<uint8_t>(bufUsed() & 0xFF);
payload[11] = static_cast<uint8_t>(bufUsed() >> 8);
AppSpiCommandBus.requestEsp32Read(SpiDefs::kTypeMotorOtaStatus, payload, 12);
}
/* ═══════════════════ 握手子步骤 ═══════════════════ */
/* 单步等对应反馈的超时:到点仍无反馈就补发。100ms 加快握手节奏(电机 BOOT 主循环~10ms 足够响应)。 */
static constexpr uint32_t kStepTimeoutMs = 100U;
/* 补发次数上限:首发后最多再补发 kMaxResends 次;仍无反馈则判该步失败并打印。 */
static constexpr uint8_t kMaxResends = 2U;
/* 数据传输每包间隔(ms):正式发固件时每包也留间隔,防电机单缓冲 RX 高速下丢包/CRC 错。
* 现行:5ms/包(用户指定)。SPI 喂数必须能跟上;跟不上时靠水位门控暂停 CAN。 */
static constexpr uint32_t kTransferGapMs = 5U;
/* 开传前环缓最低水位:至少约 1KB(~21 包 / ~100ms)再发首包,
* 避免擦除后立刻按 5ms 抽干仅有的 232B 首 chunk 导致电机超时。 */
static constexpr uint16_t kStartWaterBytes = 1024U;
/* 运行中低水位:低于此值暂停发新包,等 ESP32 回填(已在途包仍等 ACK)。 */
static constexpr uint16_t kRunWaterBytes = 256U;
/* 擦除后首包等待(ms):电机发 erase ACK = 擦除已完成,
* 100ms 仅为电机内部状态转换留余量(BOOT 主循环~10ms)。
* 之前 500ms 导致电机 RECEIVING_FIRMWARE 超时(~2s)预算被压缩。 */
static constexpr uint32_t kEraseWaitMs = 100U;
/* 预取消等待(ms):发 CANCEL 后等电机 BOOT 主循环处理完(复位到 IDLE),再进握手。
* 电机 BOOT 主循环周期 ~1-10ms,100ms 留足余量。 */
static constexpr uint32_t kBootSettleMs = 100U; /* 检测到0x103心跳后再等这么久,让电机APP残留[00FA]CAN帧先消耗完
* 原 2000ms→800ms→100ms:0x103 出现即电机已在 BOOT IDLE,
* 残帧实测 <4 帧(~40ms),100ms 足够。 */
/* 步骤名,用于“xxx 未收到”日志,让用户/上位机知道卡在协议哪一步。 */
static const char *stepName(State st) {
switch (st) {
case State::kPreCancel: return "WAIT_BOOT(0x103)";
case State::kHandshakeReq: return "HANDSHAKE_REQ(0x100:00AC)";
case State::kHandshakeNonce: return "HANDSHAKE_NONCE(0x100:16B)";
case State::kHandshakeConfirm: return "HANDSHAKE_CONFIRM(0x100:16B)";
case State::kErase: return "ERASE(0x101:fw_size)";
case State::kTransfer: return "TRANSFER(0x101:data)";
case State::kFinishOta: return "FINISH_OTA(0x100:0AAC)";
default: return "?";
}
}
static void sendNonce() {
/* 生成 nonce 并切状态;首发由 ACK handler 触发(不走 process 首发路径)。 */
generateNonce(s_nonce);
enterState(State::kHandshakeNonce);
}
/* 统一重发当前步骤的帧(超时重发 + kHandshakeReq 首发) */
static void sendCurrentStep(uint32_t now_ms) {
printf("[MOTOR-OTA] TX %s try=%u t=%lu\r\n",
stepName(s_state), (unsigned)s_retry_count, (unsigned long)now_ms);
switch (s_state) {
case State::kHandshakeReq: {
const uint8_t req[2] = {0x00, 0xAC};
canSend(kOtaReqId, req, 2);
break;
}
case State::kHandshakeNonce:
canSend(kOtaReqId, s_nonce, 16);
break;
case State::kHandshakeConfirm: {
uint8_t plain[16];
for (uint8_t i = 0; i < 16; i++) plain[i] = s_nonce[i] ^ 0x55;
uint8_t confirm[16];
aes128_set_key(&s_aes_ctx, s_k_session);
aes128_encrypt_block(&s_aes_ctx, plain, confirm);
canSend(kOtaReqId, confirm, 16);
break;
}
case State::kErase: {
uint8_t size_le[4];
size_le[0] = static_cast<uint8_t>(s_fw_size & 0xFF);
size_le[1] = static_cast<uint8_t>((s_fw_size >> 8) & 0xFF);
size_le[2] = static_cast<uint8_t>((s_fw_size >> 16) & 0xFF);
size_le[3] = static_cast<uint8_t>((s_fw_size >> 24) & 0xFF);
canSend(kOtaDataId, size_le, 4);
break;
}
case State::kFinishOta: {
const uint8_t finish_cmd[2] = {0x0A, 0xAC};
canSend(kOtaReqId, finish_cmd, 2);
break;
}
default:
break;
}
s_last_send_ms = now_ms;
}
/* ═══════════════════ 数据传输 ═══════════════════ */
/* 将 s_pkt_plain 组装成 64B CAN 帧并发送。
* 不弹缓冲,只做加密+CRC+组帧+CAN 发送。
* 供 sendNextPacket(弹新包后发)和 CRC 重发(发同一包)共用。 */
static void sendCurrentPacket() {
const uint32_t pkt_num = s_packets_acked;
const bool is_last = (pkt_num + 1U >= s_total_packets);
/* AES-ECB 加密 48B */
uint8_t encrypted[kPacketPayload];
aes128_set_key(&s_aes_ctx, s_k_session);
aes128_ecb_encrypt_48bytes(&s_aes_ctx, s_pkt_plain, encrypted);
/* keyed CRC32 */
uint8_t crc[4];
keyedCrc32(s_k_session, s_pkt_plain, crc);
/* 组装 64B 帧: session(4) + pkt_num(4) + encrypted(48) + IFEND(4) + CRC(4)
* 帧布局对齐 BOOT 工程 OTA.cpp:
* data[0-3] session, [4-7] pkt_num, [8-55] encrypted(48B),
* [56-59] IFEND (extract_u32_from_4bytes(data+56)),
* [60-63] CRC (data+60) */
uint8_t frame[kFrameDlc];
memset(frame, 0, sizeof(frame));
/* session_num LE */
frame[0] = static_cast<uint8_t>(s_session_num & 0xFF);
frame[1] = static_cast<uint8_t>((s_session_num >> 8) & 0xFF);
frame[2] = static_cast<uint8_t>((s_session_num >> 16) & 0xFF);
frame[3] = static_cast<uint8_t>((s_session_num >> 24) & 0xFF);
/* packet_num LE */
frame[4] = static_cast<uint8_t>(pkt_num & 0xFF);
frame[5] = static_cast<uint8_t>((pkt_num >> 8) & 0xFF);
frame[6] = static_cast<uint8_t>((pkt_num >> 16) & 0xFF);
frame[7] = static_cast<uint8_t>((pkt_num >> 24) & 0xFF);
/* encrypted payload */
memcpy(&frame[8], encrypted, kPacketPayload);
/* IFEND @56 (BOOT 在 data+56 读 IFEND) */
if (is_last) {
frame[56] = 0xFF; frame[57] = 0xFF; frame[58] = 0xFF; frame[59] = 0xFF;
}
/* CRC @60 (BOOT 在 data+60 读 CRC) */
memcpy(&frame[60], crc, 4);
s_ack_pending = false;
/* periodic=false:数据帧只发一次,不靠 CAN 硬件自动重传。
* 硬件重传会在 10ms 间隔下持续重发同一包,电机 BOOT 处理期间重复收到
* 同一包 → 时序错乱 → CRC 错误(06)/握手错误(05)。
* 超时重发由 onTxTick()(CRC 错误置 s_crc_resend_pending)负责。 */
/* 末包 padding 日志在 TX 前打印(sendNextPacket 设 s_last_pad_valid)。
* 用最短格式 + 一次性打印(打印后置 0xFFFF 防重发重复打印)。 */
if (s_last_pad_valid != 0xFFFFU) {
printf("[MOTOR-OTA] PAD v=%u p=%u\r\n",
(unsigned)s_last_pad_valid,
(unsigned)(kPacketPayload - s_last_pad_valid));
s_last_pad_valid = 0xFFFFU;
}
/* 逐包 TX 日志在 5ms 节奏下会拖主循环;仅末包/每 64 包打一行。 */
if (is_last || ((pkt_num & 0x3FU) == 0U)) {
printf("[MOTOR-OTA] TX pkt=%lu/%u%s t=%lu\r\n",
(unsigned long)pkt_num, (unsigned)s_total_packets,
is_last ? " LAST" : "", (unsigned long)SysTickTimer::getMs());
}
canSend(kOtaDataId, frame, kFrameDlc, /*periodic=*/false);
}
static void sendNextPacket() {
/* 从缓冲取明文到 s_pkt_plain */
const uint16_t used = bufUsed();
const bool is_last = (s_packets_acked + 1U >= s_total_packets);
/* 末包真实有效字节:fw_size % 48;整除时末包也是满 48B。 */
const uint16_t last_valid = static_cast<uint16_t>(
(s_fw_size % kPacketPayload) == 0U
? kPacketPayload
: (s_fw_size % kPacketPayload));
if (is_last) {
/* ★ 末包无论 used 是否 ≥48,都只取 last_valid 再 0xFF pad。
* 旧逻辑 used≥48 时 bufPop(48) 会把环缓里多喂的重复字节一并发出
* (BLF:末包 padding 变成向量表而非 0xFF)。 */
if (used < last_valid) {
static uint32_t s_last_tail_ms = 0;
const uint32_t now = SysTickTimer::getMs();
if (now - s_last_tail_ms >= 200U) {
printf("[MOTOR-OTA] 末包欠载,等尾字节 pkt=%u used=%u need=%u fed=%lu t=%lu\r\n",
(unsigned)s_packets_acked, (unsigned)used,
(unsigned)last_valid, (unsigned long)s_fw_fed,
(unsigned long)now);
s_last_tail_ms = now;
}
return;
}
for (uint16_t i = 0U; i < last_valid; ++i) {
s_pkt_plain[i] = s_buf[s_buf_tail];
s_buf_tail = static_cast<uint16_t>((s_buf_tail + 1U) % kBufSize);
}
for (uint16_t i = last_valid; i < kPacketPayload; ++i) {
s_pkt_plain[i] = 0xFFU;
}
s_last_pad_valid = last_valid;
} else {
if (used < kPacketPayload) {
static uint32_t s_last_empty_ms = 0;
const uint32_t now = SysTickTimer::getMs();
if (now - s_last_empty_ms >= 200U) {
printf("[MOTOR-OTA] 缓冲空,等ESP32喂 pkt=%u used=%u fed=%lu/%lu t=%lu\r\n",
(unsigned)s_packets_acked, (unsigned)used,
(unsigned long)s_fw_fed, (unsigned long)s_fw_size,
(unsigned long)now);
s_last_empty_ms = now;
}
return;
}
if (!bufPop(s_pkt_plain, kPacketPayload)) {
return;
}
s_last_pad_valid = 0xFFFFU;
}
s_pkt_pending = true;
s_retry_count = 0; /* 新数据包:重置补发计数 */
s_state_enter_ms = SysTickTimer::getMs(); /* 新数据包:重置整体超时 backstop */
/* 末包 padding 日志在 sendCurrentPacket 的 TX 行之前打印,且用最短格式,
* 避免 10ms 高频 TX 期间被 UART 吞掉(之前放在这里会被紧接的 TX pkt= 行挤掉)。 */
sendCurrentPacket();
s_last_send_ms = SysTickTimer::getMs();
}
/* BOOT 错误码(与 OTA.h ota_error_t 对齐,仅列出实际处理的) */
static constexpr uint8_t kBootErrHandshake = 0x05; /* INVALID_HANDSHAKE:直接中止(不重握手) */
static constexpr uint8_t kBootErrCrc = 0x06; /* CRC 错误:重发同一包 */
/* CRC 错误重试上限:BOOT 对 CRC 失败的包做 receive_packet_num -= 1 并
* 发 1 字节 error frame(0x06),期望上位机重发同一包。超过此限判定为
* 真实数据损坏,报错中止,避免无限循环。 */
static constexpr uint8_t kMaxCrcRetries = 3;
static uint8_t s_crc_retry_count = 0;
static uint16_t s_last_acked_pkt = 0xFFFFU; /* 上次确认的包号,用于识别重复 ACK */
static uint32_t s_dup_ack_count = 0; /* 累计忽略的重复/未前进 ACK 帧数(诊断重传风暴) */
static uint32_t s_crc_err_count = 0; /* 累计收到的 0x06 CRC 错误帧数(含电机重传) */
static void processTransferAck(const uint8_t *data, uint8_t len) {
/* 擦除等待窗口(首包尚未发出:s_packets_acked==0 且 !s_pkt_pending):电机重传的旧
* [BA C0] / 杂散帧一律忽略,绝不能推进 s_packets_acked——否则 onTxTick 的
* need=(s_packets_acked==0?kEraseWaitMs:kTransferGapMs) 会提前用 30ms 抢跑首包,
* 且首包被错标成非 0 号(实测首包变 pkt=5) → 从一开始就包号/数据错位,每包 0x06。 */
if (s_packets_acked == 0U && !s_pkt_pending) {
s_dup_ack_count++;
return;
}
/* BOOT 错误帧:1 字节 [error_code],发到 0x102 ID。
* CRC 错误(0x06):BOOT 做 receive_packet_num -= 1,期望重发同一包,
* 不改变 ota_state,仍处于 RECEIVING_FIRMWARE。
* 其他错误码:BOOT 已进 IDLE,不可恢复。 */
if (len == 1) {
if (data[0] == kBootErrCrc) {
s_crc_err_count++;
/* 去重:已排定重发时,电机 auto_retrans 重传的重复 0x06 不再计数,
* 避免一次 CRC 错的多份重传帧把重试计数提前耗尽 → 假性 0x23。 */
if (s_crc_resend_pending) {
return;
}
if (s_crc_retry_count < kMaxCrcRetries) {
s_crc_retry_count++;
/* 不立即重发:置标志,由 onTxTick() 按 kTransferGapMs 间隔重发当前包,
* 避免"挨太近→又 CRC 错"死循环。s_pkt_plain 仍含上次明文,s_pkt_pending 保持。 */
s_crc_resend_pending = true;
return;
}
printf("[MOTOR-OTA] CRC 重试耗尽 pkt=%u/%u dup=%lu crc=%lu\r\n",
(unsigned)s_packets_acked, (unsigned)s_total_packets,
(unsigned long)s_dup_ack_count, (unsigned long)s_crc_err_count);
setError(0x23); /* CRC 重试耗尽 */
return;
}
if (data[0] == kBootErrHandshake) {
/* INVALID_HANDSHAKE(0x05):电机 BOOT 已退回 IDLE。按用户要求不再做
* 「发 CANCEL + 从包 0 重握手」的自动重试(会白扔进度、坏总线下反复失败),
* 直接中止,由上位机决定是否整体重刷。 */
printf("[MOTOR-OTA] HANDSHAKE 错误(0x05),中止(不重握手) pkt=%u/%u\r\n",
(unsigned)s_packets_acked, (unsigned)s_total_packets);
setError(0x05);
return;
}
/* 其他错误码:BOOT 已中止,relay 也报错 */
setError(0x24); /* BOOT 报致命错误 */
return;
}
if (len < 6) { setError(0x21); return; }
/* ACK: [0xBA, 0xC0, next_pkt(4B LE)] */
if (data[0] != 0xBA || data[1] != 0xC0) { setError(0x22); return; }
const uint32_t next_pkt = static_cast<uint32_t>(data[2])
| (static_cast<uint32_t>(data[3]) << 8)
| (static_cast<uint32_t>(data[4]) << 16)
| (static_cast<uint32_t>(data[5]) << 24);
/* BOOT 协议:最后一包(IFEND=0xFFFFFFFF)后,BOOT 进 FINISHED 并回
* ACK(0xF0F0F0F0) 表示"全部写入完成,等 FINISH_OTA"。
* 上位机须再发 [0x0A, 0xAC] 到 REQ ID,BOOT 才真正跳转 APP。
* 旧实现误将 0xF0F0F0F0 当普通包号处理,导致 relay 永远等不到 0xFFFFFFFF,
* 既不进 kFinished 也不发 FINISH_OTA,BOOT 卡在 FINISHED 状态不跳转。 */
/* BOOT 协议(已对照电机 BOOT OTA.cpp line 430-436 确认):
* 最后一包(IFEND=0xFFFFFFFF)后,BOOT 发 ACK(0xFFFFFFFF) 并立即 jumpToApplication()
* 自行跳转 APP,不需要上位机再发 FINISH_OTA。
* 旧注释里"0xF0F0F0F0 表示等 FINISH_OTA"是错误假设——电机 BOOT 代码里根本
* 没有 0xF0F0F0F0,尾帧 ACK 就是 0xFFFFFFFF,发完即跳转。
* 因此收到 0xFFFFFFFF 即表示电机已跳转 APP,relay 直接进 kFinished。
* 若再发 [0x0A,0xAC] 反而会被已运行的电机 APP 当作握手开始信号干扰。 */
if (next_pkt == 0xFFFFFFFFU) {
/* 末包已被电机确认并跳转:把进度钉到 total,便于 QUERY/网页识别 100%。 */
s_packets_acked = s_total_packets;
s_last_acked_pkt = s_total_packets;
s_pkt_pending = false;
printf("[MOTOR-OTA] FINISHED pkt=%u/%u dup=%lu crc=%lu t=%lu\r\n",
(unsigned)s_packets_acked, (unsigned)s_total_packets,
(unsigned long)s_dup_ack_count, (unsigned long)s_crc_err_count,
(unsigned long)SysTickTimer::getMs());
enterState(State::kFinished);
pushStatus();
return;
}
/* 兼容:若未来 BOOT 改为回 0xF0F0F0F0 等待 FINISH_OTA,再发 [0x0A,0xAC]。
* 当前电机 BOOT 不会走到这里,保留分支以防协议变体。 */
if (next_pkt == 0xF0F0F0F0U) {
const uint8_t finish_cmd[2] = {0x0A, 0xAC};
canSend(kOtaReqId, finish_cmd, 2);
s_last_send_ms = SysTickTimer::getMs();
enterState(State::kFinishOta);
return;
}
const uint16_t next16 = static_cast<uint16_t>(next_pkt);
/* 重复/未前进 ACK:BOOT auto_retrans=ENABLE,relay 侧 CAN 层没回 ACK 时会重传
* “同一个 next_pkt”的 ACK 帧。next_pkt 未前进时必须整帧忽略——绝不能清
* s_pkt_pending:否则会把刚发出、尚未真正确认的在途包误判为已确认,下一次
* onTxTick 多 bufPop 一包却仍沿用旧包号发送 → 包号/数据错位、CRC 错累积,
* 正是“看着有很多重复发送”的根源。忽略后在途包的真确认(next_pkt 前进)自然到来。 */
if (s_last_acked_pkt != 0xFFFFU && next16 <= s_last_acked_pkt) {
/* 重复/未前进 ACK:只累计计数,不逐帧打印(量极大会打爆 UART 扰乱时序)。
* 汇总见每 32 包进度行的 dup=。 */
s_dup_ack_count++;
return;
}
s_packets_acked = next16;
s_last_acked_pkt = next16;
s_crc_retry_count = 0; /* 正常前进 ACK,清零 CRC 重试计数 */
s_crc_resend_pending = false;
s_pkt_pending = false; /* 本包确认;下一包由主循环 onTxTick 按钟表间隔发出 */
/* 每 32 包只打串口汇总,不再 pushStatus(0xD2)。
* 传输期 ESP32 suppress ring,0xD2 读不到还占 spi_ready/ring,
* 与 0xD1 喂数抢总线,加重缓冲欠载(CANlog 可见周期性 ~500ms 空洞)。
* 进度由 ESP32 feedTask 按已喂字节估算;终态再 push。 */
if ((s_packets_acked & 0x1F) == 0) {
printf("[MOTOR-OTA] 进度 pkt=%u/%u dup=%lu crc=%lu t=%lu\r\n",
(unsigned)s_packets_acked, (unsigned)s_total_packets,
(unsigned long)s_dup_ack_count, (unsigned long)s_crc_err_count,
(unsigned long)SysTickTimer::getMs());
}
}
/* ═══════════════════ 公开接口 ═══════════════════ */
void init() {
/* 无额外硬件初始化;CAN 已由 CanFd 模块管理 */
}
/* 数据发送推进:由主循环 TaskRunner 每圈调用(不再走 ISR 计时 + TASK 标志)。
* 全部间隔判定用 SysTick 钟表(s_last_send_ms),与任务调度器的定时器分频解耦,
* 从根上消除 s_gap_div_100us 被 ISR(++)与主循环(=0)同时读写的竞争。
* 首包等 BOOT 擦除完成(kEraseWaitMs,从进 kTransfer 起算),后续包按 kTransferGapMs。 */
void onTxTick() {
if (s_state != State::kTransfer) return;
const uint32_t now = SysTickTimer::getMs();
/* ACK 超时重发:包已发出(s_pkt_pending=true)但超过 500ms 未收到 ACK,重发。
* 场景:CAN 总线 ERR_PASSIVE(REC>128) 导致电机 ACK 丢失,
* GD32 等 ACK、电机等下一包 → 双方死锁。
* ★ 注意:检查 s_pkt_pending(包已发等ACK),不是 s_ack_pending(ACK已到等处理)!
* 重发后电机幂等处理(同包号只 ACK 不重复写 flash)。 */
if (s_pkt_pending && !s_ack_pending && !s_crc_resend_pending) {
if (now - s_last_send_ms >= 500U) {
if (s_retry_count < kMaxResends) {
s_retry_count++;
printf("[MOTOR-OTA] ACK 超时重发 pkt=%u retry=%u\r\n",
(unsigned)s_packets_acked, (unsigned)s_retry_count);
sendCurrentPacket();
s_last_send_ms = SysTickTimer::getMs();
}
/* 超过最大重试次数后由 process() 的整体超时处理 */
}
return;
}
/* CRC 错误重发:发同一包(s_pkt_plain 不变、不 bufPop),按 kTransferGapMs 节流;
* 优先于 s_pkt_pending 门,否则忽略重复 ACK 后 s_pkt_pending 长期为真会堵死重发。 */
if (s_crc_resend_pending) {
if (now - s_last_send_ms < kTransferGapMs) return;
s_crc_resend_pending = false;
sendCurrentPacket();
s_last_send_ms = SysTickTimer::getMs();
return;
}
if (s_pkt_pending) return; /* 当前包待确认,不发新包 */
/* 首包(s_packets_acked==0)等 kEraseWaitMs 让 BOOT 擦完,后续包按 kTransferGapMs。 */
const uint32_t need = (s_packets_acked == 0U) ? kEraseWaitMs : kTransferGapMs;
if (now - s_last_send_ms < need) return;
/* 水位门控:SPI 喂数跟不上时暂停 CAN,避免抽干环缓后电机 1s 超时。
* 首包要求更高水位(开传缓冲);运行中维持低水位即可。 */
const uint16_t water = (s_packets_acked == 0U) ? kStartWaterBytes : kRunWaterBytes;
const uint16_t used = bufUsed();
/* 末包:只剩 last_valid 字节时放行 */
const bool is_last = (s_packets_acked + 1U >= s_total_packets);
const uint16_t last_need = static_cast<uint16_t>(
(s_fw_size % kPacketPayload) == 0U ? kPacketPayload
: (s_fw_size % kPacketPayload));
/* 尾段:SPI 已喂完全部固件(fed>=fw_size)时缓冲里就是剩余全部数据,
* 剩余字节可能 < water(如 248<256),必须旁路水位门控,否则尾段死锁
* → 电机 BOOT 1s 无包超时。 */
const bool all_fed = (s_fw_fed >= s_fw_size);
if (!is_last && !all_fed && used < water) {
static uint32_t s_last_water_ms = 0;
if (now - s_last_water_ms >= 200U) {
printf("[MOTOR-OTA] 等SPI水位 used=%u need=%u fed=%lu pkt=%u\r\n",
(unsigned)used, (unsigned)water,
(unsigned long)s_fw_fed, (unsigned)s_packets_acked);
s_last_water_ms = now;
}
return;
}
if (is_last && used < last_need) {
return; /* sendNextPacket 内也会等,这里省一次调用 */
}
sendNextPacket(); /* 内部设 s_last_send_ms */
}
void process() {
if (s_state == State::kIdle || s_state == State::kFinished || s_state == State::kError) {
return;
}
/* kPreCancel:等待电机 BOOT 心跳(0x103)确认已进入 Bootloader。
* 电机 BOOT IDLE 每 ~10ms 发一次 0x103;收到即证明电机已在 BOOT IDLE。
* 但不立即握手——电机 APP 跳 BOOT 前可能在 CAN TX 队列残留 [00 FA](fail)帧,
* 这帧延迟发到总线会被 BOOT 的 handleLinkMessage catch-all 命中
* → 重置状态机 → 0x05。等 kBootSettleMs 让残帧在 BOOT IDLE 期间消耗完,
* 再开始握手就不会被干扰。 */
if (s_state == State::kPreCancel) {
if (s_boot_heartbeat_seen && stateTimedOut(kBootSettleMs)) {
printf("[MOTOR-OTA] 0x103 心跳确认 + %ums 残帧窗口已过,开始握手\r\n",
(unsigned)kBootSettleMs);
enterState(State::kHandshakeReq);
pushStatus();
} else if (stateTimedOut(kHandshakeTimeoutMs)) {
printf("[MOTOR-OTA] 等待 0x103 超时,电机未进入 BOOT\r\n");
setError(0x02); /* 0x02 = 电机未进入 BOOT */
}
return;
}
/* 超时检查 */
const uint32_t timeout = (s_state == State::kTransfer || s_state == State::kFinishOta)
? kAckTimeoutMs : kHandshakeTimeoutMs;
if (stateTimedOut(timeout)) {
setError(0x01); /* 超时 */
pushStatus();
return;
}
/* ===== ACK 优先处理 ===== */
if (s_ack_pending) {
s_ack_pending = false;
uint8_t ack_data[64];
uint8_t ack_len;
memcpy(ack_data, s_ack_data, 64);
ack_len = s_ack_len;
/* 日志:握手步骤全打印;传输期错误帧限频 */
bool rx_do_log = (s_state != State::kTransfer);
if (s_state == State::kTransfer && ack_len == 1) {
static uint32_t s_last_rxerr_ms = 0;
const uint32_t rnow = SysTickTimer::getMs();
if (rnow - s_last_rxerr_ms >= 200U) { rx_do_log = true; s_last_rxerr_ms = rnow; }
}
if (rx_do_log) {
printf("[MOTOR-OTA] RX %s pkt=%u/%u len=%u b0=%02X b1=%02X t=%lu\r\n",
stepName(s_state), (unsigned)s_packets_acked, (unsigned)s_total_packets,
(unsigned)ack_len,
(ack_len > 0) ? ack_data[0] : 0xFFU, (ack_len > 1) ? ack_data[1] : 0xFFU,
(unsigned long)SysTickTimer::getMs());
}
switch (s_state) {
case State::kHandshakeReq:
if (ack_len >= 2 && ack_data[0] == 0x00 && ack_data[1] == 0xAC) {
/* echo 正确 —— 立即发 nonce(与 PC 工具一致,不给干扰帧窗口) */
generateNonce(s_nonce);
enterState(State::kHandshakeNonce);
canSend(kOtaReqId, s_nonce, 16);
s_last_send_ms = SysTickTimer::getMs();
printf("[MOTOR-OTA] TX HANDSHAKE_NONCE(0x100:16B) [ACK-driven] t=%lu\r\n",
(unsigned long)s_last_send_ms);
}
/* 不匹配的 ACK 靠掉 —— 忽略,等超时重发 REQ */
break;
case State::kHandshakeNonce:
if (ack_len < 36) {
/* 短帧(0x05/干扰) —— 忽略,等真正 48B 或超时重发 */
printf("[MOTOR-OTA] NONCE ACK 忽略短帧 len=%u b0=0x%02X\r\n",
(unsigned)ack_len, (ack_len > 0) ? ack_data[0] : 0xFFU);
break;
}
if (memcmp(ack_data + 12, s_nonce + 8, 8) != 0) {
/* nonce echo 不匹配 —— 电机收到了错误的帧当 nonce */
printf("[MOTOR-OTA] NONCE ACK echo mismatch, ignore\r\n");
break;
}
/* ✔ nonce 响应正确 —— 派生 K_session,立即发 confirm */
aes128_set_key(&s_aes_ctx, K_MASTER);
aes128_encrypt_block(&s_aes_ctx, s_nonce, s_k_session);
s_session_num = static_cast<uint32_t>(s_nonce[0])
| (static_cast<uint32_t>(s_nonce[1]) << 8)
| (static_cast<uint32_t>(s_nonce[2]) << 16)
| (static_cast<uint32_t>(s_nonce[3]) << 24);
{
enterState(State::kHandshakeConfirm);
uint8_t plain[16];
for (uint8_t i = 0; i < 16; i++) plain[i] = s_nonce[i] ^ 0x55;
uint8_t confirm[16];
aes128_set_key(&s_aes_ctx, s_k_session);
aes128_encrypt_block(&s_aes_ctx, plain, confirm);
canSend(kOtaReqId, confirm, 16);
s_last_send_ms = SysTickTimer::getMs();
printf("[MOTOR-OTA] TX HANDSHAKE_CONFIRM(0x100:16B) [ACK-driven] t=%lu\r\n",
(unsigned long)s_last_send_ms);
}
break;
case State::kHandshakeConfirm:
if (ack_len >= 2 && ack_data[0] == 0xBA && ack_data[1] == 0xC0) {
/* ✔ 握手成功 —— 立即发 fw_size */
enterState(State::kErase);
uint8_t size_le[4];
size_le[0] = static_cast<uint8_t>(s_fw_size & 0xFF);
size_le[1] = static_cast<uint8_t>((s_fw_size >> 8) & 0xFF);
size_le[2] = static_cast<uint8_t>((s_fw_size >> 16) & 0xFF);
size_le[3] = static_cast<uint8_t>((s_fw_size >> 24) & 0xFF);
canSend(kOtaDataId, size_le, 4);
s_last_send_ms = SysTickTimer::getMs();
printf("[MOTOR-OTA] TX ERASE(0x101:fw_size) [ACK-driven] t=%lu\r\n",
(unsigned long)s_last_send_ms);
}
/* 非 [BA C0] —— 忽略(可能是 0x03/0x05 干扰帧) */
break;
case State::kErase:
if (ack_len >= 6 && ack_data[0] == 0xBA && ack_data[1] == 0xC0) {
/* ✔ 擦除完成 —— 进入传输态,等 kEraseWaitMs 后由 onTxTick 发首包 */
s_packets_acked = 0;
enterState(State::kTransfer);
pushStatus();
/* 清空 CAN TX 邮箱:握手期间快速连发4帧(REQ+nonce+confirm+fw_size),
* 可能有帧卡在邮箱未发出,进入 kTransfer 后才发出 → 电机收到
* 过时的 0x100 帧 → catch-all → [05] 杀死 OTA。强制 abort 降低风险。 */
CanFd::abortPendingTx(s_channel);
s_last_send_ms = SysTickTimer::getMs();
printf("[MOTOR-OTA] ERASE ACK 收到,等 %ums 再发首包\r\n",
(unsigned)kEraseWaitMs);
}
/* 非 [BA C0 ...] —— 忽略,等超时重发 fw_size */
break;
case State::kTransfer:
processTransferAck(ack_data, ack_len);
break;
case State::kFinishOta:
if (ack_len >= 2 && ack_data[0] == 0x0A && ack_data[1] == 0xAC) {
enterState(State::kFinished);
pushStatus();
} else {
/* BOOT 可能回 WAIT 表示还在处理,重置超时 */
s_state_enter_ms = SysTickTimer::getMs();
}
break;
default:
break;
}
return; /* ACK 已处理,本轮结束 */
}
/* ===== 无 ACK —— 首发或超时重发 ===== */
if (s_state == State::kTransfer) {
return; /* kTransfer 由 onTxTick 驱动 */
}
const uint32_t now_ms = SysTickTimer::getMs();
if (s_last_send_ms == 0U) {
/* 首发(仅 kHandshakeReq 初始进入时走这里,其余步骤由 ACK handler 触发发帧) */
sendCurrentStep(now_ms);
} else if ((now_ms - s_last_send_ms) >= kStepTimeoutMs) {
/* 超时重发 */
if (s_retry_count >= kMaxResends) {
printf("[MOTOR-OTA] %s 未收到应答(已发送 %u 次无响应),中止刷写\r\n",
stepName(s_state), (unsigned)(kMaxResends + 1U));
setError(static_cast<uint8_t>(0x30U + static_cast<uint8_t>(s_state)));
pushStatus();
return;
}
s_retry_count++;
sendCurrentStep(now_ms);
}
}
bool onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len) {
if (s_state == State::kIdle || s_state == State::kFinished || s_state == State::kError) {
return false;
}
if (channel != s_channel) return false;
/* BOOT 的 0x103 帧语义修正:
* 本电机 BOOT 仅在 IDLE 状态每 ~10ms 发一次 0x103 心跳(见 OTA.cpp process()),
* 擦除/传输忙时并不发 0x103。握手阶段(kHandshakeReq/Nonce/Confirm)收到 0x103
* 只说明电机仍停在 IDLE、尚未接受握手,此时绝不能重置超时——否则握手失败时
* 超时会被 IDLE 心跳无限重置 → relay 静默死循环、UI 永远卡在"握手中"且无错误码。
* 仅在 kErase/kTransfer/kFinishOta(电机确在忙、WAIT 合法)才重置超时,
* 使握手失败能在 kHandshakeTimeoutMs 后正常超时报错、可被取消/重试。 */
if (id == kOtaWaitId) {
/* kPreCancel 期间:0x103 = 电机 BOOT IDLE 心跳,证明电机已进入 Bootloader */
if (s_state == State::kPreCancel) {
s_boot_heartbeat_seen = true;
}
if (s_state == State::kErase || s_state == State::kTransfer ||
s_state == State::kFinishOta) {
s_state_enter_ms = SysTickTimer::getMs();
}
return true; /* 已消费,不再传递给其他 handler */
}
if (id != kOtaAckId) return false;
/* kPreCancel 期间丢弃残留 ACK(电机可能对上次 OTA 的帧补发 ACK),
* 不置 s_ack_pending,确保进入 kHandshakeReq 时从干净状态开始。 */
if (s_state == State::kPreCancel) return true;
/* 捕获 ACK:总是覆盖(处理最新帧)。
* 无需 s_last_send_ms guard——新模型里 ACK handler 触发后立即发下一帧,
* s_last_send_ms 在任何 ACK 到达前已被设置。 */
/* 捕获 ACK */
const uint8_t copy_len = (len <= 64) ? len : 64;
memcpy(s_ack_data, data, copy_len);
s_ack_len = copy_len;
s_ack_pending = true;
return true; /* 已消费,不再传递给其他 handler */
}
bool start(uint8_t can_channel, uint32_t fw_size) {
if (s_state != State::kIdle && s_state != State::kFinished && s_state != State::kError) {
return false; /* 已在进行中 */
}
if (fw_size == 0 || fw_size > kMaxFwSize) return false;
if (can_channel >= 3) return false;
s_channel = can_channel;
s_fw_size = fw_size;
s_fw_fed = 0U;
s_total_packets = static_cast<uint16_t>((fw_size + kPacketPayload - 1U) / kPacketPayload);
s_packets_acked = 0;
s_error = 0;
s_session_num = 0; /* 由 processNonceAck 根据 s_nonce 前4字节设置 */
s_buf_head = 0;
s_buf_tail = 0;
s_ack_pending = false;
s_last_send_ms = 0; /* 0=尚未首发,enterState(kHandshakeReq) 也会置 0,由 process() 首发 00AC */
s_crc_retry_count = 0;
s_last_acked_pkt = 0xFFFFU;
s_pkt_pending = false;
s_dup_ack_count = 0;
s_crc_err_count = 0;
s_last_pad_valid = 0xFFFFU;
/* 不在 SPI 中断里同步发握手帧(电机跳 BOOT 期间 CAN 可能短暂断开)。
* 先进 kPreCancel 等待电机 0x103 心跳确认已进 BOOT,再由 process() 异步推进到握手。 */
printf("[MOTOR-OTA] START ch=%u fw=%lu total=%u\r\n",
(unsigned)can_channel, (unsigned long)fw_size, (unsigned)s_total_packets);
s_boot_heartbeat_seen = false;
enterState(State::kPreCancel);
return true;
}
bool feedData(const uint8_t *data, uint16_t len) {
/* 接受任何活跃状态的数据(包括握手期间)——让 ESP32 在 kPreCancel/握手
* 期间就能预填充缓冲,进入 kTransfer 后立即有数据可发。
*
* payload: [offset:u32 LE][chunk...]。offset 为固件绝对字节偏移。
* ★ BLF 根因:无 offset 纯流时,SPI 超时重传同一 chunk,GD32 再次追加,
* CAN 上出现「前 236B 循环 185 次」→ CRC 全过但固件内容全错。 */
if (s_state == State::kIdle || s_state == State::kError ||
s_state == State::kFinished) {
return false;
}
if (data == nullptr || len < 4U) {
return false;
}
const uint32_t offset = static_cast<uint32_t>(data[0])
| (static_cast<uint32_t>(data[1]) << 8)
| (static_cast<uint32_t>(data[2]) << 16)
| (static_cast<uint32_t>(data[3]) << 24);
const uint8_t *chunk = data + 4U;
uint16_t chunk_len = static_cast<uint16_t>(len - 4U);
if (chunk_len == 0U) {
return true;
}
if (s_fw_size == 0U) {
return false;
}
if (offset >= s_fw_size) {
return true; /* 越界尾:幂等 ACK */
}
/* 截到固件末尾 */
if (offset + chunk_len > s_fw_size) {
chunk_len = static_cast<uint16_t>(s_fw_size - offset);
}
if (offset + chunk_len <= s_fw_fed) {
/* 完全已收过:幂等 ACK,不写环缓 */
return true;
}
if (offset > s_fw_fed) {
/* 缺口:拒收,ESP32 按 s_fw_offset 重发正确位置 */
static uint32_t s_last_gap_ms = 0;
const uint32_t now = SysTickTimer::getMs();
if (now - s_last_gap_ms >= 200U) {
printf("[MOTOR-OTA] feed 缺口 off=%lu fed=%lu len=%u\r\n",
(unsigned long)offset, (unsigned long)s_fw_fed,
(unsigned)chunk_len);
s_last_gap_ms = now;
}
return false;
}
/* 部分重叠:只追加 [s_fw_fed, offset+chunk_len) */
const uint32_t skip = s_fw_fed - offset;
const uint16_t add_len = static_cast<uint16_t>(chunk_len - skip);
if (bufFree() < add_len) {
return false; /* 环缓满 → SPI WAIT */
}
bufPush(chunk + skip, add_len);
s_fw_fed += add_len;
return true;
}
void cancel() {
if (s_state == State::kIdle) return;
printf("[MOTOR-OTA] CANCEL received in state %s\r\n", stepName(s_state));
/* ★ kFinished/kError 时电机已跳转 APP 或已中止,绝不能再发 CAN 取消帧!
* kOtaCancelId=0x00000000 与电机 APP 的 CANFD_BOOT_ID(base_id=0x0000) 冲突,
* 电机 APP 收到会当作 BOOT 命令 → enter_silent_mode 10s,APP 看似卡死。
* 仅在活跃传输态(kPreCancel..kFinishOta)发取消帧通知电机 BOOT 中止。 */
const bool was_active = (s_state != State::kFinished && s_state != State::kError);
if (was_active) {
canSend(kOtaCancelId, nullptr, 0);
}
enterState(State::kIdle);
s_buf_head = 0;
s_buf_tail = 0;
s_fw_fed = 0U;
s_pkt_pending = false;
s_crc_retry_count = 0;
pushStatus();
}
Status getStatus() {
Status st;
st.state = s_state;
st.error_code = s_error;
st.packets_acked = s_packets_acked;
st.total_packets = s_total_packets;
st.fw_size = s_fw_size;
return st;
}
bool isActive() {
return (s_state != State::kIdle && s_state != State::kFinished && s_state != State::kError);
}
} /* namespace MotorOtaRelay */