Files
DCU_APP/GD32E508_DCU_APP/device/motor_state.h
T
cbb 0cd3d9bed2 fix(comm): 回退LAN9252批量读写优化并修复多个CAN-EtherCAT桥竞态问题
回退1.6.0引入的LAN9252 PRAM批量读写优化,因其AVAIL_CNT计数器对短读存在语义问题,
导致邮箱短读和探针传输时出现FIFO陈旧数据损坏问题。

同时修复EtherCAT-CAN桥中的多个竞态条件:
- 修复消费者在fillRxBatch中直接访问共享ring buffer导致的数据混淆问题
- 修复生产者-消费者间tail指针发布时机不当造成的槽位覆盖竞态
- 修复WIN窗口基址更新位置错误导致电机状态窗口被锁定的问题
- 修复buildInput中断上下文与主循环间的数据竞争和内存撕裂问题
- 添加STATUS帧过滤机制减少批量区压力并解决队列饱和问题

此外还增加了多个诊断计数器用于监控撕裂、溢出和拼接错误,并调整了
输入PDO段布局和大小参数以支持新的48字节STATUS格式。

这些修改解决了上位机观察到的帧标签错误、电机状态丢失和数据拼接问题。
2026-09-08 00:52:35 +08:00

113 lines
5.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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 <cstdint>
#include <cstring>
#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 */