文章目录
- DMA简介
- DMA定义
- DMA传输方式
- DMA传输参数
- STM32的存储器映像
- DMA基本结构
- DMA的具体应用
- 数据转运 + DMA
- ADC扫描模式 + DMA
- DMA库函数
- DMA数据传输(数据转运 + DMA)
- 接线图
- MyDMA模块
- main.c 源程序
- DMA + AD多通道(ADC扫描模式 + DMA)
- 接线图
- AD模块
- main.c 源程序
DMA简介
DMA,全称Direct Memory Access,即直接存储器访问
DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输
DMA 这个外设是可以直接访问 STM32 内部的存储器的,包括运行内存 SRAM、程序存储器 Flash 和寄存器等等
CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,就能够更好的利用CPU的资源,因此:转移数据(尤其是转移大量数据)是可以不需要CPU参与,比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理,DMA就是基于以上设想设计的,它的作用就是解决大量数据转移过度消耗CPU资源的问题。有了DMA使CPU更专注于更加实用的操作:计算、控制等
DMA定义
DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输
无须CPU的干预,通过DMA数据可以快速地移动,这就节省了CPU的资源来做其他操作
DMA传输方式
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)
四种情况的数据传输如下:
- 外设到内存
- 内存到外设
- 内存到内存
- 外设到外设(仅限于一些高级的DMA可以实现 ,传统DMA可以实现以上三种)
DMA传输参数
数据传输,首先需要的是 ①数据的源地址 ②数据传输位置的目标地址 ,③传递数据多少的数据传输量 ,④进行多少次传输的传输模式 DMA所需 要的核心参数,便是这四个
当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为 0 时 达到传输终点,结束DMA传输 ,当然,DMA 还有循环传输模式 当到达传输终点时会重新启动DMA传输
也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输
以上内容参考 DMA原理,步骤超细详解,一文看懂DMA
原文链接:https://blog.csdn.net/as480133937/article/details/104927922
DMA的更多详细的介绍也可以看这篇文章
对于大容量的STM32芯片有2个DMA控制器 两个DMA控制器(一共有 12 个独立可配置的通道),DMA1有7个通道,DMA2有5个通道,每个通道都可以配置一些外设的地址
通道就是数据转运的路径,数据从一个地方移动到另一个地方,就需要占用一个通道,各个通道之间互不干扰,每个通道都支持软件触发和特定的硬件触发(每个DMA通道的硬件触发源是不一样的)(存储器到存储器之间的转运一般使用软件触发,外设到存储器的转运一般使用硬件触发 —— 因为外设的数据是有一定时机的)
STM32F103C8T6 DMA 资源:DMA1(7个通道)
STM32的存储器映像
DMA是在存储器之间进行数据转运的,我们就需要了解STM32中有哪些存储器,这些存储器又是被安排到了哪些地址上
STM32中所有类型的存储器及它们所被安排的地址:
存储器总共分为两大类,ROM 和 RAM,ROM是只读存储器、掉电不丢失,RAM是随机存储器、掉电丢失
DMA基本结构
【注意】自动重装和软件触发不能同时使用
DMA数据传输有 3 个条件:
- 传输计数器大于0
- 触发源有触发信号
- DMA使能
DMA的具体应用
数据转运 + DMA
ADC扫描模式 + DMA
DMA库函数
/*初始化函数********************************************************************/
void DMA_DeInit(DMA_Stream_TypeDef* DMAy_Streamx);
//恢复缺省配置
void DMA_Init(DMA_Stream_TypeDef* DMAy_Streamx, DMA_InitTypeDef* DMA_InitStruct);
//初始化DMA通道外设寄存器地址、数据存储器地址、数据传输的方向、传输的数据量、外设和存储器的增量模式、外设和存储器的数据宽度、是否开启循环模式
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
//结构体初始化
/*使能函数**********************************************************************/
void DMA_Cmd(DMA_Stream_TypeDef* DMAy_Streamx, FunctionalState NewState);
//用于DMA使能
void DMA_ITConfig(DMA_Stream_TypeDef* DMAy_Streamx, uint32_t DMA_IT, FunctionalState NewState);
//用于中断输出使能
/*传输数据量函数******************************************************************/
void DMA_SetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx, uint16_t Counter);
//设置DMA通道的传输数据量(DMA处于关闭状态)
uint16_t DMA_GetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx);
//获取当前DMA通道传输剩余数据量(DMA处于开启状态)
/*状态位函数*********************************************************************/
//获取DMA通道的各种状态位,并能清除这些状态位
FlagStatus DMA_GetFlagStatus(DMA_Stream_TypeDef* DMAy_Streamx, uint32_t DMA_FLAG);
void DMA_ClearFlag(DMA_Stream_TypeDef* DMAy_Streamx, uint32_t DMA_FLAG);
ITStatus DMA_GetITStatus(DMA_Stream_TypeDef* DMAy_Streamx, uint32_t DMA_IT);
void DMA_ClearITPendingBit(DMA_Stream_TypeDef* DMAy_Streamx, uint32_t DMA_IT);
关于DMA库函数更详细的介绍可以参考文章: STM32 DMA配置库函数
DMA数据传输(数据转运 + DMA)
接线图
MyDMA模块
MyDMA.c
#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开启时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/*第二步:初始化DMA*/
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; //DIR (Direction):传输方向(DMA_DIR_PeripheralSRC:外设站点作为数据源)
DMA_InitStructure.DMA_BufferSize = Size; //BufferSize:缓冲区大小,就是传输计数器
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //Mode:传输模式,即是否使用自动重装
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; //M2M:选择硬件触发还是软件触发
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //Priority:优先级
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);
}
MyDMA.h
#ifndef __MYDMA_H
#define __MYDMA_H
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(void);
#endif
main.c 源程序
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};
uint8_t DataB[] = {0, 0, 0, 0};
int main(void){
OLED_Init();
MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
OLED_ShowString(1, 1, "DataA");
OLED_ShowString(3, 1, "DataB");
OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
while(1){
DataA[0] ++;
DataA[1] ++;
DataA[2] ++;
DataA[3] ++;
OLED_ShowHexNum(2, 1, DataA[0], 2);
OLED_ShowHexNum(2, 4, DataA[1], 2);
OLED_ShowHexNum(2, 7, DataA[2], 2);
OLED_ShowHexNum(2, 10, DataA[3], 2);
OLED_ShowHexNum(4, 1, DataB[0], 2);
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000);
MyDMA_Transfer();
OLED_ShowHexNum(2, 1, DataA[0], 2);
OLED_ShowHexNum(2, 4, DataA[1], 2);
OLED_ShowHexNum(2, 7, DataA[2], 2);
OLED_ShowHexNum(2, 10, DataA[3], 2);
OLED_ShowHexNum(4, 1, DataB[0], 2);
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000);
}
}
DMA + AD多通道(ADC扫描模式 + DMA)
接线图
AD模块
AD.c
#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 = DISABLE;
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_Normal;
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);
}
void AD_GetValue(void){
DMA_Cmd(DMA1_Channel1,DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, 4);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}
AD.h
#ifndef __AD_H
#define __AD_H
extern uint16_t AD_Value[4];
void AD_Init(void);
void AD_GetValue(void);
#endif
main.c 源程序
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
int main(void){
OLED_Init();
AD_Init();
OLED_ShowString(1, 1, "AD0:");
OLED_ShowString(2, 1, "AD1:");
OLED_ShowString(3, 1, "AD2:");
OLED_ShowString(4, 1, "AD3:");
while(1){
AD_GetValue();
OLED_ShowNum(1, 5, AD_Value[0], 4);
OLED_ShowNum(2, 5, AD_Value[1], 4);
OLED_ShowNum(3, 5, AD_Value[2], 4);
OLED_ShowNum(4, 5, AD_Value[3], 4);
Delay_ms(100);
}
}
STM32 专栏文章均参考 《STM32入门教程-2023版 细致讲解 中文字幕》教程视频