/** * @file motor_state.h * @brief 多电机状态表 — CAN STATUS 帧识别、内存缓存、SPI 推送序列化。 * * 设计要点: * - 仅在主循环上下文 (taskCanRxParse / taskHeartbeat / pushStep) 中读写, * 无 ISR 竞态,不需要临界区。 * - GD32 不做浮点解码,只缓存原始 32B STATUS payload,由前端按 DBC 解码。 * - 离线判定:3s 无 STATUS 帧 → online=0。 */ #ifndef DEVICE_MOTOR_STATE_H #define DEVICE_MOTOR_STATE_H #include #include #include "sys/config.h" /* ── CAN ID 匹配规则 (29-bit 扩展帧) ── * 物理 ID = 0x000N_TT_SS * N = 节点号 (1-9), 位于 bits[19:16] * TT = 消息类型: 01=STATUS, 02=CMD, 03=CONFIG * SS = 子类型 * STATUS = 0x000N0100 */ static constexpr uint32_t kMotorStatusIdMask = 0xFFF0FFFFU; /* 屏蔽 node nibble [19:16] */ static constexpr uint32_t kMotorStatusIdMatch = 0x00000100U; /* STATUS 类型码 */ static constexpr uint32_t kMotorConfigIdMatch = 0x00000101U; /* CONFIG 类型码 (Motor→Host, DLC 24) */ 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 = 48U; /* v0.74:STATUS 帧 DLC 48(字节 32~35 = InitStatus) */ static constexpr uint8_t kMotorConfigDlc = 24U; /* CONFIG 帧 DLC */ 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)── * 电机在 Bootloader IDLE 每 ~10ms 发一次 0x00000103(固定 ID,不带节点号)。 * 网页据此显示一个独立 BOOT 状态(不归属任何 Nx)。 */ static constexpr uint32_t kOtaBootHeartbeatId = 0x00000103U; static constexpr uint32_t kBootHeartbeatTimeoutSec = 3U; /* >3s 无 0x103 → 视为退出 BOOT */ static constexpr uint8_t kMotorActiveBootBit = 0x80U; /* 0xC5 page0 header active 字节 bit7 = boot_active */ /* ── 序列化常量 (0xC5 payload) ── */ static constexpr uint8_t kMotorEntrySize = 92U; /* 1+1+1+1+4+4+48+4+4+24 (STATUS+CONFIG) */ static constexpr uint8_t kMotorPageHeaderLen = 4U; /* page+total+count+active */ static constexpr uint8_t kMotorEntriesPerPage = 2U; /* (236-4)/92 = 2.52 → 2 */ static constexpr uint8_t kMotorTotalPages = (kMotorSlotCount + kMotorEntriesPerPage - 1U) / kMotorEntriesPerPage; /* ceil(27/2) = 14 */ /// 单电机状态槽 struct MotorSlot { uint8_t node_id; ///< 1-9, 0=空槽 uint8_t channel; ///< 最后收到帧的 CAN 通道 (0/1/2) uint8_t online; ///< 1=在线, 0=离线 uint8_t dlc; ///< 最后帧 DLC (正常=48) uint32_t rx_count; ///< 累计收到 STATUS 帧数 uint32_t last_rx_sec; ///< 最后收到帧时的 g_app_uptime_sec uint32_t seq; ///< 更新序号 (每次刷新 +1) uint8_t data[kMotorStatusDlc]; ///< 原始 STATUS payload uint32_t config_rx_count; ///< 累计收到 CONFIG 帧数 uint32_t config_seq; ///< CONFIG 更新序号 uint8_t config_data[kMotorConfigDlc]; ///< 原始 CONFIG payload (目标值回报) /* STATUS 写侧 seqlock(1.6.5):奇数=正在写本槽的 seq/data。 * 只有主循环(onCanFrame 的 STATUS 分支)自增,ISR 只读 —— 单核下消费者是 * ISR,它执行期间生产者被冻结,故"读到偶数 ⇒ 生产者不在这段里"成立。 * 放结构体末尾,已有字段偏移全不变(SPI 序列化/PDO 布局照旧)。 */ volatile uint8_t wr_seq; }; /// 全局电机状态表 struct MotorStateTable { MotorSlot slots[kMotorSlotCount]; ///< 按 (node_id-1)*kMotorChannels+channel 索引 uint32_t global_seq; ///< 全局刷新序号 uint32_t last_push_sec; ///< 上次推送完成时间 uint8_t active_count; ///< 当前在线电机数 uint32_t boot_hb_sec; ///< 最后收到 0x103 BOOT 心跳时的 uptime(0=从未) uint8_t boot_channel; ///< 最后 0x103 所在 CAN 通道 uint8_t boot_active; ///< 1=最近 kBootHeartbeatTimeoutSec 内有 0x103 }; /// 电机状态管理命名空间 namespace MotorState { /// 获取全局状态表引用 MotorStateTable &table(); /// CAN 帧到达时调用 (主循环上下文, taskCanRxParse 内) void onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len); /// 1Hz 离线检测 (taskHeartbeat 内) void checkOffline(); /// 序列化一页电机状态到 payload 缓冲区 /// @return 实际写入字节数, 0=参数错误 uint16_t buildPayload(uint8_t page, uint8_t *buf, uint16_t maxLen); /// 设置 0xC5 主动推送周期(毫秒,钳制到 [kMotorPeriodMinMs, kMotorPeriodMaxMs])。 /// @return 实际应用的毫秒值。 uint16_t setPushPeriodMs(uint16_t ms); } // namespace MotorState #endif /* DEVICE_MOTOR_STATE_H */