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);
if (node < 1U || node > kMotorMaxNodes) {
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
return;
}
MotorSlot &slot = s_motorTable.slots[node - 1U];
/* node×channel 双键槽位:同节点号在不同通道各自独立缓存,互不覆盖 */
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
slot.node_id = node;
slot.channel = channel;
@@ -66,7 +67,7 @@ void checkOffline()
const uint32_t now = g_app_uptime_sec;
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];
if (slot.node_id == 0U) {
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) {
const uint8_t slotIdx = static_cast<uint8_t>(startIdx + i);
if (slotIdx >= kMotorMaxNodes) {
if (slotIdx >= kMotorSlotCount) {
break;
}
@@ -145,4 +146,14 @@ uint16_t buildPayload(uint8_t page, uint8_t *buf, uint16_t maxLen)
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
+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 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 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)。 */
@@ -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 kMotorPageHeaderLen = 4U; /* page+total+count+active */
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 {
@@ -57,7 +68,7 @@ struct MotorSlot {
/// 全局电机状态表
struct MotorStateTable {
MotorSlot slots[kMotorMaxNodes]; ///< 按 node_id-1 索引
MotorSlot slots[kMotorSlotCount]; ///< 按 (node_id-1)*kMotorChannels+channel 索引
uint32_t global_seq; ///< 全局刷新序号
uint32_t last_push_sec; ///< 上次推送完成时间
uint8_t active_count; ///< 当前在线电机数
@@ -82,6 +93,10 @@ void checkOffline();
/// @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 */
@@ -9,8 +9,8 @@
* - 总开关:sys/config.h 中 CONFIG_MOTOR_STATE_ENABLE
*
* payload 格式 (每页):
* [0] page 页号 (0/1)
* [1] total_pages 总页数 (2)
* [0] page 页号 (0..kMotorTotalPages-1)
* [1] total_pages 总页数 (6,27 槽 = 9 node × 3 ch)
* [2] entry_count 本页有效电机数
* [3] active_count 全局在线电机数
* [4..] 每个 entry 44B:
@@ -47,6 +47,18 @@ public:
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) */
const uint8_t page = (ctx.payload_len >= 1U) ? ctx.payload[0] : 0U;