学习STM32第十一天

本文详细介绍了STM32F4系列的DMA功能,包括其工作原理、M2M模式选择、数据流和通道配置,以及如何利用DMA进行数据搬运,如SRAM到SRAM、ADC到内存的数据传输。还提供了实验案例,展示了如何配置DMA以实现高效的数据传输和ADC触发操作。
摘要由CSDN通过智能技术生成

DMA

一、简介

直接存储器存取(Direct Memory Access, DMA),可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省CPU资源。F407具有两个通用双端口DMA,每个DMA有8个流,每个流最多8个通道(请求),每个通道都支持软件触发和特定的硬件触发。双AHB总线结构,一个专用于存储器访问,一个专用于外设访问。为外设配备了专用FIFO,旨在提供最大外围带宽。一句话总结DMA:协助CPU完成数据转运,原数据不会消失。
存储器映像
存储器映像

  • ROM
    • 程序存储器Flash
      主闪存,存储C语言编译后的程序代码,程序被下载到此处,MCU一般也是从这里开始运行的
    • 系统存储器
      存储BootLoader,用于串口下载,存储介质也是Flash,类似PC中的BIOS系统
    • 选项字节
      存储一些独立于程序代码的配置参数,一般是MCU的参数、读写保护、看门狗等配置
  • RAM
    • 运行内存SRAM
      存储运行过程中的临时变量,相当于PC主机的内存条
    • 外设寄存器
      存储各个外设的配置参数,即初始化各个外设最终所读写的东西,存储介质其实也是SRAM,寄存器是连接软件和硬件的桥梁
    • 内核外设寄存器
      存储内核各个外设的配置参数,像NVIC、SysTick等

存储器接口架构
DMA请求得根据存储器设备是否支持读写进行配置。只有DMA2支持M2M模式

  • 在选择数据流时,如果需要高优先级的数据传输可选择更低的数据流编号;如果需要大数据量传输,则可以选择更高编号的数据流,提高数据传输效率
  • 在选择通道时,通常情况下建议选择DMA2通道0或1,这两个通道支持循环模式并且具有高优先级
    DMA基本结构

外设和存储器两个数据站点都有三个参数

  • 起始地址
    决定数据从哪来,到哪去
  • 数据宽度
    指定依次转运要按多大得数据宽度来进行,可以选择ByteHalfWordWord。32位系统中一个字为4个字节,数据转运时还得注意对齐,不够补0,多了就舍弃
  • 地址是否自增
    指定一次转运完成后,下一次转运是否要把地址移动到下一个位置去,寄存器地址不会自增,存储器地址会自增

传输计数器是一个自减计数器,用于指定转运次数,减到0之后,会恢复到起始地址位置。自动重装器是可以循环为传输计数器装填计数次数,类似ADC的连续模式。M2M是选择软件触发还是硬件触发,软件触发一般适用于存储器到存储器的转运、硬件触发一般适用于外设寄存器的转运。写传输计数器时,必须先关闭DMA再进行。

STM32F4系列DMA1请求映像
DMA1请求映像
STM32F4系列DMA2请求映像
DMA2请求映像
每个通道都有一个数据选择器,可以选择硬件触发或软件触发。每个通道的硬件触发源都是不同的,应结合参考手册进行配置,软件触发则通道可以任意选择。

FIFO
必须关闭Direct Mode,才能使用FIFO。当下游模块无法及时处理上流模块输出的数据时,此时需要用FIFO暂存数据,防止数据丢失。每一个流有着一个独立的4字大小的FIFO,通过软件配置阈值。

二、实验案例

  1. 数据转运+DMA
    数据转运DMA
    将SRAM中数据DataA转运到另一个数组DataB中参数配置:起始地址即为数组地址,数据宽度保持一致,地址需要自增,传输计数器次数为7,不需要自动重装,采用软件触发。由于是软件触发,通道和流可以任意选择。自动重装和软件触发不能同时使用
    STM32F4系列DMA配置代码如下
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
	DMA_InitTypeDef DMA_InitStructure;
	//DMA_InitStructure成员非常多,根据手册进行配置
	DMA_InitStructure.DMA_BufferSize = Size;//缓存区大小,即传输计数器指定传输计次
	DMA_InitStructure.DMA_Channel = DMA_Channel_1;//DMA数据流通道选择,通道0和1具有高优先级
	DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory;//传输方向,这里是SRAM中两个数组内容转换,M2M模式
	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//使用M2M模式时,FIFO硬件开启,软件控制不了
	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//FIFO阈值和存储器突发大小必须配合使用,是内存突发传输数据量的整数倍
	DMA_InitStructure.DMA_Memory0BaseAddr = AddrB;//目的地地址
	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//单次模式,突发规定的是指针每次增加的单位宽度是多少,将指针自增由指令控制封装为硬件突发模式进行
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据宽度,这里选择字节的方式传输
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//使能自增
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//一次传输,软件触发下的M2M模式不能使用循环传输(自动重装)
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//数据源地址
	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//单次模式
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设寄存器数据宽度,这里选择字节的方式传输
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//使能自增
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;//选择高优先级
	DMA_Init(DMA2_Stream1, &DMA_InitStructure);//由于是软件触发,低编号优先级高,这里给流1
	DMA_Cmd(DMA2_Stream1, ENABLE);
}

主函数main代码如下

uint8_t DataA[] = {0x11, 0x21, 0x31, 0x41};
uint8_t DataB[] = {0, 0, 0, 0};
int main()
{
	OLED_Init();
	for(uint8_t i = 0; i < 4; i++) OLED_ShowHexNum(1, 1 + 3 * i, DataA[i], 2);
	for(uint8_t i = 0; i < 4; i++) OLED_ShowHexNum(2, 1 + 3 * i, DataB[i], 2);
	MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
	for(uint8_t i = 0; i < 4; i++) OLED_ShowHexNum(3, 1 + 3 * i, DataA[i], 2);
	for(uint8_t i = 0; i < 4; i++) OLED_ShowHexNum(4, 1 + 3 * i, DataB[i], 2);
	while(1)
	{
	}
}
  1. ADC扫描模式+DMA
    ADC扫描模式+DMA
    ADC7个通道依次进行AD转换,转换结果存放在ADC_DR数据寄存器中,因此需要做的就是在每个单独的通道转换完成后进行一个DMA数据转运,并且目的地址进行自增。DMA通过ADC硬件触发,计数器是否自动重装取决于ADC的配置,DMA转运时机需要和ADC单个通道转换完成同步,ADC通道转换完会产生DMA请求,用以触发DMA转运。
    这里是ADC外设寄存器到存储器的数据转运,因此可以使用DMA1,DMA代码配置如下
uint16_t AD_Value[2];//将外设数据转运到SRAM数组中
void ADC_MYInit()
{
	//开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
	//配置GPIO
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//ADC规则通道配置,多通道要设置次序
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_3Cycles);//规则通道0,次序1,采样时间为56个采样周期
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_3Cycles);//规则通道0,次序1,采样时间为56个采样周期
	//通用ADC配置
	ADC_CommonInitTypeDef ADC_CommonInitStructure;
	ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1;//使用DMA模式1
	ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;//独立ADC模式
	ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div6;//6分频,72MHz/6 = 12MHz
	ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//采样延时
	ADC_CommonInit(&ADC_CommonInitStructure);
	//配置ADC1,使用扫描模式
	ADC_InitTypeDef ADC_InitStructure;
	ADC_StructInit(&ADC_InitStructure);
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
	ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止触发检测,使用软件触发
	ADC_InitStructure.ADC_NbrOfConversion = 2;//所用2个通道数量,仅在扫描模式下使用
	ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//采用12位分辨率
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;//扫描模式
	ADC_Init(ADC1, &ADC_InitStructure);
	//DMA配置
	DMA_InitTypeDef DMA_InitStructure;
//	DMA_StructInit(&DMA_InitStructure);
	DMA_InitStructure.DMA_BufferSize = 2;//缓存区大小,即传输计数器指定传输计次
	DMA_InitStructure.DMA_Channel = DMA_Channel_0;//ADC1外设只能选择通道0
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设寄存器到存储器
	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
	DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)AD_Value;
	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//与DMA_PeripheralBurst相同
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//同样也是半个字
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器是连续的,需要自增
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//循环模式
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//ADC1数据寄存器地址
	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//突发规定的是指针单次增加的单位宽度,将指针自增由指令控制封装为硬件突发模式
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//DR低16位的数据,则外设数据宽度为半个字
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设寄存器不是连续的,不自增
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;//这里选择高优先级
	DMA_Init(DMA2_Stream0, &DMA_InitStructure);//ADC1外设只能选择DMA2的Channel0和Stream0
	DMA_Cmd(DMA2_Stream0, ENABLE);

	ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
	ADC_DMACmd(ADC1, ENABLE);//开启ADC到DMA的输出
	//F4系列不需要校准,内部自动校准
	ADC_Cmd(ADC1, ENABLE);//硬件触发DMA
	ADC_SoftwareStartConv(ADC1);//软件触发
}
  • 35
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值