文章参考:STM32F1 ADC模数转换简介及结构分析、STM32—ADC详解、STM32之ADC+步骤小技巧、STM32ADC的基本原理、stm32之ADC、STM32之ADC配置、STM32的ADC编程方法总结、下面来讲一下STM32的ADC应用、关于STM32F107RCT6使用8M晶振串口波特率错误的问题
一、 ADC简介
将模拟量转换为数字量的过程称为模式(A/D)转换,完成这一转换的期间成为模数转换器(简称ADC,analog to digital converter)。ADC转换器技术指标:转换速度,相对精度,分辨率。按照其转换原理主要分为逐次逼近型、双积分型、并联比较型三种。转换速度和相对精度的比较如下:
STM32的ADC是一种12位的逐次逼近型模数转换器。它有多达18个通道,可测16个外部和2个内部信号源。各通道的A/D转换可以采用单次、连续、扫描或间断模式执行。ADC转换结果可以是左对齐或右对齐方式存储在16位的数据寄存器中。模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。在拥有2个或多个ADC模块的芯片中,支持双ADC模式。 模拟输入通道ADC1_IN16连接内部温度传感器。 STM32的ADC输入时钟不得超过14MHz,它是由PCLK2经分频产生。
二、ADC的主要特征
ADC的结构框图如下,把 ADC 结构框图分成 7 个子模块,按照顺序依次进行简单介绍。
1. 电压输入引脚
ADC 输入电压范围为: VREF- ≤ VIN ≤ VREF+。由 VREF-、 VREF+ 、VDDA 、 VSSA 这四个外部引脚决定,其电气特性如下表,所以,ADC供电要求:2.4V到3.6V。一般把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3.3V,因此 ADC 的输入电压范围为:0-3.3V。我们使用的开发板 ADC输入电压范围为 0-3.3V。
如果想让 ADC 测试负电压或者更高的正电压,可以在外部加一个电压调理电路,把需要转换的电压抬升或者降压到 0~3.3V,这样 ADC 就可以测量了。但一定不要直接将高于 3.3V 的电压接到 ADC 管脚上,那样将可能烧坏芯片。
2. ADC输入时钟ADC_CLK
ADC 输入时钟 ADC_CLK 由 APB2 经过分频产生,最大值是 14MHz,分频因子由 RCC 时钟配置寄存器 RCC_CFGR 的位 15:14 ADCPRE[1:0]设置,可以是2/4/6/8 分频,注意这里没有 1 分频。
从图中可以看出,STM32F107的SYSCLK时钟有2条配置路线。第一条可以使用8M外部晶振,9倍频来配置系统时钟72MHz,即PCLK2(APB2时钟)为 72M,而 ADC最大工作频率为 14M,所以一般设置分频因子为 6,这样 ADC 的输入时钟为 12M。第二条可以使用25M外部晶振来配置,经过PLL2预分频因子5,8倍频,获得PLL2_CLK=(HSE / 5) * 8 = 40 MHz,再经过PLL预分频因子5,7倍频,获得PLL_CLK = (PLL2_CLK/ 5) * 7 = 56 MHz,即得系统56MHz时钟,即PCLK2(APB2时钟)为56MHz,PCLK2进行4分频得到 ADC_CLK = 14 MHz。
ADC 要完成对输入电压的采样需要若干个 ADC_CLK 周期,采样的周期数可通过 ADC 采样时间寄存器 ADC_SMPR1 和 ADC_SMPR2 中的 SMP[2:0]位设置,ADC_SMPR2 控制的是通道 0-9, ADC_SMPR1 控制的是通道 10-17。每个通道可以分别用不同的时间采样。其中采样周期最小是 1.5 个,即如果我们要达到最快的采样,那么应该设置采样周期为 1.5 个周期,这里说的周期就是1/ADC_CLK。采样周期有如下图。
ADC 的总转换时间跟 ADC 的输入时钟和采样时间有关,其公式如下:
Tconv = 采样时间 + 12.5 个周期
其中 Tconv 为 ADC 总转换时间,当 ADC_CLK=14Mhz 的时候,并设置 1.5 个周期的采样时间,则 Tcovn=1.5+12.5=14 个周期=1us。通常经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us,这个才是最常用的。
3. 输入通道
STM32 的 ADC 的输入通道多达 18 个,可测16个外部和2个内部信号源,其中外部的 16 个通道就是框图中的 ADCx_IN0、ADCx_IN1、…、ADCx_IN15(x=1/2/3,表示 ADC 数),通过这 16 个外部通道可以采集模拟信号。这 16 个通道对应着不同的 IO 口,如下图。其中 ADC1 还有 2 个内部通道:ADC1 的通道 16 连接到了芯片内部的温度传感器,通道 17 连接到了内部参考电压 VREFINT。ADC2 和ADC3 的通道 16、 17 全部连接到了内部的 VSS。
4. 通道选择
外部的 16 个通道在转换的时候可分为 2 组通道:规则通道组和注入通道组。
规则组:由多达16个转换通道组成。对一组指定的通道,按照指定的顺序,逐个转换这组通道,转换结束后,再从头循环;这指定的通道组就称为规则组,规则通道和它们的转换顺序在ADC_SQRx寄存器中选择。规则组中转换的总数应写入ADC_SQR1寄存器的L[3:0]位中。平时的ADC转换都是用规则通道实现的。
注入组:由多达4个转换通道组成。实际应用中,有可能需要临时中断规则组的转换,对某些通道进行转换,这些需要中断规则组而进行转换的通道组,就称为注入组。注入通道和它们的转换顺序在ADC_JSQR寄存器中选择。注入组里的转换总数目应写入ADC_JSQR寄存器的L[1:0]位中。
注: 对于ADC模块来说,它按规则转换规则组时,被要求临时转换规则组之外的某些通道,就好像这组通道临时注入了原来的顺序,所以形象地称其为注入组。当有注入通道需要转换时,规则通道的转换会停止,优先执行注入通道的转换,当注入通道的转换执行完毕后,再回到之前规则通道进行转换,相当于中断,如下图所示。
在任意多个通道上以任意顺序进行的一系列转换构成成组转换。例如,可以如下顺序完成转换:通道3、通道8、通道2、通道 2、通道0、通道2、通道2、通道15。
规则通道组序列寄存器有 3 个,分别是 SQR3、 SQR2、 SQR1,它们控制了规则通道中的转换顺序,如下图所示。只要在对应的寄存器位SQx中写入相应的通道,这个通道就是第x个转换。例如通道 3 想第一次转换,那么在 SQ1[4:0]写 3即可。具体使用多少个通道,由 SQR1 的位 L[3:0]决定,最多 16 个通道。
注入通道组序列寄存器只有一个,是 JSQR。它最多支持 4 个通道,具体多少个由 JSQR 的 JL[1:0]决定。注意:
当 JL[1:0] = 3(有 4 次注入转换)时, ADC 将按以下顺序转换通道:JSQ1[4:0]、 JSQ2[4:0]、 JSQ3[4:0] 和 JSQ4[4:0]。
当 JL[1:0] = 2 (有 3 次注入转换)时,ADC 将按以下顺序转换通道:JSQ2[4:0]、JSQ3[4:0] 和 JSQ4[4:0]。
当 JL[1:0] = 1 (有 2 次注入转换)时,ADC 转换通道的顺序为:先是 JSQ3[4:0],而后是 JSQ4[4:0]。
当 JL[1:0] = 0(有 1 次注入转换)时, ADC 将仅转换 JSQ4[4:0] 通道。
如果在转换期间修改 ADC_SQRx 或 ADC_JSQR 寄存器,将复位当前转换并向ADC 发送一个新的启动脉冲,以转换新选择的通道组。
5. 数据寄存器
转换完成后的数据就存放在数据寄存器中,但数据的存放也分为规则通道转换数据和注入通道转换数据的。
规则数据寄存器
规则数据寄存器负责存放规则通道转换的数据,通过32位寄存器ADC_DR来存放。
当使用ADC独立模式(也就是只使用一个ADC,可以使用多个通道)时,数据存放在低16位中;当使用ADC多模式时,高16位存放ADC2的数据。因为 STM32F1 的 ADC 是 12 位转换精度,而数据寄存器是 16 位,所以 ADC在存放数据的时候就有左对齐和右对齐区分。如果是左对齐,ADC转换完成数据存放在 ADC_DR 寄存器的[15:4]位内;如果是右对齐,则存放在 ADC_DR 寄存器的[11:0]位内。具体选择何种存放方式,需通过 ADC_CR2 的 11 位 ALIGN 设置。
在规则组中,含有 16 路通道,对应着存放规则数据的寄存器只有 1 个,如果使用多通道转换,那么转换后的数据就全部挤在 ADC_DR 寄存器内,前一个时间点转换的通道数据,就会被下一个时间点的另外一个通道转换的数据覆盖掉,所以当通道转换完成后就应该把数据取走,或者开启 DMA 模式,把数据传输到内存里面,不然就会造成数据的覆盖。 最常用的做法就是开启 DMA 传输。如果没有使用 DMA 传输,我们一般通过 ADC 状态寄存器 ADC_SR 获取当前 ADC 转换的进度状态,进而进行程序控制。
注入数据寄存器
注入通道转换的数据寄存器有4个,由于注入通道最多有4个,所以注入通道转换的数据都有固定的存放位置,不会跟规则寄存器那样产生数据覆盖的问题。ADC_JDRx 是 32 位的,低 16 位有效,高 16 位保留,数据同样分为左对齐和右对齐,具体是以哪一种方式存放,由ADC_CR2 的 11 位 ALIGN 设置。
6. 触发源
选择好输入通道,设置好转换顺序,接下来就可以开始转换。要开启 ADC转换,可以直接设置 ADC 控制寄存器 ADC_CR2 的 ADON 位为 1,即使能 ADC。当然 ADC 还支持外部事件触发转换,触发源有很多,具体选择哪一种触发源,由 ADC控制寄存器 2(ADC_CR2) 的 EXTSEL[2:0]和 JEXTSEL[2:0]位来控制。EXTSEL[2:0]用于选择规则通道的触发源,JEXTSEL[2:0]用于选择注入通道的触发源。选定好触发源之后,触发源是否要激活,则由 ADC 控制寄存器 ADC_CR2 的20位 EXTTRIG 和15位JEXTTRIG 这两位来激活。
7. 中断
当发生如下事件且使能相应中断标志位时,ADC 能产生中断。
1)转换结束(规则转换)与注入转换结束
数据转换结束后,如果使能中断转换结束标志位,转换一结束就会产生转换结束中断,可以在中断函数中读取规则或注入数据寄存器的值。
2)模拟看门狗事件
当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,AWD模拟看门狗状态位被置1。阀值位于ADC_HTR和ADC_LTR寄存器的最低12个有效位中。通过设置ADC_CR1寄存器的AWDIE位以允许产生相应中断。
8. DMA 请求
规则转换和注入转换均有外部触发选项,规则通道转换期间有DMA请求产生,将转换的数据从ADC_DR寄存器传输到用户指定的目的地址。而注入转换则无DMA请求,需要用查询或中断的方式保存转换的数据。
因为规则通道转换的值储存在一个仅有的数据寄存器中,所以当转换多个规则通道时需要使用DMA,这可以避免丢失已经存储在ADC_DR寄存器中的数据。
要注意的是只有 ADC1 和 ADC3 可以产生DMA 请求。由ADC2转化的数据可以通过双ADC模式,利用ADC1的DMA功能传输一般我们在使用 ADC 的时候都会开启 DMA 传输。
9. 转换模式
STM32的ADC的各通道的转换模式有:单次转换模式、连续转换模式、扫描模式、中断模式。
单次转换模式:ADC只执行一次转换。可以通过 ADC_CR2 寄存器的ADON 位(只适用于规则通道)启动,也可以通过外部触发启动(适用于规则通道和注入通道),这时 CONT 位为 0。
一旦选择通道的转换完成:1)如果一个规则通道被转换:转换数据被存储在16位ADC_DR寄存器中;EOC(转换结束)标志被设置;如果设置了EOCIE,则产生中断;2)如果一个注入通道被转换:转换数据被储存在16的ADC_DRJ1寄存器中;JEOC(注入转换结束)标志位被设置;如果设置了JEOCIE位,则产生中断。然后ADC停止;
连续转换模式:ADC 结束一个转换后立即启动一个新的转换。CONT 位为 1 时,可通过外部触发或将 ADC_CR2 寄存器中的 ADON 位置 1 来启动此模式(仅适用于规则通道)。需要注意的是:此模式无法连续转换注入通道。连续模式下唯一的例外情况是,注入通道配置为在规则通道之后自动转换(使用JAUTO 位)。
每个转换后:1)如果一个规则通道被转换:转换数据被存储在16位ADC_DR寄存器中;EOC(转换结束)标志被设置;如果设置了EOCIE,则产生中断;2)如果一个注入通道被转换:转换数据被储存在16的ADC_DRJ1寄存器中;JEOC(注入转换结束)标志位被设置;如果设置了JEOCIE位,则产生中断。然后ADC停止;
扫描模式:用来扫描一组模拟通道。扫描模式可通过设置ADC_CR1寄存器的SCAN位来选择。一旦这个位被设置,ADC扫描所有被ADC_SQRX寄存器(对规则通道)或ADC_JSQR(对注入通道)选中的所有通道。在每个组的每个通道上执行单次转换。在每个转换结束时,同一组的下一个通道被自动转换。如果设置了CONT位,转换不会在选择组的最后一个通道上停止,而是再次从选择组的第一个通道继续转换。如果设置了DMA位,在每次EOC后,DMA控制器把规则组通道的转换数据传输到SRAM中。而注入通道转换的数据总是存储在ADC_JDRx寄存器中。
间断模式:每触发一次,转换序列中n个通道,有多种触发方式 。
规则组: 此模式通过设置ADC_CR1寄存器上的DISCEN位激活。它可以用来执行一个短序列的n次转换(n<=8),此转换是ADC_SQRx寄存器所选择的转换序列的一部分。数值n由ADC_CR1寄存器的DISCNUM[2:0]位给出。 一个外部触发信号可以启动ADC_SQRx寄存器中描述的下一轮n次转换,直到此序列所有的转换完成为止。总的序列长度由ADC_SQR1寄存器的L[3:0]定义。
注入组: 此模式通过设置ADC_CR1寄存器的JDISCEN位激活。在一个外部触发事件后,该模式按通道顺序逐个转换ADC_JSQR寄存器中选择的序列。 一个外部触发信号可以启动ADC_JSQR寄存器选择的下一个通道序列的转换,直到序列中所有的转换完成为止。总的序列长度由ADC_JSQR寄存器的JL[1:0]位定义。
9. 校准
ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。在校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。 通过设置ADC_CR2寄存器的CAL位启动校准。一旦校准结束,CAL位被硬件复位,可以开始正常转换。建议在上电时执行一次ADC校准。校准阶段结束后,校准码储存在ADC_DR中。
注: 建议在每次上电后执行一次校准。
启动校准前,ADC必须处于关电状态(ADON=’0’)超过至少两个ADC时钟周期。
10. 双ADC模式
在有2个或以上ADC模块的产品中,可以使用双ADC模式 。
共有6种可能的模式:同步注入模式、同步规则模式、快速交叉模式、慢速交叉模式、交替触发模式、独立模式。
还有可以用下列方式组合使用上面的模式:同步注入模式 + 同步规则模式、同步规则模式 + 交替触发模式、同步注入模式 + 交叉模式。
11. 温度传感器
温度传感器可以用来测量器件周围的温度(TA)。温度传感器在内部和ADC1_IN16输入通道相连接,此通道把传感器输出的电压转换成数字值。温度传感器模拟输入推荐采样时间是17.1μs,也就是ADC_CLK=14MHz,采样时间是239.5个周期=239.5/14M=17.1μs。
为使用传感器:选择ADC1_IN16输入通道,选择采样时间,设置ADC控制寄存器2(ADC_CR2)的TSVREFE位,以唤醒关电模式下的温度传感器,通过设置ADON位启动ADC转换(或用外部触发), 读ADC数据寄存器上的VSENSE 数据结果,利用下列公式得出温度,
温度(°C) = {(V25 – VSENSE) / Avg_Slope} + 25
这里: V25 = VSENSE在25°C时的数值
Avg_Slope = 温度与VSENSE曲线的平均斜率(单位为mV/ °C 或 μV/ °C)
因此温度的转换结果是(1.43-(float)(ADCConvertedValue)/4096*3.3)*1000/4.3+25。
三、ADC实验
1. 实现的功能
该示例程序演示了如何使用ADC1和DMA连续地把ADC1的转换数据从ADC1传输到存储空间。ADC1被配置成从ADC的16号通道连续地转换数据。每次结束一次ADC转换后触发一次DMA传输,在DMA循环模式中,持续地把ADC1的DR数据寄存器的数据传输到ADC_ConvertedValue变量。然后通过LCD显示出温度的值。
2. 硬件连接
由上图可以知道,PC4连接在一个电阻调节器上,而PC4是作为ADC的一个采集通道的,所以模拟信号是从此处得到的,然后通过PC4这个通道传入。
3. 工程的建立
硬件:STM32F107VC( Cortex-M3)
软件:Keil μVision4,JLINK
新建工程添加的文件夹和子文件。
4. 程序流程图
4.1 配置系统时钟
选择上图的橙色路线,来配置系统时钟,获取ADCCLK时钟。HSE(25MHz)作为系统时钟来源,经过PLL2的5分频,8倍频,获得PLL2_CLK=(HSE / 5) * 8 = 40 MHz,再经过PLL的5分频,7倍频,获得PLL_CLK = (PLL2_CLK/ 5) * 7 = 56 MHz,即得系统56MHz时钟,即PCLK2(APB2时钟)为56MHz,PCLK2进行4分频得到 ADC_CLK = 14 MHz。
void RCC_Configuration(void)
{
/* RCC system reset(for debug purpose) */
// 把 RCC 外设初始化成复位状态,这句是必须的
RCC_DeInit();
/* Enable HSE */
//使能 HSE,开启外部晶振, STM32F107系列开发板用的是 8M
RCC_HSEConfig(RCC_HSE_ON);
/* Wait till HSE is ready */
// 等待 HSE 启动稳定
HSEStartUpStatus = RCC_WaitForHSEStartUp();
// 只有 HSE 稳定之后则继续往下执行
if(HSEStartUpStatus == SUCCESS)
{
/* Enable Prefetch Buffer */
// 使能 FLASH 预存取缓冲区
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
/****************************************************************/
/* HSE=25MHz, HCLK=56MHz, PCLK2=56MHz, PCLK1=28MHz,ADCCLK=14MHz */
/****************************************************************/
/* Flash 2 wait state */
// SYSCLK 周期与闪存访问时间的比例设置,这里统一设置成 2
// 设置成 2 的时候,SYSCLK 低于 48M 也可以工作,如果设置成 0 或者 1 的时候,
// 如果配置的 SYSCLK 超出了范围的话,则会进入硬件错误,程序就死了
// 0:0 < SYSCLK <= 24M
// 1:24< SYSCLK <= 48M
// 2:48< SYSCLK <= 72M
//flash操作的延时
FLASH_SetLatency(FLASH_Latency_2);
/* HCLK = SYSCLK */
//AHB 预分频因子设置为 1 分频,HCLK = SYSCLK
RCC_HCLKConfig(RCC_SYSCLK_Div1);
/* PCLK2 = HCLK */
// APB2 预分频因子设置为 1 分频,PCLK2 = HCLK
RCC_PCLK2Config(RCC_HCLK_Div1);
/* PCLK1 = HCLK/2 */
//APB1 预分频因子设置为 2 分频,PCLK1 = HCLK/2
RCC_PCLK1Config(RCC_HCLK_Div2);
/* ADCCLK = PCLK2/4 */
RCC_ADCCLKConfig(RCC_PCLK2_Div4);
//如果STM32F10X_CL 没有被宏定义过,定义STM32F10X_CL ,并进入#ifndef的程序段
#ifndef STM32F10X_CL /* PLLCLK = 8MHz * 7 = 56 MHz */
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_7);
//STM32F10X_CL 被宏定义过,进入#else的程序段
#else
/* Configure PLLs *********************************************************/
/* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
RCC_PREDIV2Config(RCC_PREDIV2_Div5);
RCC_PLL2Config(RCC_PLL2Mul_8);
/* Enable PLL2 */
RCC_PLL2Cmd(ENABLE);
/* Wait till PLL2 is ready */
while (RCC_GetFlagStatus(RCC_FLAG_PLL2RDY) == RESET)
{}
/* PLL configuration: PLLCLK = (PLL2 / 5) * 7 = 56 MHz */
RCC_PREDIV1Config(RCC_PREDIV1_Source_PLL2, RCC_PREDIV1_Div5);
RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_7);
#endif
/* Enable PLL */
// 开启 PLL
RCC_PLLCmd(ENABLE);
/* Wait till PLL is ready */
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/* Select PLL as system clock source */
// 当 PLL 稳定之后,把 PLL 时钟切换为系统时钟 SYSCLK
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* Wait till PLL is used as system clock source */
// 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
while(RCC_GetSYSCLKSource() != 0x08)
{
}
}
/* Enable peripheral clocks --------------------------------------------------*/
/* Enable DMA1 clock */
//使能 DMA1时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* Enable ADC1 and GPIOC clock *///使能ADC1、GPIOC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOC, ENABLE);
}
4.2 配置GPIO引脚
模拟信号是通过GPIO引脚PC4传输到开发板的,引脚的模式一定要是模拟输入。
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure PC.04 (ADC Channel14) as analog input -------------------------*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
4.3 初始化LCD
void LCD_Configuration(void)
{ /* Initialize the LCD */
STM3210C_LCD_Init();
/* Clear the LCD */
LCD_Clear(White);
/* Set the LCD Text Color */
LCD_SetTextColor(Black);
}
4.4 DMA的初始化
在stm32f10x_dma.h里定义了DMA_InitTypeDef
的结构体变量。
/** * @brief DMA Init structure definition */
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;
1)DMA_PeripheralBaseAddr
:外设地址
DMA_MemoryBaseAddr
:存储器地址
使用DMA传输数据时需要设置外设地址和存储器地址,外设地址是ADC的地址,而存储器的地址如果使用8位数据的话,存储器必须定义为8位缓冲器,如果使用16位数据的话,存储器必须定义为16位缓冲器,不可定义为32位或更多,否则数据将出错。
2)DMA_DIR
:传输方向选择,可选外设到存储器、存储器到外设。
#define DMA_DIR_PeripheralDST ((uint32_t)0x00000010)
#define DMA_DIR_PeripheralSRC ((uint32_t)0x00000000)
3)DMA_BufferSize
:设定待传输数据数目
4)DMA_PeripheralInc
:如果配置为 DMA_PeripheralInc_Enable
,使能外设地址自动递增功能。一般外设都是只有一个数据寄存器,所以一般不会使能该位。
#define DMA_PeripheralInc_Enable ((uint32_t)0x00000040)
#define DMA_PeripheralInc_Disable ((uint32_t)0x00000000)
5)DMA_MemoryInc
:如果配置为 DMA_MemoryInc_Enable
,使能存储器地址自动递增功能,我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。
#define DMA_MemoryInc_Enable ((uint32_t)0x00000080)
#define DMA_MemoryInc_Disable ((uint32_t)0x00000000)
6)DMA_PeripheralDataSize
:外设数据宽度,可选字节(8 位)、半字(16 位)和字(32位)
#define DMA_PeripheralDataSize_Byte ((uint32_t)0x00000000)
#define DMA_PeripheralDataSize_HalfWord ((uint32_t)0x00000100)
#define DMA_PeripheralDataSize_Word ((uint32_t)0x00000200)
7)DMA_MemoryDataSize
:存储器数据宽度,可选字节(8 位)、半字(16 位)和字(32位),当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。
#define DMA_MemoryDataSize_Byte ((uint32_t)0x00000000)
#define DMA_MemoryDataSize_HalfWord ((uint32_t)0x00000400)
#define DMA_MemoryDataSize_Word ((uint32_t)0x00000800)
8)DMA_Mode
: DMA 传输模式选择,可选一次传输或者循环传输,我们的 ADC 采集是持续循环进行的,所以使用循环传输模式。
#define DMA_Mode_Circular ((uint32_t)0x00000020)
#define DMA_Mode_Normal ((uint32_t)0x00000000)
9)DMA_Priority
:软件设置通道的优先级,有 4 个可选优先级分别为非常高、高、中和低。DMA 通道优先级只有在多个 DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。
#define DMA_Priority_VeryHigh ((uint32_t)0x00003000)
#define DMA_Priority_High ((uint32_t)0x00002000)
#define DMA_Priority_Medium ((uint32_t)0x00001000)
#define DMA_Priority_Low ((uint32_t)0x00000000)
10)DMA_M2M
:存储器到存储器模式 ,使用存储器到存储器时用到。
#define DMA_M2M_Enable ((uint32_t)0x00004000)
#define DMA_M2M_Disable ((uint32_t)0x00000000)
最终的初始化代码如下:
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel1);//开启DMA1的第一通道
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//DMA对应的外设基地址,Datasheet查
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADCConvertedValue;//DMA对应的存储器基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//DMA的转换模式是SRC模式,就是从外设向内存中搬运
DMA_InitStructure.DMA_BufferSize = 1;//DMA缓冲大小,1个
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//接收一次数据后,设备地址不后移
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //接收一次数据后,目标内存地址后移
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//DMA搬运的数据尺寸,ADC是12位,HalfWord是16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//转换模式,循环缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//DMA优先级,高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//M2M模式禁止,
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
/* Enable DMA1 channel1 */
DMA_Cmd(DMA1_Channel1, ENABLE);
4.5 ADC的初始化
在stm32f10x_adc.h里定义了ADC_InitTypeDef
结构体变量。
typedef struct
{
uint32_t ADC_Mode; /*!< Configures the ADC to operate in independent or dual mode. This parameter can be a value of @ref ADC_mode */
FunctionalState ADC_ScanConvMode; /*!< Specifies whether the conversion is performed in Scan (multichannels) or Single (one channel) mode. This parameter can be set to ENABLE or DISABLE */
FunctionalState ADC_ContinuousConvMode; /*!< Specifies whether the conversion is performed in Continuous or Single mode. This parameter can be set to ENABLE or DISABLE. */
uint32_t ADC_ExternalTrigConv; /*!< Defines the external trigger used to start the analog to digital conversion of regular channels. This parameter can be a value of @ref ADC_external_trigger_sources_for_regular_channels_conversion */
uint32_t ADC_DataAlign; /*!< Specifies whether the ADC data alignment is left or right. This parameter can be a value of @ref ADC_data_align */
uint8_t ADC_NbrOfChannel; /*!< Specifies the number of ADC channels that will be converted using the sequencer for regular channel group. This parameter must range from 1 to 16. */
}ADC_InitTypeDef;
1)ADC_Mode
是ADC工作模式选择,有下列10种选择。如果不需要ADC同步或只是用了一个ADC的时候,就应该设置成独立模式了,在这个模式下,双ADC不能同步,每个ADC接口独立工作。
#define ADC_Mode_Independent ((uint32_t)0x00000000)
#define ADC_Mode_RegInjecSimult ((uint32_t)0x00010000)
#define ADC_Mode_RegSimult_AlterTrig ((uint32_t)0x00020000)
#define ADC_Mode_InjecSimult_FastInterl ((uint32_t)0x00030000)
#define ADC_Mode_InjecSimult_SlowInterl ((uint32_t)0x00040000)
#define ADC_Mode_InjecSimult ((uint32_t)0x00050000)
#define ADC_Mode_RegSimult ((uint32_t)0x00060000)
#define ADC_Mode_FastInterl ((uint32_t)0x00070000)
#define ADC_Mode_SlowInterl ((uint32_t)0x00080000)
#define ADC_Mode_AlterTrig ((uint32_t)0x00090000)
2)ADC_ScanConvMode
是ADC 扫描(多通道)或者单次(单通道)模式选择,可设置为ENABLE
和DISABLE
两种。如果只是用了一个通道的话,DISABLE
就行了,如果使用了多个通道的话,则将其设置为ENABLE
。
3)ADC_ContinuousConvMode
是ADC 单次转换或者连续转换选择,可设置为ENABLE
(连续转换 )和DISABLE
(单次转换)两种。两者的区别在于连续转换直到所有的数据转换完成后才停止转换,而单次转换则只转换一次数据就停止,要再次触发转换才可以。如果需要一次性采集很多数据,则采用连续转换。
4)ADC_ExternalTrigConv
是ADC 转换触发信号选择,即选择外部触发方式。有三大类:
第一种是软件触发,参数为ADC_ExternalTrigConv_None
。设置好之后,调用库函数ADC_SoftwareStartConvCmd(ADC1, ENABLE);
才会启动触发。
第二种是定时器通道输出触发,ADC1、ADC2用于规则通道的外部触发有ADC_ExternalTrigConv_T1_CC1、ADC_ExternalTrigConv_T1_CC2、ADC_ExternalTrigConv_T2_CC2、ADC_ExternalTrigConv_T3_TRGO、ADC_ExternalTrigConv_T4_CC4
。ADC3用于规则通道的外部触发有ADC_ExternalTrigConv_T3_CC1、 ADC_ExternalTrigConv_T2_CC3、 ADC_ExternalTrigConv_T8_CC1、ADC_ExternalTrigConv_T8_TRGO 、ADC_ExternalTrigConv_T5_CC1、 ADC_ExternalTrigConv_T5_CC3
。同时适用于ADC1、ADC2、ADC3用于规则通道的外部触发有ADC_ExternalTrigConv_T1_CC3
。定时器输出触发比较麻烦,还需要设置相应的定时器。
第三种是外部引脚触发,对于规则通道,选择EXTI线11和TIM8_TRGO(只存在于大容量产品)作为外部触发事件;而注入通道组则选择EXTI线15和TIM8_CC4作为外部触发事件。
#define ADC_ExternalTrigConv_T1_CC1 ((uint32_t)0x00000000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T1_CC2 ((uint32_t)0x00020000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T2_CC2 ((uint32_t)0x00060000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T3_TRGO ((uint32_t)0x00080000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T4_CC4 ((uint32_t)0x000A0000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO ((uint32_t)0x000C0000) /*!< For ADC1 and ADC2 */
#define ADC_ExternalTrigConv_T1_CC3 ((uint32_t)0x00040000) /*!< For ADC1, ADC2 and ADC3 */
#define ADC_ExternalTrigConv_None ((uint32_t)0x000E0000) /*!< For ADC1, ADC2 and ADC3 */
#define ADC_ExternalTrigConv_T3_CC1 ((uint32_t)0x00000000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T2_CC3 ((uint32_t)0x00020000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T8_CC1 ((uint32_t)0x00060000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T8_TRGO ((uint32_t)0x00080000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T5_CC1 ((uint32_t)0x000A0000) /*!< For ADC3 only */
#define ADC_ExternalTrigConv_T5_CC3 ((uint32_t)0x000C0000) /*!< For ADC3 only */
5)ADC_DataAlign
是ADC 数据寄存器对齐格式,有右对齐方式和左对齐方式两种。建议采用右对齐方式,因为这样处理数据会比较方便。如果从高位开始传输数据,则采用左对齐方式。
#define ADC_DataAlign_Right ((uint32_t)0x00000000)
#define ADC_DataAlign_Left ((uint32_t)0x00000800)
6)ADC_NbrOfChannel
是ADC 采集通道数,范围是1-16。要是到多个通道采集数据的话就得设置一下这个参数。
ADC的初始化配置:
ADC_InitTypeDef ADC_InitStructure;
/* ADC1 configuration ------------------------------------------------------*/
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //只使用一个ADC,独立模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //连续转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //扫描模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部触发转换,采用软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //转换结果右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道数目
ADC_Init(ADC1, &ADC_InitStructure);
4.6 配置ADC通道、使能温度传感器、使能ADC
//规则组通道设置,转换器ADC1,选择16通道,采样顺序1,转换时间55.5
ADC_RegularChannelConfig(ADC1,16, 1, ADC_SampleTime_55Cycles5);
/* Enable the temperature sensor and vref internal channel */
ADC_TempSensorVrefintCmd(ENABLE);
/* Enable ADC1 DMA */
//ADC命令,和DMA关联
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADC1 */
//开启ADC,进行转换
ADC_Cmd(ADC1, ENABLE);
4.7 校准ADC
/* Enable ADC1 reset calibaration register */
//重置ADC校准
ADC_ResetCalibration(ADC1);
/* Check the end of ADC1 reset calibration register */
//等待ADC重置校准完成
while(ADC_GetResetCalibrationStatus(ADC1));
/* Start ADC1 calibaration */
//开始校准
ADC_StartCalibration(ADC1);
/* Check the end of ADC1 calibration */
//等待校准完成
while(ADC_GetCalibrationStatus(ADC1));
4.8 软件使能ADC通道
/* Start ADC1 Software Conversion */
//软件触发ADC转换,连续转换开始,ADC将通过DMA不断刷新制定RAM区
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
4.9 获取ADC转换值
转换后的数据是一个12位的二进制数,需要把这个二进制数代表的模拟量(电压)用数字表示出来。比如测量的电压范围是0~3.3V,转换后的二进制数是x,因为12位ADC在转换时将电压的范围大小(也就是3.3)分为4096(2^12)份,所以转换后的二进制数x代表的真实电压的计算方法就是:y=3.3* x / 4096。
while (1)
{
printf("Chip temp: %3.1fC\r",(1.42-(float)(ADCConvertedValue)/4096*3.21)*1000/4.35+25);
}
5. 完整代码
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "stm3210c_eval_lcd.h"
#include "stm32_eval.h"
#include <stdio.h>
/** @addtogroup STM32F10x_StdPeriph_Examples * @{ */
/** @addtogroup ADC_ADC1_DMA * @{ */
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define ADC1_DR_Address ((uint32_t)0x4001244C)
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
__IO uint16_t ADCConvertedValue;
ErrorStatus HSEStartUpStatus;
/* Private function prototypes -----------------------------------------------*/
void RCC_Configuration(void);
void GPIO_Configuration(void);
/* Private functions ---------------------------------------------------------*/
/** * @brief Main program * @param None * @retval None */
/*******************请在此函数中完成为实现温度传感器配置ADC功能函数*************************/
int main(void)
{
/* System clocks configuration ---------------------------------------------*/
RCC_Configuration();
/* GPIO configuration ------------------------------------------------------*/
//GPIO_Configuration();
/* Initialize the LCD */
STM3210C_LCD_Init();
/* Clear the LCD */
LCD_Clear(White);
/* Set the LCD Text Color */
LCD_SetTextColor(Black);
printf(" STM3210C-EVAL \n");
printf(" Example on how to use the ADC with ChipTemp\n");
/* DMA1 channel1 configuration ----------------------------------------------*/
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADCConvertedValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //add lwzhang Enable
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
/* Enable DMA1 channel1 */
DMA_Cmd(DMA1_Channel1, ENABLE);
/* ADC1 configuration ------------------------------------------------------*/
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1,16, 1, ADC_SampleTime_55Cycles5);
/* Enable the temperature sensor and vref internal channel */
ADC_TempSensorVrefintCmd(ENABLE);
/* Enable ADC1 DMA */
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
/* Enable ADC1 reset calibaration register */
ADC_ResetCalibration(ADC1);
/* Check the end of ADC1 reset calibration register */
while(ADC_GetResetCalibrationStatus(ADC1));
/* Start ADC1 calibaration */
ADC_StartCalibration(ADC1);
/* Check the end of ADC1 calibration */
while(ADC_GetCalibrationStatus(ADC1));
/* Start ADC1 Software Conversion */
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
printf("\r\n chip temp test \r\n");
printf("\r\n \r\n");
while (1)
{
printf("Chip temp: %3.1fC\r",(1.42-(float)(ADCConvertedValue)/4096*3.21)*1000/4.35+25);
}
}
/*************************************************************************/
/** * @brief Configures the different system clocks. * @param None * @retval None */
void RCC_Configuration(void)
{
/* RCC system reset(for debug purpose) */
RCC_DeInit();
/* Enable HSE */
RCC_HSEConfig(RCC_HSE_ON);
/* Wait till HSE is ready */
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if(HSEStartUpStatus == SUCCESS)
{
/* Enable Prefetch Buffer */
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
/* Flash 2 wait state */
FLASH_SetLatency(FLASH_Latency_2);
/* HCLK = SYSCLK */
RCC_HCLKConfig(RCC_SYSCLK_Div1);
/* PCLK2 = HCLK */
RCC_PCLK2Config(RCC_HCLK_Div1);
/* PCLK1 = HCLK/2 */
RCC_PCLK1Config(RCC_HCLK_Div2);
/* ADCCLK = PCLK2/4 */
RCC_ADCCLKConfig(RCC_PCLK2_Div4);
#ifndef STM32F10X_CL
/* PLLCLK = 8MHz * 7 = 56 MHz */
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_7);
#else
/* Configure PLLs *********************************************************/
/* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
RCC_PREDIV2Config(RCC_PREDIV2_Div5);
RCC_PLL2Config(RCC_PLL2Mul_8);
/* Enable PLL2 */
RCC_PLL2Cmd(ENABLE);
/* Wait till PLL2 is ready */
while (RCC_GetFlagStatus(RCC_FLAG_PLL2RDY) == RESET)
{}
/* PLL configuration: PLLCLK = (PLL2 / 5) * 7 = 56 MHz */
RCC_PREDIV1Config(RCC_PREDIV1_Source_PLL2, RCC_PREDIV1_Div5);
RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_7);
#endif
/* Enable PLL */
RCC_PLLCmd(ENABLE);
/* Wait till PLL is ready */
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/* Select PLL as system clock source */
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
/* Wait till PLL is used as system clock source */
while(RCC_GetSYSCLKSource() != 0x08)
{
}
}
/* Enable peripheral clocks --------------------------------------------------*/
/* Enable DMA1 clock */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* Enable ADC1 and GPIOC clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOC, ENABLE);
}
/** * @brief Configures the different GPIO ports. * @param None * @retval None */
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure PC.04 (ADC Channel14) as analog input -------------------------*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
#ifdef USE_FULL_ASSERT
/** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */
void assert_failed(uint8_t* file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif
/** * @} */
/** * @} */
/******************* (C) COPYRIGHT 2009 STMicroelectronics *****END OF FILE****/
今天的文章STM32的ADC分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/21443.html