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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user