STM32F103系列微控制器(基于ARM Cortex-M3内核)集成了**DMA(Direct Memory Access,直接内存访问)**控制器,用于在存储器与外设、存储器与存储器之间高效传输数据,减少CPU的干预,从而提升系统性能。本文将详细介绍STM32F103的DMA原理、架构、功能特性及使用方法,结合实际代码示例说明如何在开发中应用DMA,特别以STM32CubeMX和HAL库为工具。
1. DMA原理
DMA是一种硬件机制,允许数据在存储器(Flash、SRAM)或外设(UART、SPI、ADC等)之间直接传输,而无需CPU逐字节处理。其核心思想是将数据搬运任务交给DMA控制器,CPU只需配置DMA通道并启动传输,传输完成后通过中断或查询方式获取结果。
1.1 工作原理
- DMA控制器:STM32F103的DMA控制器管理多个通道,每个通道负责特定的数据传输任务。
- 数据流 :
- 存储器到存储器:如SRAM到SRAM。
- 外设到存储器:如ADC数据到SRAM。
- 存储器到外设:如SRAM数据到UART发送缓冲区。
- 触发机制 :
- DMA传输可由软件触发(手动启动)或硬件触发(外设请求,如UART发送完成)。
- 中断支持 :
- 传输完成(TC)、半传输(HT)或传输错误(TE)时可触发中断。
- 优势 :
- 降低CPU负载,适合高吞吐量任务(如ADC采样、音频流)。
- 提高实时性,适合多任务系统。
1.2 STM32F103的DMA架构
STM32F103的DMA控制器分为DMA1 和DMA2(部分高密度型号支持DMA2),具体特性如下:
- DMA1 :
- 7个通道(Channel 1-7)。
- 连接到APB1/APB2外设(如UART、SPI、ADC)及存储器。
- DMA2 (仅高密度型号,如STM32F103ZET6):
- 5个通道(Channel 1-5)。
- 通常用于高级外设或额外存储器访问。
- 通道优先级 :
- 每个通道可配置高、中、低优先级,解决多通道冲突。
- 数据宽度 :
- 支持8位、16位、32位数据传输。
- 传输模式 :
- 单次传输:传输固定长度数据后停止。
- 循环模式:传输完成后自动重新开始,适合连续数据流。
- 增量模式:支持源/目标地址自动递增或固定。
- FIFO:STM32F103的DMA无独立FIFO,依赖外设缓冲区或直接传输。
1.3 DMA工作流程
- 配置DMA通道 :
- 设置源地址、目标地址、数据长度。
- 配置传输方向(存储器到外设、外设到存储器、存储器到存储器)。
- 设置数据宽度、优先级、增量模式等。
- 启动传输 :
- 软件触发(设置ENABLE位)或硬件触发(外设信号)。
- 数据传输 :
- DMA控制器从源地址读取数据,写入目标地址。
- 完成处理 :
- 传输完成后,触发中断或置位标志,通知CPU处理结果。
2. STM32F103 DMA功能特性
- 通道数量 :
- DMA1:7通道,支持ADC1/2、SPI1/2、UART1-3、I2C1/2、TIM1-4等。
- DMA2(若有):5通道,支持高级外设或额外存储器。
- 传输方向 :
- 存储器到存储器(仅DMA1支持)。
- 外设到存储器(如ADC采样到SRAM)。
- 存储器到外设(如SRAM数据到UART)。
- 中断支持 :
- 传输完成中断(TCIF)。
- 半传输中断(HTIF)。
- 传输错误中断(TEIF)。
- 数据对齐 :
- 支持字节(8位)、半字(16位)、字(32位)传输。
- 源和目标数据宽度可不同(如8位外设到16位SRAM)。
- 循环模式 :
- 启用后,传输完成后自动重新加载计数器,适合连续数据流。
- 优先级管理 :
- 软件配置通道优先级(Very High, High, Medium, Low)。
- 硬件仲裁确保高优先级通道优先访问总线。
3. DMA使用方法
以下以STM32F103C8T6为例,结合STM32CubeMX和HAL库,介绍如何实现DMA传输,重点以ADC DMA (外设到存储器)和UART DMA(存储器到外设)为例。
3.1 开发环境准备
- 硬件 :
- STM32F103C8T6开发板(如"蓝板")。
- ST-Link V2调试器。
- USB-TTL模块(如CH340)用于串口调试。
- 软件 :
- STM32CubeMX:配置外设和DMA。
- STM32CubeIDE:编写和调试代码。
- STM32CubeProgrammer:烧录程序。
- 串口终端(如PuTTY):查看输出。
3.2 示例1:ADC DMA(外设到存储器)
目标:使用ADC1连续采样多个通道的数据,通过DMA传输到SRAM缓冲区。
3.2.1 STM32CubeMX配置
- 创建项目 :
- 打开STM32CubeMX,选择STM32F103C8T6。
- 配置ADC :
- 在"Analog"中启用ADC1。
- 配置通道(如IN0、IN1,连接到PA0、PA1)。
- 设置为"Continuous Conversion Mode"(连续转换)。
- 启用"DMA Continuous Requests"。
- 配置DMA :
- 在ADC1的"DMA Settings"中,添加DMA请求:
- 选择DMA1 Channel 1(ADC1默认通道)。
- 设置:
- Mode:Circular(循环模式)。
- Data Width:Half Word(16位,ADC为12位数据)。
- Increment Address:Memory(目标地址递增)。
- Priority:Medium。
- 在ADC1的"DMA Settings"中,添加DMA请求:
- 配置时钟 :
- 设置HSE(8MHz晶振),PLL倍频到72MHz,APB2为72MHz,ADC时钟分频到12MHz(最大14MHz)。
- 生成代码 :
- 在"Project Manager"中设置项目名称、路径,选择"STM32CubeIDE"。
- 生成代码。
3.2.2 代码实现
在STM32CubeIDE中,修改main.c
实现ADC DMA采样:
c
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "usart.h"
#include "stdio.h"
#define ADC_CHANNEL_COUNT 2
uint16_t adcData[ADC_CHANNEL_COUNT]; // 存储ADC采样数据
void SystemClock_Config(void);
int main(void) {
HAL_Init();
SystemClock_Config();
MX_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init(); // 串口用于调试
// 启动ADC DMA
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcData, ADC_CHANNEL_COUNT);
while (1) {
// 在循环模式下,DMA自动更新adcData
char msg[50];
sprintf(msg, "ADC1: %u, ADC2: %u\r\n", adcData[0], adcData[1]);
HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 100);
HAL_Delay(1000); // 每秒打印一次
}
}
// DMA传输完成回调(可选)
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
// 可在此处理传输完成后的逻辑
}
说明:
HAL_ADC_Start_DMA
启动ADC和DMA,数据自动传输到adcData
数组。- 循环模式下,DMA自动更新缓冲区,CPU无需干预。
- 串口(UART1)打印ADC采样值,用于调试。
3.2.3 测试
- 烧录程序到STM32F103。
- 连接PA0/PA1到模拟信号(如电位器)。
- 在串口终端(如PuTTY,115200波特率)查看ADC采样值。
3.3 示例2:UART DMA(存储器到外设)
目标:通过DMA将SRAM中的数据发送到UART1。
3.3.1 STM32CubeMX配置
- 配置UART :
- 启用USART1(PA9-TX,PA10-RX)。
- 设置波特率(如115200),模式为异步。
- 在"DMA Settings"中,添加DMA请求:
- 选择DMA1 Channel 4(USART1_TX默认通道)。
- 设置:
- Mode:Normal(单次传输)。
- Data Width:Byte(8位,UART数据)。
- Increment Address:Memory。
- Priority:Medium。
- 生成代码 :
- 同ADC示例,生成STM32CubeIDE项目。
3.3.2 kazhuCode实现
修改main.c
实现UART DMA发送:
c
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "string.h"
uint8_t txData[] = "Hello, STM32 DMA!\r\n";
void SystemClock_Config(void);
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
// 传输完成回调
HAL_UART_Transmit(&huart1, (uint8_t*)"TX Complete\r\n", 13, 100);
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_DMA_Init();
MX_USART1_UART_Init();
// 启动UART DMA发送
HAL_UART_Transmit_DMA(&huart1, txData, strlen((char*)txData));
while (1) {
HAL_Delay(1000); // 每秒发送一次
HAL_UART_Transmit_DMA(&huart1, txData, strlen((char*)txData));
}
}
说明:
HAL_UART_Transmit_DMA
启动DMA传输,将txData
发送到UART1。- 传输完成后,触发
HAL_UART_TxCpltCallback
中断。 - 单次模式下,需手动重新启动传输。
3.3.3 测试
- 烧录程序。
- 连接USB-TTL模块(PA9->RX,PA10->TX)。
- 在串口终端查看输出"Hello, STM32 DMA!"。
4. 关键配置注意事项
- DMA初始化顺序 :
- 在
main.c
中,MX_DMA_Init
必须在其他外设初始化(如MX_ADC1_Init
、MX_USART1_Init
)之前调用,确保DMA时钟启用。
- 在
- 通道选择 :
- 每个外设有固定DMA通道(如ADC1->DMA1 Channel 1,USART1_TX->DMA1 Channel 4)。参考STM32F103参考手册。
- 数据对齐 :
- 源和目标数据宽度需匹配(如ADC为16位,缓冲区应为
uint16_t
)。
- 源和目标数据宽度需匹配(如ADC为16位,缓冲区应为
- 中断管理 :
- 启用DMA中断(如
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn)
)。 - 在中断回调中处理传输完成或错误。
- 启用DMA中断(如
- 循环模式 vs 单次模式 :
- 循环模式适合连续数据流(如ADC采样)。
- 单次模式适合一次性传输(如UART发送固定字符串)。
- 优先级冲突 :
- 多通道同时传输时,设置高优先级给关键任务(如ADC优于UART)。
5. 常见问题及解决
- DMA不工作 :
- 检查DMA通道是否与外设匹配。
- 确保
MX_DMA_Init
在其他外设初始化前调用。 - 验证源/目标地址正确,数据长度非零。
- 数据错误 :
- 检查数据宽度(Byte/Half Word/Word)是否与外设匹配。
- 确保目标缓冲区有足够空间。
- 中断未触发 :
- 确认中断使能(
HAL_DMA_Start_IT
或外设API)。 - 检查NVIC配置(优先级、使能)。
- 确认中断使能(
- 总线冲突 :
- 避免多个DMA通道同时访问同一存储器区域。
- 调整通道优先级或降低传输速率。
6. 扩展应用
- 多通道ADC:使用DMA采集多个ADC通道数据,存储到数组,适合信号处理。
- SPI DMA:实现高速SPI数据传输(如与LCD或Flash通信)。
- FreeRTOS集成:结合DMA中断和信号量,优化实时任务调度。
- 双缓冲技术:在循环模式下使用两个缓冲区交替存储数据,避免数据覆盖。
7. 学习资源
- 官方文档 :
- STM32F103参考手册(DMA章节)。
- STM32CubeMX用户手册。
- 教程 :
- 正点原子/野火的STM32 DMA教程。
- ST社区论坛的DMA应用笔记(如AN2548)。
- 工具 :
- STM32CubeMonitor:监控DMA传输数据。
- 逻辑分析仪:调试外设信号。
8. 总结
STM32F103的DMA控制器通过高效的数据传输,显著降低CPU负载,适合高吞吐量场景。DMA1支持7通道,DMA2(部分型号)支持5通道,覆盖ADC、UART、SPI等外设。使用STM32CubeMX配置DMA参数,结合HAL库(如HAL_ADC_Start_DMA
、HAL_UART_Transmit_DMA
)可快速实现传输。关键注意初始化顺序、通道选择和数据对齐,通过中断和循环模式优化性能。