feat(ethercat): 添加EtherCAT从站功能支持并优化同步机制

- 添加CAN反馈和ID快照SPI命令文件
- 移除EXMC组件依赖,精简项目配置
- 添加RAM函数段链接脚本,优化Flash等待周期抖动
- 实现EscExmcBus类,提供16位并行总线访问LAN9252
- 实现EscLan9252驱动,支持寄存器和PDI内存访问
- 实现EthercatSlave类,采用三缓冲机制进行PDO数据交换
- 实现EthercatSync同步管理,支持SYNC0/SYNC1中断和定时器模式
- 添加ISR执行时间统计和诊断功能
- 分离周期路径和事件路径通知机制,避免RMW竞态
This commit is contained in:
cbb
2026-06-29 01:51:02 +08:00
parent 1612e326ee
commit b171767023
31 changed files with 2177 additions and 290 deletions
@@ -0,0 +1,206 @@
/**
* @file app_spi_cmd_can_feedback.cpp
* @brief CAN 不重复 ID 快照表轮询与接收模式切换 SPI 命令处理器。
*
* 协议说明(与 ESP32 端 canfd_bridge 对接):
*
* 1) kTypeCanFeedback (0xC1):ESP32 主动轮询,分页返回不重复 ID 快照表。
* 请求 payload:
* [0] channel 0/1/2
* [1] page 页号(0 = 条目 0~7,1 = 条目 8~15)
* 应答 payload(变长,最大 kTxBufSize - overhead = 123 字节):
* [0] channel
* [1] page
* [2] total_count 该通道当前条目总数
* [3] entry_count 本页实际条目数
* [4] mode 当前 CAN 接收模式
* [5..8] snapshot_seq (LE)
* [9..12] rx_count (LE) 通道累计接收
* [13..16] tx_count (LE) 通道累计发送
* [17..20] drop_count (LE)
* [21..24] error_count (LE)
* [25..] 条目逐条拼接:
* id(4LE) + dlc(1) + flags(1) + reserved(2) + data(dlc)
* 每条 = 8 + dlc 字节
*
* 2) kTypeCanSetMode (0xC2):ESP32 主动切换三路 CAN 过滤模式。
* 请求 payload:
* [0] mode 0=全部接收, 1=按 ID 过滤;其它值返回 kRspError。
* 应答 payload:
* [0] 实际生效的 mode。
*/
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
#include "device/canfd.h"
#include "sys/config.h"
#include <cstdio>
#include <cstring>
namespace {
constexpr uint8_t kFeedbackChannelCount = 3U;
constexpr uint16_t kFeedbackPayloadLen = 123U; /* kTxBufSize=128 - overhead=5 */
constexpr uint8_t kPageEntries = 8U;
constexpr uint16_t kFixedHeaderLen = 25U; /* ch(1)+page(1)+total(1)+cnt(1)+mode(1)+seq(4)+rx(4)+tx(4)+drop(4)+err(4) */
void writeU32Le(uint8_t *dst, uint32_t value)
{
dst[0] = static_cast<uint8_t>(value & 0xFFU);
dst[1] = static_cast<uint8_t>((value >> 8) & 0xFFU);
dst[2] = static_cast<uint8_t>((value >> 16) & 0xFFU);
dst[3] = static_cast<uint8_t>((value >> 24) & 0xFFU);
}
class SpiCanFeedbackHandler final : public ISpiCommandHandler {
public:
bool canHandle(uint8_t frame_type) const override
{
return (frame_type == SpiDefs::kTypeCanFeedback)
|| (frame_type == SpiDefs::kTypeCanSetMode);
}
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
{
if (ctx.frame_type == SpiDefs::kTypeCanFeedback) {
handleFeedback(bus, ctx);
} else {
handleSetMode(bus, ctx);
}
}
private:
void handleFeedback(AppSpiCommand &bus, const SpiCommandContext &ctx)
{
if (ctx.payload_len < 2U) {
uint8_t err = 0xE0U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
const uint8_t channel = ctx.payload[0];
const uint8_t page = ctx.payload[1];
if (channel >= kFeedbackChannelCount) {
uint8_t err = 0xE1U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
/* 拷贝统计快照 */
CanFd::ChannelFeedback snap{};
CanFd::copyFeedbackSnapshot(channel, &snap);
/* 拷贝 ID 快照表 */
CanFd::IdSnapshotTable table{};
CanFd::copyIdSnapshotTable(channel, &table);
const uint8_t start = static_cast<uint8_t>(page * kPageEntries);
if (start >= CANFD_ID_SNAPSHOT_MAX) {
uint8_t err = 0xE3U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
uint8_t payload[kFeedbackPayloadLen] = {};
/* 固定头 */
payload[0] = channel;
payload[1] = page;
payload[2] = table.count;
/* payload[3] = entry_count 稍后填 */
payload[4] = static_cast<uint8_t>(CanFd::mode());
writeU32Le(&payload[5], snap.snapshot_seq);
writeU32Le(&payload[9], snap.stats.rx_count);
writeU32Le(&payload[13], snap.stats.tx_count);
writeU32Le(&payload[17], snap.stats.rx_drop_count);
writeU32Le(&payload[21], snap.stats.error_count);
/* 打包 ID 条目 */
uint16_t offset = kFixedHeaderLen;
uint8_t packed = 0U;
const uint8_t end = static_cast<uint8_t>(
(start + kPageEntries < CANFD_ID_SNAPSHOT_MAX)
? (start + kPageEntries) : CANFD_ID_SNAPSHOT_MAX);
for (uint8_t i = start; i < end; ++i) {
const auto &e = table.entries[i];
if (!e.occupied) continue;
const uint8_t dataLen = (e.dlc < CANFD_ID_SNAPSHOT_DATA_MAX)
? e.dlc : static_cast<uint8_t>(CANFD_ID_SNAPSHOT_DATA_MAX);
const uint16_t entrySize = static_cast<uint16_t>(8U + dataLen);
if (offset + entrySize > kFeedbackPayloadLen) break;
writeU32Le(&payload[offset], e.id);
payload[offset + 4U] = e.dlc;
payload[offset + 5U] = static_cast<uint8_t>(
(e.fd_flag ? 1U : 0U) | (e.brs ? 2U : 0U) | (e.esi ? 4U : 0U));
payload[offset + 6U] = 0U; /* reserved */
payload[offset + 7U] = 0U; /* reserved */
memcpy(&payload[offset + 8U], e.data, dataLen);
offset += entrySize;
packed++;
}
payload[3] = packed;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspAck, payload, offset);
}
void handleSetMode(AppSpiCommand &bus, const SpiCommandContext &ctx)
{
if (ctx.payload_len < 1U) {
uint8_t err = 0xE0U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
const uint8_t requested = ctx.payload[0];
CanFd::Mode target;
switch (requested) {
case 0U: target = CanFd::Mode::kModeAllPass; break;
case 1U: target = CanFd::Mode::kModeFilterId; break;
default: {
uint8_t err = 0xE1U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
}
CanFd::setMode(target);
const uint8_t actual = static_cast<uint8_t>(CanFd::mode());
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
char log_msg[AppSpiCommand::kLogBufSize] = {};
(void)snprintf(log_msg, sizeof(log_msg),
"CAN_MODE set=%u -> actual=%u",
static_cast<unsigned>(requested),
static_cast<unsigned>(actual));
bus.postLog(log_msg);
#endif
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspAck, &actual, 1U);
}
};
static SpiCanFeedbackHandler s_handler;
} // namespace
void registerSpiCanFeedbackCommand(AppSpiCommand &bus)
{
bus.registerHandler(&s_handler);
}
@@ -0,0 +1,135 @@
/**
* @file app_spi_cmd_id_snapshot.cpp
* @brief SPI 命令处理器:kTypeIdSnapshot (0xC3)。
*
* ESP32 分页拉取指定 CAN 通道的不重复 ID 快照表。
*
* 请求 payload:
* [0] channel 0/1/2
* [1] page 页号(0 = 条目 0~7,1 = 条目 8~15)
*
* 应答 payload(变长,最大 kTxBufSize - 5 = 123 字节):
* [0] channel
* [1] page
* [2] total_count 该通道当前条目总数
* [3] entry_count 本页实际条目数
* [4..] 条目逐条拼接:
* id(4LE) + seq(4LE) + rx_count(4LE) + dlc(1) + flags(1) + data(dlc)
* 每条 = 16 + dlc 字节
*/
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
#include "device/canfd.h"
#include <cstdio>
#include <cstring>
namespace {
constexpr uint8_t kPageEntries = 8U;
constexpr uint16_t kMaxPayloadLen = 123U; /* kTxBufSize=128 - overhead=5 */
inline void writeU32Le(uint8_t *dst, uint32_t val)
{
dst[0] = static_cast<uint8_t>(val & 0xFFU);
dst[1] = static_cast<uint8_t>((val >> 8) & 0xFFU);
dst[2] = static_cast<uint8_t>((val >> 16) & 0xFFU);
dst[3] = static_cast<uint8_t>((val >> 24) & 0xFFU);
}
class SpiIdSnapshotHandler final : public ISpiCommandHandler {
public:
bool canHandle(uint8_t frame_type) const override
{
return frame_type == SpiDefs::kTypeIdSnapshot;
}
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
{
if (ctx.payload_len < 2U) {
uint8_t err = 0xE0U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
const uint8_t channel = ctx.payload[0];
const uint8_t page = ctx.payload[1];
if (channel >= 3U) {
uint8_t err = 0xE1U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
CanFd::IdSnapshotTable table{};
if (!CanFd::copyIdSnapshotTable(channel, &table)) {
uint8_t err = 0xE2U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
const uint8_t start = static_cast<uint8_t>(page * kPageEntries);
if (start >= CANFD_ID_SNAPSHOT_MAX) {
uint8_t err = 0xE3U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
return;
}
uint8_t payload[kMaxPayloadLen] = {};
payload[0] = channel;
payload[1] = page;
payload[2] = table.count;
uint16_t offset = 4U; /* [0]=ch [1]=page [2]=total [3]=entry_count(后填) */
uint8_t packed = 0U;
const uint8_t end = static_cast<uint8_t>(
(start + kPageEntries < CANFD_ID_SNAPSHOT_MAX)
? (start + kPageEntries) : CANFD_ID_SNAPSHOT_MAX);
for (uint8_t i = start; i < end; ++i) {
const auto &e = table.entries[i];
if (!e.occupied) continue;
const uint8_t dataLen = (e.dlc < CANFD_ID_SNAPSHOT_DATA_MAX)
? e.dlc : static_cast<uint8_t>(CANFD_ID_SNAPSHOT_DATA_MAX);
const uint16_t entrySize = 16U + dataLen; /* 4+4+4+1+1+1+1 + data */
if (offset + entrySize > kMaxPayloadLen) break; /* 装不下就截断 */
writeU32Le(&payload[offset], e.id);
writeU32Le(&payload[offset + 4U], e.seq);
writeU32Le(&payload[offset + 8U], e.rx_count);
payload[offset + 12U] = e.dlc;
/* flags 字节: bit0=fd, bit1=brs, bit2=esi */
payload[offset + 13U] = static_cast<uint8_t>(
(e.fd_flag ? 1U : 0U) | (e.brs ? 2U : 0U) | (e.esi ? 4U : 0U));
/* 保留 2 字节对齐 */
payload[offset + 14U] = 0U;
payload[offset + 15U] = 0U;
memcpy(&payload[offset + 16U], e.data, dataLen);
offset += entrySize;
packed++;
}
payload[3] = packed;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspAck, payload, offset);
}
};
static SpiIdSnapshotHandler s_handler;
} // namespace
void registerSpiIdSnapshotCommand(AppSpiCommand &bus)
{
bus.registerHandler(&s_handler);
}
@@ -1,4 +1,4 @@
/**
/**
* @file app_spi_command.cpp
* @brief APP 运行期 SPI 链路与命令路由实现。
*
@@ -303,6 +303,8 @@ extern void registerSpiBootRequestCommand(AppSpiCommand &bus);
extern void registerSpiOtaWaitCommand(AppSpiCommand &bus);
extern void registerSpiAppPingCommand(AppSpiCommand &bus);
extern void registerSpiCanBridgeCommand(AppSpiCommand &bus);
extern void registerSpiCanFeedbackCommand(AppSpiCommand &bus);
extern void registerSpiIdSnapshotCommand(AppSpiCommand &bus);
AppSpiCommand AppSpiCommandBus;
void AppSpiCommand::init()
{
@@ -320,6 +322,8 @@ void AppSpiCommand::init()
registerSpiOtaWaitCommand(AppSpiCommandBus);
registerSpiAppPingCommand(AppSpiCommandBus);
registerSpiCanBridgeCommand(AppSpiCommandBus);
registerSpiCanFeedbackCommand(AppSpiCommandBus);
registerSpiIdSnapshotCommand(AppSpiCommandBus);
configureGpio();
configureSpi0();
@@ -58,7 +58,7 @@ public:
const uint8_t *payload, uint16_t payload_len);
static constexpr uint16_t kRxBufSize = 128U;
static constexpr uint16_t kTxBufSize = 64U;
static constexpr uint16_t kTxBufSize = 128U;
static constexpr uint16_t kLogBufSize = 256U;
private:
@@ -23,6 +23,9 @@ static constexpr uint8_t kTypeOtaCancel = 0x03U;
static constexpr uint8_t kTypeAppPing = 0xA1U;
static constexpr uint8_t kTypeBootReq = 0xB0U;
static constexpr uint8_t kTypeCanSend = 0xC0U;
static constexpr uint8_t kTypeCanFeedback = 0xC1U; /**< ESP32 -> GD32 轮询 CAN 反馈快照 */
static constexpr uint8_t kTypeCanSetMode = 0xC2U; /**< ESP32 -> GD32 切换 CAN 接收过滤模式 */
static constexpr uint8_t kTypeIdSnapshot = 0xC3U; /**< ESP32 -> GD32 拉取不重复 ID 快照表 */
/* 响应类型 */
static constexpr uint8_t kRspAck = 0x80U;