STM32
STM32H750硬件双缓冲DMA方案
本文档使用 MrDoc 发布
-
+
首页
STM32H750硬件双缓冲DMA方案
STM32H750的DMA确实**硬件支持真正的双缓冲模式**(也称为Ping-Pang缓冲),这与F407需要用半传输中断模拟的方案有本质区别。 ### 一、硬件双缓冲的优势 | 对比项 | 半传输中断模拟 | 硬件双缓冲 | |--------|---------------|-----------| | **缓冲区要求** | 单一连续缓冲区(大小需2倍) | 两个独立缓冲区(可不连续) | | **中断频率** | 每半个缓冲区触发一次中断 | 仅在整个缓冲区传输完成时触发 | | **CPU负载** | 较高(中断频率翻倍) | 更低 | | **缓冲区切换** | 软件手动切换 | DMA硬件自动切换 | | **适用性** | F4系列通用方案 | **H7系列原生支持** | ### 二、核心API函数 STM32H7的HAL库提供了专门的双缓冲API: ```c // 双缓冲模式启动函数 HAL_StatusTypeDef HAL_DMAEx_MultiBufferStart( DMA_HandleTypeDef *hdma, uint32_t SrcAddress, // 源地址(外设寄存器地址) uint32_t DstAddress, // 目标缓冲区1地址 uint32_t SecondMemAddress, // 目标缓冲区2地址(双缓冲关键参数) uint32_t DataLength // 每次传输的数据长度 ); ``` 这个函数在搜索结果中被明确提到是H7系列实现双缓冲的核心接口。 ### 三、完整实现代码 #### 1. 定义双缓冲区 ```c // 双缓冲区——注意:不能放在TCM RAM中! // H7的DMA1/DMA2无法访问DTCM和ITCM,必须放在D2域的SRAM中 #define ADC_BUFFER_SIZE 1024 // 每个缓冲区大小 // 使用__attribute__指定内存区域 __attribute__((section(".DMA_RAM"))) uint16_t adc_buffer1[ADC_BUFFER_SIZE]; __attribute__((section(".DMA_RAM"))) uint16_t adc_buffer2[ADC_BUFFER_SIZE]; // 当前正在被DMA使用的缓冲区索引 volatile uint8_t current_buffer = 0; // 当前需要处理的数据缓冲区指针 volatile uint16_t* ready_buffer = NULL; ``` #### 2. DMA初始化配置 ```c #include "stm32h7xx_hal.h" DMA_HandleTypeDef hdma_adc; void ADC_DMA_Init(void) { __HAL_RCC_DMA2_CLK_ENABLE(); // ADC通常用DMA2 hdma_adc.Instance = DMA2_Stream0; // 选择合适的Stream hdma_adc.Init.Request = DMA_REQUEST_ADC1; // 通过DMAMUX选择ADC1请求 hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不变(ADC_DR寄存器) hdma_adc.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增 hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; // 16位 hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_adc.Init.Mode = DMA_CIRCULAR; // 循环模式(连续采集) hdma_adc.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_adc); // 将DMA句柄连接到ADC __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc); } ``` #### 3. 启动双缓冲DMA ```c void ADC_Start_DoubleBuffer(void) { // 停止可能正在进行的DMA HAL_DMA_DeInit(&hdma_adc); ADC_DMA_Init(); // 启动双缓冲模式——核心! HAL_DMAEx_MultiBufferStart( &hdma_adc, (uint32_t)&(ADC1->DR), // 外设源地址(ADC数据寄存器) (uint32_t)adc_buffer1, // 缓冲区1地址 (uint32_t)adc_buffer2, // 缓冲区2地址 ADC_BUFFER_SIZE // 每个缓冲区大小 ); // 使能DMA传输完成中断(双缓冲模式下半传输中断不需要了) __HAL_DMA_ENABLE_IT(&hdma_adc, DMA_IT_TC); // 启动ADC的DMA连续转换 HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer1, ADC_BUFFER_SIZE); } ``` #### 4. DMA传输完成中断处理 这是硬件双缓冲的核心优势——**只有当整个缓冲区填满时才触发一次中断**,中断频率减半。 ```c void DMA2_Stream0_IRQHandler(void) { // 判断是否为传输完成中断 if (__HAL_DMA_GET_FLAG(&hdma_adc, DMA_FLAG_TCIF0_4)) { __HAL_DMA_CLEAR_FLAG(&hdma_adc, DMA_FLAG_TCIF0_4); // 关键:判断当前是哪个缓冲区刚被填满 // DMA硬件传输完成时,正在使用的缓冲区已经切换到另一个 if (hdma_adc.Instance->CR & DMA_SxCR_CT) { // CT=1 表示当前使用缓冲区2,说明刚完成的是缓冲区1 ready_buffer = adc_buffer1; current_buffer = 1; } else { // CT=0 表示当前使用缓冲区1,说明刚完成的是缓冲区2 ready_buffer = adc_buffer2; current_buffer = 2; } // 处理刚填满的缓冲区数据 process_adc_data(ready_buffer, ADC_BUFFER_SIZE); } } ``` #### 5. 数据处理与串口发送 ```c // 数据处理函数(在中断中调用,需尽量轻量) void process_adc_data(uint16_t* buffer, uint32_t len) { // 方案A:直接触发DMA串口发送原始数据(推荐) // 注意:原始12位数据为2字节,100kHz = 200kB/s HAL_UART_Transmit_DMA(&huart1, (uint8_t*)buffer, len * 2); // 方案B:如需转浮点(VOFA+),注意带宽限制 // 100kHz * 4字节 = 400kB/s > 2Mbps串口极限,建议用USB CDC } ``` ### 四、关键注意事项 #### 1. 内存位置——常见踩坑点! H7的DMA1/DMA2**无法访问TCM RAM**(DTCM/ITCM)。 ```c // 错误示例——会导致DMA无法工作 uint16_t adc_buffer[1024]; // 默认可能在TCM中 // 正确做法——指定到D2域的SRAM // 方法1:修改链接脚本,添加.DMA_RAM段 // 方法2:使用以下属性 uint16_t adc_buffer[1024] __attribute__((section(".dma_d2ram"))); ``` #### 2. Cache一致性处理 如果启用了D-Cache,需要确保DMA缓冲区的Cache一致性: ```c // 在访问DMA缓冲区前,使无效化Cache(强制从内存读取最新数据) SCB_InvalidateDCache_by_Addr((uint32_t*)ready_buffer, ADC_BUFFER_SIZE * 2); ``` 或者更简单的方法:通过MPU将DMA缓冲区所在内存配置为**Non-cacheable**。 #### 3. 定时器触发ADC配置 ```c // TIM2配置(与F407类似) TIM2->PSC = 480 - 1; // 480MHz / 480 = 1MHz计数频率 TIM2->ARR = 10 - 1; // 1MHz / 10 = 100kHz TIM2->CR2 |= TIM_CR2_MMS_1; // 更新事件作为TRGO输出 TIM2->CR1 |= TIM_CR1_CEN; // 使能定时器 // ADC外部触发配置 ADC1->CFGR |= ADC_CFGR_EXTEN_0; // 上升沿触发 ADC1->CFGR |= ADC_CFGR_EXTSEL_0; // 选择TIM2_TRGO ``` ### 五、方案对比总结 | 项目 | 硬件双缓冲方案 | 之前讨论的半传输中断方案 | |------|---------------|----------------------| | **中断频率** | 每1024点一次 | 每512点一次(翻倍) | | **CPU负载** | 更低 | 较高 | | **代码复杂度** | 略高(需配置双缓冲专用API) | 较低(通用逻辑) | | **内存灵活性** | 两个缓冲区可不连续 | 必须连续且大小2倍 | | **H7支持度** | **原生支持,充分发挥硬件能力** | 兼容但非最佳 | ### 六、带宽优化建议 之前讨论的带宽问题仍然存在。若坚持使用2Mbps串口传输100kHz数据: - **原始数据(2字节/点)**:200kB/s,2Mbps勉强够用(理论极限250kB/s) - **转浮点后(4字节/点)**:400kB/s,**超过2Mbps极限** 建议: 1. **保持原始12位数据传输**,上位机自行转电压值 2. 或升级到**USB虚拟串口(CDC)**,有效带宽可达1MB/s以上 硬件双缓冲方案已为你准备好,可以直接在STM32H750上实现。如需进一步协助(如链接脚本修改、MPU配置),请告诉我。
qingkai
2026年3月22日 12:09
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码