第30章 ADC—电压采集—零死角玩转STM32-F429系列

2023-05-16

第30章     ADC—电压采集

全套200集视频教程和1000PDF教程请到秉火论坛下载:www.firebbs.cn

野火视频教程优酷观看网址:http://i.youku.com/firege

 

 

本章参考资料:《STM32F4xx中文参考手册》ADC章节。

学习本章时,配合《STM32F4xx中文参考手册》ADC章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。

30.1 ADC简介

STM32F429IGT63ADC,每个ADC12位、10位、8位和6位可选,每个ADC16个外部通道。另外还有两个内部ADC源和VBAT通道挂在ADC1上。ADC具有独立模式、双重模式和三重模式,对于不同AD转换要求几乎都有合适的模式可选。ADC功能非常强大,具体的我们在功能框图中分析每个部分的功能。

30.2 ADC功能框图剖析

01 单个ADC功能框图

掌握了ADC的功能框图,就可以对ADC有一个整体的把握,在编程的时候可以做到了然如胸,不会一知半解。框图讲解采用从左到右的方式,跟ADC采集数据,转换数据,传输数据的方向大概一致。

1.    ①电压输入范围

ADC输入范围为:VREF- VIN VREF+。由VREF-VREF+VDDAVSSA、这四个外部引脚决定。

我们在设计原理图的时候一般把VSSAVREF-接地,把VREF+VDDA3V3,得到ADC的输入电压范围为:0~3.3V

如果我们想让输入的电压范围变宽,去到可以测试负电压或者更高的正电压,我们可以在外部加一个电压调理电路,把需要转换的电压抬升或者降压到0~3.3V,这样ADC就可以测量了。

2.    ②输入通道

我们确定好ADC输入电压之后,那么电压怎么输入到ADC?这里我们引入通道的概念,STM32ADC多达19个通道,其中外部的16个通道就是框图中的ADCx_IN0ADCx_IN1...ADCx_IN5。这16个通道对应着不同的IO口,具体是哪一个IO口可以从手册查询到。其中ADC1/2/3还有内部通道: ADC1的通道ADC1_IN16连接到内部的VSS,通道ADC1_IN17连接到了内部参考电压VREFINT连接,通道ADC1_IN18连接到了芯片内部的温度传感器或者备用电源VBATADC2ADC3的通道161718全部连接到了内部的VSS

02 STM32F429IGT6 ADC 通道

外部的16个通道在转换的时候又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多有4路。那这两个通道有什么区别?在什么时候使用?

规则通道

规则通道:顾名思意,规则通道就是很规矩的意思,我们平时一般使用的就是这个通道,或者应该说我们用到的都是这个通道,没有什么特别要注意的可讲。

注入通道

注入,可以理解为插入,插队的意思,是一种不安分的通道。它是一种在规则通道转换的时候强行插入要转换的一种。如果在规则通道转换过程中,有注入通道插队,那么就要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。这点跟中断程序很像,都是不安分的主。所以,注入通道只有在规则通道存在时才会出现。

3.    ③转换顺序
规则序列

规则序列寄存器有3个,分别为SQR3SQR2SQR1SQR3控制着规则序列中的第一个到第六个转换,对应的位为:SQ1[4:0]~SQ6[4:0],第一次转换的是位4:0 SQ1[4:0],如果通道16想第一次转换,那么在SQ1[4:0]16即可。SQR2控制着规则序列中的第7到第12个转换,对应的位为:SQ7[4:0]~SQ12[4:0],如果通道1想第8个转换,则SQ8[4:0]1即可。SQR1控制着规则序列中的第13到第16个转换,对应位为:SQ13[4:0]~SQ16[4:0],如果通道6想第10个转换,则SQ10[4:0]6即可。具体使用多少个通道,由SQR1的位L[3:0]决定,最多16个通道。

03 规则序列寄存器

注入序列

注入序列寄存器JSQR只有一个,最多支持4个通道,具体多少个由JSQRJL[2:0]决定。如果JL值小于4的话,则JSQRSQR决定转换顺序的设置不一样,第一次转换的不是JSQR1[4:0],而是JCQRx[4:0] x = 4-JL),跟SQR刚好相反。如果JL=001个转换),那么转换的顺序是从JSQR4[4:0]开始,而不是从JSQR1[4:0]开始,这个要注意,编程的时候不要搞错。当JL等于4时,跟SQR一样。

04 注入序列寄存器

4.    ④触发源

通道选好了,转换的顺序也设置好了,那接下来就该开始转换了。ADC转换可以由ADC控制寄存器2: ADC_CR2ADON这个位来控制,写1的时候开始转换,写0的时候停止转换,这个是最简单也是最好理解的开启ADC转换的控制方式,理解起来没啥技术含量。

除了这种庶民式的控制方法,ADC还支持外部事件触发转换,这个触发包括内部定时器触发和外部IO触发。触发源有很多,具体选择哪一种触发源,由ADC控制寄存器2:ADC_CR2EXTSEL[2:0]JEXTSEL[2:0]位来控制。EXTSEL[2:0]用于选择规则通道的触发源,JEXTSEL[2:0]用于选择注入通道的触发源。选定好触发源之后,触发源是否要激活,则由ADC控制寄存器2:ADC_CR2EXTTRIGJEXTTRIG这两位来激活。

如果使能了外部触发事件,我们还可以通过设置ADC控制寄存器2:ADC_CR2EXTEN[1:0]JEXTEN[1:0]来控制触发极性,可以有4种状态,分别是:禁止触发检测、上升沿检测、下降沿检测以及上升沿和下降沿均检测。

5.    ⑤转换时间
ADC时钟

ADC输入时钟ADC_CLKPCLK2经过分频产生,最大值是36MHz,典型值为30MHz,分频因子由ADC通用控制寄存器ADC_CCRADCPRE[1:0]设置,可设置的分频系数有2468,注意这里没有1分频。对于STM32F429IGT6我们一般设置PCLK2=HCLK/2=90MHz。所以程序一般使用4分频或者6分频。

采样时间

ADC需要若干个ADC_CLK周期完成对输入的电压进行采样,采样的周期数可通过ADC 采样时间寄存器ADC_SMPR1ADC_SMPR2中的SMP[2:0]位设置,ADC_SMPR2控制的是通道0~9ADC_SMPR1控制的是通道10~17。每个通道可以分别用不同的时间采样。其中采样周期最小是3个,即如果我们要达到最快的采样,那么应该设置采样周期为3个周期,这里说的周期就是1/ADC_CLK

ADC的总转换时间跟ADC的输入时钟和采样时间有关,公式为:

Tconv = 采样时间 + 12个周期

ADCCLK = 30MHz,即PCLK260MHzADC时钟为2分频,采样时间设置为3个周期,那么总的转换时为:Tconv = 3 + 12 = 15个周期 =0.5us

一般我们设置PCLK2=90MHz,经过ADC预分频器能分频到最大的时钟只能是22.5M,采样周期设置为3个周期,算出最短的转换时间为0.6667us,这个才是最常用的。

6.    ⑥数据寄存器

一切准备就绪后,ADC转换后的数据根据转换组的不同,规则组的数据放在ADC_DR寄存器,注入组的数据放在JDRx。如果是使用双重或者三重模式那规矩组的数据是存放在通用规矩寄存器ADC_CDR内的。

规则数据寄存器ADC_DR

ADC规则组数据寄存器ADC_DR只有一个,是一个32位的寄存器,只有低16位有效并且只是用于独立模式存放转换完成数据。因为ADC的最大精度是12位,ADC_DR16位有效,这样允许ADC存放数据时候选择左对齐或者右对齐,具体是以哪一种方式存放,由ADC_CR211ALIGN设置。假如设置ADC精度为12位,如果设置数据为左对齐,那AD转换完成数据存放在ADC_DR寄存器的[4:15]位内;如果为右对齐,则存放在ADC_DR寄存器的[0:11]位内。

规则通道可以有16个这么多,可规则数据寄存器只有一个,如果使用多通道转换,那转换的数据就全部都挤在了DR里面,前一个时间点转换的通道数据,就会被下一个时间点的另外一个通道转换的数据覆盖掉,所以当通道转换完成后就应该把数据取走,或者开启DMA模式,把数据传输到内存里面,不然就会造成数据的覆盖。最常用的做法就是开启DMA传输。

如果没有使用DMA传输,我们一般都需要使用ADC状态寄存器ADC_SR获取当前ADC转换的进度状态,进而进行程序控制。

注入数据寄存器ADC_JDRx

ADC注入组最多有4个通道,刚好注入数据寄存器也有4个,每个通道对应着自己的寄存器,不会跟规则寄存器那样产生数据覆盖的问题。ADC_JDRx32位的,低16位有效,高16位保留,数据同样分为左对齐和右对齐,具体是以哪一种方式存放,由ADC_CR211ALIGN设置。

通用规则数据寄存器ADC_CDR

规则数据寄存器ADC_DR是仅适用于独立模式的,而通用规则数据寄存器ADC_CDR是适用于双重和三重模式的。独立模式就是仅仅适用三个ADC的其中一个,双重模式就是同时使用ADC1ADC2,而三重模式就是三个ADC同时使用。在双重或者三重模式下一般需要配合DMA数据传输使用。

7.    ⑦中断
转换结束中断

数据转换结束后,可以产生中断,中断分为四种:规则通道转换结束中断,注入转换通道转换结束中断,模拟看门狗中断和溢出中断。其中转换结束中断很好理解,跟我们平时接触的中断一样,有相应的中断标志位和中断使能位,我们还可以根据中断类型写相应配套的中断服务程序。

模拟看门狗中断

当被ADC转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,前提是我们开启了模拟看门狗中断,其中低阈值和高阈值由ADC_LTRADC_HTR设置。例如我们设置高阈值是2.5V,那么模拟电压超过2.5V的时候,就会产生模拟看门狗中断,反之低阈值也一样。

溢出中断

如果发生DMA传输数据丢失,会置位ADC状态寄存器ADC_SROVR位,如果同时使能了溢出中断,那在转换结束后会产生一个溢出中断。

DMA请求

规则和注入通道转换结束后,除了产生中断外,还可以产生DMA请求,把转换好的数据直接存储在内存里面。对于独立模式的多通道AD转换使用DMA传输非常有必须要,程序编程简化了很多。对于双重或三重模式使用DMA传输几乎可以说是必要的。有关DMA请求需要配合《STM32F4xx中文参考手册》DMA控制器这一章节来学习。一般我们在使用ADC的时候都会开启DMA传输。

8.    ⑧电压转换

模拟电压经过ADC转换后,是一个相对精度的数字值,如果通过串口以16进制打印出来的话,可读性比较差,那么有时候我们就需要把数字电压转换成模拟电压,也可以跟实际的模拟电压(用万用表测)对比,看看转换是否准确。

我们一般在设计原理图的时候会把ADC的输入电压范围设定在:0~3.3v,如果设置ADC12位的,那么12位满量程对应的就是3.3V12位满量程对应的数字值是:2^12。数值0对应的就是0V。如果转换后的数值为  X X对应的模拟电压为Y,那么会有这么一个等式成立:  2^12 / 3.3 = X / Y=> Y = (3.3 * X ) / 2^12

30.3 ADC初始化结构体详解

标准库函数对每个外设都建立了一个初始化结构体xxx_InitTypeDef(xxx为外设名称),结构体成员用于设置外设工作参数,并由标准库函数xxx_Init()调用这些设定参数进入设置外设相应的寄存器,达到配置外设工作环境的目的。

结构体xxx_InitTypeDef和库函数xxx_Init配合使用是标准库精髓所在,理解了结构体xxx_InitTypeDef每个成员意义基本上就可以对该外设运用自如了。结构体xxx_InitTypeDef定义在stm32f4xx_xxx.h文件中,库函数xxx_Init定义在stm32f4xx_xxx.c文件中,编程时我们可以结合这两个文件内注释使用。

ADC_InitTypeDef结构体

ADC_InitTypeDef结构体定义在stm32f4xx_adc.h文件内,具体定义如下:

1 typedef struct {

2 uint32_t ADC_Resolution; //ADC分辨率选

3 FunctionalState ADC_ScanConvMode; //ADC扫描选择

4 FunctionalState ADC_ContinuousConvMode; //ADC连续转换模式选择

5 uint32_t ADC_ExternalTrigConvEdge; //ADC外部触发极性

6 uint32_t ADC_ExternalTrigConv; //ADC外部触发选择

7 uint32_t ADC_DataAlign; //输出数据对齐方式

8 uint8_t ADC_NbrOfChannel; //转换通道数目

9 } ADC_InitTypeDef;

ADC_Resolution:配置ADC的分辨率,可选的分辨率有12位、10位、8位和6位。分辨率越高,AD转换数据精度越高,转换时间也越长;分辨率越低,AD转换数据精度越低,转换时间也越短。

ScanConvMode:可选参数为ENABLEDISABLE,配置是否使用扫描。如果是单通道AD转换使用DISABLE,如果是多通道AD转换使用ENABLE

ADC_ContinuousConvMode:可选参数为ENABLEDISABLE,配置是启动自动连续转换还是单次转换。使用ENABLE配置为使能自动连续转换;使用DISABLE配置为单次转换,转换一次后停止需要手动控制才重新启动转换。

ADC_ExternalTrigConvEdge:外部触发极性选择,如果使用外部触发,可以选择触发的极性,可选有禁止触发检测、上升沿触发检测、下降沿触发检测以及上升沿和下降沿均可触发检测。

ADC_ExternalTrigConv:外部触发选择,图 01中列举了很多外部触发条件,可根据项目需求配置触发来源。实际上,我们一般使用软件自动触发。

ADC_DataAlign:转换结果数据对齐模式,可选右对齐ADC_DataAlign_Right或者左对齐ADC_DataAlign_Left。一般我们选择右对齐模式。

ADC_NbrOfChannelAD转换通道数目。

ADC_CommonInitTypeDef结构体

ADC除了有ADC_InitTypeDef初始化结构体外,还有一个ADC_CommonInitTypeDef通用初始化结构体。ADC_CommonInitTypeDef结构体内容决定三个ADC共用的工作环境,比如模式选择、ADC时钟等等。

ADC_CommonInitTypeDef结构体也是定义在stm32_f4xx.h文件中,具体定义如下:

1 typedef struct {

2 uint32_t ADC_Mode; //ADC模式选择

3 uint32_t ADC_Prescaler; //ADC分频系数

4 uint32_t ADC_DMAAccessMode; //DMA模式配置

5 uint32_t ADC_TwoSamplingDelay; //采样延迟

6 } ADC_InitTypeDef;

ADC_ModeADC工作模式选择,有独立模式、双重模式以及三重模式。

ADC_PrescalerADC时钟分频系数选择,ADC时钟是有PCLK2分频而来,分频系数决定ADC时钟频率,可选的分频系数为2468ADC最大时钟配置为36MHz

ADC_DMAAccessModeDMA模式设置,只有在双重或者三重模式才需要设置,可以设置三种模式,具体可参考参考手册说明。

ADC_TwoSamplingDelay:2个采样阶段之前的延迟,仅适用于双重或三重交错模式。

30.4 独立模式单通道采集实验

STM32ADC功能繁多,我们设计三个实验尽量完整的展示ADC的功能。首先是比较基础实用的单通道采集,实现开发板上电位器的动触点输出引脚电压的采集并通过串口打印至PC端串口调试助手。单通道采集适用AD转换完成中断,在中断服务函数中读取数据,不使用DMA传输,在多通道采集时才使用DMA传输。

30.4.1 硬件设计

开发板板载一个贴片滑动变阻器,电路设计见图 05。

05 开发板电位器部分原理图

贴片滑动变阻器的动触点通过连接至STM32芯片的ADC通道引脚。当我们使用旋转滑动变阻器调节旋钮时,其动触点电压也会随之改变,电压变化范围为0~3.3V,亦是开发板默认的ADC电压采集范围。

30.4.2 软件设计

这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本章配套的工程。

我们编写两个ADC驱动文件,bsp_adc.h bsp_adc.c,用来存放ADC所用IO引脚的初始化函数以及ADC配置相关函数。

1.    编程要点

1)    初始化配置ADC目标引脚为模拟输入模式;

2)    使能ADC时钟;

3)    配置通用ADC为独立模式,采样4分频;

4)    设置目标ADC为12位分辨率,1通道的连续转换,不需要外部触发;

5)    设置ADC转换通道顺序及采样时间;

6)    配置使能ADC转换完成中断,在中断内读取转换完数据;

7)    启动ADC转换;

8)    使能软件触发ADC转换。

ADC转换结果数据使用中断方式读取,这里没有使用DMA进行数据传输。

2.    代码分析
ADC宏定义

代码清单 01 ADC宏定义

1 #define Rheostat_ADC_IRQ ADC_IRQn

2 #define Rheostat_ADC_INT_FUNCTION ADC_IRQHandler

3

4 #define RHEOSTAT_ADC_GPIO_PORT GPIOC

5 #define RHEOSTAT_ADC_GPIO_PIN GPIO_Pin_3

6 #define RHEOSTAT_ADC_GPIO_CLK RCC_AHB1Periph_GPIOC

7

8 #define RHEOSTAT_ADC ADC1

9 #define RHEOSTAT_ADC_CLK RCC_APB2Periph_ADC1

10 #define RHEOSTAT_ADC_CHANNEL ADC_Channel_13

使用宏定义引脚信息方便硬件电路改动时程序移植。

ADC GPIO初始化函数

代码清单 02 ADC GPIO初始化

1 static void Rheostat_ADC_GPIO_Config(void)

2 {

3 GPIO_InitTypeDef GPIO_InitStructure;

4

5 // 使能 GPIO 时钟

6 RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK, ENABLE);

7

8 // 配置 IO

9 GPIO_InitStructure.GPIO_Pin = RHEOSTAT_ADC_GPIO_PIN;

10 // 配置为模拟输入

11 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;

12 // 不上拉不下拉

13 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;

14 GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);

15 }

使用到GPIO时候都必须开启对应的GPIO时钟,GPIO用于AD转换功能必须配置为模拟输入模式。

配置ADC工作模式

代码清单 03 ADC工作模式配置

1 static void Rheostat_ADC_Mode_Config(void)

2 {

3 ADC_InitTypeDef ADC_InitStructure;

4 ADC_CommonInitTypeDef ADC_CommonInitStructure;

5

6 // 开启ADC时钟

7 RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);

8

9 // -------------------ADC Common 结构体参数初始化--------------------

10 // 独立ADC模式

11 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;

12 // 时钟为fpclk x分频

13 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;

14 // 禁止DMA直接访问模式

15 ADC_CommonInitStructure.ADC_DMAAccessMode=ADC_DMAAccessMode_Disabled;

16 // 采样时间间隔

17 ADC_CommonInitStructure.ADC_TwoSamplingDelay=

18 ADC_TwoSamplingDelay_10Cycles;

19 ADC_CommonInit(&ADC_CommonInitStructure);

20

21 // -------------------ADC Init 结构体参数初始化---------------------

22 // ADC 分辨率

23 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;

24 // 禁止扫描模式,多通道采集才需要

25 ADC_InitStructure.ADC_ScanConvMode = DISABLE;

26 // 连续转换

27 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;

28 //禁止外部边沿触发

29 ADC_InitStructure.ADC_ExternalTrigConvEdge =

30 ADC_ExternalTrigConvEdge_None;

31 //使用软件触发,外部触发不用配置,注释掉即可

32 //ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T1_CC1;

33 //数据右对齐

34 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

35 //转换通道 1

36 ADC_InitStructure.ADC_NbrOfConversion = 1;

37 ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);

38 //------------------------------------------------------------------

39 // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为56个时钟周期

40 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL,

41 1, ADC_SampleTime_56Cycles);

42

43 // ADC 转换结束产生中断,在中断服务程序中读取转换值

44 ADC_ITConfig(RHEOSTAT_ADC, ADC_IT_EOC, ENABLE);

45 // 使能ADC

46 ADC_Cmd(RHEOSTAT_ADC, ENABLE);

47 //开始adc转换,软件触发

48 ADC_SoftwareStartConv(RHEOSTAT_ADC);

49 }

首先,使用ADC_InitTypeDefADC_CommonInitTypeDef结构体分别定义一个ADC初始化和ADC通用类型变量,这两个结构体我们之前已经有详细讲解。

我们调用RCC_APB2PeriphClockCmd()开启ADC时钟。

接下来我们使用ADC_CommonInitTypeDef结构体变量ADC_CommonInitStructure来配置ADC为独立模式、分频系数为4、不需要设置DMA模式、20个周期的采样延迟,并调用ADC_CommonInit函数完成ADC通用工作环境配置。

我们使用ADC_InitTypeDef结构体变量ADC_InitStructure来配置ADC112位分辨率、单通道采集不需要扫描、启动连续转换、使用内部软件触发无需外部触发事件、使用右对齐数据格式、转换通道为1,并调用ADC_Init函数完成ADC1工作环境配置。

ADC_RegularChannelConfig函数用来绑定ADC通道转换顺序和时间。它接收4个形参,第一个形参选择ADC外设,可为ADC1ADC2ADC3;第二个形参通道选择,总共可选18个通道;第三个形参为转换顺序,可选为116;第四个形参为采样周期选择,采样周期越短,ADC转换数据输出周期就越短但数据精度也越低,采样周期越长,ADC转换数据输出周期就越长同时数据精度越高。PC3对应ADC通道ADC_Channel_13,这里我们选择ADC_SampleTime_56Cycles56周期的采样时间。

利用ADC转换完成中断可以非常方便的保证我们读取到的数据是转换完成后的数据而不用担心该数据可能是ADC正在转换时"不稳定"的数据。我们使用ADC_ITConfig函数使能ADC转换完成中断,并在中断服务函数中读取转换结果数据。

ADC_Cmd函数控制ADC转换启动和停止。

最后,如果使用软件触发需要调用ADC_SoftwareStartConvCmd函数进行使能配置。

ADC中断配置

代码清单 04 ADC中断配置

1 static void Rheostat_ADC_NVIC_Config(void)

2 {

3 NVIC_InitTypeDef NVIC_InitStructure;

4

5 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

6

7 NVIC_InitStructure.NVIC_IRQChannel = Rheostat_ADC_IRQ;

8 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;

9 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;

10 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

11

12 NVIC_Init(&NVIC_InitStructure);

13 }

在Rheostat_ADC_NVIC_Config函数中我们配置了ADC转换完成中断,使用中断同时需要配置中断源和中断优先级。

ADC中断服务函数

代码清单 05 ADC中断服务函数

1 void ADC_IRQHandler(void)

2 {

3 if (ADC_GetITStatus(RHEOSTAT_ADC,ADC_IT_EOC)==SET) {

4 // 读取ADC的转换值

5 ADC_ConvertedValue = ADC_GetConversionValue(RHEOSTAT_ADC);

6

7 }

8 ADC_ClearITPendingBit(RHEOSTAT_ADC,ADC_IT_EOC);

9

10 }

中断服务函数一般定义在stm32f4xx_it.c文件内,我们只使能了ADC转换完成中断,在ADC转换完成后就会进入中断服务函数,我们在中断服务函数内直接读取ADC转换结果保存在变量ADC_ConvertedValue(main.c中定义)中。

ADC_GetConversionValue函数是获取ADC转换结果值的库函数,只有一个形参为ADC外设,可选为ADC1ADC2ADC3,该函数还返回一个16位的ADC转换结果值。

主函数

代码清单 06 主函数

1 int main(void)

2 {

3 /*初始化USART1*/

4 Debug_USART_Config();

5

6 /* 初始化滑动变阻器用到的DACADC采集完成会产生ADC中断,

7 stm32f4xx_it.c文件中的中断服务函数更新ADC_ConvertedValue的值 */

8 Rheostat_Init();

9

10 printf("\r\n ----这是一个ADC实验(NO DMA传输)----\r\n");

11

12

13 while (1) {

14 Delay(0xffffee);

15 printf("\r\n The current AD value = 0x%04X \r\n",

16 ADC_ConvertedValue);

17 printf("\r\n The current AD value = %f V \r\n",ADC_Vol);

18

19 ADC_Vol =(float)(ADC_ConvertedValue*3.3/4096); // 读取转换的AD

20

21 }

22 }

主函数先调用USARTx_Config函数配置调试串口相关参数,函数定义在bsp_debug_usart.c文件中。

接下来调用Rheostat _Init函数进行ADC初始化配置并启动ADCRheostat _Init函数是定义在bsp_adc.c文件中,它只是简单的分别调用Rheostat_ADC_GPIO_Config ()Rheostat_ADC_Mode_Config ()Rheostat_ADC_NVIC_Config()

Delay函数只是一个简单的延时函数。

ADC中断服务函数中我们把AD转换结果保存在变量ADC_ConvertedValue中,根据我们之前的分析可以非常清楚的计算出对应的电位器动触点的电压值。

最后就是把相关数据打印至串口调试助手。

30.4.3 下载验证

USB线连接开发板"USB TO UART"接口跟电脑,在电脑端打开串口调试助手,把编译好的程序下载到开发板。在串口调试助手可看到不断有数据从开发板传输过来,此时我们旋转电位器改变其电阻值,那么对应的数据也会有变化。

30.5 独立模式多通道采集实验

30.5.1 硬件设计

开发板已通过排针接口把部分ADC通道引脚引出,我们可以根据需要选择使用。实际使用时候必须注意保存ADC引脚是单独使用的,不可能与其他模块电路共用同一引脚。

30.5.2 软件设计

这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本章配套的工程。

跟单通道例程一样,我们编写两个ADC驱动文件,bsp_adc.h bsp_adc.c,用来存放ADC所用IO引脚的初始化函数以及ADC配置相关函数,实际上这两个文件跟单通道实验的文件是非常相似的。

1.    编程要点

1)    初始化配置ADC目标引脚为模拟输入模式;

2)    使能ADC时钟和DMA时钟;

3)    配置DMA从ADC规矩数据寄存器传输数据到我们指定的存储区;

4)    配置通用ADC为独立模式,采样4分频;

5)    设置ADC为12位分辨率,启动扫描,连续转换,不需要外部触发;

6)    设置ADC转换通道顺序及采样时间;

7)    使能DMA请求,DMA在AD转换完自动传输数据到指定的存储区;

8)    启动ADC转换;

9)    使能软件触发ADC转换。

ADC转换结果数据使用DMA方式传输至指定的存储区,这样取代单通道实验使用中断服务的读取方法。实际上,多通道ADC采集一般使用DMA数据传输方式更加高效方便。

2.    代码分析
ADC宏定义

代码清单 07 多通道ADC相关宏定义

1 //转换的通道个数

2 #define RHEOSTAT_NOFCHANEL 4

3

4 #define RHEOSTAT_ADC_DR_ADDR ((u32)ADC3+0x4c)

5 #define RHEOSTAT_ADC_GPIO_PORT GPIOF

6 #define RHEOSTAT_ADC_GPIO_CLK RCC_AHB1Periph_GPIOF

7 #define RHEOSTAT_ADC ADC3

8 #define RHEOSTAT_ADC_CLK RCC_APB2Periph_ADC3

9

10 #define RHEOSTAT_ADC_GPIO_PIN1 GPIO_Pin_6

11 #define RHEOSTAT_ADC_CHANNEL1 ADC_Channel_4

12

13 #define RHEOSTAT_ADC_GPIO_PIN2 GPIO_Pin_7

14 #define RHEOSTAT_ADC_CHANNEL2 ADC_Channel_5

15

16 #define RHEOSTAT_ADC_GPIO_PIN3 GPIO_Pin_8

17 #define RHEOSTAT_ADC_CHANNEL3 ADC_Channel_6

18

19 #define RHEOSTAT_ADC_GPIO_PIN4 GPIO_Pin_9

20 #define RHEOSTAT_ADC_CHANNEL4 ADC_Channel_7

21

22 // DMA2 数据流0 通道2

23 #define RHEOSTAT_ADC_DMA_CLK RCC_AHB1Periph_DMA2

24 #define RHEOSTAT_ADC_DMA_CHANNEL DMA_Channel_2

25 #define RHEOSTAT_ADC_DMA_STREAM DMA2_Stream0

定义4个通道进行多通道ADC实验,并且定义DMA相关配置。

ADC GPIO初始化函数

代码清单 08 ADC GPIO初始化

1 static void Rheostat_ADC_GPIO_Config(void)

2 {

3 GPIO_InitTypeDef GPIO_InitStructure;

4

5 // 使能 GPIO 时钟

6 RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK, ENABLE);

7

8 // 配置 IO

9 GPIO_InitStructure.GPIO_Pin = RHEOSTAT_ADC_GPIO_PIN1 |

10 RHEOSTAT_ADC_GPIO_PIN2 |

11 RHEOSTAT_ADC_GPIO_PIN3 |

12 RHEOSTAT_ADC_GPIO_PIN4;

13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;

14 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; //不上拉不下拉

15 GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);

16 }

使用到GPIO时候都必须开启对应的GPIO时钟,GPIO用于AD转换功能必须配置为模拟输入模式。

配置ADC工作模式

代码清单 09 ADC工作模式配置

1 static void Rheostat_ADC_Mode_Config(void)

2 {

3 DMA_InitTypeDef DMA_InitStructure;

4 ADC_InitTypeDef ADC_InitStructure;

5 ADC_CommonInitTypeDef ADC_CommonInitStructure;

6

7 // 开启ADC时钟

8 RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);

9 // 开启DMA时钟

10 RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE);

11

12 // ------------------DMA Init 结构体参数初始化------------------------

13 // 选择 DMA 通道,通道存在于数据流中

14 DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL;

15

16 // 外设基址为:ADC 数据寄存器地址

17 DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_DR_ADDR;

18 // 存储器地址

19 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADC_ConvertedValue;

20

21 // 数据传输方向为外设到存储器

22 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;

23 // 缓冲区大小,指一次传输的数据项

24 DMA_InitStructure.DMA_BufferSize = RHEOSTAT_NOFCHANEL;

25

26 // 外设寄存器只有一个,地址不用递增

27 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

28 // 存储器地址递增

29 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

30 //DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;

31

32 // // 外设数据大小为半字,即两个字节

33 DMA_InitStructure.DMA_PeripheralDataSize =

34 DMA_PeripheralDataSize_HalfWord;

35 // 存储器数据大小也为半字,跟外设数据大小相同

36 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

37

38 // 循环传输模式

39 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

40 // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响

41 DMA_InitStructure.DMA_Priority = DMA_Priority_High;

42

43 // 禁止DMA FIFO ,使用直连模式

44 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;

45 // FIFO 阈值大小,FIFO模式禁止时,这个不用配置

46 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;

47

48 // 存储器突发单次传输

49 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;

50 // 外设突发单次传输

51 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

52

53 //初始化DMA数据流,流相当于一个大的管道,管道里面有很多通道

54 DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);

55

56 // 使能DMA数据流

57 DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);

58

59 // -------------------ADC Common 结构体参数初始化--------------------

60 // 独立ADC模式

61 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;

62 // 时钟为fpclk x分频

63 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;

64 // 禁止DMA直接访问模式

65 ADC_CommonInitStructure.ADC_DMAAccessMode=ADC_DMAAccessMode_Disabled;

66 // 采样时间间隔

67 ADC_CommonInitStructure.ADC_TwoSamplingDelay =

68 ADC_TwoSamplingDelay_10Cycles;

69 ADC_CommonInit(&ADC_CommonInitStructure);

70

71 // -------------------ADC Init 结构体参数初始化---------------------

72 // ADC 分辨率

73 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;

74 // 开启扫描模式

75 ADC_InitStructure.ADC_ScanConvMode = ENABLE;

76 // 连续转换

77 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;

78 //禁止外部边沿触发

79 ADC_InitStructure.ADC_ExternalTrigConvEdge =

80 ADC_ExternalTrigConvEdge_None;

81 //使用软件触发,外部触发不用配置,注释掉即可

82 //ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T1_CC1;

83 //数据右对齐

84 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

85 //转换通道 x

86 ADC_InitStructure.ADC_NbrOfConversion = RHEOSTAT_NOFCHANEL;

87 ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);

88 //------------------------------------------------------------------

89

90 // 配置 ADC 通道转换顺序和采样时间周期

91 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL1,

92 1, ADC_SampleTime_56Cycles);

93 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL2,

94 2, ADC_SampleTime_56Cycles);

95 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL3,

96 3, ADC_SampleTime_56Cycles);

97 ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL4,

98 4, ADC_SampleTime_56Cycles);

99 // 使能DMA请求 after last transfer (Single-ADC mode)

100 ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE);

101 // 使能ADC DMA

102 ADC_DMACmd(RHEOSTAT_ADC, ENABLE);

103 // 使能ADC

104 ADC_Cmd(RHEOSTAT_ADC, ENABLE);

105 //开始adc转换,软件触发

106 ADC_SoftwareStartConv(RHEOSTAT_ADC);

107 }

首先,我们使用了DMA_InitTypeDef定义了一个DMA初始化类型变量,该结构体内容我们在DMA篇已经做了非常详细的讲解;另外还使用ADC_InitTypeDefADC_CommonInitTypeDef结构体分别定义一个ADC初始化和ADC通用类型变量,这两个结构体我们之前已经有详细讲解。

调用RCC_APB2PeriphClockCmd()开启ADC时钟以及RCC_AHB1PeriphClockCmd()开启DMA时钟。

我们需要对DMA进行必要的配置。首先设置外设基地址就是ADC的规则数据寄存器地址;存储器的地址就是我们指定的数据存储区空间,ADC_ConvertedValue是我们定义的一个全局数组名,它是一个无符号16位含有4个元素的整数数组;ADC规则转换对应只有一个数据寄存器所以地址不能递增,而我们定义的存储区是专门用来存放不同通道数据的,所以需要自动地址递增。ADC的规则数据寄存器只有低16位有效,实际存放的数据只有12位而已,所以设置数据大小为半字大小。ADC配置为连续转换模式DMA也设置为循环传输模式。设置好DMA相关参数后就使能DMAADC通道。

接下来我们使用ADC_CommonInitTypeDef结构体变量ADC_CommonInitStructure来配置ADC为独立模式、分频系数为4、不需要设置DMA模式、20个周期的采样延迟,并调用ADC_CommonInit函数完成ADC通用工作环境配置。

我们使用ADC_InitTypeDef结构体变量ADC_InitStructure来配置ADC112位分辨率、使能扫描模式、启动连续转换、使用内部软件触发无需外部触发事件、使用右对齐数据格式、转换通道为4,并调用ADC_Init函数完成ADC3工作环境配置。

ADC_RegularChannelConfig函数用来绑定ADC通道转换顺序和采样时间。分别绑定四个ADC通道引脚并设置相应的转换顺序。

ADC_DMARequestAfterLastTransferCmd函数控制是否使能ADCDMA请求,如果使能请求,并调用ADC_DMACmd函数使能DMA,则在ADC转换完成后就请求DMA实现数据传输。

ADC_Cmd函数控制ADC转换启动和停止。

最后,如果使用软件触发需要调用ADC_SoftwareStartConvCmd函数进行使能配置。

主函数

代码清单 010 主函数

1 int main(void)

2 {

3 /*初始化USART1*/

4 Debug_USART_Config();

5

6 /* 初始化滑动变阻器用到的DAC

7 ADC数据采集完成后直接由DMA运输数据到ADC_ConvertedValue变量

8 DMA直接改变ADC_ConvertedValue的值*/

9 Rheostat_Init();

10

11 printf("\r\n ----这是一个ADC实验(多通道采集)----\r\n");

12

13 while (1) {

14 Delay(0xffffff);

15 ADC_ConvertedValueLocal[0]=(float)(ADC_ConvertedValue[0]*3.3/4096);

16 ADC_ConvertedValueLocal[1]=(float)(ADC_ConvertedValue[1]*3.3/4096);

17 ADC_ConvertedValueLocal[2]=(float)(ADC_ConvertedValue[2]*3.3/4096);

18 ADC_ConvertedValueLocal[3]=(float)(ADC_ConvertedValue[3]*3.3/4096);

19

20 printf("\r\n CH1_PF6 value = %fV\r\n",ADC_ConvertedValueLocal[0]);

21 printf("\r\n CH2_PF7 value = %fV\r\n",ADC_ConvertedValueLocal[1]);

22 printf("\r\n CH3_PF8 value = %fV\r\n",ADC_ConvertedValueLocal[2]);

23 printf("\r\n CH4_PF9 value = %fV\r\n",ADC_ConvertedValueLocal[3]);

24

25 printf("\r\n");

26 }

27 }

主函数先调用USARTx_Config函数配置调试串口相关参数,函数定义在bsp_debug_usart.c文件中。

接下来调用Rheostat_Init函数进行ADC初始化配置并启动ADC。Rheostat_Init函数是定义在bsp_adc.c文件中,它只是简单的分别调用Rheostat_ADC_GPIO_Config()和Rheostat_ADC_Mode_Config ()。

Delay函数只是一个简单的延时函数。

我们配置了DMA数据传输所以它会自动把ADC转换完成后数据保存到数组ADC_ConvertedValue内,我们只要直接使用数组就可以了。经过简单地计算就可以得到每个通道对应的实际电压。

最后就是把相关数据打印至串口调试助手。

30.5.3 下载验证

将待测电压通过杜邦线接在对应引脚上,用USB线连接开发板"USB TO UART"接口跟电脑,在电脑端打开串口调试助手,把编译好的程序下载到开发板。在串口调试助手可看到不断有数据从开发板传输过来,此时我们改变输入电压值,那么对应的数据也会有变化。

30.6 三重ADC交替模式采集实验

AD转换包括采样阶段和转换阶段,在采样阶段才对通道数据进行采集;而在转换阶段只是将采集到的数据进行转换为数字量输出,此刻通道数据变化不会改变转换结果。独立模式的ADC采集需要在一个通道采集并且转换完成后才会进行下一个通道的采集。双重或者三重ADC的机制使用两个或以上ADC同时采样两个或以上不同通道的数据或者使用两个或以上ADC交叉采集同一通道的数据。双重或者三重ADC模式较独立模式一个最大的优势就是转换速度快。

我们这里只介绍三重ADC交替模式,关于双重或者三重ADC的其他模式与之类似,可以参考三重ADC交替模式使用。三重ADC交替模式是针对同一通道的使用三个ADC交叉采集,就是在ADC1采样完等几个时钟周期后ADC2开始采样,此时ADC1处在转换阶段,当ADC2采样完成再等几个时钟周期后ADC3就进行采样此时ADC1ADC2处在转换阶段,如果ADC3采样完成并且ADC1已经转换完成那么就可以准备下一轮的循环,这样充分利用转换阶段时间达到增快采样速度的效果。AD转换过程见图 06,利用ADC的转换阶段时间另外一个ADC进行采样,而不用像独立模式必须等待采样和转换结束后才进行下一次采样及转换。

06 三重ADC交叉模式

30.6.1 硬件设计

三重ADC交叉模式是针对同一个通道的ADC采集模式,这种情况跟00小节的单通道实验非常类似,只是同时使用三个ADC对同一通道进行采集,所以电路设计与之相同即可,具体可参考图 05

30.6.2 软件设计

这里只讲解核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本章配套的工程。

跟单通道例程一样,我们编写两个ADC驱动文件,bsp_adc.h bsp_adc.c,用来存放ADC所用IO引脚的初始化函数以及ADC配置相关函数,实际上这两个文件跟单通道实验的文件非常相似。

1.    编程要点

1)    初始化配置ADC目标引脚为模拟输入模式;

2)    使能ADC1、ADC2、ADC3以及DMA时钟;

3)    配置DMA控制将ADC通用规矩数据寄存器数据转存到指定存储区;

4)    配置通用ADC为三重ADC交替模式,采样4分频,使用DMA模式2;

5)    设置ADC1、ADC2和ADC3为12位分辨率,禁用扫描,连续转换,不需要外部触发;

6)    设置ADC1、ADC2和ADC3转换通道顺序及采样时间;

7)    使能ADC1的 DMA请求,在ADC转换完后自动请求DMA进行数据传输;

8)    启动ADC1、ADC2和ADC3转换;

9)    使能软件触发ADC转换。

ADC转换结果数据使用DMA方式传输至指定的存储区,这样取代单通道实验使用中断服务的读取方法。

2.    代码分析
ADC宏定义

代码清单 011 多通道ADC相关宏定义

1 #define RHEOSTAT_ADC_CDR_ADDR ((uint32_t)0x40012308)

2

3 #define RHEOSTAT_ADC_GPIO_PORT GPIOC

4 #define RHEOSTAT_ADC_GPIO_PIN GPIO_Pin_3

5 #define RHEOSTAT_ADC_GPIO_CLK RCC_AHB1Periph_GPIOC

6

7 #define RHEOSTAT_ADC1 ADC1

8 #define RHEOSTAT_ADC1_CLK RCC_APB2Periph_ADC1

9 #define RHEOSTAT_ADC2 ADC2

10 #define RHEOSTAT_ADC2_CLK RCC_APB2Periph_ADC2

11 #define RHEOSTAT_ADC3 ADC3

12 #define RHEOSTAT_ADC3_CLK RCC_APB2Periph_ADC3

13 #define RHEOSTAT_ADC_CHANNEL ADC_Channel_13

14

15 #define RHEOSTAT_ADC_DMA_CLK RCC_AHB1Periph_DMA2

16 #define RHEOSTAT_ADC_DMA_CHANNEL DMA_Channel_0

17 #define RHEOSTAT_ADC_DMA_STREAM DMA2_Stream0

双重或者三重ADC需要使用通用规则数据寄存器ADC_CDR,这点跟独立模式不同。定义电位器动触点引脚作为三重ADC的模拟输入。

ADC GPIO初始化函数

代码清单 012 ADC GPIO初始化

1 static void Rheostat_ADC_GPIO_Config(void)

2 {

3 GPIO_InitTypeDef GPIO_InitStructure;

4

5 // 使能 GPIO 时钟

6 RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK, ENABLE);

7

8 // 配置 IO

9 GPIO_InitStructure.GPIO_Pin = RHEOSTAT_ADC_GPIO_PIN;

10 // 配置为模拟输入

11 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;

12 // 不上拉不下拉

13 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;

14 GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);

15 }

使用到GPIO时候都必须开启对应的GPIO时钟,GPIO用于AD转换功能必须配置为模拟输入模式。

配置三重ADC交替模式

代码清单 013 三重ADC交替模式配置

1 static void Rheostat_ADC_Mode_Config(void)

2 {

3 DMA_InitTypeDef DMA_InitStructure;

4 ADC_InitTypeDef ADC_InitStructure;

5 ADC_CommonInitTypeDef ADC_CommonInitStructure;

6

7 // 开启ADC时钟

8 RCC_APB2PeriphClockCmd(RHEOSTAT_ADC1_CLK , ENABLE);

9 RCC_APB2PeriphClockCmd(RHEOSTAT_ADC2_CLK , ENABLE);

10 RCC_APB2PeriphClockCmd(RHEOSTAT_ADC3_CLK , ENABLE);

11

12 // ------------------DMA Init 结构体参数初始化-----------------------

13 // ADC1使用DMA2,数据流0,通道0,这个是手册固定死的

14 // 开启DMA时钟

15 RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE);

16 // 外设基址为:ADC 数据寄存器地址

17 DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_CDR_ADDR;

18 // 存储器地址,实际上就是一个内部SRAM的变量

19 DMA_InitStructure.DMA_Memory0BaseAddr = (u32)ADC_ConvertedValue;

20 // 数据传输方向为外设到存储器

21 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;

22 // 缓冲区大小为3,缓冲区的大小应该等于存储器的大小

23 DMA_InitStructure.DMA_BufferSize = 3;

24 // 外设寄存器只有一个,地址不用递增

25 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

26 // 存储器地址自动递增

27 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

28 // // 外设数据大小为字,即四个字节

29 DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Word;

30 // 存储器数据大小也为字,跟外设数据大小相同

31 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;

32 // 循环传输模式

33 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

34 // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响

35 DMA_InitStructure.DMA_Priority = DMA_Priority_High;

36 // 禁止DMA FIFO ,使用直连模式

37 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;

38 // FIFO 大小,FIFO模式禁止时,这个不用配置

39 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;

40 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;

41 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

42 // 选择 DMA 通道,通道存在于流中

43 DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL;

44 //初始化DMA流,流相当于一个大的管道,管道里面有很多通道

45 DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);

46 // 使能DMA

47 DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);

48

49 // -------------------ADC Common 结构体参数初始化--------------------

50 // 三重ADC交替模式

51 ADC_CommonInitStructure.ADC_Mode = ADC_TripleMode_Interl;

52 // 时钟为fpclk2 4分频

53 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;

54 // 禁止DMA直接访问模式

55 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_2;

56 // 采样时间间隔

57 ADC_CommonInitStructure.ADC_TwoSamplingDelay =

58 ADC_TwoSamplingDelay_10Cycles;

59 ADC_CommonInit(&ADC_CommonInitStructure);

60

61 // -------------------ADC Init 结构体参数初始化----------------------

62 // ADC 分辨率

63 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;

64 // 禁止扫描模式,多通道采集才需要

65 ADC_InitStructure.ADC_ScanConvMode = DISABLE;

66 // 连续转换

67 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;

68 //禁止外部边沿触发

69 ADC_InitStructure.ADC_ExternalTrigConvEdge =

70 ADC_ExternalTrigConvEdge_None;

71 //使用软件触发,外部触发不用配置,注释掉即可

72 //ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T1_CC1;

73 //数据右对齐

74 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

75 //转换通道 1

76 ADC_InitStructure.ADC_NbrOfConversion = 1;

77 ADC_Init(RHEOSTAT_ADC1, &ADC_InitStructure);

78 // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期

79 ADC_RegularChannelConfig(RHEOSTAT_ADC1, RHEOSTAT_ADC_CHANNEL,

80 1, ADC_SampleTime_3Cycles);

81 //------------------------------------------------------------------

82 ADC_Init(RHEOSTAT_ADC2, &ADC_InitStructure);

83 // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期

84 ADC_RegularChannelConfig(RHEOSTAT_ADC2, RHEOSTAT_ADC_CHANNEL,

85 1, ADC_SampleTime_3Cycles);

86 //------------------------------------------------------------------

87 ADC_Init(RHEOSTAT_ADC3, &ADC_InitStructure);

88 // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期

89 ADC_RegularChannelConfig(RHEOSTAT_ADC3, RHEOSTAT_ADC_CHANNEL,

90 1, ADC_SampleTime_3Cycles);

91

92 // 使能DMA请求 after last transfer (multi-ADC mode)

93 ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);

94 // 使能ADC DMA

95 ADC_DMACmd(RHEOSTAT_ADC1, ENABLE);

96 // 使能ADC

97 ADC_Cmd(RHEOSTAT_ADC1, ENABLE);

98 ADC_Cmd(RHEOSTAT_ADC2, ENABLE);

99 ADC_Cmd(RHEOSTAT_ADC3, ENABLE);

100

101 //开始adc转换,软件触发

102 ADC_SoftwareStartConv(RHEOSTAT_ADC1);

103 ADC_SoftwareStartConv(RHEOSTAT_ADC2);

104 ADC_SoftwareStartConv(RHEOSTAT_ADC3);

105 }

首先,我们使用了DMA_InitTypeDef定义了一个DMA初始化类型变量,该结构体内容我们在DMA篇已经做了非常详细的讲解;另外还使用ADC_InitTypeDef和ADC_CommonInitTypeDef结构体分别定义一个ADC初始化和ADC通用类型变量,这两个结构体我们之前已经有详细讲解。

调用RCC_APB2PeriphClockCmd()开启ADC时钟以及RCC_AHB1PeriphClockCmd()开启DMA时钟。

我们需要对DMA进行必要的配置。首先设置外设基地址就是ADC的通用规则数据寄存器地址;存储器的地址就是我们指定的数据存储区空间,ADC_ConvertedValue是我们定义的一个全局数组名,它是一个无符号32位有三个元素的整数数字;ADC规则转换对应只有一个数据寄存器所以地址不能递增,我们指定的存储区也需要递增地址。ADC的通用规则数据寄存器是32位有效,我们配置ADC为DMA模式2,设置数据大小为字大小。ADC配置为连续转换模式DMA也设置为循环传输模式。设置好DMA相关参数后就使能DMA的ADC通道。

接下来我们使用ADC_CommonInitTypeDef结构体变量ADC_CommonInitStructure来配置ADC为三重ADC交替模式、分频系数为4、需要设置DMA模式2、10个周期的采样延迟,并调用ADC_CommonInit函数完成ADC通用工作环境配置。

我们使用ADC_InitTypeDef结构体变量ADC_InitStructure来配置ADC1为12位分辨率、不使用扫描模式、启动连续转换、使用内部软件触发无需外部触发事件、使用右对齐数据格式、转换通道为1,并调用ADC_Init函数完成ADC1工作环境配置。ADC2和ADC3使用与ADC1相同配置即可。

ADC_RegularChannelConfig函数用来绑定ADC通道转换顺序和采样时间。绑定ADC通道引脚并设置相应的转换顺序。

ADC_MultiModeDMARequestAfterLastTransferCmd函数控制是否使能ADC的DMA请求,如果使能请求,并调用ADC_DMACmd函数使能DMA,则在ADC转换完成后就请求DMA实现数据传输。三重模式只需使能ADC1的DMA通道。

ADC_Cmd函数控制ADC转换启动和停止。

最后,如果使用软件触发需要调用ADC_SoftwareStartConvCmd函数进行使能配置。

主函数

代码清单 014 主函数

1 int main(void)

2 {

3 /*初始化USART1*/

4 Debug_USART_Config();

5

6 /*

7 初始化滑动变阻器用到的DACADC数据采集完成后直接由DMA运输数据到

8 ADC_ConvertedValue变量DMA直接改变ADC_ConvertedValue的值*/

9 Rheostat_Init();

10

11 printf("\r\n ----这是一个ADC实验(DMA传输)----\r\n");

12

13 while (1) {

14 Delay(0xffffee);

15 ADC_ConvertedValueLocal[0] =

16 (float)((uint16_t)ADC_ConvertedValue[0]*3.3/4096);

17 ADC_ConvertedValueLocal[1] =

18 (float)((uint16_t)ADC_ConvertedValue[2]*3.3/4096);

19 ADC_ConvertedValueLocal[2] =

20 (float)((uint16_t)ADC_ConvertedValue[1]*3.3/4096);

21 printf("\r\n The current AD value = 0x%08X \r\n",

22 ADC_ConvertedValue[0]);

23 printf("\r\n The current AD value = 0x%08X \r\n",

24 ADC_ConvertedValue[1]);

25 printf("\r\n The current AD value = 0x%08X \r\n",

26 ADC_ConvertedValue[2]);

27 printf("\r\n The current ADC1 value = %f V \r\n",

28 ADC_ConvertedValueLocal[0]);

29 printf("\r\n The current ADC2 value = %f V \r\n",

30 ADC_ConvertedValueLocal[1]);

31 printf("\r\n The current ADC3 value = %f V \r\n",

32 ADC_ConvertedValueLocal[2]);

33 }

34 }

主函数先调用USARTx_Config函数配置调试串口相关参数,函数定义在bsp_debug_usart.c文件中。

接下来调用Rheostat_Init函数进行ADC初始化配置并启动ADCRheostat_Init函数是定义在bsp_adc.c文件中,它只是简单的分别调用Rheostat_ADC_GPIO_Config()Rheostat_ADC_Mode_Config ()

Delay函数只是一个简单的延时函数。

我们配置了DMA数据传输所以它会自动把ADC转换完成后数据保存到数组变量ADC_ConvertedValue内,根据DMA模式2的数据存放规则,ADC_ConvertedValue[0]的低16位存放ADC1数据、高16位存放ADC2数据,ADC_ConvertedValue[1]的低16位存放ADC3数据、高16位存放ADC1数据,ADC_ConvertedValue[2]的低16位存放ADC2数据、高16位存放ADC3数据,我们可以根据需要提取出对应ADC的转换结果数据。经过简单地计算就可以得到每个ADC对应的实际电压。

最后就是把相关数据打印至串口调试助手。

30.6.3 下载验证

保证开发板相关硬件连接正确,用USB线连接开发板"USB TO UART"接口跟电脑,在电脑端打开串口调试助手,把编译好的程序下载到开发板。在串口调试助手可看到不断有数据从开发板传输过来,此时我们旋转电位器改变其电阻值,那么对应的数据也会有变化。

30.7 每课一问

1、如果设置ADC分辨率为6、8或者10位时,输入电压值如何计算?

2、根据独立模式单通道采集实验设计实现相同功能,要求使用外部硬件触发转换方式。

3、编写一个双重ADC交替模式程序,实现类似三重ADC交替模式实验功能,要求使用DMA模式2实现程序功能。

 

转载于:https://www.cnblogs.com/firege/p/5805876.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

第30章 ADC—电压采集—零死角玩转STM32-F429系列 的相关文章

  • ch07-Pytorch的训练技巧

    ch07 Pytorch的训练技巧 0 引言1 模型保存与加载1 1 序列化与反序列化1 2 PyTorch 中的模型保存与加载1 3 模型的断点续训练 2 模型 Finetune2 1 Transfer Learning amp Mode
  • opencv-contrib-Python编译module 'cv2.cv2' has no attribute 'xfeatures2d'

    opencv contrib Python编译module 39 cv2 cv2 39 has no attribute 39 xfeatures2d 39 引言解决步骤一解决步骤二 引言 opencv contrib Python编译出现
  • find_package()函数

    find package函数 引言1 find package用法2 find package原理3 A required library with LAPACK API not found 错误解决4 添加findpackage查询路径
  • py安装文件时报错usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]

    py安装文件时报错usage setup py global opts cmd1 cmd1 opts cmd2 cmd2 opts 引言solved 引言 报错 xff1a python setup py fastentrypoints u
  • VScode单步调试

    VScode配置 0 快捷键1 安装clang2 VScodeDebug3 Cmake支持gdb调试的方法 0 快捷键 稍大工程在vscode下的调试参考该博客 Ctrl 43 打开默认终端 Ctrl 43 Shift 43 新建新的终端
  • 串口通信简介

    串口通信 串口通信是一种串行异步通信 xff0c 通信双方以字符帧作为数据传输单位 xff0c 字符帧按位依次传输 xff0c 每个位占固定的时间长度 两个字符帧之间的传输时间间隔可以是任意的 xff0c 即传输完一个字符帧之后 xff0c
  • ubuntu16.0 ROS(介绍EAI的YDLIDAR-X4激光雷达在ROS下使用方法)

    YDLIDAR X4激光雷达介绍 YDLIDAR X4激光雷达是深圳越登智能科技有限公司 xff08 YDLIDAR xff0c 这家公司属于EAI xff09 研发的一款 360 度二维测距产品 xff0c 本产品基于三角测距原理 xff
  • php使用http_build_query,parse_url,parse_str创建与解析url

    1 http build query http build query 可以创建urlencode之后的请求字符串 span class hljs keyword string span http build query mixed spa
  • 无人驾驶小车调试笔记(六)-- 车轮校准

    简介 xff1a 小车的动力完全来自于两个电机带动的车轮 xff0c 在理想状态下 xff0c 给两个电机同样的驱动参数 xff0c 两个车轮会以同样的转速带动小车直线行驶 xff0c 而实际情况是每个电机可能都会有个体差异 xff0c 也
  • Nginx HTTP详解

    正文 1 Nginx启动流程 2 HTTP 初始化 新连接建立时的行为 在上次博客的最后可以看到 xff0c 在ngx event accept方法建立连接的最后一步 xff0c 将会调用ngx listening t监听结构体的handl
  • 时钟周期,机器周期,指令周期的相互关系

    1 时钟周期 61 振荡周期 xff0c 名称不同而已 xff0c 都是等于单片机晶振频率的倒数 xff0c 如常见的外接12M晶振 xff0c 那它的时钟周期 61 1 12M 2 机器周期 xff1a 8051系列单片机的机器周期 61
  • 单片机的分频是什么意思?

    分频就是单片机的时钟频率 xff08 也就是晶振的震荡频率 xff09 F经过12分频 xff0c 变换成F 12的频率 简单的来说就是以整数倍降低频率 2分频就是分频前的频率除以2 xff1b 4分频就是分频前的频率除以4 比如 xff1
  • NMOS和PMOS管

    这里我先说一下我自己分辨MOS管的方法 对于NMOS我们看下图中的箭头 xff0c 都是远离源头 对于PMOS我们看箭头 xff0c 都是指向源头 P xff1a POSITIVE积极的寻找自己的起源 N xff1a NEGTIVE消极的远
  • 基本运算放大电路

    我先说明 下面的内容应该很多人都看到过 xff0c 但是我建议还是细看 xff0c 最好自己推一下 我就是这么做的 运算放大器工作原理综述 xff1a 运算放大器组成的电路五花八门 xff0c 令人眼花瞭乱 xff0c 在分析运算放大器工作
  • PCB板框的绘制——AD19

    pcb板框的绘制当然首先要切换到keep out 层才行 找到设置 xff0c 找到keep out 假如我们要绘制一个矩形的板框 xff0c 我们选择线径就可以 手动绘制一个矩形的板框 我们需要让我们的板子边框按照我们所绘制的走线来定义
  • 零基础自学STM32-野火——GPIO复习篇——使用绝对地址操作GPIO

    今天主要是复习一下 结合野火的 零基础开发指南 名字没记住大概是这个 先放一张结构图 存储器映射 xff08 初学重点 xff09 xff1a 我们的片内外设比如 xff1a Flash Sram Fsmc 以及挂在AHB 总线上的外设 x
  • Lcd1602——斌哥51

    最新修改时间2022 7 22 LCD1602 16代表显示16个字符 xff0c 2代表总共显示两行 芯片的工作电压是4 5 5 5v 工作电流2 0ma xff08 5V xff09 模块最佳工作电压5 0v 字符尺寸 xff1a 2
  • 无人驾驶小车调试笔记(七)-- 相机校准

    简介 xff1a 在第五节的内容中 xff0c 我们学习了使用rqt工具集观看摄像头视频流的方法 xff0c 细心的同学应该会发现camera node发布的视频数据中的图像有变形现象 xff0c 图像变形会导致直线不直 xff0c 部分区
  • Python实现MySql、SqlServer增删改查操作

    span class token keyword import span pymssql span class token keyword def span span class token function connection sql
  • ds1302——斌哥51

    以下内容分别借鉴了 清翔 51 xff0c 斌哥51 xff0c 以及CSDN 普通的不普通少年 内部结构 xff1a DS1302 包括时钟 日历寄存器和 31 字节 xff08 8 位 xff09 的数据暂存寄存器 xff0c 数据通信

随机推荐

  • AD添加LOGO

    先上原文链接 xff1a http www allchiphome com circuit pcb logo creator http www allchiphome com circuit pcb logo creator http ww
  • 视频播放组件实战【LivePlayer H5播放器】

    在公司项目开发中 xff0c 有一个项目里面需要做一个视频播放的功能 xff0c 播放方式是调用海康平台提供的接口获取流地址来进行视频的播放并且最重要的是需要支持flash 由于前端用的Vue xff0c 对比了几个 xff0c 最后选择了
  • 如何用示波器测量串口

    如何确定时基 假如要测量的波特率为9600 则每一比特位的时间为 xff1a 1 9600 104 s xff0c 一般示波器横向上每个大格子里5个小格子 xff0c 要想看清一比特位一般需要一个小格子就够了 xff0c 则时基为 xff1
  • Keil使用命令行附加预定义宏编译

    1 前言 很多时候 xff0c 一份Keil工程代码可能需要满足多个不同的应用场景 可以通过逻辑判断 xff0c 将多个不同的点集成在一份代码之中 xff0c 但是嵌入式往往特别关注RAM空间 xff0c 集成过多的逻辑判断 xff0c R
  • Python的函数装饰器,@staticmethod、@classmethod 和 @property

    什么是Python 的 函数装饰器 xff1f Python 内置的 3 种函数装饰器 xff0c 分别是 xff20 staticmethod xff20 classmethod 和 64 property 那么 xff0c 函数装饰器的
  • C++11:原子交换函数compare_exchange_weak和compare_exchange_strong

    我们知道在C 43 43 11中引入了mutex和方便优雅的lock guard 但是有时候我们想要的是性能更高的无锁实现 xff0c 下面我们来讨论C 43 43 11中新增的原子操作类Atomic xff0c 我们可以利用它巧妙地实现无
  • C++11条件变量:notify_one()与notify_all()的区别

    notify one 与notify all 常用来唤醒阻塞的线程 notify one xff1a 因为只唤醒等待队列中的第一个线程 xff1b 不存在锁争用 xff0c 所以能够立即获得锁 其余的线程不会被唤醒 xff0c 需要等待再次
  • 数据库:group by 的使用

    一 概述 group by的意思是根据by对数据按照哪个字段进行分组 xff0c 或者是哪几个字段进行分组 二 语法 select 字段 from 表名 where 条件 group by 字段 或者 select 字段 from 表名 g
  • C++中 std::vector 的6种初始化方法

    1 vector lt int gt list1 默认初始化 xff0c 最常用 此时 xff0c vector为空 xff0c size为0 xff0c 表明容器中没有元素 xff0c 而且 capacity 也返回 0 xff0c 意味
  • MIMO雷达处理1

    参考文献 MIMO RADAR SIGNAL PROCESSING 以下为我自己的理解 xff0c 如有问题 xff0c 请指出 目录 初步分析虚拟阵列123 确认目标数 初步分析 MIMO radar与相控阵雷达区别在于MIMO中的各天线
  • AndroidStudio生成aar包和如何使用aar包

    我用的是android studio 2 0正式版 1 简介 aar包是Android studio下打包android工程中src res lib后生成的aar文件 xff0c aar包导入其他android studio 工程后 xff
  • C++智能指针详解:shared_ptr

    C 43 43 没有内存回收机制 xff0c 每次程序员new出来的对象需要手动delete xff0c 流程复杂时可能会漏掉delete xff0c 导致内存泄漏 于是C 43 43 引入智能指针 xff0c 可用于动态资源管理 xff0
  • C++算法题:关于树的算法

    问题1 xff1a 输入一棵二元查找树 xff0c 将该二元查找树转换成一个排序的双向链表 要求不能创建任何新的结点 xff0c 只调整指针的指向 什么是二元查找树 xff1f 比如 xff1a 转换成双向链表的顺序是 xff1a 1 3
  • C++算法题:递归和栈的算法

    问题1 xff1a 跳台阶问题 具体描述 xff0c 一个台阶总共有n级 xff0c 如果一次可以跳1级 xff0c 也可以跳2级 求总共有多少总跳法 xff0c 并分析算法的时间杂度 相当于从下往上跳 xff0c 最后剩一个 xff08
  • Linux的.service服务 实现程序开机自启

    一 service文件的位置 所有可用的单元文件存放在 lib systemd system 和 etc systemd system 目录 我们需要在 lib systemd system 下存放 service文件 xff0c 当sys
  • 计算机网络 复习提纲(完整版)

    第一章 概述 计算机网络 xff1a 利用通信线路和通信设备 xff0c 将地理位置和功能不同的多台计算机互联起来 xff0c 用完善的网络软件实现资源共享和信息传递的网络 组成 xff1a 计算机 xff0c 网络操作系统 xff0c 传
  • 无人机多任务寻径仿真软件与实验平台(一)

    项目背景 xff1a 近年来 xff0c 无人机的应用领域已经得到了极大的拓展 xff0c 旋翼无人机凭借较大的载荷 xff0c 稳定的飞行状态与对高空环境的高度适应 xff0c 成为了应用最为广泛的一类无人机 在欧洲 xff0c 北美洲等
  • 无人机实验平台(七) 实验平台的坐标转换(上)

    为什么要做坐标转换 xff1f 大疆提供的sdk中指出 xff0c 无人机通常情况下通过两个坐标系来控制方向 xff1a 自身坐标系 xff08 Body xff09 和地面坐标系 xff08 N E D xff09 这两个坐标系可以相互转
  • 无人机实验平台(十) 室内悬停问题

    VirtualStick Mode VirtualStick Mode是Sdk提供的一种底层控制模式 xff0c 通过模拟物理摇杆的运动向无人机按照一定频率发送摇杆差分信息 xff0c 使得无人机按照类似于被物理摇杆控制的方式运行 xff0
  • 第30章 ADC—电压采集—零死角玩转STM32-F429系列

    第30章 ADC 电压采集 全套 200 集视频教程和 1000 页 PDF 教程请到秉火论坛下载 xff1a www firebbs cn 野火视频教程优酷观看网址 xff1a http i youku com firege 本章参考资料