Files
DCU_APP/GD32E508_DCU_APP/device/motor_state.cpp
T
cbb a2d0b28b3d feat(project): 更新项目配置并增加ESC EEPROM功能
- 添加新的SPI命令文件:app_spi_cmd_can_burst.cpp 和 app_spi_cmd_exmc_diag.cpp
- 移除旧的SPI命令文件:app_spi_cmd_ota_wait.cpp, app_spi_cmd_ping.cpp,
  app_spi_cmd_can_bridge.cpp, app_spi_cmd_can_feedback.cpp,
  app_spi_cmd_id_snapshot.cpp, app_spi_cmd_adc_feedback.cpp,
  app_spi_cmd_motor_state.cpp, app_spi_cmd_motor_version.cpp,
  app_spi_cmd_motor_ota.cpp
- 新增SSC EtherCAT从站协议栈相关文件组,包含头文件和源文件
- 添加ESC EEPROM操作相关的头文件和源文件
- 修正HBI寄存器EXMC偏移地址映射错误
- 实现8位、16位、32位ESC寄存器读写函数
- 改进EEPROM初始化逻辑,添加重试机制和诊断功能
2026-08-09 03:27:21 +08:00

159 lines
5.1 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.cpp
* @brief 多电机状态表实现 — CAN STATUS 帧识别、内存缓存、SPI 推送序列化。
*
* 所有函数仅在主循环上下文调用(taskCanRxParse / taskHeartbeat / pushStep),
* 无 ISR 竞态,不需要临界区保护。
*/
#include "device/motor_state.h"
#include "device/spi/app_spi_command_defs.h"
#include "main.h"
#include <cstring>
/* ── 全局状态表实例 ── */
static MotorStateTable s_motorTable = {};
namespace MotorState {
MotorStateTable &table()
{
return s_motorTable;
}
void onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len)
{
/* BOOT 心跳 0x103(固定 ID,不带节点号):电机在 Bootloader IDLE 每 ~10ms 发一次。
* 记录最近收到时刻与通道,供网页显示独立 BOOT 状态(不归属任何 Nx)。
* 注意:必须在 STATUS 掩码过滤之前拦截,否则 0x103 会被下面的掩码判断丢弃。 */
if (id == kOtaBootHeartbeatId) {
s_motorTable.boot_hb_sec = g_app_uptime_sec;
s_motorTable.boot_channel = channel;
s_motorTable.boot_active = 1U;
/* 0x103 仅更新 boot_active 状态,不再串口打印(高频心跳会刷屏)。 */
return;
}
/* 掩码匹配 STATUS ID: 0x000N0100 */
if ((id & kMotorStatusIdMask) != kMotorStatusIdMatch) {
return;
}
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
return;
}
/* node×channel 双键槽位:同节点号在不同通道各自独立缓存,互不覆盖 */
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
slot.node_id = node;
slot.channel = channel;
slot.online = 1U;
slot.dlc = (len <= kMotorStatusDlc) ? len : kMotorStatusDlc;
slot.rx_count++;
slot.last_rx_sec = g_app_uptime_sec;
slot.seq = ++s_motorTable.global_seq;
/* 拷贝原始 payload */
std::memcpy(slot.data, data, slot.dlc);
if (slot.dlc < kMotorStatusDlc) {
std::memset(&slot.data[slot.dlc], 0, kMotorStatusDlc - slot.dlc);
}
}
void checkOffline()
{
const uint32_t now = g_app_uptime_sec;
uint8_t active = 0U;
for (uint8_t i = 0U; i < kMotorSlotCount; ++i) {
MotorSlot &slot = s_motorTable.slots[i];
if (slot.node_id == 0U) {
continue; /* 空槽 */
}
if ((now - slot.last_rx_sec) >= kMotorOfflineSec) {
slot.online = 0U;
}
if (slot.online != 0U) {
active++;
}
}
s_motorTable.active_count = active;
/* BOOT 心跳超时:>kBootHeartbeatTimeoutSec 无 0x103 → 电机已退出 BOOT。 */
if (s_motorTable.boot_active != 0U &&
(now - s_motorTable.boot_hb_sec) >= kBootHeartbeatTimeoutSec) {
s_motorTable.boot_active = 0U;
}
}
uint16_t buildPayload(uint8_t page, uint8_t *buf, uint16_t maxLen)
{
if (page >= kMotorTotalPages || buf == nullptr) {
return 0U;
}
/* 需要的最小空间: header + 至少 1 entry */
const uint16_t needed = kMotorPageHeaderLen + kMotorEntrySize;
if (maxLen < needed) {
return 0U;
}
const uint8_t startIdx = static_cast<uint8_t>(page * kMotorEntriesPerPage);
uint8_t count = 0U;
/* Header (4B) */
buf[0] = page;
buf[1] = kMotorTotalPages;
buf[2] = 0U; /* entry_count, 稍后回填 */
/* bit[6:0]=在线电机数,bit7=BOOT 心跳(0x103)活跃标志(供网页显示独立 BOOT 状态)。
* active_count 上限 9,bit7 空闲可复用,旧解析方读到会多算 128,故 ESP32 侧需掩 0x7F。 */
buf[3] = static_cast<uint8_t>((s_motorTable.active_count & 0x7FU)
| (s_motorTable.boot_active ? kMotorActiveBootBit : 0U));
uint16_t offset = kMotorPageHeaderLen;
for (uint8_t i = 0U; i < kMotorEntriesPerPage; ++i) {
const uint8_t slotIdx = static_cast<uint8_t>(startIdx + i);
if (slotIdx >= kMotorSlotCount) {
break;
}
const MotorSlot &slot = s_motorTable.slots[slotIdx];
if (slot.node_id == 0U) {
continue; /* 跳过空槽 */
}
/* 检查剩余空间 */
if (offset + kMotorEntrySize > maxLen) {
break;
}
buf[offset + 0U] = slot.node_id;
buf[offset + 1U] = slot.channel;
buf[offset + 2U] = slot.online;
buf[offset + 3U] = slot.dlc;
writeU32Le(&buf[offset + 4U], slot.rx_count);
writeU32Le(&buf[offset + 8U], slot.seq);
std::memcpy(&buf[offset + 12U], slot.data, kMotorStatusDlc);
offset += kMotorEntrySize;
count++;
}
buf[2] = count;
return offset;
}
uint16_t setPushPeriodMs(uint16_t ms)
{
/* 2026-08 请求驱动改造:0xC5 主动推送已移除,周期设置不再生效。
* 仅钳制后回显,保留 0xFE 子命令协议兼容(ESP32 当前不发送)。 */
if (ms < kMotorPeriodMinMs) { ms = kMotorPeriodMinMs; }
if (ms > kMotorPeriodMaxMs) { ms = kMotorPeriodMaxMs; }
return ms;
}
} // namespace MotorState