feat(app): 1.6.39 —— 1.6.29 起未上板业务改动入库,启动链与已提交基线对齐

主体为工作区累积的业务改动(CAN/PDO/桥/SSC/task_manager 等,详目见
config.h 版本注释链),新增 CoE 0x100A 真版本上报与 0x2502 APP→BOOT
总线交接。启动链(startup/SystemInit/initial/systick/hardfault)与
HEAD 逐字节一致;1.6.35~38 的跳转诊断实验(3M 化/BKP 面包屑/
haltWithWatchdog)未提交、已整体回退,补丁存 _revert_backup_20260916/。
真机实测(配对 BOOT 1.1.0.18):刷写 warm jump 与 reset cold jump 均可。
This commit is contained in:
cbb
2026-09-16 00:48:34 +08:00
parent 6136c2363f
commit 7d9d9c70a1
17 changed files with 399 additions and 95 deletions
+96 -48
View File
@@ -255,6 +255,49 @@ static void hardResetCan(uint32_t canPeriph, rcu_periph_enum canRcu)
}
}
/**
* @brief CAN TX 并发保护 RAII 守卫。
*
* 屏蔽 EXTI10_15_IRQn(SPI CS Assert/Release, Preempt 0)与该通道的 txIrq(Preempt 2),
* 防止 EtherCAT PDI_Isr(Preempt 1)与 SPI CS EXTI ISR(Preempt 0)在操作 s_txQueues
* 或硬件发送邮箱寄存器时产生并发撕裂与重入竞争。
* 具备重入安全性:若某中断当前未使能(例如已被外层守卫屏蔽),退出时不会误使能。
*/
class CanTxConcurrencyGuard {
public:
explicit CanTxConcurrencyGuard(uint8_t channelIndex)
: m_txIrq(kChannels[channelIndex].txIrq)
{
m_exti15_enabled = (NVIC_GetEnableIRQ(EXTI10_15_IRQn) != 0U);
m_tx_enabled = (NVIC_GetEnableIRQ(m_txIrq) != 0U);
if (m_exti15_enabled) {
NVIC_DisableIRQ(EXTI10_15_IRQn);
}
if (m_tx_enabled) {
NVIC_DisableIRQ(m_txIrq);
}
__DSB();
__ISB();
}
~CanTxConcurrencyGuard() {
if (m_tx_enabled) {
NVIC_EnableIRQ(m_txIrq);
}
if (m_exti15_enabled) {
NVIC_EnableIRQ(EXTI10_15_IRQn);
}
__DSB();
__ISB();
}
private:
IRQn_Type m_txIrq;
bool m_exti15_enabled;
bool m_tx_enabled;
};
} // namespace
CanFd::Diag CanFd::s_diag{};
@@ -541,7 +584,6 @@ void CanFd::initChannelHardware(uint8_t i)
s_rxQueues[i].head = 0U;
s_rxQueues[i].tail = 0U;
s_rxQueues[i].count = 0U;
s_txQueues[i].head = 0U;
s_txQueues[i].tail = 0U;
s_txQueues[i].count = 0U;
@@ -982,23 +1024,20 @@ CanFd::Mode CanFd::mode()
* @param[in] 无。
* @param[out] 无。
* @retval bool true 表示至少一条接收队列非空,false 表示当前三路均无待消费报文。
* @note 无锁检查 head != tail,免关中断。
*/
bool CanFd::hasRxFrame()
{
bool found = false;
uint32_t save = RT_CRITICAL_SAVE();
for(size_t i = 0; i < kChannelCount; ++i) {
if(s_rxQueues[i].count != 0U) {
found = true;
break;
if(s_rxQueues[i].head != s_rxQueues[i].tail) {
return true;
}
}
RT_CRITICAL_RESTORE(save);
return found;
return false;
}
/**
* @brief 轮转读取并消费一帧接收报文。
* @brief 轮转读取并消费一帧接收报文(Lock-Free SPSC 消费者端)。
* @param[in] 无。
* @param[out] id 输出报文标识符;允许为空。
* @param[out] data 输出报文数据缓存;允许为空。
@@ -1006,42 +1045,37 @@ bool CanFd::hasRxFrame()
* @param[out] channel 输出报文来源通道号;允许为空。
* @retval bool true 表示成功消费一帧报文,false 表示当前三路接收队列均为空。
* @note 消费顺序采用从 `s_nextConsumeChannel` 开始的轮转扫描,避免长期偏向固定通道。
* 基于 SPSC 无锁环形缓冲区,消费端读取 payload 并执行 __DMB() 后推进 head,
* 无需关中断或依赖 BASEPRI,彻底消除与 Preempt 0 RX ISR 的并发撕裂。
*/
bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel)
{
RxFrame localFrame{};
bool found = false;
uint32_t save = RT_CRITICAL_SAVE();
for(size_t n = 0; n < kChannelCount; ++n) {
const uint8_t idx = static_cast<uint8_t>((s_nextConsumeChannel + n) % kChannelCount);
auto &queue = s_rxQueues[idx];
if(queue.count == 0U) {
const uint8_t head = queue.head;
const uint8_t tail = queue.tail;
if(head == tail) {
continue;
}
localFrame = queue.frames[queue.head];
queue.head = static_cast<uint8_t>((queue.head + 1U) % kRxQueueDepth);
queue.count--;
s_nextConsumeChannel = static_cast<uint8_t>((idx + 1U) % kChannelCount);
found = true;
break;
}
RT_CRITICAL_RESTORE(save);
if(!found) {
return false;
}
if(channel) *channel = localFrame.channel;
if(id) *id = localFrame.id;
if(length) *length = localFrame.length;
if(data) {
for(uint8_t i = 0; i < localFrame.length; ++i) {
data[i] = localFrame.data[i];
const auto &src = queue.frames[head];
if(channel) *channel = src.channel;
if(id) *id = src.id;
if(length) *length = src.length;
if(data && src.length > 0U) {
for(uint8_t i = 0; i < src.length; ++i) {
data[i] = src.data[i];
}
}
__DMB();
queue.head = static_cast<uint8_t>((head + 1U) & kRxQueueMask);
s_nextConsumeChannel = static_cast<uint8_t>((idx + 1U) % kChannelCount);
return true;
}
return true;
return false;
}
/**
@@ -1068,6 +1102,8 @@ CanFd::CanTxResult CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
return CanTxResult::kErrInvalid;
}
CanTxConcurrencyGuard guard(channelIndex);
/* 诊断:捕获任何通过 transmitFrame 发 0x100 DLC=16 的帧(排查幽灵帧来源) */
if (ext_id == 0x00000100U && dlc == 16U) {
CAN_LOGI("[CAN-DIAG] TX 0x100 DLC=16 via transmitFrame ch=%u\r\n",
@@ -1181,6 +1217,10 @@ CanFd::CanTxResult CanFd::enqueueTx(uint8_t channelIndex, uint32_t ext_id,
TxQueue &q = s_txQueues[channelIndex];
/* 关中断并发保护:屏蔽 EXTI10_15_IRQn(SPI burst, Preempt 0)与 txIrq(Preempt 2),
* 解决 PDI_Isr(Preempt 1)与 SPI CS release(Preempt 0)并发访问 s_txQueues 的竞态。 */
CanTxConcurrencyGuard guard(channelIndex);
/* 快速路径:队列空 → 直通硬件邮箱。装得下立即返回,不用等待 TX 完成中断。
* ★ allow_abort=false:邮箱满时禁止 abort 旧帧顶替(那会把同通道已装载的
* 旧帧静默丢弃,>3 帧同通道突发只剩最后 3 帧上线),一律落入软件队列,
@@ -1193,16 +1233,9 @@ CanFd::CanTxResult CanFd::enqueueTx(uint8_t channelIndex, uint32_t ext_id,
/* 邮箱满:落入下面的入队路径 */
}
/* 关中段:EXTI15(SPI burst) 与 CANx TX ISR 并发,保护 head/tail/count。
* 用 CMSIS NVIC 接口(SPL 的 nvic_irq_enable 是 3 参版)。
* 关的是该通道自身的 txIrq,避免关全局影响其他通道的实时性。 */
const IRQn_Type irqn = kChannels[channelIndex].txIrq;
NVIC_DisableIRQ(irqn);
if (q.count >= kTxQueueDepth) {
/* 队列也满了:彻底装不下。此时不能再返回 kOk,
* 否则前端会误判成功;只能丢弃并返回 kErrMailbox。 */
NVIC_EnableIRQ(irqn);
s_diag.error_count++;
s_channelStats[channelIndex].error_count++;
return CanTxResult::kErrMailbox;
@@ -1217,14 +1250,13 @@ CanFd::CanTxResult CanFd::enqueueTx(uint8_t channelIndex, uint32_t ext_id,
q.tail = static_cast<uint8_t>((q.tail + 1U) % kTxQueueDepth);
q.count++;
NVIC_EnableIRQ(irqn);
return CanTxResult::kOk;
}
/**
* @brief CAN TX 完成中断末尾调用:用硬件邮箱空闲出的位置,把队列里的帧装回去。
* @note 只在 ISR 内调用;关中断保护已在 transmitFrame 返回值判定外层完成,
* 本函数本身不关中(邮箱空位是"单调减少再单调增加",多读无害)。
* @note 由 CAN TX 完成 ISR 调用;使用 CanTxConcurrencyGuard 屏蔽 EXTI10_15 与 txIrq,
* 防止出队期间被高优先级的 SPI CS release 抢占导致 head/count 撕裂。
*/
void CanFd::drainTxQueueIsr(uint8_t channelIndex)
{
@@ -1232,6 +1264,8 @@ void CanFd::drainTxQueueIsr(uint8_t channelIndex)
return;
}
TxQueue &q = s_txQueues[channelIndex];
CanTxConcurrencyGuard guard(channelIndex);
while (q.count > 0U) {
TxQueuedFrame &f = q.frames[q.head];
/* 复用 transmitFrame 的装载逻辑;allow_abort=false:这里邮箱刚空出来,
@@ -1261,6 +1295,16 @@ uint32_t CanFd::txQueueBacklog(uint8_t channelIndex)
return s_txQueues[channelIndex].count;
}
uint32_t CanFd::rxQueueCount(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount) {
return 0U;
}
const uint8_t head = s_rxQueues[channelIndex].head;
const uint8_t tail = s_rxQueues[channelIndex].tail;
return static_cast<uint32_t>((tail - head) & kRxQueueMask);
}
void CanFd::abortPendingTx(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
@@ -1612,13 +1656,17 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex)
}
auto &queue = s_rxQueues[channelIndex];
if(queue.count >= kRxQueueDepth) {
const uint8_t head = queue.head;
const uint8_t tail = queue.tail;
const uint8_t next_tail = static_cast<uint8_t>((tail + 1U) & kRxQueueMask);
if(next_tail == head) {
s_diag.rx_drop_count++;
stats.rx_drop_count++;
continue;
}
auto &frame = queue.frames[queue.tail];
auto &frame = queue.frames[tail];
frame.channel = channelIndex;
frame.id = rxId;
frame.length = rx_message.rx_dlen;
@@ -1626,8 +1674,8 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex)
frame.data[i] = rx_message.rx_data[i];
}
queue.tail = static_cast<uint8_t>((queue.tail + 1U) % kRxQueueDepth);
queue.count++;
__DMB();
queue.tail = next_tail;
}
if(fifoFull) {