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
+167 -8
View File
@@ -1,4 +1,4 @@
/**
/**
* @file canfd.cpp
* @brief CANFD 通信模块实现。
*/
@@ -8,6 +8,7 @@
#include "sys/config.h"
#include "sys/critical.h"
#include "sys/task_manager.h"
#include <cstring>
namespace {
struct TxFrame {
@@ -49,6 +50,11 @@ constexpr uint32_t kCanRxParseTaskMask = (1UL << TASK_CAN_RX_PARSE);
CanFd::Diag CanFd::s_diag{};
CanFd::RxQueue CanFd::s_rxQueues[3]{};
uint8_t CanFd::s_nextConsumeChannel = 0U;
volatile CanFd::Mode CanFd::s_mode = CanFd::Mode::kModeAllPass;
CanFd::ChannelStats CanFd::s_channelStats[3]{};
CanFd::ChannelFeedback CanFd::s_feedback[3]{};
uint32_t CanFd::s_feedbackSeq = 0U;
CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{};
static TxQueue s_txQueues[3]{};
/**
@@ -77,20 +83,30 @@ void CanFd::initGpio()
* @brief 为指定 CAN 控制器配置接收过滤器。
* @param[in] canPeriph 目标 CAN 外设实例。
* @param[in] filterNumber 过滤器编号。
* @param[in] mode 目标接收过滤模式。
* @param[out] 无。
* @retval 无返回值。
* @note 当前默认按 `CONFIG_CAN_RX_FILTER_ID`/`CONFIG_CAN_RX_FILTER_MASK` 对标准帧做掩码匹配,并统一进入 FIFO0。
* @note kModeAllPass 模式下使用 32 位掩码模式且 mask=0/list=0,硬件直接放行所有标准/扩展帧到 FIFO0;
* kModeFilterId 模式下按 CONFIG_CAN_RX_FILTER_ID/MASK 做标准帧掩码匹配。
*/
void CanFd::configureRxFilter(uint32_t canPeriph, uint8_t filterNumber)
void CanFd::configureRxFilter(uint32_t canPeriph, uint8_t filterNumber, Mode mode)
{
can_filter_parameter_struct can_filter{};
can_filter.filter_number = filterNumber;
can_filter.filter_mode = CAN_FILTERMODE_MASK;
can_filter.filter_bits = CAN_FILTERBITS_32BIT;
can_filter.filter_list_high = static_cast<uint16_t>((CONFIG_CAN_RX_FILTER_ID & 0x7FFU) << 5U);
can_filter.filter_list_low = 0x0000U;
can_filter.filter_mask_high = static_cast<uint16_t>((CONFIG_CAN_RX_FILTER_MASK & 0x7FFU) << 5U);
can_filter.filter_mask_low = static_cast<uint16_t>(1U << 2U);
if (mode == Mode::kModeAllPass) {
/* 全部接收:列表与掩码都为 0,硬件不再对 ID 做比较。 */
can_filter.filter_list_high = 0x0000U;
can_filter.filter_list_low = 0x0000U;
can_filter.filter_mask_high = 0x0000U;
can_filter.filter_mask_low = 0x0000U;
} else {
can_filter.filter_list_high = static_cast<uint16_t>((CONFIG_CAN_RX_FILTER_ID & 0x7FFU) << 5U);
can_filter.filter_list_low = 0x0000U;
can_filter.filter_mask_high = static_cast<uint16_t>((CONFIG_CAN_RX_FILTER_MASK & 0x7FFU) << 5U);
can_filter.filter_mask_low = static_cast<uint16_t>(1U << 2U);
}
can_filter.filter_fifo_number = CAN_FIFO0;
can_filter.filter_enable = ENABLE;
can_filter_init(canPeriph, &can_filter);
@@ -204,6 +220,7 @@ bool CanFd::enqueueTxFrame(uint8_t channelIndex, const StatusPayload *status)
auto &queue = s_txQueues[channelIndex];
if(queue.count >= kTxQueueDepth) {
s_diag.tx_drop_count++;
s_channelStats[channelIndex].error_count++;
return false;
}
@@ -245,6 +262,12 @@ void CanFd::init()
{
s_diag = {};
s_nextConsumeChannel = 0U;
s_mode = Mode::kModeAllPass;
for (size_t i = 0; i < kChannelCount; ++i) {
s_channelStats[i] = {};
s_feedback[i] = {};
}
s_feedbackSeq = 0U;
initGpio();
@@ -255,7 +278,7 @@ void CanFd::init()
can_deinit(ch.canPeriph);
initNominalTiming(ch.canPeriph);
initFdMode(ch.canPeriph);
configureRxFilter(ch.canPeriph, ch.filterNumber);
configureRxFilter(ch.canPeriph, ch.filterNumber, s_mode);
can_interrupt_enable(ch.canPeriph, CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0);
nvic_irq_enable(ch.rx0Irq, CONFIG_IRQ_PREEMPT_CAN, static_cast<uint8_t>(i));
@@ -270,6 +293,29 @@ void CanFd::init()
s_nextConsumeChannel = 0U;
}
/**
* @brief 切换 CANFD 接收过滤模式。
* @param[in] mode 目标过滤模式。
* @note 切换时逐路重新配置过滤器;can_filter_init 内部会清 FCTL_FLD,无需手动进入 INITIAL 模式。
*/
void CanFd::setMode(Mode mode)
{
uint32_t save = RT_CRITICAL_SAVE();
s_mode = mode;
for (size_t i = 0; i < kChannelCount; ++i) {
configureRxFilter(kChannels[i].canPeriph, kChannels[i].filterNumber, mode);
}
RT_CRITICAL_RESTORE(save);
}
/**
* @brief 查询当前 CANFD 接收过滤模式。
*/
CanFd::Mode CanFd::mode()
{
return s_mode;
}
/**
* @brief 执行兼容保留的 CAN 轮询入口。
* @param[in] 无。
@@ -334,10 +380,12 @@ void CanFd::sendPrepared()
s_diag.last_payload_length = frame.tx_message.tx_dlen;
s_diag.last_tx_id = frame.tx_message.tx_sfid;
s_diag.last_tx_channel = static_cast<uint8_t>(i);
s_channelStats[i].tx_count++;
queue.head = static_cast<uint8_t>((queue.head + 1U) % kTxQueueDepth);
queue.count--;
} else {
s_diag.error_count++;
s_channelStats[i].error_count++;
}
}
}
@@ -464,9 +512,11 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
s_diag.last_tx_id = ext_id;
s_diag.last_tx_channel = channelIndex;
s_diag.last_payload_length = dlc;
s_channelStats[channelIndex].tx_count++;
return true;
}
s_diag.error_count++;
s_channelStats[channelIndex].error_count++;
return false;
}
@@ -546,9 +596,71 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex)
s_diag.last_rx_esi = rx_message.fd_esi;
s_diag.last_rx_id = (rx_message.rx_ff == CAN_FF_STANDARD) ? rx_message.rx_sfid : rx_message.rx_efid;
/* 维护每通道独立计数与最近一帧快照,用于 1Hz 反馈给 ESP32。 */
auto &stats = s_channelStats[channelIndex];
stats.rx_count++;
stats.last_rx_id = s_diag.last_rx_id;
stats.last_rx_dlc = rx_message.rx_dlen;
stats.last_rx_fd_flag = rx_message.fd_flag;
stats.last_rx_brs = rx_message.fd_brs;
stats.last_rx_esi = rx_message.fd_esi;
const uint8_t copyLen = (rx_message.rx_dlen < CANFD_FEEDBACK_LAST_DATA_MAX)
? rx_message.rx_dlen : static_cast<uint8_t>(CANFD_FEEDBACK_LAST_DATA_MAX);
for (uint8_t i = 0U; i < copyLen; ++i) {
stats.last_rx_data[i] = rx_message.rx_data[i];
}
for (uint8_t i = copyLen; i < CANFD_FEEDBACK_LAST_DATA_MAX; ++i) {
stats.last_rx_data[i] = 0U;
}
/* 维护每通道不重复 ID 快照表,用于 1Hz 分页拉取给 ESP32。 */
{
auto &table = s_idSnapshot[channelIndex];
const uint32_t rxId = s_diag.last_rx_id;
/* 查找已有条目 */
uint8_t found = CANFD_ID_SNAPSHOT_MAX;
for (uint8_t si = 0U; si < table.count; ++si) {
if (table.entries[si].occupied && table.entries[si].id == rxId) {
found = si;
break;
}
}
if (found >= CANFD_ID_SNAPSHOT_MAX) {
/* 未找到:表未满则新建,满了淘汰最旧(index 0) */
if (table.count < CANFD_ID_SNAPSHOT_MAX) {
found = table.count;
table.count++;
} else {
found = 0U;
/* 淘汰后整体左移一格让新条目在末尾 */
for (uint8_t si = 0U; si < CANFD_ID_SNAPSHOT_MAX - 1U; ++si) {
table.entries[si] = table.entries[si + 1U];
}
found = CANFD_ID_SNAPSHOT_MAX - 1U;
}
table.entries[found] = {};
table.entries[found].id = rxId;
table.entries[found].occupied = true;
}
auto &entry = table.entries[found];
entry.rx_count++;
entry.dlc = rx_message.rx_dlen;
entry.fd_flag = rx_message.fd_flag;
entry.brs = rx_message.fd_brs;
entry.esi = rx_message.fd_esi;
const uint8_t snapCopy = (rx_message.rx_dlen < CANFD_ID_SNAPSHOT_DATA_MAX)
? rx_message.rx_dlen : static_cast<uint8_t>(CANFD_ID_SNAPSHOT_DATA_MAX);
for (uint8_t si = 0U; si < snapCopy; ++si) {
entry.data[si] = rx_message.rx_data[si];
}
for (uint8_t si = snapCopy; si < CANFD_ID_SNAPSHOT_DATA_MAX; ++si) {
entry.data[si] = 0U;
}
}
auto &queue = s_rxQueues[channelIndex];
if(queue.count >= kRxQueueDepth) {
s_diag.rx_drop_count++;
stats.rx_drop_count++;
continue;
}
@@ -587,6 +699,53 @@ void CanFd::onRx0Interrupt()
handleRxInterrupt(0U);
}
/**
* @brief 刷新三路 CAN 通道的 1Hz 反馈快照。
* @note 在临界区内拷贝实时计数与最近一帧信息到 s_feedback;保证 SPI 命令读到一致快照。
* 典型调用频率 1Hz,由任务调度器在心跳节拍触发。
*/
void CanFd::refreshFeedbackSnapshot()
{
uint32_t save = RT_CRITICAL_SAVE();
s_feedbackSeq++;
for (size_t i = 0; i < kChannelCount; ++i) {
s_feedback[i].snapshot_seq = s_feedbackSeq;
s_feedback[i].stats = s_channelStats[i];
s_idSnapshot[i].seq = s_feedbackSeq;
}
RT_CRITICAL_RESTORE(save);
}
/**
* @brief 拷贝指定通道的最新反馈快照。
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
* @param[out] out 目标快照缓冲;为空或通道非法时返回 false。
* @retval bool true 表示成功拷贝。
*/
/**
* @brief 拷贝指定通道的不重复 ID 快照表。
*/
bool CanFd::copyIdSnapshotTable(uint8_t channelIndex, IdSnapshotTable *out)
{
if (out == nullptr || channelIndex >= kChannelCount) {
return false;
}
uint32_t save = RT_CRITICAL_SAVE();
*out = s_idSnapshot[channelIndex];
RT_CRITICAL_RESTORE(save);
return true;
}
bool CanFd::copyFeedbackSnapshot(uint8_t channelIndex, ChannelFeedback *out)
{
if (out == nullptr || channelIndex >= kChannelCount) {
return false;
}
uint32_t save = RT_CRITICAL_SAVE();
*out = s_feedback[channelIndex];
RT_CRITICAL_RESTORE(save);
return true;
}
/* ----- 中断入口 C 包装 ------------------------------------------------- */
/**
* @brief CAN0 RX0 中断向量入口。