stm32如何通过DMA转运数据
文章目录
1. 什么是DMA?
DMA是“直接内存访问”(Direct Memory Access)的缩写。它是一种让计算机硬件外设(如硬盘驱动器、声卡、网络接口卡等)可以直接与系统内存进行数据交换的技术,而不需要中央处理器(CPU)介入并执行数据传输。
在传统的数据传输过程中,外设需要通过CPU来读取或写入内存中的数据。这种方式会占用CPU的时间和资源。相比之下,DMA技术允许外设直接与内存通信,从而大大减轻了CPU的负担,并提高了系统的整体性能和效率。
DMA的工作流程通常如下:
- 外设请求DMA控制器发起一个DMA传输。
- DMA控制器向CPU申请控制总线。
- CPU将总线控制权交给DMA控制器。
- DMA控制器控制数据从外设到内存(或反之)的传输。
- 传输完成后,DMA控制器释放总线控制权,CPU恢复对总线的控制。
DMA广泛应用于各种计算机系统中,特别是在需要高速数据传输的应用场景中。例如,在高速网络通信、音频处理、图像渲染等领域,DMA都是不可或缺的关键技术之一。
2. stm32中的DMA
STM32系列微控制器中的DMA(Direct Memory Access,直接存储器访问)是一个非常重要的硬件组件,它允许数据在外部设备和内存之间或者内存和内存之间进行高速传输,而无需CPU的直接参与。这有助于减少CPU的负载,提高系统的整体效率。
1. 特点
-
DMA控制器:STM32中的DMA控制器通常包含多个DMA控制器,例如DMA1和DMA2。这些控制器各自拥有一组DMA通道,每个通道可以用于特定的外设。
-
DMA通道:每个DMA控制器通常包含多个DMA通道,例如DMA1可能有7个通道,而DMA2可能有5个通道。这些通道可以分配给不同的外设使用。
-
数据传输类型:DMA支持不同类型的传输,比如从外设到内存、从内存到外设以及内存到内存的传输。
-
增量模式:DMA支持源地址和目标地址的自动递增或递减,以适应不同类型的数据传输需求。
-
中断管理:DMA传输完成后可以触发中断,通知CPU完成状态,以便CPU执行进一步的操作。
-
错误处理:DMA支持错误检测机制,例如数据校验错误。
-
优先级管理:DMA通道支持优先级设定,以解决多个DMA请求同时发生时的冲突问题。
2. 使用步骤
-
使能时钟:首先需要使能相关的DMA时钟。
-
配置DMA通道:
- 配置外设地址和内存地址。
- 设置传输方向(外设到内存、内存到外设或内存到内存)。
- 设置传输数量。
- 选择合适的增量模式(如果适用)。
- 设置传输优先级。
- 配置中断选项。
-
启动DMA传输:初始化DMA配置后,可以通过软件启动DMA传输。
-
处理中断:如果启用了中断,需要编写中断服务程序来处理DMA完成事件或其他异常情况。
-
传输完成:DMA传输完成后,根据应用需求进行后续处理。
3. stm32的存储器映像
STM32微控制器的存储器映像是指其内部和外部存储器及外设寄存器如何被映射到处理器的地址空间中。理解存储器映像是编程STM32微控制器的基础之一,因为它决定了如何访问存储器和外设。
1. 地址空间
- STM32拥有4GB的线性地址空间(2^32 = 4GB)。
- 这个地址空间被分为8个512MB的块(block),每个块大小为512MB。
2. 存储器区域
- Flash: 通常用于存放程序代码。
- 例如,STM32F10x的某些型号的Flash大小为256KB,位于地址
0x08000000
至0x0807FFFF
之间。
- 例如,STM32F10x的某些型号的Flash大小为256KB,位于地址
- SRAM: 用于存放运行时的数据。
- 例如,STM32F10xxx内置20K字节的静态SRAM,起始地址为
0x2000 0000
。
- 例如,STM32F10xxx内置20K字节的静态SRAM,起始地址为
- System Memory: 通常用于存放一些特殊的功能模块,如调试寄存器等。
- 例如,STM32F10x的系统存储区大小为2KB,位于地址
0x1FFFF000
至0x1FFFF7FF
之间。
- 例如,STM32F10x的系统存储区大小为2KB,位于地址
- Option Bytes: 用于存放一些配置信息,如用户密码、读保护设置等。
- 例如,STM32F10x的选项字节大小为16字节,位于地址
0x1FFFF800
至0x1FFFF80F
之间。
- 例如,STM32F10x的选项字节大小为16字节,位于地址
3. 外设映像
- 外设寄存器也被映射到地址空间中,通常位于特定的地址范围内。
- 每个外设都有一个基地址,通过这个基地址加上偏移量可以访问到具体的寄存器。
- 例如,GPIO端口A的基地址可能是
0x40020000
,而端口A的数据输出寄存器(ODR)可能位于0x4002040C
(基地址加上0x0C的偏移量)。
4. 其他重要信息
- 启动映像: 根据不同的启动方式(例如从Flash启动或从SRAM启动),不同的地址空间会被映射为有效的启动地址。
- 未使用的地址空间: 除了上述提到的已分配区域之外,其他未使用的地址空间通常被视为无效或保留。
5. 示例
以STM32F10x为例,其存储器映像可以总结如下:
0x00000000
至0x07FFFFFF
: 映射区,不同启动方式映射到不同范围。0x08000000
至0x0807FFFF
: 512KB Flash,用于外部Flash。0x1FFFF000
至0x1FFFF7FF
: 2KB System Memory,系统存储区。0x1FFFF800
至0x1FFFF80F
: 16B Option Bytes,选项字节。0x20000000
至0x20004FFF
: 20KB SRAM,静态RAM。
4. DMA基本结构
1 DMA控制器
STM32F1系列的DMA控制器通常包含两个独立的DMA控制器:DMA1和DMA2。每个控制器都有自己的DMA通道,这些通道可以分配给不同的外设使用。
- DMA1:通常包含7个DMA通道。
- DMA2:通常包含5个DMA通道。需要注意的是,DMA2只存在于大容量的STM32F1系列微控制器中。
2. DMA通道
每个DMA通道都具有以下特点:
- 独立的硬件请求:每个DMA通道都直接连接到专用的硬件DMA请求。
- 软件触发:除了硬件请求外,还可以通过软件触发DMA传输。
- 配置选项:每个通道都可以配置为不同的数据宽度(如字节、半字或字)、传输方向(从外设到内存、从内存到外设或从内存到内存)以及其他参数。
3. DMA传输类型
DMA支持多种类型的传输,包括:
- 外设到内存:数据从外设传输到内存。
- 内存到外设:数据从内存传输到外设。
- 内存到内存:数据在内存之间进行传输。
4. DMA配置
配置DMA通常涉及以下几个步骤:
- 使能时钟:确保相关的DMA时钟已经启用。
- 初始化DMA通道:
- 配置外设地址和内存地址。
- 设置传输方向。
- 设置传输数量。
- 选择增量模式(如果适用)。
- 设置优先级。
- 配置中断选项。
- 启动DMA传输:初始化DMA配置后,可以通过软件启动DMA传输。
5. DMA操作
一旦配置好DMA,它就可以开始工作。数据传输可以在DMA请求到来时启动,也可以通过软件命令启动。在传输过程中,DMA控制器负责管理数据流,包括地址递增或递减、数据长度管理等。
6. 中断处理
DMA传输完成后,可以触发相应的中断,通知CPU完成状态。这样,CPU可以在DMA传输完成时执行必要的处理。
3. DMA转运数据
1. 存储器到存储器
#include "stm32f10x.h" // Device header
uint16_t MyDMA_Size;
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
MyDMA_Size = Size;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = Size;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, DISABLE);
}
void MyDMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
DMA_Cmd(DMA1_Channel1, ENABLE);
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}
1. 代码解释
#include "stm32f10x.h" // 设备头文件
这一行包含了特定于设备的头文件,该文件包含了访问 STM32F10x 微控制器硬件外设所需的全部定义和原型。
uint16_t MyDMA_Size;
这定义了一个 uint16_t
类型的全局变量 MyDMA_Size
,用来保存 DMA 转移的数据大小。
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
MyDMA_Size = Size;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = Size;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, DISABLE);
}
这是 MyDMA_Init
函数,用于初始化 DMA 通道 1。
MyDMA_Size = Size;
: 设置全局变量MyDMA_Size
为传入的参数Size
。RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
: 启用 DMA1 的时钟。- 初始化
DMA_InitTypeDef
结构体DMA_InitStructure
,并设置各个字段:DMA_PeripheralBaseAddr
: 外设基地址,这里是AddrA
。DMA_PeripheralDataSize
: 指定每次传输的数据大小(字节)。DMA_PeripheralInc
: 如果启用,则每次传输后,外设地址自动递增。DMA_MemoryBaseAddr
: 内存基地址,这里是AddrB
。DMA_MemoryDataSize
: 指定每次传输的数据大小(字节)。DMA_MemoryInc
: 如果启用,则每次传输后,内存地址自动递增。DMA_DIR
: 数据传输方向,这里是外设到内存。DMA_BufferSize
: DMA 缓冲区大小,这里使用了全局变量MyDMA_Size
。DMA_Mode
: 工作模式,这里是普通模式。DMA_M2M
: 启用内存到内存模式。DMA_Priority
: DMA 请求优先级,这里是中等优先级。
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
: 使用上述配置初始化 DMA 通道 1。DMA_Cmd(DMA1_Channel1, DISABLE);
: 禁用 DMA 通道 1。
void MyDMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
DMA_Cmd(DMA1_Channel1, ENABLE);
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}
这是 MyDMA_Transfer
函数,用于启动 DMA 传输。
DMA_Cmd(DMA1_Channel1, DISABLE);
: 禁用 DMA 通道 1。DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
: 设置当前数据计数器为全局变量MyDMA_Size
。DMA_Cmd(DMA1_Channel1, ENABLE);
: 启用 DMA 通道 1。while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
: 等待 DMA 完成标志位被置位。DMA_ClearFlag(DMA1_FLAG_TC1);
: 清除 DMA 完成标志位。
2. DMA+AD多通道
#include "stm32f10x.h" // Device header
uint16_t AD_Value[4];
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_NbrOfChannel = 4;
ADC_Init(ADC1, &ADC_InitStructure);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 4;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
1. 代码解释
#include "stm32f10x.h" // Device header
这行代码包含了一个特定于STM32F10x微控制器的头文件,该文件包含了所有必要的寄存器定义和函数声明。
uint16_t AD_Value[4];
这定义了一个数组 AD_Value
,用于存储从ADC通道读取的4个模拟值。
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
这部分代码启用了ADC1、GPIOA端口和DMA1的时钟。
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
这行设置了ADC的时钟频率,即APB2时钟频率除以6。
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
这部分初始化了GPIOA端口的引脚0至3为模拟输入模式,并且配置了最大速度为50MHz。
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
这些调用配置了四个ADC通道(0至3),每个通道的采样顺序和采样时间为55个周期。
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_NbrOfChannel = 4;
ADC_Init(ADC1, &ADC_InitStructure);
这部分配置了ADC的基本操作模式,包括独立模式、右对齐的数据格式、无外部触发、连续转换模式、扫描模式和4个通道。
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 4;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
这部分配置了DMA1通道1,用于将ADC1的数据寄存器中的数据传输到 AD_Value
数组。以下是各配置项的含义:
DMA_PeripheralBaseAddr
: 外设基地址,指向ADC1的数据寄存器。DMA_PeripheralDataSize
: 指定每次传输的数据大小(半字)。DMA_PeripheralInc
: 禁止外设地址自动递增。DMA_MemoryBaseAddr
: 内存基地址,指向AD_Value
数组。DMA_MemoryDataSize
: 指定每次传输的数据大小(半字)。DMA_MemoryInc
: 允许内存地址自动递增。DMA_DIR
: 数据传输方向,从外设到内存。DMA_BufferSize
: DMA缓冲区大小,这里为4。DMA_Mode
: 工作模式,循环模式。DMA_M2M
: 不启用内存到内存模式。DMA_Priority
: DMA请求优先级,中等优先级。
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
这三行代码分别启用了DMA1通道1、ADC1的DMA功能和ADC1本身。
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
这部分代码首先重置ADC1的校准状态,然后等待校准状态完成,接着启动ADC1的校准过程,最后等待校准完成。
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
这行代码启动ADC1的软件触发转换。
通过以上步骤,ADC1和DMA1通道1就被正确地配置好了,可以开始进行连续的模拟信号采样并将结果存储到 AD_Value
数组中。