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:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user