目录
1. ADC简介
2. 逐次逼近型ADC(ADC0809)
3. ADC框图(STM32)
4. ADC基本结构
5. 输入通道
6. 转换模式
6.1 单次转换
6.1.1 非扫描模式
6.1.2 扫描模式
6.2 连续转换
6.2.1 非扫描模式
6.2.2 扫描模式
7. 触发控制
8. 转换时间
9. 代码编写
9.1 引脚使能
9.2 预分频器配置
9.3 ADC配置介绍
9.4 ADC相关配置
9.4.1 开启时钟
9.4.2 规组通道配置
9.4.3 ADC初始化
9.4.4 ADC使能
9.4.5 ADC校准
9.5 获取AD转换的值
10. 完整代码
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; //模式,选择独立模式,即单独使用ADC1ADC_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的数,在非扫描模式下,只能是1ADC_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; //模式,选择独立模式,即单独使用ADC1ADC_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的数,在非扫描模式下,只能是1ADC_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_Hvoid 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,手动增加一些转换的间隔时间}
}
STM32学习笔记_时光の尘的博客-CSDN博客