STM32江科大————DMA数据转运(实现AD多通道)

声明:本人跟随b站江科大学习,本文章是观看完视频后的一些个人总结和经验分享,也同时为了方便日后的复习,如果有错误请各位大佬指出,如果对你有帮助可以点个赞小小鼓励一下,本文章建议配合原视频使用❤️

如果你也正在学习STM32可以订阅本专栏,后续将不定期更新( ˘ ³˘)❤️


文章目录

  • 前言
  • 理论部分
  • 1.DMA简介
  • 2.存储器映像
  • 3.DMA框图
  • 4.DMA基本结构
  • 5.DMA请求
  • 6.数据宽度与对齐
  • 7.DAM转运
  • 软件触发
  • 硬件触发
  • 8.补充
  • 代码部分
  • DMA数据转运
  • DMA实现AD多通道
  • 前言

    还是在观看文章之前给大家留下几个问题,你可以在读完文章后问问自己有没有弄清楚这几个问题,如若清楚了,那么恭喜你已经基本了解了DMA的使用

  • 外设和存储器概念
  • DMA的作用
  • DMA的转运实际上是地址的转运,为什么
  • 软件触发与外部中断的软件触发的区别
  • 什么是特定的硬件触发
  • 站点的三个参数如何选择
  • 如何根据实际情况配置DMA

  • 理论部分

    1.DMA简介

  • 可以直接访问运行内存SRAM,程序存储器Flash,寄存器等存储器
  • 外设:一般是数据寄存器DR(Digital Register)
  • 存储器:内存SRAM,程序存储器Flash
  • 外设到存储器的转运:由于外设的数据是有时机的,所以这里不能够随便转运,我们要采取硬件触发,例如要等ADC转换完成,串口收到数据,定时时间到之后硬件触发一次DMA才能使用DMA转运,触发一次转运一次。(类似中断的更新事件)
  • 存储器到存储器的转运:一般使用软件触发
  • 特定的硬件触发:对应的硬件触发有固定的通道,等下具体介绍
  • 这里的软件触发和外部中断以及ADC的软件触发不一样,等下解释
  • 该芯片只有DMA1
  • 2.存储器映像

  • 计算机的组成:运算器,控制器,存储器,输入和输出设备五个部分,其中运算器和控制器合称CPU,其中核心部分是CPU和存储器。存储器中的核心部分是存储器的内容和存储器的地址,上述表就是STM32中所具有的所有类型的存储器和其地址
  • 起始地址:就是给存储器分配的固定开头地址,起始地址后面的地址都是存储器内存的扩展
  • STM32的ROM存储器的存储介质其实都是FLASH,但是我们一般说的Flash指的是主闪存,也就是程序储存器Flash
  • 选项字节:主要存储的是Flash的读保护,写保护和看门狗等等配置
  • 临时变量:定义的变量,数组,结构体等等统称为临时变量
  • 内核外设:NVIC,SysTick等等

  • STM32F103的CPU是32位的,其存储器有4G的寻址空间,但是存储器都是KB级别的,所以就有很多空间没有使用到,图中灰色部分[Reserved]都是没有使用到的地址
  • 中间一列是ROM,左边一列是RAM,右边一列是Peripherals外设
  • 可以发现外设的地址都是0x4000开头,然后后面的地址再分是哪一个寄存器,这样每一个外设寄存器包括其存储内容都有对应地址了,其他的存储器也都是一样的
  • BOOT引脚的选择控制启动区域
  • 3.DMA框图

  • 核心:包括CPU和内核外设
  • 红色:主动单元,拥有访问存储器的权限,其中Dcode是专门用来访问Flash的,系统总线是用来访问APB1,APB2,AHBx等等总线上的存储器的,DMA总线什么都可以访问,有3个DMA分支通向DMA总线,用绿色标记
  • 褐色:被动单元,只能被主动单元读写
  • 紫色:仲裁器,由于DMA总线只有一条,所以所有的分支都只能分时复用这一条总线,如果产生冲突就由仲裁器决定先后使用顺序,红色的总线矩阵里面也有仲裁器,如果DMA总线和系统总线都要访问同一个目标,那么DMA就会让CPU停止访问,仲裁其会将总线一半的带宽给CPU,使CPU正常工作
  • 黄色:AHB从设备也就是DMA的寄存器,DMA作为一个外设也有自己的配置寄存器,这个寄存器连接在了AHB总线上,用粉色标记
  • DAM既是总线矩阵的主动单元,读写其他寄存器,也是AHB总线的被动单元,被读写
  • Flash特殊情况下可以写,一般只能读,SRAM可以读写,外设寄存器是否能够读写要看手册,不过一般都是操作数据寄存器,可以读写
  • 4.DMA基本结构

  • 图中红色框框就是我们的站点,一个是外设站点,一个是存储器站点,站点是存储要交换的数据的地方,究竟是外设到存储器还是存储器到外设,这个方向我们也有参数可以控制。我们也可以存储器到存储器,但是由于Flash是只读存储器,所以只能Flash到SRAM或者SRAM到SRAM。
  • 站点都有三个参数:起始地址,数据宽度,地址是否自增起始地址是前面讲的每个存储器特有的地址,用来确认数据从哪里来,到哪里去;数据宽度就是被交换数据的大小,这里宽度有三种,分别是字节Byte,半字Half Word,字Word,大小分别为8位,16位,32位,例如我们的AD值是12位的,这里就要选择半字的宽度;地址是否自增是指每次转运完成之后,起始地址是否自增,比如说我们的ADC_DR(ADC数据寄存器),这个转运后肯定不能自增吧,改了地址可能下次就去转运其他寄存器了,但是存储器肯定要自增吧,不然这次存在存储器这个地址,下次还是存在这个地址,上一次存进来的值就会被覆盖了,数据寄存器本来就不用担心被覆盖,它的值已经转运到存储器部分被记录下来了,直接读取存储器就能知道上一次存的值是什么。
  • 这个外设站点的起始地址也不一定非要是外设寄存器的,写存储器的也行,只要改变一下控制方向的参数就行了,而且进行存储器之间的转运的话,两个站点就都是存储器了
  • 传输寄存器是用来控制转运次数的,我们可以给该寄存器一个值,这个值会自减,减到0之后DMA就不会再转运了,并且减到0之后之前站点自增的地址又会回到增长之前
  • 自动重装器是用来使传输寄存器的起始值清零后复原的,如果不使用的话,就只能够进行一次转运,传输寄存器清零后就不能够再次转运了,也就是单次模式,如果使用,就能够一直转运,也就是循环模式
  • M2M(Memory to Memory):即存储器到存储器。当给M2M置1时就是软件触发,置0就是硬件触发,这个软件触发的逻辑是以最快的速度连续不断触发DMA使传输寄存器自减为0,完成这一轮的转换,所以这个模式不能和循环模式一起使用,不然DMA转运会停不下来。所以存储器到存储器的转运一般使用软件触发,因为存储器之间的转运不需要时机,而且越快越好,硬件触发则需要看时机,一般用于存储器和外设之间的转运
  • DMA转运的条件:使能DMA,传输寄存器不为0,有触发源(软件或者硬件)
  • 如果没有使用循环模式,那么传输寄存器自减清0之后就需要先失能DAM才能够再次设置初始值,再使能DMA使之启动转运
  • 5.DMA请求

  • 这个图表示的是几个外设硬件触发产生的7个DMA请求
  • 红色部分是M2M和EN(cmd)使能,这里的排版比较奇怪,一般在数据选择器侧边的是控制通道的引脚,左边是通道,这里EN在侧边但是表示的是使能,M2M才是控制通道的引脚2
  • 上图蓝色部分的也就是上面讲述过的特定的硬件触发,看图可知ADC1的DMA请求通道就是通道一,每一个外设硬件触发的请求通道都是固定对应的
  • 如果使用ADC请求通道,那么使能这个DMA通道就要用ADC_DMAcmd函数使能,使用定时器也有TIM_DMAcmd使能,使用什么外设就要用其对应的DMA使能函数使能DMA,如果三个通道都开启了,我们可以看到这个或门,理论上三个通道都是有效的,但是一般只开启一个
  • 最后七个DMA通道只有一个总线,这里也有一个仲裁器来控制优先级,默认的是通道号越小优先级越高,也可以在程序中配置优先级
  • DMA_cmd使能DMA转运,xxxDMA_cmd使能DMA请求通道
  • 6.数据宽度与对齐

  • 源端宽度(转运起点),目标宽度(转运终点),传输数目(要转运几个数据)
  • B0[7:0]表示B0这个数据是8位的,B0[15:0]表示B0这个数据是16位的
  • 0x1,0x2是源端和目标的地址
  • 这个传输数目都为4,所以每一行都是,源端0x0处的B0数据传输到目标0x0处,源端0x1处的B1数据传输到目标0x1处,传输4个数据也就是一直到B4
  • 结论:
    当源端宽度=目标宽度时,数据高低位一一对应,eg.都是8位大小,0x0B0->0x0B0
    当源端宽度>目标宽度时,舍弃高位,低位对齐,eg.源端16位大小,目标8位大小,0x0B1B0->0x0B0
    当源端宽度<目标宽度时,高位补0,低位对齐,eg.源端8位大小,目标16位大小,0x0B0->0x000B0
  • 7.DAM转运

    软件触发


    DMA的配置:定义两个SRAM数组,并把数组地址放进站点,两个站点地址都自增,转运7次,软件触发,单次模式(不用自动重装器),数据宽度示情况而定,优先级配置也示情况而定

    硬件触发


    DMA配置(以ADC为例):把ADC_DR的地址塞进外设站点,定义的数组塞进存储器站点,如果ADC是连续转换则设置为循环模式,非连续则设置为单次模式,通道有几个就转运几次,并且每一次转运完成都硬件触发一次DMA请求进行DMA转运(对应外设要对应器通道),ADC_DR地址不自增,存储器地址自增,数据宽度都为半字,优先级配置示情况而定

    8.补充

  • 位段:两个位段区映射了外设寄存器和SRAM全部的位,也就是可以位寻址(外设寄存器和SRAM中每一位都分配了地址),操作位段区的地址就相当于单独操作外设或者SRAM的某一位,SRAM位段区的起始地址是0x2200,外设寄存器是0x4200
  • 真实地址=基地址+偏移地址
    看下面代码可以发现固件库代码的条理非常清晰,除了第一个外设基地址是固定值,其他的基地址都是通过 上一级基地址+偏移 计算出来的,最后GPIOA是一个 指定地址强制转换结构,也就是把这个地址强制转换成一个结构体指针,这个地方实现偏移的方法非常巧妙,因为结构体里存放寄存器的顺序刚好对应GPIOA实际上寄存器顺序,也就是说只要结构体的地址刚好为GPIOA_BASE的地址,由于结构体内容的地址也是在结构体地址基础上的延续,那么结构体寄存器的顺序就刚好就实现了在GPIOA_BASE基地址上的偏移,从而得到每一个寄存器的真实地址
  • #define PERIPH_BASE           ((uint32_t)0x40000000)		//外设基地址
    #define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)		//APB2总线基地址
    #define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)	//GPIOA 基地址
    
    typedef struct
    {
      __IO uint32_t CRL;
      __IO uint32_t CRH;
      __IO uint32_t IDR;
      __IO uint32_t ODR;
      __IO uint32_t BSRR;
      __IO uint32_t BRR;
      __IO uint32_t LCKR;
    } GPIO_TypeDef;
    
    #define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)	//GPIOA结构
    
    

    代码部分

    DMA数据转运

    MyDMA.c

    #include "stm32f10x.h"                  // Device header
    
    uint16_t MyDMA_Size;					//定义全局变量,用于记住Init函数的Size,供Transfer函数使用
    
    /**
      * 函    数:DMA初始化
      * 参    数:AddrA 原数组的首地址
      * 参    数:AddrB 目的数组的首地址
      * 参    数:Size 转运的数据大小(转运次数)
      * 返 回 值:无
      */
    void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
    {
    	MyDMA_Size = Size;					//将Size写入到全局变量,记住参数Size
    	
    	/*开启时钟*/
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);						//开启DMA的时钟
    	
    	/*DMA初始化*/
    	DMA_InitTypeDef DMA_InitStructure;										//定义结构体变量
    	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;						//外设基地址,给定形参AddrA
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//外设数据宽度,选择字节
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;			//外设地址自增,选择使能
    	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;							//存储器基地址,给定形参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_Init,配置DMA1的通道1
    	
    	/*DMA使能*/
    	DMA_Cmd(DMA1_Channel1, DISABLE);	//这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始
    }
    
    /**
      * 函    数:启动DMA数据转运
      * 参    数:无
      * 返 回 值:无
      */
    void MyDMA_Transfer(void)
    {
    	DMA_Cmd(DMA1_Channel1, DISABLE);					//DMA失能,在写入传输计数器之前,需要DMA暂停工作
    	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);	//写入传输计数器,指定将要转运的次数
    	DMA_Cmd(DMA1_Channel1, ENABLE);						//DMA使能,开始工作
    	
    	while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);	//等待DMA工作完成
    	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};				//定义测试数组DataA,为数据源
    uint8_t DataB[] = {0, 0, 0, 0};							//定义测试数组DataB,为数据目的地
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();				//OLED初始化
    	
    	MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);	//DMA初始化,把源数组和目的数组的地址传入
    	
    	/*显示静态字符串*/
    	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);		//显示数组DataA
    		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);		//显示数组DataB
    		OLED_ShowHexNum(4, 4, DataB[1], 2);
    		OLED_ShowHexNum(4, 7, DataB[2], 2);
    		OLED_ShowHexNum(4, 10, DataB[3], 2);
    		
    		Delay_ms(1000);		//延时1s,观察转运前的现象
    		
    		MyDMA_Transfer();	//使用DMA转运数组,从DataA转运到DataB
    		
    		OLED_ShowHexNum(2, 1, DataA[0], 2);		//显示数组DataA
    		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);		//显示数组DataB
    		OLED_ShowHexNum(4, 4, DataB[1], 2);
    		OLED_ShowHexNum(4, 7, DataB[2], 2);
    		OLED_ShowHexNum(4, 10, DataB[3], 2);
    
    		Delay_ms(1000);		//延时1s,观察转运后的现象
    	}
    }
    
    

    DMA实现AD多通道

    AD.c

    #include "stm32f10x.h"                  // Device header
    
    uint16_t AD_Value[4];					//定义用于存放AD转换结果的全局数组
    
    /**
      * 函    数:AD初始化
      * 参    数:无
      * 返 回 值:无
      */
    void AD_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);		//开启DMA1的时钟
    	
    	/*设置ADC时钟*/
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
    	
    	/*GPIO初始化*/
    	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);					//将PA0、PA1、PA2和PA3引脚初始化为模拟输入
    	
    	/*规则组通道配置*/
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);	//规则组序列1的位置,配置为通道0
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);	//规则组序列2的位置,配置为通道1
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);	//规则组序列3的位置,配置为通道2
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);	//规则组序列4的位置,配置为通道3
    	
    	/*ADC初始化*/
    	ADC_InitTypeDef ADC_InitStructure;											//定义结构体变量
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;							//模式,选择独立模式,即单独使用ADC1
    	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_NbrOfChannel确定
    	ADC_InitStructure.ADC_NbrOfChannel = 4;										//通道数,为4,扫描规则组的前4个通道
    	ADC_Init(ADC1, &ADC_InitStructure);											//将结构体变量交给ADC_Init,配置ADC1
    	
    	/*DMA初始化*/
    	DMA_InitTypeDef DMA_InitStructure;											//定义结构体变量
    	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;				//外设基地址,给定形参AddrA
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	//外设数据宽度,选择半字,对应16为的ADC数据寄存器
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;			//外设地址自增,选择失能,始终以ADC数据寄存器为源
    	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;					//存储器基地址,给定存放AD转换结果的全局数组AD_Value
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;			//存储器数据宽度,选择半字,与源数据宽度对应
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;						//存储器地址自增,选择使能,每次转运后,数组移到下一个位置
    	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;							//数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
    	DMA_InitStructure.DMA_BufferSize = 4;										//转运的数据大小(转运次数),与ADC通道数一致
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;								//模式,选择循环模式,与ADC的连续转换一致
    	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;								//存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
    	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;						//优先级,选择中等
    	DMA_Init(DMA1_Channel1, &DMA_InitStructure);								//将结构体变量交给DMA_Init,配置DMA1的通道1
    	
    	/*DMA和ADC使能*/
    	DMA_Cmd(DMA1_Channel1, ENABLE);							//DMA1的通道1使能
    	ADC_DMACmd(ADC1, ENABLE);								//ADC1触发DMA1的信号使能
    	ADC_Cmd(ADC1, ENABLE);									//ADC1使能
    	
    	/*ADC校准*/
    	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
    	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
    	ADC_StartCalibration(ADC1);
    	while (ADC_GetCalibrationStatus(ADC1) == SET);
    	
    	/*ADC触发*/
    	ADC_SoftwareStartConvCmd(ADC1, ENABLE);	//软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作
    }
    
    

    AD.h

    #ifndef __AD_H
    #define __AD_H
    
    extern uint16_t AD_Value[4];
    
    void AD_Init(void);
    
    #endif
    
    

    main.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "AD.h"
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();				//OLED初始化
    	AD_Init();					//AD初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "AD0:");
    	OLED_ShowString(2, 1, "AD1:");
    	OLED_ShowString(3, 1, "AD2:");
    	OLED_ShowString(4, 1, "AD3:");
    	
    	while (1)
    	{
    		OLED_ShowNum(1, 5, AD_Value[0], 4);		//显示转换结果第0个数据
    		OLED_ShowNum(2, 5, AD_Value[1], 4);		//显示转换结果第1个数据
    		OLED_ShowNum(3, 5, AD_Value[2], 4);		//显示转换结果第2个数据
    		OLED_ShowNum(4, 5, AD_Value[3], 4);		//显示转换结果第3个数据
    		
    		Delay_ms(100);							//延时100ms,手动增加一些转换的间隔时间
    	}
    }
    
    

    作者:南梦也要学习

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32江科大————DMA数据转运(实现AD多通道)

    发表回复