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:
@@ -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 中断向量入口。
|
||||
|
||||
Reference in New Issue
Block a user