当前位置: 首页 > article >正文

DMA直接存储器存取

参考视频:[8-1] DMA直接存储器存取_哔哩哔哩_bilibili

DMA简介

DMADirect Memory Access)直接存储器存取

DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源

12个独立可配置的通道:DMA1(7个通道),DMA2(5个通道)

每个通道都支持软件触发和特定的硬件触发

存储器到存储器的转运:一般使用软件触发

外设到存储器的数据转运:一般使用硬件触发

STM32F103C8T6 DMA资源:DMA1(7个通道)

存储器映像

  1. ROM是只读存储器,是一种非易失性、掉电不丢失的存储器。
  2. RAM是随机存储器,是一种易失性、掉电丢失的存储器。

DMA框图

Cortex-M3内核,包含了CPU和内核外设;其余的都可以看成存储器。

Flash是主闪存,SRAM是运行内存。各个外设可以看成是寄存器,也是一种SRAM存储器。

寄存器可以看成是一种特殊的存储器:

  • 一方面,CPU可以对寄存器进行读写。

  • 另一方面,寄存器的每一位背后,都连接了一根导线,这些导线可以用于控制外设电路的状态,比如置高低电平、导通和断开开关、切换数据选择器等等。

所以寄存器是连接软件和硬件的桥梁。软件读写寄存器,就当于在控制硬件的执行。

总线矩阵的左端,是主动单元,也就是拥有存储器的访问权;右端的是被动单元,它们的存储器只能被左边的主动单元读写。

DCode总线是专门访问Flash的,系统总线是访问其他东西的。

由于DMA要转运数据,DMA也必须要有访问的主动权。

DMA1、DMA2、以太网MAC都有一条DMA总线。

DMA1有七条通道,DMA2有五条通道,各个通道可以分别设置它们转运数据的源地址和目的地址。

仲裁器存在的原因:虽然有多个通道可以独立转运数据,但是最终DMA总线只有一条,所以所有的通道都只能分时复用这一条DMA总线,如果产生了冲突,那就会由仲裁器,根据通道的优先级来决定谁先用谁后用。

另外,在总线矩阵这里也有仲裁器,如果DMA和CPU都要访问同一个目标,那么DMA就会暂停CPU的访问,以防止冲突,不过总线仲裁器,仍然会保证CPU得到一半的总线带宽,保证CPU也会正常的工作。

AHB从设备,也就是DMA自身的寄存器。因为DMA作为一个外设,它自己也会有相应的配置寄存器。AHB从设备连接在了总线右边的AHB总线上。

所以DMA既是总线矩阵的主动单元,可以读写各种存储器,也是AHB总线上的被动单元。

DMA请求,也就是DMA触发,线路右边的触发源是各个外设,所以DMA请求就是DMA的硬件触发源,比如ADC转换完成,串口接收到数据。需要触发DMA转运数据的时候,就会通过这条线路,向DMA发出硬件触发信号,之后DMA就可以执行数据转运的工作了。

DMA基本结构

方向参数是用来控制从外设到存储器,还是从存储器到外设。另外还有一种转运方式,就是存储器到存储器。由于Flash是只读的,所以只能进行从SRAM到SRAM,或者Flash到SRAM。

外设存储器两个站点,都有三个参数:

起始地址:有外设端的起始地址,存储器端的起始地址。这两个参数决定了数据是从哪里来到哪里去的。

数据宽度:指定一次转运要按多大的数据宽度来进行,可以选择字节、半字和字。字节是8位,半字是16位,字是32位。

地址是否自增:指定一次转运完成后,下一次转运,是不是要把地址移动到下一个位置去。

传输计数器:这个是用来指定总共需要转运几次,是一个自减计数器。当减到0之后,之前自增的地址,也会恢复到起始地址的位置。

自动重装器:传输计数器减到0之后,是否要自动恢复到最初的值。不重装就是单次模式,重装就是循环模式。

触发,就是决定DMA需要在什么时机进行转运的。触发源有硬件触发和软件触发,具体选择哪个由M2M来决定。

给M2M位1时,DMA就会选择软件触发,软件触发是指以最快的速度,连续不断地触发DMA,争取早日把传输计数器清零,完成这一轮的转换(这里的软件触发和之前的ADC和外部中断的软件触发不太一样)。软件触发和循环模式不能同时用。软件触发一般是存储器到存储器的转运。

硬件触发源可以选择ADC、串口、定时器等等。硬件触发源的转运一般都是与外设有关的转运,这些转运需要一定的时机,比如ADC转换完成、串口收到数据、定时时间到等等,在硬件达到这些时机时,传一个信号过来,来触发DMA进行转运。

开关控制,DMA_Cmd函数,使能之后就可以进行DMA转运了。

DMA进行转运的几个条件:

1. 开关控制,DMA_Cmd必须使能。

2. 传输计数器必须大于0。

3. 触发源,必须有触发信号。

触发一次,转运一次,传输计数器自减一次,当传输计数器等于0,且没有自动重装时,这时无论是否触发,DMA都不会再进行DMA转运了。此时就需要DMA_Cmd,给DISABLE,关闭DMA,再为传输计数器写一个大于0的数,再DMA_Cmd,给ENABLE,开启DMA,DMA才能继续工作。注意一下,写传输计数器时,必须要先关闭DMA,再进行。

DMA1请求映像

DMA1的七个通道,每个通道都有一个数据选择器,可以选择硬件触发或者软件触发。EN是开关控制;M2M位是数据选择器的控制位,用于选择是硬件触发还是软件触发。

外设请求信号那里,每个通道的硬件触发源都是不同的,如果需要用ADC1来触发的话,就必须选择通道1,如果需要定时器2的更新事件来触发的话,就必须选择通道2。如果用软件触发,就可以任意选择通道了。

通道1,选择哪个硬件触发源是由对应的外设是否开启了DMA输出来决定的,比如要使用DMA1,那会有个库函数ADC_DMACmd,必须使用这个库函数开启ADC1的这一路输出,它才有效。如果都开启了,理论上3个硬件都可以触发。

之后七个通道进入仲裁器,进行优先级判断,最终产生内部的DMA1请求。

数据宽度与对齐

此部分是为了解决外设寄存器和存储器的数据宽度不一致问题。

当目标宽度比源端宽度大,就在目标数据前面多出来的空位补0。

当目标宽度比源端宽度小,把源端多出来的高位舍弃掉。

数据转运+DMA

首先是外设站点和存储器站点的起始地址、数据宽度、地址是否自增。

在这个例子里,外设地址显然应该填DataA数组的首地址。存储器地址给DataB数组的首地址。

数据宽度,两个数组的类型都是uint8_t,所以数据宽度都是按8位的字节传输。

由下图可以看出,两边地址都应该自增。

方向参数是外设站点转移到存储器站点。

显然要转运7次,所以传输计数器给7,自动重装暂时不需要。

这里选择软件触发,因为是存储器到存储器的转运,不需要等待硬件时机。

最后调用DMA_Cmd,给DMA使能。

ADC扫描模式+DMA

触发一次后,七个通道依次进行AD转换,然后转换结果都放到ADC_DR数据寄存器里面,在每个单独的通道转换完成后,进行一次DMA数据转运,并且目的地址进行自增。

所以DMA的配置:

外设地址写入ADC_DR这个寄存器的地址。存储器的地址,可以在SRAM中定义一个数组ADValue,然后把ADValue的地址当作存储器的地址。

数据宽度,因为ADC_DR和SRAM数组,都是uint16_t的数据,所以数据的宽度都是16位的半字传输。

地址是否自增:显然外设地址不自增,存储器地址自增。

传输方向是外设站点到存储器站点。

传输计数器,通道有7个,所以计数7次,计数器是否自动重装,可以看ADC的配置,如果ADC是单次扫描,那DMA的传输计数器可以不自动重装,转换一轮就停止,如果ADC是连续扫描,那么DMA就可以使用自动重装。在ADC启动下一轮的转换时,DMA也启动下一轮的转运。

触发选择:ADC_DR的值是在ADC单个通道转换完成后才会有效,所以DMA转运的时机,需要和ADC单个通道转换完成同步。所以DMA的触发要选择ADC的硬件触发。ADC扫描模式,在每个单独的通道转换完成后,没有任何标志位,也不会触发中断,但是它会产生DMA请求,去触发DMA转运。

DMA.h中的函数

// 恢复缺省配置
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);
// 初始化
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
// 结构体初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
// 使能
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
// 中断输出使能
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
// DMA设置当前数据寄存器
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 
// 给传输计数器写入数据,之后DMA获取当前数据寄存器
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
// 获取标志位状态
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
// 清除标志位
void DMA_ClearFlag(uint32_t DMAy_FLAG);
// 获取中断状态
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
// 清除中断挂起位
void DMA_ClearITPendingBit(uint32_t DMAy_IT);

DMA数据转运

  1. RCC开启DMA的时钟
  2. 调用DMA_Init,初始化各个参数
  3. 进行开关控制,DMA_Cmd

接线图:

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_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_VeryHigh;
	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);
}

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多通道

接线图:

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_ContinuousConvMode = ENABLE;  // 非连续转换模式
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  // 对齐方式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 触发方式
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	ADC_InitStructure.ADC_NbrOfChannel = 4;  // 通道数目
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;  // 扫描转换模式
	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_VeryHigh;
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
	DMA_Cmd(DMA1_Channel1, ENABLE);
	ADC_DMACmd(ADC1, ENABLE);
	ADC_Cmd(ADC1, ENABLE);
	
	// 调用复位校准
	ADC_ResetCalibration(ADC1);
	// 等待复位校准完成,该函数是返回复位校准的状态,所以要等待复位校准完成的话,要加一个while循环
	while (ADC_GetResetCalibrationStatus(ADC1) == SET) ;
	// 开始校准
	ADC_StartCalibration(ADC1);
	// 等待校准完成
	while (ADC_GetCalibrationStatus(ADC1) == SET);
}

// 首先软件触发,然后等待转换完成,也就是等待EOC标志位置1,最后读取ADC数据寄存器
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);
}

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);
	}
}

http://www.kler.cn/news/334580.html

相关文章:

  • CertiK《Hack3d:2024年第三季度安全报告》(附报告全文链接)
  • SpringBoot在线教育系统:架构设计与技术选型
  • 基于auth2的单点登录原理理解
  • 抽象类和接口以及异常处理
  • 【平方差 / C】
  • Python Flask 和 Django 的区别与适用场景
  • 论文翻译 | Model-tuning Via Prompts Makes NLP Models Adversarially Robust
  • android + tflite 分类APP开发-2
  • 【Spring Boot React】Spring Boot和React教程 完整版
  • OpenGL ES 索引缓冲区(4)
  • picgo + typora + gitee图床
  • vscode+stfp插件,实现远程自动同步文件代码
  • D - Laser Marking
  • 【代码模板】统计数据集的均值和标准差
  • C++面试速通宝典——16
  • Spring Boot大学生就业招聘系统的设计与优化
  • 9.29 LeetCode 3304、3300、3301
  • Kubernetes云原生存储解决方案之 Rook Ceph实践探究
  • 【可答疑】基于51单片机的智能台灯(含仿真、代码、报告、演示视频等)
  • 用Spring Boot搭建IT交流与学习平台