STM32标准库——(11)DMA直接存储器存取

目录

1.DMA简介

2.存储器映像

3.DMA框图

4.DMA基本结构

5.DMA请求

6.数据宽度与对齐

 7.数据转运+DMA

8.ADC扫描模式+DMA

9.相关API

9.1 DMA_Init

9.2 DMA_Cmd

9.3 DMA_SetCurrDataCounter

9.4 DMA_GetFlagStatus

9.5 DMA_ClearFlag

10.DMA数据转运

10.1 接线图

10.2 程序调试

10.3 相关代码

MyDMA.c

MyDMA.h

main.c

11.DMA+AD多通道

11.1 接线图

11.2 相关代码

AD2.c

AD2.h

main.c


1.DMA简介

  • 外设一般指的是数据寄存器DR( Data Register),比如ADC的数据寄存器,串口的数据寄存器等等,这里存储器指的就是运行内存SRAM程序存储器flash,是我们存储变量数组和程序代码的地方,在外设和存储器或者存储器和存储器之间进行数据转运,就可以使用DMA来完成,并且在转运的过程中无需CPU的参与,节省了CPU的资源

  • 如果执行的是存储器到存储器的转运,比如我们想把flash里的一批数据转运到SRAM里去,那就需要软件触发,使用软件触发之后,DMA就会迅速地把这批数据部转运完成;如果DMA进行的是外设到存储器的数据转运,就不能一股脑的转运了,因为外设的数据是有一定时机的,所以这时我们就需要用硬件触发,比如转运ADC的数据,那就得ADC每个通道AD转换完成后,硬件触发一次DMA,之后DMA再转运,触发一次,转运一次。总之就是:存储器到存储器的数据转运,我们一般使用软件触发,外设到存储器的数据转运,我们一般使用硬件触发

2.存储器映像

  • ROM就是只读存储器,是一种非易失性、掉电不丢失的存储器
    RAM是随机存储器(可读可写),是一种易失性、掉电丢失的存储器

3.DMA框图

  1. 仲裁器:虽然多个通道可以独立转运数据,但是最终DMA总线只有一条,所以所有的通道都只能分时复用这一条DMA总线。如果产生了冲突,那就会由仲裁器,根据通道的优先级来决定谁先使用和后使用。另外在总线矩阵这里,也会有个仲裁器,如果DMA和CPU都要访问同一个目标,那么DMA就会暂停CPU的访问,以防止冲突。不过总线仲裁器,仍然会保证CPU得到一半的总线带宽,使CPU也能正常的工作

  2. 下面这里是AHB从设备,也就是DMA自身的寄存器,因为DMA作为一个外设,它自己也会有相应的配置寄存器,这里连接在了总线右边的AHB总线上,所以DMA,既是总线矩阵的主动单元,可以读写各种存储器,也是AHB总线上的被动单元。CPU通过这一条线路,就可以对DMA进行配置了

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

  4. 注意:右上角的Flash是ROM只读存储器的一种,如果通过总线直接访问的话,无论是CPU,还是DMA,都是只读的,只能读取数据,而不能写入,如果你DMA的目的地址,填了Flash的区域,那转运时,就会出错。然后SRAM是运行内存,可以任意读写。

4.DMA基本结构

  1.  第一个是起始地址,有外设端的起始地址和存储器端的起始地址,这两个参数决定了数据是从哪里来到哪里去的,之后第二个参数是数据宽度,这个参数的作用是指定一次转运要按多大的数据宽度来进行,他可以选择字节byte、半字HalfWord和字word字节是8位就是一次转运一个uint8_t这么大的数据,半字是16位就是一次转运一个uint16_t这么大的数据,字是32位就是一次转运unit32_t这么大的数据。比如转运ADC的数据,ADC的结果是unit16_t这么大,所以这个参数就要选择半字,一次转运一个unit16_t,然后第三个参数是地址是否自增,这个参数的作用是指定一次转移完成后,下一次转运是不是要把地址移动到下一个位置去,这就相当于是指针p++这个意思,比如ADC扫描模式,用DMA进行数据转运,外设地址是ADC_DR寄存器,寄存器这边显然地址是不用指针的,如果自增,那下一次转运就跑到别的寄存器那里去了,存储器这边地址就需要指针,每转运一个数据后就往后挪个坑,要不然下次再转就把上次的覆盖掉了,这就是地址是否自增的作用,就是指定是不是要转运一次挪个坑这个意思,这就是外设站点和存储基站点各自的三个参数了。
  2. 传输计数器是用来指定我总共需要转运几次的,这个传输计数器是一个自减计数器,比如你给他写个5,那DMA就只能进行5次数据转运,转运过程中每转运一次计数器的数就会减一,当传输计数器减到零之后,DMA就不会再进行数据转运了,另外它减到零之后,之前自增的地址也会恢复到起始地址的位置,以方便之后DMA开始新一轮的转换,在传输计数器的右边有一个自动重装器,这个自动重装器的作用就是传输计数器减到零之后,是否要自动恢复到最初的值,比如最初传输计数器给5,如果不使用自动重装器,那转运5次后DMA就结束了,如果使用自动重装器,那转运5次计数器减到零后就会立即重装到初始值5,这个就是自动重装器,它决定了转运的模式,如果不重装就是正常的单次模式如果重装就是循环模式,比如如果你想转运一个数组,那一般就是单次模式转运一轮就结束了,如果是ADC扫描模式加连续转换,那为了配合ADC,DMA也需要使用循环模式,所以这个循环模式和ADC的连续模式差不多,都是指定一轮工作完成后,是不是立即开始下一轮工作。

  3. 触发就是决定DMA需要在什么时机进行转运的。触发源有硬件触发和软件触发,具体选择哪个,由M2M这个参数决定,M2M就是memory to memory因为2的英文two和to同音,所以M2M就是m to m存储器到存储器的意思,当我们给M2M位1时,DMA就会选择软件触发,这个软件触发并不是调用某个函数一次触发一次,这个软件触发的执行逻辑是,以最快的速度,连续不断地触发DMA,争取早日把传输计数器清零,完成这一轮的转换,所以这里的软件触发和我们之前外部中断和ADC的软件出发,可能不太一样,你可以把它理解成连续触发,那这个软件触发和循环模式不能同时用,因为软件触发就是想把传输计数器清零循环模式是清零后自动重装,如果同时用的话,那DMA就停不下来了,这就是软件触发,软件触发一般适用于存储器到存储器的转运,因为存储器到存储器的转运,是软件启动不需要时机,并且想尽快完成的任务,所以上面这里M2M位给1就是软件触发,就是应用在存储器到存储器转运的情况,M2M位给0,那就是使用硬件触发,硬件触发源可以选择ADC、串口、定时器等等,使用硬件触发的转运一般都是与外设有关的转运,这些转运需要一定的时机,比如ADC转换完成、串口收到数据、定时时间到等等,所以需要使用硬件触发,在硬件达到这些时机时,传个信号过来来触发DMA进行转运,这就是硬件触发。

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

  5. 选择硬件触发 需要在对应外设调用XXX_MDACmd 开启触发信号的输出   需要DMA中断 调用DMA_ITConfig 开启中断输出 再在NVIC里配置相应的中断通道 写入中断函数即可

5.DMA请求

  1. EN位是开关控制,EN等于0时不工作,EN等于1时工作。
  2. 每个通道的硬件触发源都是不同的,如果你需要用ADC1来触发的话,那就必须选择通道一,如果需要定时器二的更新事件来触发的话,那就必须选择通道二

6.数据宽度与对齐

 7.数据转运+DMA

  • 首先是外设站点和存储器站点的起始地址、数据宽度、地址是否自增这三个参数。那在这个任务里,外设地址显然应该填DataA数组的首地址,存储器地址给DataB数组的首地址,然后数据宽度,两个数组的类型都是uint8_t,所以数据宽度都是按8位的字节传输。之后地址是否自增,在中间可以看到,我们想要的效果是DataA[0]转到DataB[0]DataA[1]转DataB[1],等等。所以转运完DataA[0]和DataB[0]之后,两个站点的地址都应该自增,都移动到下一个数据的位置,继续转运DataA[1]和DataB[1],这样来进行。

8.ADC扫描模式+DMA

  • 左边是ADC扫描模式的执行流程,在这里有7个通道,触发一次后,7个通道依次进行AD转换,然后转换结果都放到ADC_DR数据寄存器里面。那我们要做的就是,在每个单独的通道转换完成后,进行一个DMA数据转运,并且目的地址进行自增,这样数据就不会被覆盖了。所以在这里DMA的配置就是,外设地址,写入ADC_DR这个寄存器的地址;存储器的地址,可以在SRAM中定义一个数组ADValue,然后把ADValue的地址当做存储器的地址

9.相关API

9.1 DMA_Init

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
功能:
	根据 DMA_InitStruct 中指定的参数初始化 DMA 的通道 x 寄存器
参数:
   DMA Channelx:x 可以是 1,2…,或者 7 来选择 DMA 通道 x
   DMA_InitStruct:指向结构 DMA_InitTypeDef 的指针,包含了 DMA 通道 x 的配置信息    
返回值:
	无       

9.2 DMA_Cmd

void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
功能:
	使能或者失能指定的通道 x
参数:
   DMA Channelx:x 可以是 1,2…,或者 7 来选择 DMA 通道 x
   NewState:DMA 通道 x 的新状态 这个参数可以取:ENABLE 或者 DISABLE  
返回值:
	无        

9.3 DMA_SetCurrDataCounter

void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
功能:
	设置DMA转换数据个数
参数:
   DMA Channelx:x 可以是 1,2…,或者 7 来选择 DMA 通道 x
   DataNumber:数据个数
返回值:
	无      
    

9.4 DMA_GetFlagStatus

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
功能:
	检查指定的 DMA 通道 x 标志位设置与否
参数:
   DMA_FLAG:待检查的 DMA 标志位
返回值:
	DMA_FLAG 的新状态(SET 或者 RESET)     

9.5 DMA_ClearFlag

void DMA_ClearFlag(uint32_t DMAy_FLAG);
功能:
	清除 DMA 通道 x 待处理标志位
参数:
    DMA_FLAG:待清除的 DMA 标志位,使用操作符“|”可以同时选中多个DMA 标志位
返回值:
	无     

10.DMA数据转运

10.1 接线图

10.2 程序调试

//存储器映像的调试
const uint16_t aa = 0x66;//const是C语言的关键字 为定义一个常量 常量就无法改变 对应Flash主闪存(可读不可写)
						//若这里没加const 在第13行代码执行完OLED显示的是2000开头的 即运行内存SRAM
						//这里加了const 在第13行代码执行完OLED显示的是0800开头的 即程序存储器Flash

OLED_ShowHexNum(1,1,aa,2);
OLED_ShowHexNum(2,1,(uint32_t)&aa,8);

寄存器的实际地址 = 起始地址 + 偏移

起始地址表

偏移表 

10.3 相关代码

MyDMA.c
#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size;//定义全局变量,用于记住Init函数的Size,供Transfer函数使用

void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)
{
	MyDMA_Size = Size;
	/*开启时钟*/
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//开启DMA的时钟 DMA是AHB的总线设备 要用AHB开启时钟的函数 开启DMA的时钟
												//互联型是STM32F105/107的型号 本次使用的是F103 所以在下面参数表选
	
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//外设基地址,给定形参AddrA
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据宽度,选择字节(uint8_t)
	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;//指定传输计数器是否要自动重装 (模式,选择正常模式)自减到0后停下来
	//注意:自动重装与软件触发不能调试使用 若同时使用 则DMA不会停下来 会连续触发 
	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后,再开始
}

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 __MYDME_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"

/*存储器映像的调试
const uint16_t aa = 0x66;//const是C语言的关键字 为定义一个常量 常量就无法改变 对应Flash主闪存(可读不可写)
						//若这里没加const 在第13行代码执行完OLED显示的是2000开头的 即运行内存SRAM
						//这里加了const 在第13行代码执行完OLED显示的是0800开头的 即程序存储器Flash
*/

uint8_t DataA[] = {0x01,0x02,0x03,0x04};
uint8_t DataB[] = {0,0,0,0};

int main(void)
{
	OLED_Init();
	
//	OLED_ShowHexNum(1,1,aa,2);
//	OLED_ShowHexNum(2,1,(uint32_t)&aa,8);
//	OLED_ShowHexNum(2,1,(uint32_t)&ADC1->DR,8);
	
	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,观察转运后的现象
	}
}

现象:DataA数组中的数每隔1us自增一次 并将数据通过DMA数据传输到DataB DataB的数据也随之自增比同步

11.DMA+AD多通道

11.1 接线图

11.2 相关代码

AD2.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就可以一直连续不断地工作
}
AD2.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 "AD2.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,手动增加一些转换的间隔时间
	}
}

现象:与之前的AD多通道一样 上电之后光敏传感器、热敏传感器、反射式红外传感器以及电位器随着调试其数值会发生变化

  • 21
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值