STM32F429I中SRAM读取缓冲区的低延迟无丢数实现方案问询
STM32F4 低延迟无丢包的总线数据读取与处理方案
需求背景
在STM32F4平台上,通过GPIO下降沿中断触发从总线系统读取缓冲区数据,要求:
- 主循环中处理数据,且处理延迟尽可能低
- 中断触发间隔仅500ns,绝对不能丢失任何中断,因此开关中断的方式不可行
初步实现方案
变量声明
#define BUFFER_SIZE ((uint32_t)0x0100) #define WRITE_READ_ADDR ((uint32_t)0x0800) /* Private macro -------------------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ SRAM_HandleTypeDef hsram1; FMC_NORSRAM_TimingTypeDef SRAM_Timing; /* Read/Write Buffers */ volatile uint16_t aRxBuffer[BUFFER_SIZE]; volatile uint8_t iowr_set = 0; // 补充缺失的状态标记变量
主循环代码
while (1) { if(iowr_set) { iowr_set = 0; for (int i = 0; i < BUFFER_SIZE; i++) { processData(aRxBuffer[i]); } } }
中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_6) { // IORD 触发引脚 HAL_SRAM_Read_16b(&hsram1, (uint32_t *)(SRAM_BANK_ADDR + WRITE_READ_ADDR), aRxBuffer, BUFFER_SIZE); iowr_set = 1; } }
现有方案的核心隐患
当前方案存在致命风险:中断回调内的HAL_SRAM_Read_16b操作耗时远超500ns。即使STM32F4的FMC配置最快时序,读取256个16位数据(BUFFER_SIZE=0x100)的时间也远大于中断间隔,下一个中断触发时前一次中断未执行完毕,会直接导致中断丢失,完全违反需求。
优化实现方案
1. 双缓冲区切换,剥离中断内的大流量读取
使用两组缓冲区,中断仅负责切换缓冲区标记,耗时的读取操作交给主循环执行,避免中断阻塞:
#define BUFFER_SIZE ((uint32_t)0x0100) #define WRITE_READ_ADDR ((uint32_t)0x0800) volatile uint16_t bufferA[BUFFER_SIZE]; volatile uint16_t bufferB[BUFFER_SIZE]; volatile uint8_t active_buffer = 0; // 0: 当前总线写入的缓冲区,1: 待处理的缓冲区 volatile uint8_t new_data_ready = 0; // 中断回调 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_6) { // 仅切换缓冲区标记,操作耗时可忽略 active_buffer = active_buffer ^ 1; new_data_ready = 1; } } // 主循环 while (1) { if (new_data_ready) { new_data_ready = 0; // 读取非当前写入的缓冲区数据 volatile uint16_t *target_buf = (active_buffer == 0) ? bufferB : bufferA; HAL_SRAM_Read_16b(&hsram1, (uint32_t *)(SRAM_BANK_ADDR + WRITE_READ_ADDR), (uint16_t*)target_buf, BUFFER_SIZE); // 处理数据 for (int i = 0; i < BUFFER_SIZE; i++) { processData(target_buf[i]); } } }
注:此方式要求总线数据在两次中断间隔内不会被覆盖,若总线持续刷新数据,需结合DMA做后台读取。
2. 启用DMA自动完成数据读取
利用FMC的DMA请求功能,配置DMA在中断触发时自动完成SRAM数据读取,中断仅负责启动传输,DMA完成后标记数据就绪:
// DMA配置示例(需根据硬件调整通道) DMA_HandleTypeDef hdma_fmc; void MX_DMA_Init(void) { __HAL_RCC_DMA2_CLK_ENABLE(); hdma_fmc.Instance = DMA2_Stream0; hdma_fmc.Init.Channel = DMA_CHANNEL_0; hdma_fmc.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_fmc.Init.PeriphInc = DMA_PINC_ENABLE; hdma_fmc.Init.MemInc = DMA_MINC_ENABLE; hdma_fmc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_fmc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_fmc.Init.Mode = DMA_NORMAL; hdma_fmc.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_fmc); __HAL_LINKDMA(&hsram1, hdma, hdma_fmc); } // 中断回调 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_6) { // 启动DMA传输,无需等待完成 HAL_SRAM_Read_DMA(&hsram1, (uint32_t *)(SRAM_BANK_ADDR + WRITE_READ_ADDR), (uint16_t*)aRxBuffer, BUFFER_SIZE); } } // DMA传输完成回调 void HAL_SRAM_RxCpltCallback(SRAM_HandleTypeDef *hsram) { iowr_set = 1; } // 主循环保持原逻辑 while (1) { if(iowr_set) { iowr_set = 0; for (int i = 0; i < BUFFER_SIZE; i++) { processData(aRxBuffer[i]); } } }
3. 系统级优化
- 将EXTI中断优先级设置为最高,避免被其他中断抢占
- 确保STM32F4运行在最高时钟频率(168MHz),减少指令执行周期
- 精简
processData函数,仅保留必要预处理,复杂逻辑移至主循环空闲时段或低优先级任务
内容的提问来源于stack exchange,提问作者Andre Ahmed
相关产品推荐
相关产品推荐

