- 新增ESC硬件复位序列(1.5.25):PB10驱动LAN9252 RST#,拉低30ms+释放后300ms轮询HBI就绪, 满足tpurstd/trstia时序;此前RST#仅靠R44上拉自然释放 - 修正EXMC 16-bit地址左移(1.5.27):HBI DATA偏移0x20000/0x30000→0x40000/0x60000; ADDSET 2→4 HCLK;片选更名NE1→NE0(LQFP100仅Region0) - 按LAN9252手册/AN1907修正EEPROM镜像(1.5.28):word1高字节0x00→0xCC(SYNC0/1 Map+Output, 否则SYNC0留LATCH态DC过程数据死)、word2=5000(脉宽50µs)、尾部补真实0xFF; gen_eeprom.py按ETG.1000.6重写;DCU_ESI.xml PDI 0x05→0x8D、RxPdo/TxPdo互换改4×64bit对齐 - 删除GD32侧EEPROM烧录路径(1.5.29):移除EscEeprom/esc_eeprom、eeprom_image.h及主循环 烧录块,ESI镜像改由主站ecat_flash_eeprom.py经ESC 0x0500刷写(162字全校验通过); HBI互斥补屏蔽EXTI2(SYNC1),防Sync1_Isr两步事务打断主循环HBI访问致错址 - DC SYNC0通路可观测(1.5.30/31):EthercatSync新增sync0/sync1/escirq三个volatile计数器, task_runner 5s按增量打印速率;SSC OD新增只读0x2000 SYNC Diag,主站SDO轮询网页显示 - AL通路诊断(1.5.32):ECAT_Main/SetALStatus打印主站AL请求与写回状态+错误码, task_runner新增5s [ECAT][HB]心跳(仅同步ISR激活时);dc_app.c导出EcatDbg_ReadAlDiag - 删除死代码EthercatSlave::periodicIsr三缓冲PDO桥,PDO交换由SSC完成(4×64bit=32B); EthercatSlave::init()恢复调用并以isBusReady()(BYTE_TEST)门控 - CANFD修复同通道突发只发3帧:transmitFrame新增allow_abort区分突发/软件队列路径, 邮箱满落入软件TX队列(深度13)由TX完成中断drainTxQueueIsr回灌;BRS重新启用(0→1) - motor_state新增CONFIG帧缓存(0x00000101, DLC 24)记录目标值回报,条目44B→76B、每页5→3 - SPI 0xA2诊断扩展action 20~24(ECAT_NRST拉低/拉高/浮空/回读);新增ESP32 EN自检打印 - 版本号更新至1.5.32
184 lines
6.2 KiB
C++
184 lines
6.2 KiB
C++
/**
|
||
* @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;
|
||
}
|
||
|
||
const uint32_t masked = id & kMotorStatusIdMask;
|
||
|
||
/* STATUS 帧 0x000N0100 (DLC 32, 实时值) */
|
||
if (masked == kMotorStatusIdMatch) {
|
||
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
|
||
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
|
||
return;
|
||
}
|
||
|
||
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;
|
||
|
||
std::memcpy(slot.data, data, slot.dlc);
|
||
if (slot.dlc < kMotorStatusDlc) {
|
||
std::memset(&slot.data[slot.dlc], 0, kMotorStatusDlc - slot.dlc);
|
||
}
|
||
return;
|
||
}
|
||
|
||
/* CONFIG 帧 0x000N0101 (DLC 24, 目标值回报) */
|
||
if (masked == kMotorConfigIdMatch) {
|
||
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
|
||
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
|
||
return;
|
||
}
|
||
|
||
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
|
||
|
||
/* CONFIG 帧不更新 online 状态(仅 STATUS 帧维护在线检测) */
|
||
slot.node_id = node; /* 确保槽位不空 */
|
||
slot.channel = channel;
|
||
slot.config_rx_count++;
|
||
slot.config_seq = ++s_motorTable.global_seq;
|
||
|
||
const uint8_t copyLen = (len <= kMotorConfigDlc) ? len : kMotorConfigDlc;
|
||
std::memcpy(slot.config_data, data, copyLen);
|
||
if (copyLen < kMotorConfigDlc) {
|
||
std::memset(&slot.config_data[copyLen], 0, kMotorConfigDlc - copyLen);
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
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);
|
||
writeU32Le(&buf[offset + 44U], slot.config_rx_count);
|
||
writeU32Le(&buf[offset + 48U], slot.config_seq);
|
||
std::memcpy(&buf[offset + 52U], slot.config_data, kMotorConfigDlc);
|
||
|
||
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
|