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 中断向量入口。
|
||||
|
||||
@@ -40,11 +40,10 @@ void SchedulerTimer::configureTimer()
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 设置优先级分组,并为调度定时器中断向量配置抢占优先级与子优先级。
|
||||
* @note 优先级分组已前移至 AppInitializer::init() 最开头统一设置;此处仅为调度定时器中断向量配置抢占优先级与子优先级。
|
||||
*/
|
||||
void SchedulerTimer::configureNvic()
|
||||
{
|
||||
nvic_priority_group_set(CONFIG_NVIC_PRIORITY_GROUP);
|
||||
nvic_irq_enable(CONFIG_SCHED_TIMER_IRQn, CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO, CONFIG_SCHED_TIMER_IRQ_SUB_PRIO);
|
||||
}
|
||||
|
||||
|
||||
+129
-134
@@ -1,4 +1,4 @@
|
||||
#ifndef DEVICE_CANFD_H
|
||||
#ifndef DEVICE_CANFD_H
|
||||
#define DEVICE_CANFD_H
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -6,165 +6,155 @@
|
||||
/** @brief CANFD 状态报文的数据长度,单位字节。 */
|
||||
#define CANFD_STATUS_DLEN 12U
|
||||
|
||||
/** @brief 每通道反馈快照中保存的最近一帧最大数据长度,受 SPI 应答帧总长度约束。 */
|
||||
#define CANFD_FEEDBACK_LAST_DATA_MAX 32U
|
||||
|
||||
/** @brief 每通道不重复 ID 快照表最大条目数。 */
|
||||
#define CANFD_ID_SNAPSHOT_MAX 16U
|
||||
|
||||
/** @brief 快照条目中最大数据字节数(等于 CANFD DLC 上限)。 */
|
||||
#define CANFD_ID_SNAPSHOT_DATA_MAX 64U
|
||||
|
||||
/**
|
||||
* @brief CANFD 通信模块。
|
||||
* @note 负责 GPIO/模式控制、波特率配置、异步接收缓存与按需发送能力。
|
||||
*/
|
||||
class CanFd {
|
||||
public:
|
||||
/**
|
||||
* @brief CANFD 接收过滤模式枚举。
|
||||
* @note 默认模式为 kModeAllPass,即三路控制器关闭硬件 ID 过滤,全部接收。
|
||||
*/
|
||||
enum class Mode : uint8_t {
|
||||
kModeAllPass = 0U, /**< 全部接收模式:禁用硬件 ID/掩码匹配,所有 11/29 位帧都进入 FIFO0。 */
|
||||
kModeFilterId = 1U, /**< 过滤模式:按 CONFIG_CAN_RX_FILTER_ID/MASK 做标准帧掩码匹配。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief CANFD 运行诊断统计信息。
|
||||
* @note 记录发送、接收、错误计数以及最近一次收发报文的关键信息。
|
||||
*/
|
||||
struct Diag {
|
||||
uint32_t tx_count; /**< 成功发送的报文计数。 */
|
||||
uint32_t rx_count; /**< 成功接收的报文计数。 */
|
||||
uint32_t error_count; /**< 发送失败或检测到的错误计数。 */
|
||||
uint32_t rx_drop_count; /**< 软件接收队列满导致丢弃的报文计数。 */
|
||||
uint32_t rx_fifo_full_count; /**< 硬件 FIFO0 进入满状态的次数。 */
|
||||
uint32_t rx_fifo_overfull_count; /**< 硬件 FIFO0 发生 overfull 的次数。 */
|
||||
uint32_t tx_drop_count; /**< 发送 FIFO 满导致丢弃的报文计数。 */
|
||||
uint32_t tx_abort_count; /**< 因邮箱全占主动中止占用邮箱的次数(典型:总线无 ACK 导致自动重传卡死)。 */
|
||||
uint32_t last_rx_id; /**< 最近一次接收报文的标识符。 */
|
||||
uint32_t last_tx_id; /**< 最近一次发送报文的标识符。 */
|
||||
uint8_t last_rx_channel; /**< 最近一次接收报文所在的 CAN 通道号(0/1/2)。 */
|
||||
uint8_t last_tx_channel; /**< 最近一次发送报文所在的 CAN 通道号(0/1/2)。 */
|
||||
uint8_t last_payload_length; /**< 最近一次收发报文的数据长度。 */
|
||||
uint8_t last_rx_fd_flag; /**< 最近一次接收报文的 FD 帧标志。 */
|
||||
uint8_t last_rx_brs; /**< 最近一次接收报文的波特率切换标志。 */
|
||||
uint8_t last_rx_esi; /**< 最近一次接收报文的错误状态指示位。 */
|
||||
uint32_t tx_count;
|
||||
uint32_t rx_count;
|
||||
uint32_t error_count;
|
||||
uint32_t rx_drop_count;
|
||||
uint32_t rx_fifo_full_count;
|
||||
uint32_t rx_fifo_overfull_count;
|
||||
uint32_t tx_drop_count;
|
||||
uint32_t tx_abort_count;
|
||||
uint32_t last_rx_id;
|
||||
uint32_t last_tx_id;
|
||||
uint8_t last_rx_channel;
|
||||
uint8_t last_tx_channel;
|
||||
uint8_t last_payload_length;
|
||||
uint8_t last_rx_fd_flag;
|
||||
uint8_t last_rx_brs;
|
||||
uint8_t last_rx_esi;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 单通道运行计数(每路独立累计)。
|
||||
*/
|
||||
struct ChannelStats {
|
||||
uint32_t rx_count; /**< 该通道累计成功接收帧数。 */
|
||||
uint32_t tx_count; /**< 该通道累计成功发送帧数。 */
|
||||
uint32_t rx_drop_count; /**< 该通道累计软件丢帧数。 */
|
||||
uint32_t error_count; /**< 该通道累计错误计数(与发送邮箱失败合并)。 */
|
||||
uint32_t last_rx_id; /**< 该通道最近一帧报文标识符。 */
|
||||
uint8_t last_rx_dlc; /**< 该通道最近一帧报文数据长度。 */
|
||||
uint8_t last_rx_fd_flag; /**< 该通道最近一帧 FD 帧标志。 */
|
||||
uint8_t last_rx_brs; /**< 该通道最近一帧 BRS 标志。 */
|
||||
uint8_t last_rx_esi; /**< 该通道最近一帧 ESI 标志。 */
|
||||
uint8_t last_rx_data[CANFD_FEEDBACK_LAST_DATA_MAX];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 单通道 1Hz 反馈快照(每秒刷新一次)。
|
||||
*/
|
||||
struct ChannelFeedback {
|
||||
uint32_t snapshot_seq; /**< 第几次刷新(自启动起递增)。 */
|
||||
ChannelStats stats; /**< 抓拍时刻的该通道统计与最近一帧信息。 */
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief 单个不重复 ID 的最新帧快照。
|
||||
*/
|
||||
struct IdSnapshotEntry {
|
||||
uint32_t id; /**< CAN 报文标识符。 */
|
||||
uint32_t seq; /**< 该条目被更新的序号(自启动起递增)。 */
|
||||
uint32_t rx_count; /**< 该 ID 累计接收帧数。 */
|
||||
uint8_t dlc; /**< 最新一帧数据长度。 */
|
||||
uint8_t fd_flag; /**< 最新一帧 FD 标志。 */
|
||||
uint8_t brs; /**< 最新一帧 BRS 标志。 */
|
||||
uint8_t esi; /**< 最新一帧 ESI 标志。 */
|
||||
uint8_t data[CANFD_ID_SNAPSHOT_DATA_MAX]; /**< 最新一帧数据。 */
|
||||
bool occupied; /**< 条目是否已被占用。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 每通道不重复 ID 快照表(1Hz 刷新,SPI 命令分页拉取)。
|
||||
*/
|
||||
struct IdSnapshotTable {
|
||||
IdSnapshotEntry entries[CANFD_ID_SNAPSHOT_MAX];
|
||||
uint8_t count; /**< 当前占用条目数。 */
|
||||
uint32_t seq; /**< 全局更新序号(每次 refresh 时递增)。 */
|
||||
};
|
||||
/**
|
||||
* @brief CANFD 状态发送载荷。
|
||||
* @note 当前保持协议兼容,仅包含温度与供电相关字段;VIN_ADC 单独在诊断打印中输出。
|
||||
*/
|
||||
struct StatusPayload {
|
||||
uint16_t temp_raw; /**< 内部温度通道原始 ADC 数值。 */
|
||||
uint16_t vref_raw; /**< 内部参考电压通道原始 ADC 数值。 */
|
||||
float vdda; /**< 根据内部参考电压换算得到的 MCU 供电电压,单位 V。 */
|
||||
float temperature; /**< 根据温度通道换算得到的芯片温度,单位 ℃。 */
|
||||
uint16_t temp_raw;
|
||||
uint16_t vref_raw;
|
||||
float vdda;
|
||||
float temperature;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 初始化 CANFD 外设、GPIO、模式控制、过滤器与中断。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 初始化后默认进入 Normal 模式,并使能 3 路 CAN 控制器的 RX0 中断用于异步接收。
|
||||
*/
|
||||
static void init();
|
||||
|
||||
/**
|
||||
* @brief 兼容保留的轮询接口,中断模式下为空壳。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 实际接收已由 CAN0/CAN1/CAN2 的 RX0 中断驱动。
|
||||
* @brief 切换 CANFD 接收过滤模式。
|
||||
*/
|
||||
static void setMode(Mode mode);
|
||||
|
||||
/** @brief 查询当前 CANFD 接收过滤模式。 */
|
||||
static Mode mode();
|
||||
|
||||
static void poll();
|
||||
|
||||
/**
|
||||
* @brief 根据状态载荷准备一帧待发送的 CANFD 状态报文,并广播入队到 3 路发送 FIFO。
|
||||
* @param[in] status 指向 StatusPayload 的指针;为空时不入队。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 当前模块默认不会周期性调用该接口;仅在外部显式触发发送测试或业务发送时使用。
|
||||
*/
|
||||
static void prepareStatus(const StatusPayload *status);
|
||||
|
||||
/**
|
||||
* @brief 根据状态载荷仅向指定 CAN 通道的发送 FIFO 入队一帧状态报文。
|
||||
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
|
||||
* @param[in] status 指向 StatusPayload 的指针;为空时不入队。
|
||||
* @param[out] 无。
|
||||
* @retval bool true 表示成功入队,false 表示通道非法或该通道发送 FIFO 已满。
|
||||
* @note 当前模块默认不会周期性调用该接口;仅在外部显式触发指定通道发送时使用。
|
||||
*/
|
||||
static bool prepareStatusForChannel(uint8_t channelIndex, const StatusPayload *status);
|
||||
|
||||
/**
|
||||
* @brief 发送当前已准备好的 CANFD 状态报文。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 当前模块默认不会周期性调用该接口;仅发送调用方显式准备过的报文;若队列为空则直接返回。
|
||||
*/
|
||||
static void sendPrepared();
|
||||
|
||||
/**
|
||||
* @brief 查询 3 个 CAN 通道中是否存在尚未消费的接收报文。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval bool true 表示至少有一路存在待消费报文,false 表示当前所有通道缓存为空。
|
||||
*/
|
||||
static bool hasRxFrame();
|
||||
|
||||
/**
|
||||
* @brief 读取并消费最近一轮扫描到的某一路缓存接收帧。
|
||||
* @param[in] 无。
|
||||
* @param[out] id 输出报文标识符的指针;允许为空。
|
||||
* @param[out] data 输出报文数据缓存区指针;允许为空。
|
||||
* @param[out] length 输出报文数据长度的指针;允许为空。
|
||||
* @param[out] channel 输出报文来源的 CAN 通道号指针;允许为空。
|
||||
* @retval bool true 表示成功消费一帧报文,false 表示当前 3 路缓存均为空。
|
||||
* @note 每路通道使用轻量环形缓冲保留多帧;消费顺序采用轮转扫描,避免长期偏向固定通道。
|
||||
*/
|
||||
static bool consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel = nullptr);
|
||||
|
||||
/**
|
||||
* @brief 直接向指定 CAN 通道发送一帧任意数据报文(扩展帧)。
|
||||
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
|
||||
* @param[in] ext_id 扩展帧 ID(29 位)。
|
||||
* @param[in] data 数据负载指针。
|
||||
* @param[in] dlc 数据长度(1~64)。
|
||||
* @retval bool true 表示成功入队硬件邮箱,false 表示参数无效或邮箱已满。
|
||||
* @note 绕过软件发送队列,直接调用硬件发送;适用于 SPI 桥接等即时发送场景。
|
||||
*/
|
||||
static bool transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc);
|
||||
|
||||
/**
|
||||
* @brief 获取 CANFD 诊断统计信息只读指针。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval const Diag* 指向内部诊断统计结构体的只读指针。
|
||||
*/
|
||||
static const Diag *diag();
|
||||
|
||||
/**
|
||||
* @brief 将 TCAN1057A 收发器置为正常工作模式。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 模式控制脚按 STB 语义处理:低电平为 Normal,高电平为 Silent。
|
||||
* @brief 刷新三路 1Hz 反馈快照。
|
||||
* @note 通常由任务调度器在 1 秒节拍调用,将各通道实时计数与最近一帧信息抓拍到 s_feedback。
|
||||
*/
|
||||
static void refreshFeedbackSnapshot();
|
||||
|
||||
/**
|
||||
* @brief 拷贝指定通道的最新反馈快照到调用方缓冲。
|
||||
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
|
||||
* @param[out] out 目标快照缓冲;为空或通道非法时返回 false。
|
||||
* @retval bool true 表示成功拷贝,false 表示参数非法。
|
||||
*/
|
||||
static bool copyFeedbackSnapshot(uint8_t channelIndex, ChannelFeedback *out);
|
||||
|
||||
/**
|
||||
* @brief 拷贝指定通道的不重复 ID 快照表。
|
||||
* @param[in] channelIndex 目标通道 0/1/2。
|
||||
* @param[out] out 输出缓冲。
|
||||
* @retval true 成功。
|
||||
*/
|
||||
static bool copyIdSnapshotTable(uint8_t channelIndex, IdSnapshotTable *out);
|
||||
|
||||
static void setNormalMode();
|
||||
|
||||
/**
|
||||
* @brief 将 TCAN1057A 收发器置为静默模式。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 静默模式下收发器不驱动总线显性位,可用于总线监听或受控隔离。
|
||||
*/
|
||||
static void setSilentMode();
|
||||
|
||||
/**
|
||||
* @brief CAN0 兼容中断处理入口。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 兼容保留给 CAN0 中断包装使用;三路统一接收逻辑已收口到 handleRxInterrupt()。
|
||||
*/
|
||||
static void onRx0Interrupt();
|
||||
|
||||
/**
|
||||
* @brief 处理指定 CAN 通道的 RX0 中断接收。
|
||||
* @param[in] channelIndex CAN 通道索引(0/1/2)。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
*/
|
||||
static void handleRxInterrupt(uint8_t channelIndex);
|
||||
|
||||
private:
|
||||
@@ -184,25 +174,30 @@ private:
|
||||
volatile uint8_t count;
|
||||
};
|
||||
|
||||
/** @brief 初始化 CAN0/CAN1/CAN2 相关 GPIO。 */
|
||||
static void initGpio();
|
||||
/** @brief 为指定控制器配置接收过滤器。 */
|
||||
static void configureRxFilter(uint32_t canPeriph, uint8_t filterNumber);
|
||||
/** @brief 配置指定控制器的 CAN 仲裁段标称位时序。 */
|
||||
/**
|
||||
* @brief 根据当前模式为指定控制器配置接收过滤器。
|
||||
* @param[in] canPeriph 目标 CAN 外设实例。
|
||||
* @param[in] filterNumber 过滤器编号。
|
||||
* @param[in] mode 目标接收过滤模式。
|
||||
*/
|
||||
static void configureRxFilter(uint32_t canPeriph, uint8_t filterNumber, Mode mode);
|
||||
static void initNominalTiming(uint32_t canPeriph);
|
||||
/** @brief 配置指定控制器的 CAN FD 数据段位时序与 TDC。 */
|
||||
static void initFdMode(uint32_t canPeriph);
|
||||
/** @brief 构造内部发送缓冲中的状态帧内容。 */
|
||||
static void buildStatusFrame(const StatusPayload *status);
|
||||
/** @brief 向指定 CAN 通道的发送 FIFO 写入一帧状态报文。 */
|
||||
static bool enqueueTxFrame(uint8_t channelIndex, const StatusPayload *status);
|
||||
|
||||
static Diag s_diag;
|
||||
static RxQueue s_rxQueues[3];
|
||||
static uint8_t s_nextConsumeChannel;
|
||||
static Diag s_diag;
|
||||
static RxQueue s_rxQueues[3];
|
||||
static uint8_t s_nextConsumeChannel;
|
||||
static volatile Mode s_mode;
|
||||
static ChannelStats s_channelStats[3];
|
||||
static ChannelFeedback s_feedback[3];
|
||||
static uint32_t s_feedbackSeq;
|
||||
static IdSnapshotTable s_idSnapshot[3];
|
||||
};
|
||||
|
||||
using canfd_diag_t = CanFd::Diag;
|
||||
using canfd_status_payload_t = CanFd::StatusPayload;
|
||||
|
||||
#endif /* DEVICE_CANFD_H */
|
||||
#endif /* DEVICE_CANFD_H */
|
||||
@@ -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;
|
||||
|
||||
@@ -74,7 +74,7 @@ private:
|
||||
|
||||
static bool putCharNonBlocking(int ch);
|
||||
|
||||
static constexpr uint16_t kTxBufSize = 256U;
|
||||
static constexpr uint16_t kTxBufSize = 1024U;
|
||||
static volatile uint32_t s_dropCount;
|
||||
static uint8_t s_txBuf[kTxBufSize];
|
||||
static volatile uint16_t s_txHead;
|
||||
|
||||
Reference in New Issue
Block a user