用STM32F407的SDIO给TF卡测个速:轮询、中断、DMA哪种方式最快?
STM32F407 SDIO性能对决轮询、中断、DMA三种模式实测指南当你的嵌入式系统需要高速存储数据时SD卡无疑是最常见的选择之一。但你是否想过同样的硬件环境下不同的数据传输方式会带来多大的性能差异今天我们就用STM32F407的SDIO接口对轮询、中断和DMA三种方式进行一次全面的速度测试。1. 测试环境搭建在开始性能测试前我们需要确保硬件和软件环境正确配置。以下是我们的测试平台配置硬件配置STM32F407VET6开发板168MHz主频16GB Class10 microSD卡SanDisk Ultra逻辑分析仪用于精确测量时序示波器验证时钟信号质量软件配置STM32CubeMX 6.10.0Keil MDK-ARM 5.37HAL库版本1.27.11.1 CubeMX关键配置在CubeMX中配置SDIO时有几个关键参数直接影响性能/* SDIO初始化参数示例 */ hsd.Instance SDIO; hsd.Init.ClockEdge SDIO_CLOCK_EDGE_RISING; hsd.Init.ClockBypass SDIO_CLOCK_BYPASS_DISABLE; hsd.Init.ClockPowerSave SDIO_CLOCK_POWER_SAVE_DISABLE; hsd.Init.BusWide SDIO_BUS_WIDE_4B; hsd.Init.HardwareFlowControl SDIO_HARDWARE_FLOW_CONTROL_DISABLE; hsd.Init.ClockDiv 2; // 24MHz时钟注意SD卡初始化阶段必须使用400kHz以下时钟频率和1位总线宽度初始化完成后再切换到高速模式。2. 测试方法论为了公平比较三种传输方式的性能我们设计了以下测试方案测试内容连续写入4MB数据8192个512字节块连续读取4MB数据随机读写混合操作性能指标传输速度MB/sCPU占用率通过空闲任务统计系统响应延迟中断响应时间2.1 精确计时实现准确的性能测量需要高精度计时器。我们使用STM32的DWT周期计数器#define DWT_CYCCNT *(volatile uint32_t *)0xE0001004 #define DWT_CONTROL *(volatile uint32_t *)0xE0001000 #define DEMCR *(volatile uint32_t *)0xE000EDFC void DWT_Init(void) { DEMCR | 0x01000000; // 启用跟踪单元 DWT_CYCCNT 0; // 重置计数器 DWT_CONTROL | 1; // 启用计数器 } uint32_t get_ticks() { return DWT_CYCCNT; } float ticks_to_ms(uint32_t ticks) { return (float)ticks / (SystemCoreClock / 1000.0f); }3. 轮询模式实测轮询是最基础的传输方式实现简单但效率有限。3.1 实现代码void test_polling_write(uint32_t block_addr, uint32_t block_count) { uint8_t buffer[BLOCKSIZE]; uint32_t start get_ticks(); for(uint32_t i0; iblock_count; i) { memset(buffer, i%256, BLOCKSIZE); HAL_SD_WriteBlocks(hsd, buffer, block_addri, 1, 1000); } uint32_t elapsed get_ticks() - start; float speed (block_count * BLOCKSIZE) / (ticks_to_ms(elapsed) / 1000.0f); printf(Polling Write: %.2f KB/s\n, speed/1024); }3.2 性能数据测试项目传输速度CPU占用率连续写入1.8MB/s98%连续读取2.1MB/s97%混合操作1.5MB/s99%轮询模式的瓶颈显而易见CPU几乎完全被数据传输占用无法执行其他任务。4. 中断模式实测中断方式可以释放CPU资源但频繁中断也会带来额外开销。4.1 实现要点volatile uint32_t sdio_transfer_complete 0; void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd) { sdio_transfer_complete 1; } void test_interrupt_write(uint32_t block_addr, uint32_t block_count) { uint8_t buffer[BLOCKSIZE]; uint32_t start get_ticks(); for(uint32_t i0; iblock_count; i) { memset(buffer, i%256, BLOCKSIZE); sdio_transfer_complete 0; HAL_SD_WriteBlocks_IT(hsd, buffer, block_addri, 1); while(!sdio_transfer_complete) { // 这里可以执行其他低优先级任务 __WFI(); // 进入低功耗模式 } } uint32_t elapsed get_ticks() - start; // ...计算并输出速度 }4.2 性能对比测试项目传输速度CPU占用率中断次数连续写入2.3MB/s65%8192连续读取2.7MB/s60%8192混合操作2.0MB/s70%16384中断模式虽然提高了CPU利用率但每块数据都触发中断的方式效率仍然不高。5. DMA模式深度优化DMA是三种方式中最复杂但性能最好的方案需要仔细配置。5.1 DMA配置技巧/* CubeMX中DMA配置建议 */ hdma_sdio_rx.Instance DMA2_Stream3; hdma_sdio_rx.Init.Channel DMA_CHANNEL_4; hdma_sdio_rx.Init.Direction DMA_PERIPH_TO_MEMORY; hdma_sdio_rx.Init.PeriphInc DMA_PINC_DISABLE; hdma_sdio_rx.Init.MemInc DMA_MINC_ENABLE; hdma_sdio_rx.Init.PeriphDataAlignment DMA_PDATAALIGN_WORD; hdma_sdio_rx.Init.MemDataAlignment DMA_MDATAALIGN_WORD; hdma_sdio_rx.Init.Mode DMA_NORMAL; hdma_sdio_rx.Init.Priority DMA_PRIORITY_HIGH; hdma_sdio_rx.Init.FIFOMode DMA_FIFOMODE_ENABLE; hdma_sdio_rx.Init.FIFOThreshold DMA_FIFO_THRESHOLD_FULL; hdma_sdio_rx.Init.MemBurst DMA_MBURST_INC4; hdma_sdio_rx.Init.PeriphBurst DMA_PBURST_INC4;5.2 多块传输优化HAL库默认支持多块传输可以大幅减少中断次数#define BUFFER_SIZE (16*1024) // 16KB缓冲区 #define BLOCKS_PER_TRANSFER (BUFFER_SIZE/BLOCKSIZE) void test_dma_multiblock_write(uint32_t block_addr, uint32_t block_count) { uint8_t buffer[BUFFER_SIZE]; uint32_t start get_ticks(); for(uint32_t i0; iblock_count; iBLOCKS_PER_TRANSFER) { uint32_t blocks MIN(BLOCKS_PER_TRANSFER, block_count-i); // 填充缓冲区... HAL_SD_WriteBlocks_DMA(hsd, buffer, block_addri, blocks); // 等待传输完成... } // ...计算速度 }5.3 DMA模式性能数据测试项目传输速度CPU占用率DMA中断次数连续写入6.8MB/s15%512连续读取7.2MB/s12%512混合操作5.5MB/s20%1024DMA模式不仅提供了最高的传输速度还将CPU占用率降到了最低。6. 三种模式综合对比我们将所有测试数据汇总如下表性能指标轮询模式中断模式DMA模式最大写入速度1.8MB/s2.3MB/s6.8MB/s最大读取速度2.1MB/s2.7MB/s7.2MB/sCPU占用率98%65%15%系统响应性差中等优秀实现复杂度简单中等复杂适用场景低速简单应用中等速率系统高性能系统从实际项目经验来看如果你的应用只是偶尔存取小量数据中断模式可能是性价比最高的选择。但对于需要持续高速存储的应用如数据采集DMA模式是唯一可行的方案。