目录
1. ADC简介
ADC全称是(Analog-to-Digital Converter)模拟-数字转换器,一般我们把模拟信号(Analog signal) 用A来进行简写,数字信号(digital signal) 用D来表示。主要用于将连续传输的模拟信号转换为数字信号,便于数字系统(如中央处理器CPU、微控制器MCU等)对传输信息进行快速处理和分析。
模拟信号是指用连续变化的物理量所表达的信息,如温度、湿度、压力、电压、电流等。ADC模块所采集的模拟信号是连续变化的电压或电流信号,其数值在一定范围内连续变化,如下所示:
在单片机的使用中,我们可以通过ADC的转换将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。
ADC采样的实现方式主要有两种:外接采样芯片和控制核心内部的采样模块。许多MCU和DSP内部集成了ADC模块。例如,STM32F103C8T6具备一个12位的ADC,1us的转换时间,其电压输出范围在0~3.3V,转换结果范围0~4095(即2^12-1),其ADC资源有ADC1、ADC2,10个外部输入通道。
2. 逐次逼近型ADC(ADC0809)
我们先来了解一下ADC0809的工作原理。
首先是通道选择开关,通过通道选择开关,选择(IN0~IN7)其中的一路,输入到这个点进行转换:
然后是地址锁存和译码,这里你想要选择哪一路,需要将通道号放到(ADDA、ADDB、ADDC)这三个脚上,然后通过ALE给出锁存信号,对应的通道选择开关就会自动拨好了:
比较器是一个电压比较器,他可以判断两个输入信号电压的大小关系,其中通道选择开关输入的是待测的电压,另一端是DAC(数模转换器)的电压:
这里待测电压是未知的,但是DAC所出的电压是我们通过数模转换来的,其编码是已知的,我们通过比较器进行比较,当待测电压大于DAC所示电压,我们就调大DAC的值,如果待测电压小于DAC所示电压,我们就调小DAC的值,直到待测电压等于DAC所示电压,这样DAC的输入数据就是待测电压的数据,我们就可以知道待测电压的编码(类似于二分法)。
得出待测电压的编码通过三态锁存缓冲器进行输出,8位就有8根线,12位就有12根:
3. ADC框图(STM32)
STM32F1系列其芯片内部有多达18个通道,可测量16个外部和2个内部信号源:
通道检测数据进入搭配模拟多路开关,模拟多路开关将数据输出的“模拟至数字转换器”(这里的作用类似于上面介绍的逐次逼近ADC),将转换数据传至数据寄存器,取出结果:
对于这里,我们可以将注入通道或者规则通道比作饭店上菜的菜单,注入通道的通道相当于菜单上的菜,菜单上有菜(有数据),厨房做菜(注入通道寄存器接受数据),菜单上有多少菜做多少,从上往下一道一道做(数据逐次注入);
规则通道有16个通道(也就是菜单上能写16个),但是规则通道数据寄存器只有一个,所以每当有新菜,就会将之前的菜划掉,只能保存最后一个数据,这里就需要搭配DMA来使用(作用就是当接收到一个数据之后,将这个数据挪到其他地方去,防止被覆盖):
这里相当于ADC0809的START信号(开始转换信号),对于STM32的ADC触发ADC开始转换的信号又两种:软件触发和硬件触发。
软件触发就是在程序中手动调用一条代码,就可以启动转换。
硬件触发如下,由于ADC需要经过一个固定时间转换一次,因此我们需要通过定时器进行控制时间,但是若是实时采集数据,会频繁进入定时器,频繁进入中断,而中断又有优先级,若是多处地方需要频繁进入中断,这将会导致程序卡死,不过对于这种需要频繁进入中断,并且中断之进行简单的操作,一般会有硬件的支持:
这里需要注意一点,ADC最大范围14MHz,如果分频系数选择2或者4分频,最大分别是36MHz和18MHz超范围了,因此只能选择6或者8分频:
4. ADC基本结构
5. 输入通道
6. 转换模式
6.1 单次转换
6.1.1 非扫描模式
只有第一个序列1的位置有效,在其中选择转换的通道,例如选择通道2,这样ADC就会对通道2进行模数转换,过一会转换完成,将其放到数据寄存器里,将EOC标志位置1,表示转换完成:
6.1.2 扫描模式
其与非扫描模式不同的是,非扫描模式只使用了序列1,而扫描模式将下面序列使用了起来,在结构体初始化时,需要初始化通道数目:
6.2 连续转换
6.2.1 非扫描模式
这个和单次转换非扫描模式有些类似,但是不同的是,这个在转换结束后不需要停止,可以直接进行下一次转换,不需要多次触发:
6.2.2 扫描模式
这个就是在单次转换扫描模式基础上,使其连续触发:
7. 触发控制
ADC1和ADC2用于规则通道的外部触发
触发源 | 类型 | EXTSEL[2:0] |
TIM1_CC1事件 | 来自片上定时器的内部信号 | 000 |
TIM1_CC2事件 | 001 | |
TIM1_CC3事件 | 010 | |
TIM2_CC2事件 | 011 | |
TIM3_TRGO事件 | 100 | |
TIM4_CC4事件 | 101 | |
EXTI线11/TIM8_TRGO事件 | 外部引脚/来自片上定时器的内部信号 | 110 |
SWSTART | 软件控制位 | 111 |
8. 转换时间
ADC转换步骤:采样、保持、量化、编码
STM32的ADC的总转换时间为:
=采样时间+12.5个ADC周期
例如:
当ADCCLK=14MHz,采样时间为1.5个ADC周期
=1.5+12.5=14个ADC周期=1us
9. 代码编写
9.1 引脚使能
首先初始化GPIO口:
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
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); //将PA0引脚初始化为模拟输入
}
9.2 预分频器配置
然后是RCC预分频器的配置,找到stm32f10x_rcc.h找到:
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);
其可以对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCLK:
/** @defgroup ADC_clock_source
* @{
*/
#define RCC_PCLK2_Div2 ((uint32_t)0x00000000)
#define RCC_PCLK2_Div4 ((uint32_t)0x00004000)
#define RCC_PCLK2_Div6 ((uint32_t)0x00008000)
#define RCC_PCLK2_Div8 ((uint32_t)0x0000C000)
#define IS_RCC_ADCCLK(ADCCLK) (((ADCCLK) == RCC_PCLK2_Div2) || ((ADCCLK) == RCC_PCLK2_Div4) || \
((ADCCLK) == RCC_PCLK2_Div6) || ((ADCCLK) == RCC_PCLK2_Div8))
例如设置成6分频:
/*设置ADC时钟*/
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
9.3 ADC配置介绍
ADC相关配置存放在stm32f10x_adc相关文件内:
/** @defgroup ADC_Exported_Functions
* @{
*/
void ADC_DeInit(ADC_TypeDef* ADCx);
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
void ADC_StartCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
uint32_t ADC_GetDualModeConversionValue(void);
void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);
uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);
void ADC_TempSensorVrefintCmd(FunctionalState NewState);
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);
方便后续需要使用我们简单介绍一下:
DeInit恢复缺省配置(恢复默认初始状态),Init初始化,StructInit结构体初始化:
void ADC_DeInit(ADC_TypeDef* ADCx);
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);
用于给ADC上电:
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
用于开启DMA输出信号,如果使用DMA转运数据,需要调用这个:
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
用于中断输出控制:
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
复位校准,获取复位校准状态:
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
开始校准,获取开始校准状态:
void ADC_StartCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
ADC软件开始转换控制,这个就是用于软件触发的函数:
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
ADC获取软件开始转换状态:
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);
用于间断模式控制,第一个函数表示每隔几个通道间断一次,第二个函数表示启用间断模式:
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
用于ADC规则组通道配置,配置选定的ADC常规通道、其在序列器中的对应等级及其采样时间::
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
ADCx: 其中x可以是1、2或3,以选择ADC外设。
ADC_Channel: 要配置的ADC通道。
此参数可以是以下值之一: @arg ADC_Channel_0: ADC Channel0 selected @arg ADC_Channel_1: ADC Channel1 selected @arg ADC_Channel_2: ADC Channel2 selected @arg ADC_Channel_3: ADC Channel3 selected @arg ADC_Channel_4: ADC Channel4 selected @arg ADC_Channel_5: ADC Channel5 selected @arg ADC_Channel_6: ADC Channel6 selected @arg ADC_Channel_7: ADC Channel7 selected @arg ADC_Channel_8: ADC Channel8 selected @arg ADC_Channel_9: ADC Channel9 selected @arg ADC_Channel_10: ADC Channel10 selected @arg ADC_Channel_11: ADC Channel11 selected @arg ADC_Channel_12: ADC Channel12 selected @arg ADC_Channel_13: ADC Channel13 selected @arg ADC_Channel_14: ADC Channel14 selected @arg ADC_Channel_15: ADC Channel15 selected @arg ADC_Channel_16: ADC Channel16 selected @arg ADC_Channel_17: ADC Channel17 selected
Rank: 在常规组序列器中的等级。该参数必须在1到16之间。
ADC_SampleTime: 要为所选通道设置的采样时间值。
此参数可以是以下值之一: @arg ADC_SampleTime_1Cycles5: 采样时间等于1.5个周期 @arg ADC_SampleTime_7Cycles5: 采样时间等于7.5个周期 @arg ADC_SampleTime_13Cycles5: 采样时间等于13.5个周期 @arg ADC_SampleTime_28Cycles5: 采样时间等于28.5个周期 @arg ADC_SampleTime_41Cycles5: 采样时间等于41.5个周期 @arg ADC_SampleTime_55Cycles5: 采样时间等于55.5个周期 @arg ADC_SampleTime_71Cycles5: 采样时间等于71.5个周期 @arg ADC_SampleTime_239Cycles5: 采样时间等于239.5个周期
获取AD转换的数据寄存器,读取转换结果:
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
双ADC读取转换结果的函数:
uint32_t ADC_GetDualModeConversionValue(void);
9.4 ADC相关配置
9.4.1 开启时钟
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟
9.4.2 规组通道配置
/*规则组通道配置*/
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0
9.4.3 ADC初始化
/*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 = DISABLE; //连续转换,失能,每转换一次规则组序列后停止
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //扫描模式,失能,只转换规则组的序列1这一个位置
ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1
ADC_InitTypeDef相关参数介绍:
typedef struct
{
uint32_t ADC_Mode; /*!< 配置ADC以独立模式或双模工作。
此参数可以是 @ref ADC_mode 的值。 */
FunctionalState ADC_ScanConvMode; /*!< 指定转换是否在扫描(多通道)或单通道模式下进行。
此参数可以设置为 ENABLE 或 DISABLE。 */
FunctionalState ADC_ContinuousConvMode; /*!< 指定转换是连续模式还是单次模式进行。
此参数可以设置为 ENABLE 或 DISABLE。 */
uint32_t ADC_ExternalTrigConv; /*!< 定义用于启动常规通道的模拟到数字转换的外部触发。
此参数可以是 @ref ADC_external_trigger_sources_for_regular_channels_conversion 的值。 */
uint32_t ADC_DataAlign; /*!< 指定ADC数据对齐方式是左对齐还是右对齐。
此参数可以是 @ref ADC_data_align 的值。 */
uint8_t ADC_NbrOfChannel; /*!< 指定将通过序列器转换的ADC通道数量
用于常规通道组。
此参数的范围必须在1到16之间。 */
} ADC_InitTypeDef;
9.4.4 ADC使能
/*ADC使能*/
ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行
9.4.5 ADC校准
/*ADC校准*/
ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
9.5 获取AD转换的值
我们配置的是单次转换非扫描模式,因此根据下图,编写获取AD转换的值的代码:
uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束
return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果
}
10. 完整代码
AD.c代码:
#include "stm32f10x.h" // Device header
/**
* 函 数:AD初始化
* 参 数:无
* 返 回 值:无
*/
void AD_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*设置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_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为模拟输入
/*规则组通道配置*/
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0
/*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 = DISABLE; //连续转换,失能,每转换一次规则组序列后停止
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //扫描模式,失能,只转换规则组的序列1这一个位置
ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1
/*ADC使能*/
ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行
/*ADC校准*/
ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
}
/**
* 函 数:获取AD转换的值
* 参 数:无
* 返 回 值:AD转换的值,范围:0~4095
*/
uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束
return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果
}
AD.h代码:
#ifndef __AD_H
#define __AD_H
void AD_Init(void);
uint16_t AD_GetValue(void);
#endif
主函数代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t ADValue; //定义AD值变量
float Voltage; //定义电压变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
AD_Init(); //AD初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "ADValue:");
OLED_ShowString(2, 1, "Voltage:0.00V");
while (1)
{
ADValue = AD_GetValue(); //获取AD转换的值
Voltage = (float)ADValue / 4095 * 3.3; //将AD值线性变换到0~3.3的范围,表示电压
OLED_ShowNum(1, 9, ADValue, 4); //显示AD值
OLED_ShowNum(2, 9, Voltage, 1); //显示电压值的整数部分
OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2); //显示电压值的小数部分
Delay_ms(100); //延时100ms,手动增加一些转换的间隔时间
}
}