STM32模数转换器(ADC)原理与应用详解

1.ADC的简要 

我们首先说一下ADC的转换过程,然后说一下原理,当然如果嫌啰嗦可以直接跳过。 ADC是英文Analog-to-Digital Converter缩写,翻译过来就是模数转换器,是指将连续变化的模拟信号转换为离散的数字信号的器件。A/D转换的作用是将时间连续、幅值也连续的模拟信号转换为时间离散、值也离散的数字信号,因此,A/D转换一般要经过采样、保持、量化及编码4个过程,如下图所示。在实际电路中,这些过程有的是合并进行的。例如,采样和保持、量化和编码往往都是在转换过程中同时实现的。

2.ADC框图分析 

 3、ADC的触发转换方式:

1、通用定时器触发ADC转换,这里没有基本定时器,因为基本定时器智能触发DAC转换;
2、外部中断可以产生一个触发脉冲,触发ADC转换;
3、软件触发。

4、数据存储:

ADC 的结果存储在一个左对齐或右对齐的 16 位数据寄存器中。
十二位的ADC放入16位的数据寄存器中需要左右对齐
ADC 具有模拟看门狗特性,允许应用检测输入电压是否超过了用户自定义的阈值上限或下限。
ADC在STM32内部是将电压值转换为数字量,这里锁输入的电压值的范围一般都是固定的,如果用户没有自定义输入电压阈值的上下限,一般输入的电压范围都是0V-3.3V。加入用户自定义输入电压阈值的下限为1.2V,上限为3.0V,这个时候ADC允许输入的电压范围是1.2V-3.0V。

5、ADC输入引脚

目前STM32F4有3个ADC,每个ADC的外部输入源16个,可能有的引脚被三个ADC或者两个ADC共有。
假设PA0,可以是ADC1的通道1,可以是ADC2的通道5,可以是ADC3的通道10。
对于ADC1来说,有8个模拟输入通道可以被3个ADC共用;

6、ADC时钟

ADC 具有两个时钟方案:
● 用于模拟电路的时钟:ADCCLK,所有 ADC 共用
此时钟来自于经可编程预分频器分频的 APB2 时钟,该预分频器允许 ADC 在 fPCLK2/2、/4、/6 或 /8 下工作。有关 ADCCLK 的最大值,请参见数据手册。

● 用于数字接口的时钟(用于寄存器读/写访问)
此时钟等效于 APB2 时钟。可以通过 RCC APB2 外设时钟使能寄存器 (RCC_APB2ENR) 分别为每个 ADC 使能/禁止数字接口时钟。

对于ADC1来说,有8个模拟输入通道可以被3个ADC共用;
对于ADC2来说,有8个模拟输入通道可以被ADC1和ADC2共用;
对于ADC3来说,有8个模拟输入通道专门用于ADC3;

7、ADC输入通道

1、外部输入通道–16个
有 16 条复用通道。可以将转换分为两组:规则转换和注入转换。每个组包含一个转换序列,该序列可按任意顺序在任意通道上完成。例如,可按以下顺序对序列进行转换:ADC_IN3、ADC_IN8、ADC_IN2、ADC_IN2、ADC_IN0、ADC_IN2、ADC_IN2、ADC_IN15。
● 一个规则转换组最多由 16 个转换构成。必须在 ADC_SQRx 寄存器中选择转换序列的规则通道及其顺序。规则转换组中的转换总数必须写入 ADC_SQR1 寄存器中的 L[3:0] 位。
● 一个注入转换组最多由 4 个转换构成。必须在 ADC_JSQR 寄存器中选择转换序列的注入通道及其顺序。注入转换组中的转换总数必须写入 ADC_JSQR 寄存器中的 L[1:0] 位。
如果在转换期间修改 ADC_SQRx 或 ADC_JSQR 寄存器,将复位当前转换并向 ADC 发送一个新的启动脉冲,以转换新选择的组。
2、内部输入通道–3个温度传感器、VREFINT 和 VBAT 内部通道
● 温度传感器内部连接到通道 ADC1_IN16。
● 内部参考电压 VREFINT 连接到 ADC1_IN17。
● VBAT 通道连接到通道 ADC1_IN18。该通道也可转换为注入通道或规则通道。
注意: 温度传感器、VREFINT 和 VBAT 通道只在主 ADC1 外设上可用

8、数据对齐

 如果觉得分辨率不需要太高,可以选择左对齐,要不然一般是选择右对齐模式

9、EOC标志位

位 1 EOC:规则通道转换结束 (Regular channel end of conversion)
规则组通道转换结束后,硬件将该位置 1。通过软件或通过读取 ADC_DR 寄存器将该位清零。
0:转换未完成 (EOCS=0) 或转换序列未完成 (EOCS=1)
1:转换已完成 (EOCS=0) 或转换序列已完成 (EOCS=1)
 

10、规则组的四种转换模式

A.单次转化、非扫描: 

在这种模式下,当ADC转化了一个通道的数据之后,就会给EOC置一,表示转化结束,如果要防放置其他的通道,直接将这个通道连接到序列一的位置即可

B.连续非扫描模式

 在这种情况下就不是像A选项那样子了,而是只需要触发一次,然后直接到寄存器取数据即可

C.单次转换、扫描模式

 在这里要注意为了防止数据被覆盖,那么就需要DMA进行数据转换,防止数据被覆盖

11、代码

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);  //设置分频,在这里设置为12HZ,72/6=12
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;   //模拟输入模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); 
	   //在这里表示使用ADC1,然后通道为ADC_Channel_0,通道数目为1,采样时间为 
                                                 ADC_SampleTime_55Cycles5
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;   //设置为单通道
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;   //数据向右对齐,准确率较高
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  //软件触发
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;  //非连续转换模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;    //非扫描模式
	ADC_InitStructure.ADC_NbrOfChannel = 1;
	ADC_Init(ADC1, &ADC_InitStructure);
	
	ADC_Cmd(ADC1, ENABLE);
	
	ADC_ResetCalibration(ADC1);    //初始化校准寄存器
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);   //等待校准寄存器初始化完成
	ADC_StartCalibration(ADC1);                //ADC开始校准
	while (ADC_GetCalibrationStatus(ADC1) == SET);   //等待校准完成
}

uint16_t AD_GetValue(void)
{
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);   //软件触发ADC
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);  //EOC成功判断标志位
	return ADC_GetConversionValue(ADC1);   //读取ADC的值
}

注意在设置转换序列的时候,一定要设置好转换序列的数目

在这里好需要注意可以使用判断EOC标志位的形式ADC_GetFlagStatus(),就是使用这个函数

也可以采用判断中断是否进行的函数,例如:

if(ADC_GetITStatus(ADCx, ADC_IT_EOC)==SET)

这个时候通过判断中断是否发生的标志位来判断是否已经发生了转化

但是这个时候要注意要在前面开启中断函数

ADC_ITConfig(ADCx,ADC_IT_EOC,ENABLE);

这个时候在开启了中断函数之后,就能实时观察到是否发生了转换了

要计算STM32中的AD采样率,必须先知道STM32的ADC时钟频率。STM32的ADC时钟频率一般为2-14MHz,所以一般使用以下代码实现12HZ

RCC_ADCCLKConfig(RCC_PCLK2_Div6);

mian.c代码

uint16_t ADValue;
float Voltage;

int main(void)
{
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1, 1, "ADValue:");
	OLED_ShowString(2, 1, "Volatge:0.00V");
	
	while (1)
	{
		ADValue = AD_GetValue();
		Voltage = (float)ADValue / 4095 * 3.3;
		
		OLED_ShowNum(1, 9, ADValue, 4);
		OLED_ShowNum(2, 9, Voltage, 1);
		OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2);
		
		Delay_ms(100);
	}
}

ADC的扫描模式配合DMA进行数据转运

AD.C

#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	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);
	
  
  //配置转化序列,就是上图当中的菜单序列
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
		
	ADC_InitTypeDef ADC_InitStructure;
  //只使用ADC1
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	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_InitStructure.ADC_NbrOfChannel = 4;
	ADC_Init(ADC1, &ADC_InitStructure);
	
	DMA_InitTypeDef DMA_InitStructure;
  //将地址指向ADC模数转换后的寄存器地址
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
  //转换16位数据
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  //外设地址不用自增
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  //指向目的地址
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
  //目的地址接受16位地址
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  
  //存储器地址自增,但是外设地址保持不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  
  //传输方向为外设到地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  //缓冲区大小
  
	DMA_InitStructure.DMA_BufferSize = 4;
  //循环传输模式
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  //外设到存储器
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	
	DMA_Cmd(DMA1_Channel1, ENABLE);
	ADC_DMACmd(ADC1, ENABLE);
	ADC_Cmd(ADC1, ENABLE);
	
  //等待校验位初始化以及校验完成
	ADC_ResetCalibration(ADC1);
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
	
  //软件触发
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

int main(void)
{
	OLED_Init();
	AD_Init();
	
	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);
		OLED_ShowNum(2, 5, AD_Value[1], 4);
		OLED_ShowNum(3, 5, AD_Value[2], 4);
		OLED_ShowNum(4, 5, AD_Value[3], 4);
		
		Delay_ms(100);
	}
}

物联沃分享整理
物联沃-IOTWORD物联网 » STM32模数转换器(ADC)原理与应用详解

发表评论