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:
cbb
2026-07-30 02:11:49 +08:00
parent 46ef19d882
commit e06f5eb9a9
8 changed files with 107 additions and 34 deletions
+15 -4
View File
@@ -40,11 +40,12 @@ void onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len)
} }
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU); const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
if (node < 1U || node > kMotorMaxNodes) { if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
return; return;
} }
MotorSlot &slot = s_motorTable.slots[node - 1U]; /* node×channel 双键槽位:同节点号在不同通道各自独立缓存,互不覆盖 */
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
slot.node_id = node; slot.node_id = node;
slot.channel = channel; slot.channel = channel;
@@ -66,7 +67,7 @@ void checkOffline()
const uint32_t now = g_app_uptime_sec; const uint32_t now = g_app_uptime_sec;
uint8_t active = 0U; uint8_t active = 0U;
for (uint8_t i = 0U; i < kMotorMaxNodes; ++i) { for (uint8_t i = 0U; i < kMotorSlotCount; ++i) {
MotorSlot &slot = s_motorTable.slots[i]; MotorSlot &slot = s_motorTable.slots[i];
if (slot.node_id == 0U) { if (slot.node_id == 0U) {
continue; /* 空槽 */ continue; /* 空槽 */
@@ -115,7 +116,7 @@ uint16_t buildPayload(uint8_t page, uint8_t *buf, uint16_t maxLen)
for (uint8_t i = 0U; i < kMotorEntriesPerPage; ++i) { for (uint8_t i = 0U; i < kMotorEntriesPerPage; ++i) {
const uint8_t slotIdx = static_cast<uint8_t>(startIdx + i); const uint8_t slotIdx = static_cast<uint8_t>(startIdx + i);
if (slotIdx >= kMotorMaxNodes) { if (slotIdx >= kMotorSlotCount) {
break; break;
} }
@@ -145,4 +146,14 @@ uint16_t buildPayload(uint8_t page, uint8_t *buf, uint16_t maxLen)
return offset; return offset;
} }
uint16_t setPushPeriodMs(uint16_t ms)
{
if (ms < kMotorPeriodMinMs) { ms = kMotorPeriodMinMs; }
if (ms > kMotorPeriodMaxMs) { ms = kMotorPeriodMaxMs; }
/* ms→tick:100us/tick,每 ms 10 tick。写 volatile uint16_t 在 Cortex-M 上为原子,
* tick100us(ISR) 读该值无擕裂风险。 */
g_scheduler.motor_state_period_ticks = static_cast<uint16_t>(ms * kMotorTicksPerMs);
return ms;
}
} // namespace MotorState } // namespace MotorState
+17 -2
View File
@@ -27,9 +27,19 @@ static constexpr uint32_t kMotorStatusIdMask = 0xFFF0FFFFU; /* 屏蔽 node nibb
static constexpr uint32_t kMotorStatusIdMatch = 0x00000100U; /* STATUS 类型码 */ static constexpr uint32_t kMotorStatusIdMatch = 0x00000100U; /* STATUS 类型码 */
static constexpr uint8_t kMotorMaxNodes = CONFIG_MOTOR_MAX_NODES; static constexpr uint8_t kMotorMaxNodes = CONFIG_MOTOR_MAX_NODES;
/* 同一节点号可能同时挂在多个 CAN 通道上(两台电机同号异线),
* 状态表按 node×channel 双键存储,各通道独立槽位互不覆盖。 */
static constexpr uint8_t kMotorChannels = 3U;
static constexpr uint8_t kMotorSlotCount = kMotorMaxNodes * kMotorChannels; /* 9×3 = 27 */
static constexpr uint8_t kMotorStatusDlc = 32U; static constexpr uint8_t kMotorStatusDlc = 32U;
static constexpr uint16_t kMotorOfflineSec = CONFIG_MOTOR_OFFLINE_MS / 1000U; static constexpr uint16_t kMotorOfflineSec = CONFIG_MOTOR_OFFLINE_MS / 1000U;
/* ── 0xC5 推送周期运行时可调(ESP32 经 0xC5 子命令 0xFE 下发)── */
static constexpr uint16_t kMotorPeriodMinMs = 50U; /* 最快 20Hz */
static constexpr uint16_t kMotorPeriodMaxMs = 5000U; /* 最慢 0.2Hz */
static constexpr uint16_t kMotorTicksPerMs = 10U; /* 100us/tick → 每 ms 10 tick */
static constexpr uint8_t kMotorSubCmdSetPeriod = 0xFEU; /* 0xC5 请求 payload[0]=0xFE:设推送周期 */
/* ── BOOT 心跳(0x103)── /* ── BOOT 心跳(0x103)──
* 电机在 Bootloader IDLE 每 ~10ms 发一次 0x00000103(固定 ID,不带节点号)。 * 电机在 Bootloader IDLE 每 ~10ms 发一次 0x00000103(固定 ID,不带节点号)。
* 网页据此显示一个独立 BOOT 状态(不归属任何 Nx)。 */ * 网页据此显示一个独立 BOOT 状态(不归属任何 Nx)。 */
@@ -41,7 +51,8 @@ static constexpr uint8_t kMotorActiveBootBit = 0x80U; /* 0xC5 page0 header
static constexpr uint8_t kMotorEntrySize = 44U; /* 1+1+1+1+4+4+32 */ static constexpr uint8_t kMotorEntrySize = 44U; /* 1+1+1+1+4+4+32 */
static constexpr uint8_t kMotorPageHeaderLen = 4U; /* page+total+count+active */ static constexpr uint8_t kMotorPageHeaderLen = 4U; /* page+total+count+active */
static constexpr uint8_t kMotorEntriesPerPage = 5U; /* (236-4)/44 = 5.27 → 5 */ static constexpr uint8_t kMotorEntriesPerPage = 5U; /* (236-4)/44 = 5.27 → 5 */
static constexpr uint8_t kMotorTotalPages = 2U; /* ceil(9/5) = 2 */ static constexpr uint8_t kMotorTotalPages =
(kMotorSlotCount + kMotorEntriesPerPage - 1U) / kMotorEntriesPerPage; /* ceil(27/5) = 6 */
/// 单电机状态槽 /// 单电机状态槽
struct MotorSlot { struct MotorSlot {
@@ -57,7 +68,7 @@ struct MotorSlot {
/// 全局电机状态表 /// 全局电机状态表
struct MotorStateTable { struct MotorStateTable {
MotorSlot slots[kMotorMaxNodes]; ///< 按 node_id-1 索引 MotorSlot slots[kMotorSlotCount]; ///< 按 (node_id-1)*kMotorChannels+channel 索引
uint32_t global_seq; ///< 全局刷新序号 uint32_t global_seq; ///< 全局刷新序号
uint32_t last_push_sec; ///< 上次推送完成时间 uint32_t last_push_sec; ///< 上次推送完成时间
uint8_t active_count; ///< 当前在线电机数 uint8_t active_count; ///< 当前在线电机数
@@ -82,6 +93,10 @@ void checkOffline();
/// @return 实际写入字节数, 0=参数错误 /// @return 实际写入字节数, 0=参数错误
uint16_t buildPayload(uint8_t page, uint8_t *buf, uint16_t maxLen); uint16_t buildPayload(uint8_t page, uint8_t *buf, uint16_t maxLen);
/// 设置 0xC5 主动推送周期(毫秒,钳制到 [kMotorPeriodMinMs, kMotorPeriodMaxMs])。
/// @return 实际应用的毫秒值。
uint16_t setPushPeriodMs(uint16_t ms);
} // namespace MotorState } // namespace MotorState
#endif /* DEVICE_MOTOR_STATE_H */ #endif /* DEVICE_MOTOR_STATE_H */
@@ -9,8 +9,8 @@
* - 总开关:sys/config.h 中 CONFIG_MOTOR_STATE_ENABLE * - 总开关:sys/config.h 中 CONFIG_MOTOR_STATE_ENABLE
* *
* payload 格式 (每页): * payload 格式 (每页):
* [0] page 页号 (0/1) * [0] page 页号 (0..kMotorTotalPages-1)
* [1] total_pages 总页数 (2) * [1] total_pages 总页数 (6,27 槽 = 9 node × 3 ch)
* [2] entry_count 本页有效电机数 * [2] entry_count 本页有效电机数
* [3] active_count 全局在线电机数 * [3] active_count 全局在线电机数
* [4..] 每个 entry 44B: * [4..] 每个 entry 44B:
@@ -47,6 +47,18 @@ public:
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
{ {
/* 子命令:payload[0]=0xFE 且 len>=3 → 设置 0xC5 推送周期(payload[1..2]=毫秒 LE)。
* 有效页号仅 0..kMotorTotalPages-1,0xFE 作子操作码不冲突。回 ACK 携实际应用毫秒 LE。 */
if (ctx.payload_len >= 3U && ctx.payload[0] == kMotorSubCmdSetPeriod) {
const uint16_t ms = static_cast<uint16_t>(ctx.payload[1] | (ctx.payload[2] << 8));
const uint16_t applied = MotorState::setPushPeriodMs(ms);
uint8_t ack[2] = { static_cast<uint8_t>(applied & 0xFFU),
static_cast<uint8_t>((applied >> 8) & 0xFFU) };
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspAck, ack, sizeof(ack));
return;
}
/* 请求 payload[0] = page (可选, 默认 0) */ /* 请求 payload[0] = page (可选, 默认 0) */
const uint8_t page = (ctx.payload_len >= 1U) ? ctx.payload[0] : 0U; const uint8_t page = (ctx.payload_len >= 1U) ? ctx.payload[0] : 0U;
+4 -1
View File
@@ -46,8 +46,11 @@ struct task_scheduler_t {
volatile uint16_t div_can_tx; volatile uint16_t div_can_tx;
/** @brief 调度分频计数:心跳(1s)。 */ /** @brief 调度分频计数:心跳(1s)。 */
volatile uint16_t div_heartbeat; volatile uint16_t div_heartbeat;
/** @brief 调度分频计数:多电机状态推送(250ms)。 */ /** @brief 调度分频计数:多电机状态推送(默认 250ms)。 */
volatile uint16_t div_motor_state; volatile uint16_t div_motor_state;
/** @brief 0xC5 推送周期(100us tick 数,运行时可由 ESP32 SPI 命令调整)。
* 默认 CONFIG_TASK_PERIOD_MOTOR_STATE_TICKS,范围 500(50ms)~50000(5000ms)。 */
volatile uint16_t motor_state_period_ticks;
/** @brief 调度分频计数:APP 状态/诊断打印(5s)。 */ /** @brief 调度分频计数:APP 状态/诊断打印(5s)。 */
volatile uint16_t div_diag_print; volatile uint16_t div_diag_print;
}; };
+3 -3
View File
@@ -68,8 +68,8 @@ constexpr uint16_t CONFIG_MOTOR_OFFLINE_MS = 3000U;
#define CONFIG_CAN_PERIODIC_TEST_TX_ENABLE 0 #define CONFIG_CAN_PERIODIC_TEST_TX_ENABLE 0
/** @brief CAN0/1/2 通道使能。CH1/CH2 未接设备,关闭避免持续 CAN 错误 + 占 SPI 带宽。 */ /** @brief CAN0/1/2 通道使能。CH1/CH2 未接设备,关闭避免持续 CAN 错误 + 占 SPI 带宽。 */
constexpr bool CONFIG_CAN_CH0_ENABLE = true; constexpr bool CONFIG_CAN_CH0_ENABLE = true;
constexpr bool CONFIG_CAN_CH1_ENABLE = false; constexpr bool CONFIG_CAN_CH1_ENABLE = true;
constexpr bool CONFIG_CAN_CH2_ENABLE = false; constexpr bool CONFIG_CAN_CH2_ENABLE = true;
/** @brief 主循环 WFI(main.cpp #if)。 */ /** @brief 主循环 WFI(main.cpp #if)。 */
#define CONFIG_MAIN_LOOP_WFI_ENABLE 0 #define CONFIG_MAIN_LOOP_WFI_ENABLE 0
/** @brief EtherCAT SYNC0 同步模式(#if)。 */ /** @brief EtherCAT SYNC0 同步模式(#if)。 */
@@ -115,7 +115,7 @@ constexpr bool CONFIG_CAN_CH2_ENABLE = false;
* ============================================================ */ * ============================================================ */
constexpr uint32_t APP_FW_VERSION_MAJOR = 1U; constexpr uint32_t APP_FW_VERSION_MAJOR = 1U;
constexpr uint32_t APP_FW_VERSION_MINOR = 5U; constexpr uint32_t APP_FW_VERSION_MINOR = 5U;
constexpr uint32_t APP_FW_VERSION_PATCH = 0U; /* 1.5.0:大版本:Flash瘦身-34.5%(-fno-rtti/异常+size优化+日志分级宏),启用HardFault诊断,新增尺寸护栏 */ constexpr uint32_t APP_FW_VERSION_PATCH = 4U; /* 1.5.4:0xC5 推送周期运行时可调(子命令 0xFE 设周期,50~5000ms) */
constexpr uint32_t APP_FW_VERSION_BUILD = 0U; constexpr uint32_t APP_FW_VERSION_BUILD = 0U;
+50 -20
View File
@@ -293,11 +293,14 @@ static constexpr uint8_t kAckMaxResends = 4U;
* 100ms 仅为电机内部状态转换留余量(BOOT 主循环~10ms)。 * 100ms 仅为电机内部状态转换留余量(BOOT 主循环~10ms)。
* 之前 500ms 导致电机 RECEIVING_FIRMWARE 超时(~2s)预算被压缩。 */ * 之前 500ms 导致电机 RECEIVING_FIRMWARE 超时(~2s)预算被压缩。 */
static constexpr uint32_t kEraseWaitMs = 100U; static constexpr uint32_t kEraseWaitMs = 100U;
/* 预取消等待(ms):发 CANCEL 后等电机 BOOT 主循环处理完(复位到 IDLE),再进握手。 /* 等新 APP 运行确认(ms):尾包 ACK 后电机复位跳 APP,新 APP 上电初始化完 CAN 会
* 电机 BOOT 主循环周期 ~1-10ms,100ms 留足余量。 */ * 自主发 [0A AC](刷写结束/APP 运行有效,见协议表 15/16 行)。
static constexpr uint32_t kBootSettleMs = 100U; /* 检测到0x103心跳后再等这么久,让电机APP残留[00FA]CAN帧先消耗完 * 复位+初始化实测 <1s,3s 留足余量;超时 = 新 APP 未启动 → error 0x25。 */
* 原 2000ms→800ms→100ms:0x103 出现即电机已在 BOOT IDLE, static constexpr uint32_t kAppRunTimeoutMs = 3000U;
* 残帧实测 <4 帧(~40ms),100ms 足够。 */ /* 残帧窗口 kBootSettleMs 已移除(2026-07-27):收到 0x103 即直接进握手。
* 旧担忧:电机 APP 跳转前 TX 队列残留 [00 FA] 帧延迟上总线被 BOOT catch-all 命中 → 0x05。
* 现行握手阶段对垃圾帧一律忽略,靠 kStepTimeoutMs=100ms 步进重发(≤2次)自愈,
* 窗口不再必要;若 0x05 复现优先怀疑此处。 */
/* 步骤名,用于“xxx 未收到”日志,让用户/上位机知道卡在协议哪一步。 */ /* 步骤名,用于“xxx 未收到”日志,让用户/上位机知道卡在协议哪一步。 */
static const char *stepName(State st) { 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::kErase: return "ERASE(0x101:fw_size)";
case State::kTransfer: return "TRANSFER(0x101:data)"; case State::kTransfer: return "TRANSFER(0x101:data)";
case State::kFinishOta: return "FINISH_OTA(0x100:0AAC)"; case State::kFinishOta: return "FINISH_OTA(0x100:0AAC)";
case State::kWaitAppRun: return "WAIT_APP_RUN(0x102:0AAC)";
default: return "?"; default: return "?";
} }
} }
@@ -572,19 +576,20 @@ static void processTransferAck(const uint8_t *data, uint8_t len) {
* 自行跳转 APP,不需要上位机再发 FINISH_OTA。 * 自行跳转 APP,不需要上位机再发 FINISH_OTA。
* 旧注释里"0xF0F0F0F0 表示等 FINISH_OTA"是错误假设——电机 BOOT 代码里根本 * 旧注释里"0xF0F0F0F0 表示等 FINISH_OTA"是错误假设——电机 BOOT 代码里根本
* 没有 0xF0F0F0F0,尾帧 ACK 就是 0xFFFFFFFF,发完即跳转。 * 没有 0xF0F0F0F0,尾帧 ACK 就是 0xFFFFFFFF,发完即跳转。
* 因此收到 0xFFFFFFFF 即表示电机已跳转 APP,relay 直接进 kFinished。 * 收到 0xFFFFFFFF = 电机已跳转,但不证明新 APP 能跑 → 进 kWaitAppRun
* 若再发 [0x0A,0xAC] 反而会被已运行的电机 APP 当作握手开始信号干扰。 */ * 等新 APP 上电自主发的 [0A AC] 运行确认(协议表 15/16 行),收到才 kFinished。
* 此阶段绝不能主动发 [0x0A,0xAC]——会被已运行的电机 APP 当作握手开始信号干扰。 */
if (next_pkt == 0xFFFFFFFFU) { if (next_pkt == 0xFFFFFFFFU) {
/* 末包已被电机确认并跳转:把进度钉到 total,便于 QUERY/网页识别 100%。 */ /* 末包已被电机确认:把进度钉到 total,便于 QUERY/网页识别 100%。 */
s_packets_acked = s_total_packets; s_packets_acked = s_total_packets;
s_last_acked_pkt = s_total_packets; s_last_acked_pkt = s_total_packets;
s_pkt_pending = false; 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)s_packets_acked, (unsigned)s_total_packets,
(unsigned long)s_dup_ack_count, (unsigned long)s_crc_err_count, (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)s_ack_resend_total, (unsigned long)s_fast_resend_total,
(unsigned long)SysTickTimer::getMs()); (unsigned long)SysTickTimer::getMs());
enterState(State::kFinished); enterState(State::kWaitAppRun);
pushStatus(); pushStatus();
return; return;
} }
@@ -742,15 +747,11 @@ void process() {
} }
/* kPreCancel:等待电机 BOOT 心跳(0x103)确认已进入 Bootloader。 /* kPreCancel:等待电机 BOOT 心跳(0x103)确认已进入 Bootloader。
* 电机 BOOT IDLE 每 ~10ms 发一次 0x103;收到即证明电机已在 BOOT IDLE。 * 电机 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_state == State::kPreCancel) {
if (s_boot_heartbeat_seen && stateTimedOut(kBootSettleMs)) { if (s_boot_heartbeat_seen) {
OTA_LOGI("[MOTOR-OTA] 0x103 ok, residue-win %ums passed, handshake\r\n", OTA_LOGI("[MOTOR-OTA] 0x103 ok, handshake\r\n");
(unsigned)kBootSettleMs);
enterState(State::kHandshakeReq); enterState(State::kHandshakeReq);
pushStatus(); pushStatus();
} else if (stateTimedOut(kHandshakeTimeoutMs)) { } else if (stateTimedOut(kHandshakeTimeoutMs)) {
@@ -760,6 +761,33 @@ void process() {
return; 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) const uint32_t timeout = (s_state == State::kTransfer || s_state == State::kFinishOta)
? kAckTimeoutMs : kHandshakeTimeoutMs; ? kAckTimeoutMs : kHandshakeTimeoutMs;
@@ -1062,11 +1090,13 @@ bool feedData(const uint8_t *data, uint16_t len) {
void cancel() { void cancel() {
if (s_state == State::kIdle) return; if (s_state == State::kIdle) return;
LOG_ERR("[MOTOR-OTA] CANCEL received in state %s\r\n", stepName(s_state)); 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) 冲突, * kOtaCancelId=0x00000000 与电机 APP 的 CANFD_BOOT_ID(base_id=0x0000) 冲突,
* 电机 APP 收到会当作 BOOT 命令 → enter_silent_mode 10s,APP 看似卡死。 * 电机 APP 收到会当作 BOOT 命令 → enter_silent_mode 10s,APP 看似卡死。
* kWaitAppRun = 尾包已确认、电机已复位跳 APP,同样不能发。
* 仅在活跃传输态(kPreCancel..kFinishOta)发取消帧通知电机 BOOT 中止。 */ * 仅在活跃传输态(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) { if (was_active) {
canSend(kOtaCancelId, nullptr, 0); canSend(kOtaCancelId, nullptr, 0);
} }
+2 -1
View File
@@ -41,7 +41,8 @@ enum class State : uint8_t {
kFinishOta, /* 收到 0xF0F0F0F0,已发 [0A AC],等 FINISH ACK */ kFinishOta, /* 收到 0xF0F0F0F0,已发 [0A AC],等 FINISH ACK */
kFinished, /* 完成 */ kFinished, /* 完成 */
kError, /* 错误 */ kError, /* 错误 */
kPreCancel = 9 /* 等待电机 BOOT 心跳(0x103)确认已进入 Bootloader,然后进 kHandshakeReq */ kPreCancel = 9, /* 等待电机 BOOT 心跳(0x103)确认已进入 Bootloader,然后进 kHandshakeReq */
kWaitAppRun = 10 /* 尾包 ACK 后电机已跳转,等新 APP 上电自主发的 [0A AC] 运行确认,收到才进 kFinished */
}; };
struct Status { struct Status {
+2 -1
View File
@@ -33,6 +33,7 @@ void TaskManager::init()
g_scheduler.div_can_tx = 0U; g_scheduler.div_can_tx = 0U;
g_scheduler.div_heartbeat = 0U; g_scheduler.div_heartbeat = 0U;
g_scheduler.div_motor_state = 0U; g_scheduler.div_motor_state = 0U;
g_scheduler.motor_state_period_ticks = CONFIG_TASK_PERIOD_MOTOR_STATE_TICKS;
g_scheduler.div_diag_print = 0U; g_scheduler.div_diag_print = 0U;
} }
@@ -108,7 +109,7 @@ void TaskManager::tick100us()
/* 多电机状态 0xC5 推送:250ms 独立节拍。 /* 多电机状态 0xC5 推送:250ms 独立节拍。
* 不挂在 1Hz heartbeat 上,避免连带加速 OTA Boot 轮询/CAN快照/ADC推送/checkOffline。 */ * 不挂在 1Hz heartbeat 上,避免连带加速 OTA Boot 轮询/CAN快照/ADC推送/checkOffline。 */
if(++g_scheduler.div_motor_state >= CONFIG_TASK_PERIOD_MOTOR_STATE_TICKS) { if(++g_scheduler.div_motor_state >= g_scheduler.motor_state_period_ticks) {
g_scheduler.div_motor_state = 0U; g_scheduler.div_motor_state = 0U;
set(1UL << TASK_MOTOR_STATE_PUSH); set(1UL << TASK_MOTOR_STATE_PUSH);
} }