feat(canfd): 添加收发器静默模式支持和SPI命令接口

添加了CAN FD收发器静默模式功能,允许通过硬件引脚控制发送使能:
- 新增setTxSilent/isTxSilent接口控制TCAN1057收发器Silent模式
- 静默时驱动器关断但接收机继续工作,支持只听不发场景
- 添加SPI命令0xC8用于ESP32远程控制静默模式切换
- 在总线状态位图中增加bit5表示静默状态
- 新增canBusyWaitUs函数用于ISR安全的微秒延时

fix(esc_lan9252): 改进warm jump后LAN9252探测逻辑

修复warm jump导致的LAN9252初始化问题:
- 识别warm jump后芯片非POR状态(idx0!=0x1234)的情况
- 自动补发硬件复位对齐到POR状态后再进行正常探测
- 改进调试信息输出,区分POR状态和warm jump残值情况
- 修正失败路径的复位标志初始化逻辑

refactor(spi): 整理SPI命令文件结构

调整SPI命令相关文件的组织结构:
- 移动诊断相关文件到更合适的位置
- 重新整理SPI命令处理文件的分组
This commit is contained in:
cbb
2026-10-10 02:25:56 +08:00
parent a777be1cb4
commit f3d7fc0c30
13 changed files with 1247 additions and 105 deletions
+86 -4
View File
@@ -19,6 +19,27 @@ static inline void spinDelay(uint32_t loops) {
for (volatile uint32_t d = 0; d < loops; ++d) { }
}
/** @brief DWT CYCCNT 忙等微秒(ISR 安全)。
* @note setTxSilent 退 Silent 时要等收发器 tMODE=20µs;本函数可能在 SPI CS
* EXTI ISR 内被 0xC8 handler 调用,而 SysTickTimer::delayMs 依赖 SysTick
* 中断计数,在该上下文会被冻死(同 esc_lan9252.cpp 的 pmBusyWaitUs)。
* **不得复位 CYCCNT**:cpu_load / ethercat_diff_stats / SSC 软件定时器都拿它
* 当时间基,清零会让它们的窗口计算瞬时报错。只在未使能时补使能位。 */
static inline void canBusyWaitUs(uint32_t us)
{
if ((DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk) == 0U) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
const uint32_t start = DWT->CYCCNT;
const uint32_t cycles = us * (SystemCoreClock / 1000000U);
/* 有界自旋:DWT 因故没在跑时不能变成无限等(最多多转 ~1ms 的圈数) */
const uint32_t spin_cap = cycles + (SystemCoreClock / 1000U);
for (uint32_t i = 0U; i < spin_cap; ++i) {
if ((DWT->CYCCNT - start) >= cycles) break;
}
}
/* DLENC>8 的长度映射,与厂商库 g_can_fdlength_table 一致 */
constexpr uint8_t kFdLenTable[7] = { 12U, 16U, 20U, 24U, 32U, 48U, 64U };
@@ -308,6 +329,7 @@ uint32_t CanFd::s_feedbackSeq = 0U;
CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{};
bool CanFd::s_channelHwOk[3]{};
bool CanFd::s_brsSuppress[3]{};
volatile uint8_t CanFd::s_txSilent[3]{};
/* TX 错误事件标志(ARD=1 单发丢帧通知):TX 完成 ISR 置位,主循环
* consumeTxError() 读清;单写单读,无锁。 */
@@ -492,7 +514,8 @@ void CanFd::initChannelHardware(uint8_t i)
}
s_channelHwOk[i] = false;
rcu_periph_clock_enable(ch.canRcu);
gpio_bit_reset(ch.slpPort, ch.slpPin); /* STB=0 -> Normal */
gpio_bit_reset(ch.slpPort, ch.slpPin); /* S=0 → Normal(静默不跨重启,上电一律放开发送) */
s_txSilent[i] = 0U;
const uint8_t rxLevelBefore = (gpio_input_bit_get(ch.rxPort, ch.rxPin) != RESET) ? 1U : 0U;
const uint8_t stbLevel = (gpio_output_bit_get(ch.slpPort, ch.slpPin) != RESET) ? 1U : 0U;
@@ -1096,6 +1119,13 @@ CanFd::CanTxResult CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
if (dlc > 0U && data == nullptr) {
return CanTxResult::kErrInvalid;
}
/* 收发器 Silent(只听不发):本端主动配置的发送禁能,不是总线状态。
* 与下面「BUS_OFF/TEC 不做软件拦截」的约定不冲突——那两条是粘性总线侧标志会误判,
* 这条是 s_txSilent 单一真源,且驱动器已关断:硬发只会让控制器白涨 TEC。
* 拒绝路径不碰任何计数/诊断,静默期间的拒发不是错误。 */
if (s_txSilent[channelIndex] != 0U) {
return CanTxResult::kErrSilent;
}
CanTxConcurrencyGuard guard(channelIndex);
@@ -1211,6 +1241,12 @@ CanFd::CanTxResult CanFd::enqueueTx(uint8_t channelIndex, uint32_t ext_id,
if (dlc > 0U && data == nullptr) {
return CanTxResult::kErrInvalid;
}
/* 静默通道在入队口就拒掉:否则帧会进软件队列,等 TX 完成中断 drainTxQueueIsr
* 调 transmitFrame 时才被拒,那一轮会把 head/count 推进并静默丢帧,
* 表面上"已受理"实则必丢。 */
if (s_txSilent[channelIndex] != 0U) {
return CanTxResult::kErrSilent;
}
TxQueue &q = s_txQueues[channelIndex];
@@ -1351,6 +1387,49 @@ void CanFd::setChannelBrsSuppress(uint8_t channelIndex, bool suppress)
CAN_LOGI("[CAN] CH%u set brs_suppress=%u\r\n", (unsigned)channelIndex, (unsigned)suppress);
}
bool CanFd::setTxSilent(uint8_t channelIndex, bool silent)
{
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
return false;
}
const auto &ch = kChannels[channelIndex];
if (silent) {
/* 先立旗再动脚:并发路径(主循环/其它 ISR)调 transmitFrame 立刻被拒,
* 不会在关驱动器的当口又塞一帧进邮箱。 */
s_txSilent[channelIndex] = 1U;
/* 驱动器即将关断:残留 PENDING 邮箱发不出也等不到 ACK,只会白涨 TEC,
* 进静默前先把它们停掉。 */
abortPendingTx(channelIndex);
gpio_bit_set(ch.slpPort, ch.slpPin); /* S=1 → TCAN1057 Silent(驱动关断,接收照常) */
} else {
gpio_bit_reset(ch.slpPort, ch.slpPin); /* S=0 → Normal */
/* 收发器模式建立时间 tMODE=20µs(手册 6.9):等够再放开发送门,
* 否则紧随其后的第一帧会被驱动器忽略。 */
canBusyWaitUs(20U);
s_txSilent[channelIndex] = 0U;
}
const uint8_t pinHigh = (gpio_output_bit_get(ch.slpPort, ch.slpPin) != RESET) ? 1U : 0U;
CAN_LOGI("[CAN] CH%u tx_silent=%u (S pin=%u)\r\n", (unsigned)channelIndex,
(unsigned)silent, (unsigned)pinHigh);
/* 回读确认脚电平与意图一致;不一致就把标志回滚成原状态再报错,
* 否则 isTxSilent()(发送门控 + 0xC1 bit5)会与物理脚电平各说一套。 */
if (pinHigh != (silent ? 1U : 0U)) {
s_txSilent[channelIndex] = silent ? 0U : 1U;
return false;
}
return true;
}
bool CanFd::isTxSilent(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount) {
return false;
}
return s_txSilent[channelIndex] != 0U;
}
uint32_t CanFd::readChannelErrorReg(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
@@ -1826,14 +1905,17 @@ void CanFd::refreshFeedbackSnapshot()
/* 写入硬件层错误状态供 SPI 反馈使用 */
s_feedback[i].stats.tec = static_cast<uint8_t>(hw_tec[i]);
s_feedback[i].stats.rec = static_cast<uint8_t>(hw_rec[i]);
/* 总线状态位图:bit0=init, bit1=sleep, bit2=error_passive, bit3=bus_off, bit4=error_warning
* init/sleep 来自 CAN_STAT,error_passive/bus_off/error_warning 来自 CAN_ERR */
/* 总线状态位图:bit0=init, bit1=控制器sleep, bit2=error_passive, bit3=bus_off,
* bit4=error_warning, bit5=本端收发器 Silent(只听不发)
* bit0~1 来自 CAN_STAT,bit2~4 来自 CAN_ERR;bit5 来自 s_txSilent(唯一真源,
* 与发送门控同一个变量),别拿 bit1 当收发器状态——两者不是一回事。 */
uint8_t busState = 0U;
if (hw_stat[i] & CAN_STAT_IWS) busState |= (1U << 0); /* 初始化模式 */
if (hw_stat[i] & CAN_STAT_SLPWS) busState |= (1U << 1); /* 睡眠模式 */
if (hw_stat[i] & CAN_STAT_SLPWS) busState |= (1U << 1); /* 控制器睡眠模式 */
if (hw_err[i] & CAN_ERR_PERR) busState |= (1U << 2); /* 错误被动 */
if (hw_err[i] & CAN_ERR_BOERR) busState |= (1U << 3); /* 总线关闭 */
if (hw_err[i] & CAN_ERR_WERR) busState |= (1U << 4); /* 错误警告 */
if (s_txSilent[i] != 0U) busState |= (1U << 5); /* 收发器 Silent */
s_feedback[i].stats.bus_state = busState;
s_idSnapshot[i].seq = s_feedbackSeq;
}