feat(motor_state): 支持多通道电机状态管理并添加推送周期动态调节功能
- 实现 node×channel 双键槽位管理,支持同一节点号在不同CAN通道独立缓存 - 扩展电机状态表容量从9个槽位到27个槽位(9节点×3通道) - 添加SPI命令子命令0xFE用于动态设置0xC5推送周期(50-5000ms) - 增加运行时可调推送周期功能,默认250ms,支持50ms-5s范围调节 - 更新CAN通道配置,启用CH1和CH2通道支持 - 版本号更新至1.5.4
This commit is contained in:
@@ -293,11 +293,14 @@ static constexpr uint8_t kAckMaxResends = 4U;
|
||||
* 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 足够。 */
|
||||
/* 等新 APP 运行确认(ms):尾包 ACK 后电机复位跳 APP,新 APP 上电初始化完 CAN 会
|
||||
* 自主发 [0A AC](刷写结束/APP 运行有效,见协议表 15/16 行)。
|
||||
* 复位+初始化实测 <1s,3s 留足余量;超时 = 新 APP 未启动 → error 0x25。 */
|
||||
static constexpr uint32_t kAppRunTimeoutMs = 3000U;
|
||||
/* 残帧窗口 kBootSettleMs 已移除(2026-07-27):收到 0x103 即直接进握手。
|
||||
* 旧担忧:电机 APP 跳转前 TX 队列残留 [00 FA] 帧延迟上总线被 BOOT catch-all 命中 → 0x05。
|
||||
* 现行握手阶段对垃圾帧一律忽略,靠 kStepTimeoutMs=100ms 步进重发(≤2次)自愈,
|
||||
* 窗口不再必要;若 0x05 复现优先怀疑此处。 */
|
||||
|
||||
/* 步骤名,用于“xxx 未收到”日志,让用户/上位机知道卡在协议哪一步。 */
|
||||
static const char *stepName(State st) {
|
||||
@@ -309,6 +312,7 @@ static const char *stepName(State st) {
|
||||
case State::kErase: return "ERASE(0x101:fw_size)";
|
||||
case State::kTransfer: return "TRANSFER(0x101:data)";
|
||||
case State::kFinishOta: return "FINISH_OTA(0x100:0AAC)";
|
||||
case State::kWaitAppRun: return "WAIT_APP_RUN(0x102:0AAC)";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
@@ -572,19 +576,20 @@ static void processTransferAck(const uint8_t *data, uint8_t len) {
|
||||
* 自行跳转 APP,不需要上位机再发 FINISH_OTA。
|
||||
* 旧注释里"0xF0F0F0F0 表示等 FINISH_OTA"是错误假设——电机 BOOT 代码里根本
|
||||
* 没有 0xF0F0F0F0,尾帧 ACK 就是 0xFFFFFFFF,发完即跳转。
|
||||
* 因此收到 0xFFFFFFFF 即表示电机已跳转 APP,relay 直接进 kFinished。
|
||||
* 若再发 [0x0A,0xAC] 反而会被已运行的电机 APP 当作握手开始信号干扰。 */
|
||||
* 收到 0xFFFFFFFF = 电机已跳转,但不证明新 APP 能跑 → 进 kWaitAppRun
|
||||
* 等新 APP 上电自主发的 [0A AC] 运行确认(协议表 15/16 行),收到才 kFinished。
|
||||
* 此阶段绝不能主动发 [0x0A,0xAC]——会被已运行的电机 APP 当作握手开始信号干扰。 */
|
||||
if (next_pkt == 0xFFFFFFFFU) {
|
||||
/* 末包已被电机确认并跳转:把进度钉到 total,便于 QUERY/网页识别 100%。 */
|
||||
/* 末包已被电机确认:把进度钉到 total,便于 QUERY/网页识别 100%。 */
|
||||
s_packets_acked = s_total_packets;
|
||||
s_last_acked_pkt = s_total_packets;
|
||||
s_pkt_pending = false;
|
||||
LOG_ERR("[MOTOR-OTA] FINISHED pkt=%u/%u dup=%lu crc=%lu resend=%lu fast=%lu t=%lu\r\n",
|
||||
LOG_ERR("[MOTOR-OTA] tail-ACK pkt=%u/%u dup=%lu crc=%lu resend=%lu fast=%lu t=%lu, wait APP-run 0AAC\r\n",
|
||||
(unsigned)s_packets_acked, (unsigned)s_total_packets,
|
||||
(unsigned long)s_dup_ack_count, (unsigned long)s_crc_err_count,
|
||||
(unsigned long)s_ack_resend_total, (unsigned long)s_fast_resend_total,
|
||||
(unsigned long)SysTickTimer::getMs());
|
||||
enterState(State::kFinished);
|
||||
enterState(State::kWaitAppRun);
|
||||
pushStatus();
|
||||
return;
|
||||
}
|
||||
@@ -742,15 +747,11 @@ void process() {
|
||||
}
|
||||
|
||||
/* 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 期间消耗完,
|
||||
* 再开始握手就不会被干扰。 */
|
||||
* 电机 BOOT IDLE 每 ~10ms 发一次 0x103;收到即证明电机已在 BOOT IDLE,
|
||||
* 直接进握手(残帧窗口已移除,握手阶段对干扰帧忽略+步进重发兜底)。 */
|
||||
if (s_state == State::kPreCancel) {
|
||||
if (s_boot_heartbeat_seen && stateTimedOut(kBootSettleMs)) {
|
||||
OTA_LOGI("[MOTOR-OTA] 0x103 ok, residue-win %ums passed, handshake\r\n",
|
||||
(unsigned)kBootSettleMs);
|
||||
if (s_boot_heartbeat_seen) {
|
||||
OTA_LOGI("[MOTOR-OTA] 0x103 ok, handshake\r\n");
|
||||
enterState(State::kHandshakeReq);
|
||||
pushStatus();
|
||||
} else if (stateTimedOut(kHandshakeTimeoutMs)) {
|
||||
@@ -760,6 +761,33 @@ void process() {
|
||||
return;
|
||||
}
|
||||
|
||||
/* kWaitAppRun:尾包 ACK 后电机已复位跳 APP,等新 APP 自主发的 [0A AC] 运行确认。
|
||||
* GD32 此阶段不发任何帧(不能走下方首发/补发路径,否则 sendCurrentStep 空转
|
||||
* kMaxResends 次后误报 0x3A);启动期杂帧一律忽略,只认 [0A AC]。
|
||||
* kAppRunTimeoutMs 无确认 → error 0x25(新 APP 未启动)。 */
|
||||
if (s_state == State::kWaitAppRun) {
|
||||
if (s_ack_pending) {
|
||||
s_ack_pending = false;
|
||||
uint8_t ack_data[64];
|
||||
memcpy(ack_data, s_ack_data, 64);
|
||||
const uint8_t ack_len = s_ack_len;
|
||||
if (ack_len >= 2 && ack_data[0] == 0x0A && ack_data[1] == 0xAC) {
|
||||
LOG_ERR("[MOTOR-OTA] APP-run 0AAC ok, FINISHED t=%lu\r\n",
|
||||
(unsigned long)SysTickTimer::getMs());
|
||||
enterState(State::kFinished);
|
||||
pushStatus();
|
||||
return;
|
||||
}
|
||||
/* 非 [0A AC](APP 启动期其他帧)——忽略继续等 */
|
||||
}
|
||||
if (stateTimedOut(kAppRunTimeoutMs)) {
|
||||
LOG_ERR("[MOTOR-OTA] no APP-run 0AAC in %lums, new APP not running\r\n",
|
||||
(unsigned long)kAppRunTimeoutMs);
|
||||
setError(0x25); /* 0x25 = 刷写传完但新 APP 未发运行确认 */
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* 超时检查 */
|
||||
const uint32_t timeout = (s_state == State::kTransfer || s_state == State::kFinishOta)
|
||||
? kAckTimeoutMs : kHandshakeTimeoutMs;
|
||||
@@ -1062,11 +1090,13 @@ bool feedData(const uint8_t *data, uint16_t len) {
|
||||
void cancel() {
|
||||
if (s_state == State::kIdle) return;
|
||||
LOG_ERR("[MOTOR-OTA] CANCEL received in state %s\r\n", stepName(s_state));
|
||||
/* ★ kFinished/kError 时电机已跳转 APP 或已中止,绝不能再发 CAN 取消帧!
|
||||
/* ★ kFinished/kError/kWaitAppRun 时电机已跳转 APP 或已中止,绝不能再发 CAN 取消帧!
|
||||
* kOtaCancelId=0x00000000 与电机 APP 的 CANFD_BOOT_ID(base_id=0x0000) 冲突,
|
||||
* 电机 APP 收到会当作 BOOT 命令 → enter_silent_mode 10s,APP 看似卡死。
|
||||
* kWaitAppRun = 尾包已确认、电机已复位跳 APP,同样不能发。
|
||||
* 仅在活跃传输态(kPreCancel..kFinishOta)发取消帧通知电机 BOOT 中止。 */
|
||||
const bool was_active = (s_state != State::kFinished && s_state != State::kError);
|
||||
const bool was_active = (s_state != State::kFinished && s_state != State::kError &&
|
||||
s_state != State::kWaitAppRun);
|
||||
if (was_active) {
|
||||
canSend(kOtaCancelId, nullptr, 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user