【STM32学习】——DMA数据转运&存储器映像&DMA请求&数据宽度于对齐&数据转运+DMA实操&ADC扫描模式+DMA实操


声明:学习笔记根据b站江科大自化协stm32入门教程编辑,仅供学习交流使用!

前言

本次学习共有两个程序。第一个为DMA数据转运,使用DMA进行存储器到存储器的数据转运,即把一个数组里的数据复制到另一个数组里。
第二个为DMA+AD多通道,


一、DMA简介

1.概述

1、DMA(Direct Memory Access)直接存储器存取或访问。从名字看,DMA这个外设可以直接访问STM32内部的存储器,包括运行内存SRAM、程序存储器Flash和寄存器等等。


2、DMA可以提供外设存储器或者存储器存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。外设指外设的寄存器,一般是外设的数据寄存器DR(Data Register),比如ADC的数据寄存器、串口的数据寄存器等等。存储器指运行内存SRAM和程序存储器Flash,是存储变量数组和程序代码的地方。


3、12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)。通道是指数据转运的路径,从一个地方移动到另一个地方就需要占用一个通道,如果有多个通道进行转运,之间互不干扰。


4、每个通道都支持软件触发特定的硬件触发如果DMA进行的是存储器到存储器的数据转运,比如想把Flash里的一批数据转运到SRAM,就需要软件触发(DMA会一股脑地把这批数据以最快速度全部转运完成。如果DMA进行得是外设到存储器的数据转运就不可一股脑地转运,因为外设数据有一定时机,这时需要用硬件触发。比如转运ADC的数据,就需要ADC每个通道AD转换完成后硬件触发一次DMA,之后DMA再转运。触发一次转运一次,这样数据才是正确的。特定的意思是每个DMA通道硬件触发源是不一样的。


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


2.存储器映像

计算机系统的五大组成部分是运算器、控制器、存储器、输入设备和输出设备,其中运算器和控制器一般会合在一起叫CPU,所以计算机核心就是CPU和存储器。存储器有两个知识点,一是存储器的内容,另一个是存储器的地址,STM32同理。


》所谓存储器映像是指STM32有哪些存储器,这些存储器对应哪些地址。注意外设寄存器本质也是一种存储器,概述那里为了表述做了区分。
在这里插入图片描述
1、ROM为只读存储器,是一种非易失性、掉电不丢失的存储器。ROM分三块,第一块为程序存储器Flash,即主闪存,用途是存储C语言编译后的程序代码,也就是下载程序的位置,运行程序一般也是从主闪存里开始。地址第一个字节0800,然后剩余地址依次增长,每个字节都分配一个独一无二的地址,终止地址取决于容量,如果在软件里看到某个数据的地址是0800开头的,就可确定它是主闪存的数据。后面两块系统存储器和选项字节,实际上它们的存储介质也是Flash,只不过一般提到Flash是指主闪存Flash。第一个字节都是1FFF,下一个2000就是RAM区了,说明它们处于ROM区的最后面。用途如表,BootLoader程序是芯片出厂自动写入的不允许修改,下载程序时可选择不刷新选项字节的内容,这样选项字节的配置就可保持不变,选项字节里存的主要为Flash的读保护、写保护、看门狗等配置。


2、RAM为随机存储器,是一种易失性、掉电丢失的存储器。第一块运行内存SRAM,用途如表,也就是在程序中定义变量、数组、结构体的地方,定义的变量取地址后一定为2000开头,类比电脑,运行内存就是内存条。第二块外设寄存器用途如表,也就是初始化各个外设,最终所读写的东西,实质也是存储器的一种,存储介质也是SRAM。第三块内核外设寄存器用途如表,内核外设就是NVIC和SysTick,内核外设和其他外设不是一个厂家设计,所以地址被分开了,地址以E000开头。
详细可参考芯片数据手册第4章存储器映像!


3.图示详解

(1)DMA框图(辅助理解,未显示出具体工作流程)

在这里插入图片描述
左上角为Cortex-M3内核,里面包含了CPU和内核外设等,其他部分可都看作存储器,如图包括Flash、SRAM、外设寄存器(本质也属于SRAM)等。外设就是寄存器,寄存器是特殊存储器一方面CPU可以对寄存器进行读写,就像读写运行内存一样,另一方面寄存器的每一位背后都连接了一根导线,这些导线可用于控制外设电路的状态,比如置引脚的高低电平、导通和断开开关、切换数据选择器,或者多位结合当作计数器、数据寄存器等,故而寄存器是连接软件和硬件的桥梁,软件读写寄存器就相当于在控制硬件的执行


既然外设就是寄存器,寄存器就是存储器,那么DMA数据转运都可归为“从某个地址取内容,再放到另一个地址去”一类问题。为了高效有序地访问存储器,上图设计一个总线矩阵,其左端为主动单元,拥有存储器的访问权,右端为被动单元,它们的存储器只能被左边的主动单元读取。

主动单元,内核有DCode和系统(总线),可以访问右边的存储器,其中DCode专门访问Flash的,系统(总线)可以访问其他东西。另外因为DMA要转运数据,所以DMA(Cortex-M3内核下面的DMA1和DMA2)也要有访问的主动权,所以另一个主动单元为DMA总线,除了DMA1和DMA2分别有一条DMA总线通向总线矩阵,以太网外设自己也私有DMA,可不用管。

DMA1和DMA2里有一个仲裁器,这是因为虽然多个通道可以独立转运数据,但是最终DMA总线只有一条,所以所有通道只能分时复用,如果产生抽个图就会由仲裁器根据通道优先级仲裁使用顺序。(其实总线矩阵里也有仲裁器,如果DMA和CPU要访问同一目标,那么DMA会暂停CPU访问以防止冲突,不过总线仲裁器仍会保证CPU得到一半的总线带宽,使CPU也可正常工作)。

仲裁器下面是AHB从设备,用于配置DMA。是DMA自身的寄存器,DMA作为一个外设也会有相应的配置寄存器,连接到了总线矩阵右边的AHB总线上。所以DMA既是总线矩阵的主动单元,可读写各种寄存器,也是AHB总线上的被动单元,CPU通过如图红色线路可对DMA进行配置(CPU主动,DMA被动)。

DMA请求,请求就是触发的意思,绿色线路右边的触发源是各个外设(图中ADC和DAC),所以DMA请求就是DMA的硬件触发源,比如ADC转换完成、串口接收到数据,需要触发DMA转运数据的时候,就会通过绿色线路向DMA发出硬件触发信号,然后DMA就可执行数据转运工作了。

因为Flash是ROM只读存储器的一种,如果通过总线直接访问的话,无论CPU还是DMA都只能读取Flash数据,不能写入,如果DMA的目的地址填了Flash的区域,那么转运时就会出错(因为DMA写不进去)。但Flash不是绝对不可写入的,可以配置Flash接口控制器对Flash进行读写,流程比较麻烦,要先对Flash按页进行擦除,再写入数据。SRAM是运行内存可任意读写,数据寄存器一般都是可正常读写的。


(2)DMA基本结构(与写代码流程一致,显示工作流程)

在这里插入图片描述
注:以下也是为了STM32里的表述,将外设寄存器与存储器区分开了!

外设与存储器之间分双向,外设到存储器、存储器到外设由方向控制;存储器与存储器,如Flah到SRAM或SRAM到SRAM,Flash只读所以不可SRAM到Flash或者Flash到Flash转运操作。


外设和存储器两个站点都有3个参数起始地址、数据宽度、地址是否自增。外设的起始地址和存储器的起始地址决定了数据从哪里来到哪里去;数据宽度,作用是指定一次转运要按多大的数据宽度进行,可选择字节Byte、半字HalfWord和字Word,字节8位也就是一次转运一个uint8_t大的数据,半字16位一次转运一个uint16_t大的数据,字是32位uint32_t这么大。比如转运ADC的数据,ADC的结果uint16_t这么大,所以此事这个参数选择半字;地址是否自增,作用是指定一次转运完成后,下一次转运是否要把地址移动到下一个位置去,类似于指针,p++的意思。比如ADC扫描模式,用DMA进行数据转运,外设地址是ADC_DR寄存器,寄存器这边,显然地址是不必自增,如果自增下一次转运就换到别的寄存器了,存储器这边地址需要自增,每转运一个数据后,就往后挪个“坑”,要不然下次转运数据就被覆盖了。


如果要进行存储器到存储器的数据转运,那么就需要把其中一个存储器的地址,放在外设站点。只要再外设起始地址里写Flash或SRAM的地址,它就会去Flash和SRAM找数据,这里画出的左站点虽然叫外设存储器但就是个名字,并不是只可写寄存器地址。甚至可再外设站点写存储器地址,存储器站点写外设地址。


传输计数器用来指定总共需要转运几次,是一个自减计数器,写入的值减到0后停止转运,之前自增的地址也会恢复到起始地址的位置,以方便新一轮转换。自动重装器的作用为当传输计数器减到0后,是否自动恢复到最初的值,不重装就是的单次模式,重装就是循环模式。比如想转运一个数组,一般就是单次模式,如果是ADC扫描模式+连续模式,为了配合ADC,DMA也需要循环模式,所以DMA循环模式和ADC的连续模式差不多,都是指定一轮工作完成后,是否立即开始下一轮工作。


绿框内的为DMA的触发控制,触发就是决定DMA需要在什么时机进行转运,M2M(Memory to Memory存储器到存储器)这个参数决定选择软件触发还是硬件触发。给M2M位1时,DMA会选择软件触发,此处的软件触发不是调用某个函数一次触发一次,它的执行逻辑是以最快的速度连续不断地触发DMA,争取早点把传输计数器清零,完成这一轮转换(和之前外部中断和ADC的软件触发不太一样,可以理解为连续触发),软件触发和循环模式不能同时使用,因为软件触发就是想把传输计数器清零,循环模式是清零后自动重装,如果同时使用DMA就停不下来了。软件触发一般适用于存储器到存储器的转运。当M2M位给0,就是硬件触发,硬件触发源可选择ADC、串口、定时器等,使用硬件触发的转运一般都是与外设有关的转运,这些转运需要一定的时机,比如ADC转换完成、串口收到数据、定时器时间到等,在硬件达到某个时机时传个信号过来,触发DMA进行转运。


最后的开关控制也就是DMA_Cmd()函数,给DMA使能后准备就绪就可进行转运。


注意DMA进行转运工作的几个条件:第一是开关控制,DMA_Cmd使能,第二传输计数器大于零,第三触发源必须有触发信号。触发一次转运一次传输计数器自减一次,当传输计数器等于0且没有自动重装时,无论是否触发DMA都不会进行转运,此时就需要DMA_Cmd给DISABLE关闭DMA,再为传输寄存器写入一个大于0的数,再DMA_Cmd给ENABLE开启DMA,才能继续工作。即写传输计数器时必须要先关闭DMA再进行写入,这是手册规定。

二、细节部分

1.DMA请求

DMA请求即DMA触发,即基本结构里绿框部分。下图为DMA1的请求映像,7个通道:
在这里插入图片描述
7个通道每个都有一个数据选择器(梯形符号),可选择软件触发还是硬件(外设请求信号)触发,每个通道的硬件触发源是不同的要对应,而软件触发都一样。比如通道1对应了ADC1、TIM2_CH3、TIM4_CH1(定时器4的通道1)三个外设触发源,到底哪个由该外设是否开启DMA输出决定:比如要使用ADC1,那么有个库函数为ADC_DMACmd()开启此路输出,其他同理。也可开启多个,一般开启一个。
默认优先级是通道号越小优先级越高,也可在程序中配置。


2.数据宽度与对齐

在DMA基本结构图里提到过数据宽度这个参数,如果数据宽度都一样就正常一个个转运,如果宽度不一样会怎么处理?如下表:
在这里插入图片描述
总结:这个表如果把小的数据转到大的里面去,高位补零;如果把大的数据转运到小的里面去,高位就会舍去。


三、实操案例

1.数据转运+DMA

在这里插入图片描述
任务目标:将SRAM里的数组DataA转运到另一个数组DataB中,由示意图显然起始地址位DataA数组的首地址,目的地址位DataB数组的首地址,数据宽度都是8位字节传输,两边地址都需要设置自增,才能按箭头一一对应。方向选择如图,触发源为软件触发(因为这是存储器到存储器,不需要等待硬件时机),传输计数器给7,自动重装不需要。这里的数据转运是一种复制转运,转运完成后DataA的数据并不会消失。
接线图与OLED一样,因为数据转运都在内部进行:
在这里插入图片描述

//MyDMA.c
#include "stm32f10x.h"                  // Device header
//还是根据DMA基本结构图打通(该图未画通道优先级和中断部分):
//1、RCC开启DMA时钟
//2、直接调用DMA_Init,初始化各个参数,所有参数通过一个结构体就可配置好
//3、开关控制,DMA_Cmd,给指定的通道使能
//注意如果选择的是硬件触发,不要忘记在对应外设调用以下XXX_DMACmd,开启下触发信号的输出
//如果需要DMA的中断,就调用DMA_ITConfig,开启中断输出,再在NVIC里配置相应的中断通道,然后写中断函数就可
//最后,如果转运成功传输计数器清零了,再想给计数器赋值的话,DMA需先失能、写计数器值、DMA使能

uint16_t MyDMA_Size;//全局变量为了使在第二个传输函数也可使用Size
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size){
    
    
	
	MyDMA_Size =Size;//存一份Size
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE );//DMA为AHB总线设备
	
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_MemoryBaseAddr= AddrA;//一般不写绝对地址,用变量(SRAM分到哪就是哪)
	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 ;
	//传输方向:外设站点作为数据源(SRC为Source,DST为目的地),即从外设到存储器。
	//注意:这里的外设为所谓的外设与函数叫法对应,实际是存储器与存储器之间的数据转运
	DMA_InitStructure.DMA_BufferSize=Size;//缓存区大小(传输计数器):用变量Size传参
	DMA_InitStructure.DMA_Mode= DMA_Mode_Normal;
	//传输模式(是否自动重装): 否(Normal),是(Circle)
	//注意自动重装DMA_Mode_Circle与软件触发DMA_M2M_Enable(存储器到存储器数据转运)不可同时使用
	DMA_InitStructure.DMA_M2M= DMA_M2M_Enable;//是否存储器到存储器(软件还是硬件触发):软件触发
	DMA_InitStructure.DMA_Priority= DMA_Priority_Medium;//中等优先级
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);//第一个参数既选择了哪个DMA也选择了通道
	
	DMA_Cmd(DMA1_Channel1,DISABLE);//第一个参数既选择了哪个DMA也选择了通道
}

void MyDMA_Transfer(void){
    
    
	//传输函数,调用一次就再启动一次DMA转运。
	//只有上面的Init函数转运一次(Cmd里写ENABLE时。这里把它改为了DISABLE)当计数值为0时就停止了
	
	DMA_Cmd(DMA1_Channel1,DISABLE);//先失能
	DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);//重新给传输计数器赋值
	DMA_Cmd(DMA1_Channel1,ENABLE);
	
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1)== RESET);//DMA1_FLAG_TC1为转运完成标志位,完成后置1,未完成一直在此等待
	DMA_ClearFlag(DMA1_FLAG_TC1);//清0标志位
}

//main.c

//const uint8_t aa = 0x66;
//const常量的意思,只读不可写,此时被存在ROM里,特定情况使用可节省SRAM空间,如OLED_Font.h的数组库;
//无const时变量存储在RAM。可观察地址第一个字节验证
//对于变量或常量,地址不固定,由编译器确定;而外设寄存器的地址是固定的,如&ADC1 -> DR为4001244C
//结构体访问寄存器:
//&ADC1 -> DR中ADC1为结构体指针,指向的是ADC1外设的起始地址,访问结构体成员就相当于加个地址偏移
//起始地址+偏移就是指定的寄存器,->箭头符号取结构体指针的成员。

//	OLED_ShowHexNum(1,1,aa,2);//16位进制数
//	OLED_ShowHexNum(1,1,(uint32_t)&aa,8);
//取地址后要存在一个指针变量里,如果要直接当数字显示需要强制类型转换
//显示长度为8,8个十六进制的数表示32位,32位系统地址都是32位
#include "stm32f10x.h"   
#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);
	//数组名就是地址,加&是数组首元素地址。但需类型转换
	//整个过程源端数据DataA不会变化
	
	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);
	}
}

2.ADC扫描模式+DMA

xxxxx
左边是ADC扫描模式的执行流程,7个通道,触发一次后7个通道会依次进行AD转换,然后转换结果都放到ADC_DR数据寄存器里。我们要做的是在每一次单独的通道转换完成后做一次DMA数据转运,并且目的地址进行自增,解决数据覆盖问题,所以这里DMA的配置为:
外设地址(起始)写入ADC_DR这个寄存器的地址,存储器的地址可在SRAM中定义一个数组ADValue,然后把ADValue的地址当作存储器的地址数据宽度为16位的半字传输。地址自增,外设地址不自增,存储器地址自增。传输方向位外设站点到存储器站点。传输计数器因为通道7个所以计数7次。计数器是否重装需要看ADC的配置,如果ADC为单次扫描,那么DMA的传输计数器可以不自动重装,转换一轮就停止;如果ADC是连续扫描,那么DMA就可自动重装,在ADC启动下一轮转换的时候DMA也期待下一轮转运。
触发选择,ADC_DR的值是在ADC单个通道转换完成后才会有效,所以DMA转运的时机要与ADC单个通道转换完成同步,所以要选择ADC的硬件触发。之前说过,ADC扫描模式在单个通道转换完成后不会产生任何中断或标志位,所以程序不好判断每个通道转换完成的时机,不适用软件触发。
ADC的数据覆盖问题使得与DMA“关系极好”,两者结合最为常见,其他外设也可使用DMA但最多是“锦上添花”,顶多损失性能!


接线图与上一届AD多通道实操一样,只是代码实现方式不同:
在这里插入图片描述
代码也是AD多通道与数据转运+DMA的结合:

//AD.c
//该代码就是ADC多通道与DMA配置的结合(属于外设到存储器的数据转运)。
//我们使用的是ADC单次扫描模式+DMA单次转运。
//也可尝试配置成ADC连续扫描+DMA循环转运模式:可自己尝试,教程8-2处50min。这种模式硬件外设实现了相互配合和高度自动化
#include "stm32f10x.h"  

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 );//DMA为AHB总线设备
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//6分频
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN ;//选择模拟输入模式即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);//点四个菜,通道0放在序列1...
	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;//独立模式,ADC1和ADC2各转换各的。其他为双ADC模式较复杂
	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;//通道数目:4
	ADC_Init(ADC1,&ADC_InitStructure);
	
	//可把ADC看作厨师,DMA为服务员,菜做好(转换完成)要赶紧端走(转运走),防止覆盖
	
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_MemoryBaseAddr= (uint32_t)&ADC1->DR;//起始在ADC的DR寄存器
	DMA_InitStructure.DMA_PeripheralDataSize= DMA_PeripheralDataSize_HalfWord;//半字
	DMA_InitStructure.DMA_PeripheralInc= DMA_PeripheralInc_Disable ;//地址不自增,始终转运同一个位置(DR)的数据
	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;//硬件触发,触发源为ADC1,此时还没有触发信号
	DMA_InitStructure.DMA_Priority= DMA_Priority_Medium;
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);//由DMA框架图可知,ADC1对应DMA通道1
	
	DMA_Cmd(DMA1_Channel1,ENABLE);
	ADC_DMACmd(ADC1,ENABLE);//开启ADC到DMA的输出,才能产生硬件触发信号
	ADC_Cmd(ADC1,ENABLE);
	
	ADC_ResetCalibration(ADC1);//校准
	while (ADC_GetCalibrationStatus(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);//软件触发,ADC开始转换
	
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1)== RESET);//等待DMA转运完成。因为转运总在转换后,所以省去了等待ADC转换完成得代码
	DMA_ClearFlag(DMA1_FLAG_TC1);//清0标志位
}
//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"

uint16_t AD0,AD1,AD2,AD3;//表示四个ADC输入通道的转换结果的接收变量

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();//调用后数据就会直接转运到AD_Value数组里
		
		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定时器输入捕获(IC)
STM32定时器输出比较(PWM波)
STM32定时中断
STM32外部中断
STM32GPIO精讲

猜你喜欢

转载自blog.csdn.net/weixin_51658186/article/details/129759250
今日推荐