SPI中断陷入循环无法返回主程序问题求助
解决SPI中断风暴及循环队列出队问题
核心问题分析
- 中断标志清除时机错误:原ISR仅在SPI不忙时清除中断标志,若队列满导致FIFO仍有数据未读取,硬件会持续触发RNE(接收非空)中断,引发中断风暴。
- 队列线程安全缺失:循环队列的
front和rear未标记为volatile,编译器优化可能导致ISR与主循环看到的队列状态不一致。 - 满队列未处理中断屏蔽:队列满时未暂时关闭SPI接收中断,导致硬件反复触发中断,无法回到主循环处理出队。
具体修复方案
1. 确保队列线程安全
修改队列结构体,将front、rear和缓冲区标记为volatile,避免编译器优化导致的状态不一致:
#define BUF_LEN 64 // 根据实际需求调整 typedef struct { volatile uint16_t buffer[BUF_LEN]; volatile size_t front; volatile size_t rear; } CircularQueue; CircularQueue myQueue = {.front = 0, .rear = 0};
2. 修正ISR中断处理逻辑
- 优先清除已触发的中断标志,避免硬件反复触发;
- 队列满时暂时关闭SPI接收中断,防止中断风暴;
- 处理完数据后立即设置中断标志通知主循环:
PRIVATE void irq_handler(void) { // 先清除所有已触发的中断标志,避免重复触发 spi_get_hw(spi0)->icr = SPI_SSPICR_RTIC_BITS | SPI_SSPICR_RORIC_BITS; // 读取FIFO数据,直到FIFO为空或队列满 while ((spi_get_hw(spi0)->sr & SPI_SSPSR_RNE_BITS) && !queue_is_full()) { uint16_t word = spi_get_hw(spi0)->dr & SPI_SSPDR_DATA_BITS; enqueue(word); } // 队列满时,关闭SPI接收中断,避免持续触发 if (queue_is_full()) { spi_get_hw(spi0)->imsc &= ~SPI_SSPIMSC_RIM_BITS; } // 通知主循环有数据需要处理 spi_interrupt = true; }
3. 主循环中恢复中断逻辑
在主循环处理完队列数据后,检查队列是否有空间,若有则重新启用SPI接收中断:
while (1) { // tinyusb host task ... stuff if (spi_interrupt) { uint32_t s = save_and_disable_interrupts(); spi_interrupt = false; // 这里执行出队处理逻辑(示例) uint16_t data; while (dequeue(&data)) { // 处理data,比如写入in_buf或其他操作 in_buf[index++] = data; } // 队列有空间时,重新启用SPI接收中断 if (!queue_is_full()) { spi_get_hw(spi0)->imsc |= SPI_SSPIMSC_RIM_BITS; } restore_interrupts_from_disabled(s); } }
4. 完善出队函数(若未实现)
确保出队函数正确更新队列状态,且线程安全:
PUBLIC bool dequeue(uint16_t *x) { if (myQueue.front == myQueue.rear) { return false; // 队列空 } *x = myQueue.buffer[myQueue.front]; myQueue.front = (myQueue.front + 1) % BUF_LEN; return true; }
额外注意事项
- 确认
BUF_LEN的大小:建议设置为SPI FIFO深度的2-4倍,避免频繁触发满队列逻辑; - 检查SPI硬件配置:确保中断触发源仅启用必要的(如RNE),避免不必要的中断;
- 主循环中
tinyusb host task的执行时间:若该任务耗时过长,会导致队列处理不及时,间接引发满队列中断风暴,需优化该任务的执行效率。
内容的提问来源于stack exchange,提问作者Maaz Madha
相关产品推荐
相关产品推荐

