前言
当有大量数据需要转运的时候,如果由CPU来进行数据转运的操作,数据转运属于简单操作,而CPU主要是运行一些较为复杂的程序,这样会占用大量CPU资源。为此就存在一个专门转运数据的外设,也就是DMA,译为(Direct Memory Access)直接存储器存取,DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。
1.DMA的结构
上图为DMA的内部结构,可以看到DMA1有7个通道,DMA2有5个通道,同时也要根据自身芯片的类型,来判断是否有DMA2这个外设。
首先是最上层的Cortex-M3内核(包含CPU和内核外设), 有三条总线通道通向总线矩阵(DCode、系统、DMA),这三条总线通道被称为主控单元,能够读取总线矩阵右边的被控单元。
首先看到DCode总线,该总线将Cortex™-M3内核的DCode总线与闪存存储器的数据接口相连接(常量加载和调试访问)。换句话说,主要就是实现对FLASH闪存的读取。
这里需要说明的是,FLASH在任何情况下都是只读的,而不能写入,而FLASH里面一般存放的是c语言编译后的程序代码。如果要写入的话,需要先对FLASH进行页擦除,然后再进行写入。写入则是通多ICode总线经过FLASH接口控制器,通过控制FLASH控制器为写,就能够对FLASH进行写操作了,具体的细节可以洗查阅FLASH闪存这方面的章节。
然后是系统总线,此总线连接Cortex™-M3内核的系统总线(外设总线)到总线矩阵来控制外设,因此相当于是CPU控制外设寄存器(被控单元)。
接着是DMA总线,也就是本章要展开的,DMA用于转运数据,可以是存储器和存储器之间,也可以是寄存器和寄存器之间。
最后是总线矩阵,总线矩阵协调着内核和DMA间的访问。因为当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求会暂停CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽,带宽也就是指传输速度多(例如:多少kb/s)。
以上就可以看出CPU控制外设的路线,被控单元外设可以是APB1上的,也可是APB2上的。
同时还能看到SRAM运行内存,也由CPU和DMA控制着,SRAM是用来存放程序中临时存放的变量。
上图是DMA作为被控单元,此时CPU控制DMA外设,这样就可以用来配置DMA外设的各种参数。
可见,DMA既可以做主控单元(总线矩阵左边),也可以作为被控单元(总线矩阵右边)。
2.DMA的软件、硬件触发
2.1硬件触发
同时DMA可以被软件触发,也可以被硬件触发,如上图,APB1和APB2上的外设发送DMA请求,这个叫做硬件触发。
例如:ADC的采集,在此过程中非常依赖DMA,可以说DMA是ADC外设中不“可或缺的部分”,在其他外设中,如果不使用DMA则会损失一些性能,也就是用CPU来转运操作。而在ADC的规则组通道扫描模式下,每一个通道的数据都会被采集,而最终输出到ADC规则组存储寄存器只有最后采集的数据,这样就非常依赖DMA的转运功能,不仅能够防止数据被覆盖,也能够保证程序高效且有条理的进行。
2.2软件触发
这样通过外设发出DMA请求的叫做硬件触发,而软件触发,也就是由CPU执行。程序一经启动,DMA就开“全速”工作,因为在软件触发中,DMA只需要考虑程序是否执行完,不需要考虑指定什么时候才开始执行。
软件触发一般都作用在存储器和存储器之间,因为外设的寄存器之间的转运需要一定的时机,例如上述说所的ADC采样转换,DAM不可能一上电就开工作,而是需要等待ADC采样到一个完整的数据,才进行以此DMA转运操作。
3.存储器和寄存器
3.1ROM存储器
每一个存储器都有对应的地址,这样程序就通过访问对应的地址来访问存储器。
由上图,首先看到两种类型,ROM是一种只读、非易失性、掉电不丢失的存储器,存放的是FLASH闪存,可以发现在ROM中不仅包含程序存储器FLASH,同时也包含系统存储器以及选项字节。这是因为,系统存储器、选项字节的存储介质也是FLASH存储器,而我们一般说的FLASH一般指的是FLASH主闪存。
程序存储器FLASH,也就是存储C语言编译后的程序代码,也就是存储我们下载的代码,地址是0x0800 0000是32位的,同时只要是0x08开头的,一般都是属于FLASH,因为其他存储器都是在FLASH存储器的起始地址上经过偏移而来。
然后是系统存储器,存储BootLoader启动程序以及初始化硬件环境,用于串口下载。
接着是选项字节,用于存储一些独立于程序代码的配置参数,在选项字节里面,存放的是读保护、写保护、以及看门狗的配置等,位于ROM的最后面,在下载程序的时候不刷新选项字节来保持选项配置保持不变。
3.2RAM存储器
RAM存储器译为随机存储器,是一种掉电易丢失的存储器。
如上图,运行内存SRAM,用于存放一些程序中产生的临时变量,例如结构体变量、指针变量等,地址是0x2000 0000。
接着是外设寄存器和内核寄存器,外设寄存器也就是指APB1和APB2总线上所连接的外设寄存器,而内核寄存器对应的就是内核外设寄存器,例如NVIC就是内核的外设,而NVIC外设寄存器就属于内核寄存器。外设寄存器和内核寄存器地址分别为,0x4000 0000、0xE000 0000。
寄存器是连接CPU和硬件的桥梁,每一个寄存器后面都连接着片上外设,这样就通过对寄存器的控制就可以对应的硬件(片上外设)。而CPU访问这些寄存器,只需要对该寄存器的地址进行访问即可,如下图。
3.3ROM和RAM的区别
本质上,寄存器就是特俗的存储器,只不过在手册中对其进行了进一步的分类,存储器一般都是存储在内核中只读的寄存器(ROM)中的,而程序中产生的临时变量、函数产生的栈帧都是在RAM(SRAM)中存储的。
ROM相较RAM只能读不能写,而存储容量较大,成本便宜。RAM需要对变量进行输入输出,操作空间较大、较复杂,相对的运行内存较小,与成本成正比。
二者相符相乘,这样才能以最优的成本、不错的性能来高效地完成任务。
4.DAM通道
如上图为DMA1内部通道执行情况,如果需要使用到7个通道,同时输出的话必然会造成程序错误,此时就需要用到仲裁器,来对每个通道的优先级进行判断。
4.1DMA通道优先级的判断
通过软件对这7个通道进行优先级设置,优先级分为4个等级:
可以看到,由两位PL[1:0]控制着优先级的配置,设置对应的位即可配置通道x(x=1~7)的优先级。
如果用到了多个DMA通道,此时就需要配置DMA不同通道的优先级,在经过仲裁器进行仲裁,来得到谁先执行谁后执行。
对应的优先级配置是对结构体变量DMA_InitTypeDef来进行参数配置的。
另外需要注意,当两个通道的优先级一致的时候,则较低编号的通道比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4。
4.2DMA通道结构
4.2.1硬件触发源
如上图,为DMA1的7个通道,每一个通道的输入有所限制,例如通道1的硬件触发来源只能够是ADC1、TIM2_CH3、TIM4_CH1。在需要硬件触发DMA的转运功能时,需要查阅手册,查明硬件触发源是属于哪个通道,然后在配置相应的参数。
同时,可以看到最终输出只能有一个,这就意味着在同一时间内只有一个请求有效。
更直观的,上图可以看为下表:
这个DAM1硬件触发源更加清晰。
下图为DMA的大致结构简化图:
DMA可以由存储器到存储器之间的转运,最主要是完成寄存器和存储器之间的数据转运。
存储器到存储器之间的转运是由软件触发DMA转运,运行速度快,花费时间也就短。
而寄存器到存储器,例如ADC采集,将采集转换后的数据由DMA转运到存储器中,需要一定的时机,运行速度较慢于前一种,时间也就更长。
首先看到左边,属于外设部分,外设需要配置参数起始地址、数据宽度、地址是否自增,这些参数实际上就是对外设寄存器的配置,对应的存储器(FLASH、SRAM)需要配置对应的起始地址、数据宽度、地址是否自增。
同样的,还能看到可以配置存储器和寄存器之间的转运方向,下方还有一个传输计数器,同时在传输计数器的下方还有一个数据选择器用来选择是硬件触发,还是软件触发,接下来对这些参数进行讲解:
4.2.2数据宽度
上述为输入源和输入目标宽度不同情况下所执行的操作。
如果宽度一致,则源地址数据等于目标地址数据。
如果源宽度小于目标宽度,则在源宽度的高位补上0,例如,上面源宽度为8的0xB0,输出目标宽度为16,得到0x00B0。
如果源宽度大于目标宽度,则舍弃源宽度的高位,例如,源宽度为16的0xB1B0,输出目标宽度为8,得到0xB0,高位的B1被舍弃掉了。
4.2.3起始地址、地址是否自增、转运方向
起始地址,也就是标明寄存器/存储器的首地址,如果有多个数据需要转运,配置地址自增就能实现一组数据的转运。
实际上,在上图中外设寄存器框图中并没有规定外设寄存器参数中一定只能寄存器地址、宽度、是否自增,存储器也是如此。可以将寄存器和存储器的配置位置对调,然后只需要将DMA的转运方向反向一下,这样依旧能够实现寄存器到存储器方向的转运。进而,也能够理解存储器到存储器是如何实现的,也就是将本应放置寄存器的参数配置相应的存储寄存器即可实现。
接下里细说一下地址是否自增(在软件触发的情况下):
如果外设寄存器自增,存储器不自增会发生什么:
寄存器中的数据每一位按顺序都被转运到存储器的第一位中,得到的结果是DataA[6],前面的数据都被覆盖掉了,显然是不行的。
如果外设寄存器不自增,存储器自增:
这样寄存器上数据第一位DataA[0]就会转运到存储器的每一位上。
如果外设寄存器自增,存储器也自增:
显然,这是我们需要的结果,寄存器上的每一位,对应的转运到了存储器中。
宽度、是否自增(在数据手册中叫做增量模式)对应下面四位:
传输方向的控制位:
另外需要注意的是,图中是存储器到存储器,而我所描述的是寄存器到存储器,前面说过,寄存器是特殊的存储器,只不过在数据手册中有明确的区分(便于理解)。
4.2.4传输计数器、自动重装器
这两个器件类似TIM定时器的计数器CNT和自动重装值ARR,实现的功能是类似的。
传输计数器,例如上述的例子,转运七次,也即传输计数器的值是从7开始自减到0,类比TIM定时器中的CNT。
如果在控制位上设置CIRC,也即循环模式:
对应的需要在在自动重装器中设置转运的数据数量,上述的例子,也即自动重装器设置为7。
如果不配置这一位,对应的就属于单次模式)(非循环模式),转运完一次就结束。
在ADC采集模式下,如果使用了扫描模式,则需要使用DMA来转运,对应的DMA需要配置为循环模式。
传输计数器对应下面:
4.2.5触发源选择
触发源选择无非就是对软件还是硬件触发DMA转运来进行选择,对应的有一个数据选择器:
非存储器(也即寄存器)到存储器,使MEM2MEM位置0实现,同样的置1实现存储器到存储器。
前面提到过两种触发的情况,这里不在赘述。
5.DMA中断
DMA中的中断,包括传输过半、传输完成、传输错误这三种情况会会产生中断,前提是使能控制位。
7个通道,每一个通道都对应这三个中断标志位。
对应的,还有专门用来清除中断标志的寄存器:
在配置的时候需要写上一个函数,DMA_ITConfig,选择对应的参数。
配置中断函数 void DMA1_Channelx_IRQn_Handler(void)。
6.DMA的处理过程
上述为手册对DMA处理操作的描述,先看到源地址存储的寄存器:
其次是存储器的:
这里假设从寄存器中转运至存储器,这样方便理解。
也就是说,首先DMA从CPARx外设地址寄存器中获取初始地址,然后DMA在从CMARx存储器地址寄存器中(先获取初始地址后)执行存储操作。然后传输数量寄存器自减,类似CNT,传输一次就自减1。
同时需要注意当通道配置为非循环模式时,传输结束后(即传输计数变为0)将不再产生DMA操作。要开始新的 DMA传输,需要在关闭DMA通道的情况下,在DMA_CNDTRx寄存器中重新写入传输数目。
在循环模式下,最后一次传输结束时,DMA_CNDTRx寄存器的内容会自动地被重新加载为其初始数值,内部的当前外设/存储器地址寄存器也被重新加载为DMA_CPARx/DMA_CMARx寄存器 设定的初始基地址。
同时还需要注意,在手册中,如果当通道配置为非循环模式时,传输结束后(即传输计数变为0)将不再产生DMA操作。要开始新的DMA传输,需要在关闭DMA通道的情况下,DMA_CNDTRx寄存器中重新写入传输数目。
7.DMA的配置
7.1存储器到存储器DMA的配置
实际上,对上述结构图的解释就是配置DMA的参数。
对应的,看到DMA_InitTypeDef的定义:
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; /*!< Specifies the peripheral base address for DMAy Channelx. */
uint32_t DMA_MemoryBaseAddr; /*!< Specifies the memory base address for DMAy Channelx. */
uint32_t DMA_DIR; /*!< Specifies if the peripheral is the source or destination.
This parameter can be a value of @ref DMA_data_transfer_direction */
uint32_t DMA_BufferSize; /*!< Specifies the buffer size, in data unit, of the specified Channel.
The data unit is equal to the configuration set in DMA_PeripheralDataSize
or DMA_MemoryDataSize members depending in the transfer direction. */
uint32_t DMA_PeripheralInc; /*!< Specifies whether the Peripheral address register is incremented or not.
This parameter can be a value of @ref DMA_peripheral_incremented_mode */
uint32_t DMA_MemoryInc; /*!< Specifies whether the memory address register is incremented or not.
This parameter can be a value of @ref DMA_memory_incremented_mode */
uint32_t DMA_PeripheralDataSize; /*!< Specifies the Peripheral data width.
This parameter can be a value of @ref DMA_peripheral_data_size */
uint32_t DMA_MemoryDataSize; /*!< Specifies the Memory data width.
This parameter can be a value of @ref DMA_memory_data_size */
uint32_t DMA_Mode; /*!< Specifies the operation mode of the DMAy Channelx.
This parameter can be a value of @ref DMA_circular_normal_mode.
@note: The circular buffer mode cannot be used if the memory-to-memory
data transfer is configured on the selected Channel */
uint32_t DMA_Priority; /*!< Specifies the software priority for the DMAy Channelx.
This parameter can be a value of @ref DMA_priority_level */
uint32_t DMA_M2M; /*!< Specifies if the DMAy Channelx will be used in memory-to-memory transfer.
This parameter can be a value of @ref DMA_memory_to_memory */
}DMA_InitTypeDef;
下面六个参数DMA_PeripheralBaseAddr、DMA_MemoryBaseAddr、DMA_PeripheralDataSize、DMA_MemoryDataSize、DMA_PeripheralInc、DMA_MemoryInc,
也就对应:
DMA_PeripheralBaseAddr、DMA_MemoryBaseAddr,这两个参数填写具体的地址,根据查阅数据手册,或者地址偏移来计算。
DMA_PeripheralDataSize、DMA_MemoryDataSize,这两个参数填写数据的大小,也就是数据的类型:
可以是字节Byte、半字HalfWord、字Word。
然后是DMA_PeripheralInc、DMA_MemoryInc是否自增:
参数为使能或者是失能。
接着是DMA_DIR,也即传输方向:
正向还是反向传输对应的后缀DST/SRC。
然后是DMA_Mode,对应DMA是否需要循环模式:
接着DMA_Priority,DMA通道优先级设置:
对应四个等级的优先级,优先级相同的两个通道,编号小的优先级高。
然后是DMA_BufferSize,对应转运次数,也即转运数据大小,输入具体的位数。
最后是DMA_M2M,也就是DMA触发源选择,是软件触发(存储器和存储器之间),还是硬件触发(寄存器和存储器之间)。
ENABLE使能就是Memory to Memory(M2M,其中to发two的音,写为2)存储器到存储器使能,也即软件触发。
对应的DISABLE失能就是硬件触发。
以上就是该结构体配置的全部参数了,以下是完整配置代码:
/*开启时钟*/
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后,再开始
7.2AD多通道下的DMA配置
这里讲一些细节,具体代码自行理解:
- ADC配置几个采集通道(对应几个规则组通道),DMA的CNT的计数器值也就是传输计数器的值就为多少。
- ADC配置为扫描模式,则对应的DMA就要配置为循环模式。
- 只需要用到一个DMA通道,对DMA通道的优先级没有要求。
- ADC采集属于硬件触发,选择对应M2M参数DISABLE。
- 需要打开DMA对ADC的通道,如ADC_DMACmd(ADC1, ENABLE);
- ADC的起始地址为ADC->DR,而存储器的地址通过定义一个数组来存储。
- ADC的采集选择软件触发,ADC_SoftwareStartConvCmd(ADC1, ENABLE);
代码如下:
#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就可以一直连续不断地工作
}
对应的显示代码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,手动增加一些转换的间隔时间
}
}
8.总结
以上就是关于DMA的全部总结了,欢迎各位来讨论以及指正错误,另外保持对学习的一个上进心,才能够正真学好嵌入式。