六、ADC模数转换
ADC简介
- ADC(Analog-Digital Converter)模拟-数字转换器
- ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
- 12位逐次逼近型ADC,1us转换时间
- 输入电压范围:03.3V,转换结果范围:04095
- 18个输入通道,可测量16个外部和2个内部信号源
- 规则组和注入组两个转换单元
- 模拟看门狗自动监测输入电压范围
STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道
逐次逼近型ADC简介
“地址锁存和译码”用来选择通道
“IN0~IN7”是8路输入通道,可以输入模拟信号
使用“通道选择开关”选择其中的一路,输入到“比较器”的待测端
“比较器“可以判断两个输入信号的电压大小关系,输出高低电平指示大小。一端是待测端,另一端是”DAC“的电压输出端
给“DAC”一个数据,他就可以输出数据对应的电压,其内部原理是使用加权电阻网络实现
将”比较器“待测端的未知电压与“DAC”输出的已知电压进行比较,当未知电压不等于“DAC”输出的电压时,就调整输入”DAC“的数据来改变”DAC“输出的电压,直到与未知电压近似,此时”比较器“待测端的电压的数字信号就等于输入给”DAC“的数据。比较的方法是:二分法
获取模拟信号对应的数字信号后,将数字信号存放到“8位三态锁存缓冲器”中,并将标志位“EOC”置1,表示成功获取到了数字信号,可以从“8位三态锁存缓冲器”中取值了
STM32的ADC外设
- 规则组:
- 规则通道:规则组转换单元,一次可以选中16个通道,然后依次进行转换。也可以选择16个通道以下的通道数。
- 规则通道数据寄存器:存放规则组转换出来的数据,只有一个数据寄存器,所以当规则组同时转换多个通道的数据时,转换完成一个通道就需要立马将该通道转换的数据取出来,否则会被后面通道转换的数据覆盖。一般配合DMA来使用
- 注入组:
- 注入通道:注入组转换单元,一次可以选中4个通道,然后依次进行转换。也可以选择4个通道以下的通道数。
- 注入通道数据寄存器:存放注入组转换出来的数据,有四个数据寄存器,分别对应注入通道连接的4个通道,因此注入组不必担心转换出来的数据被覆盖的情况
ADC的基本结构
ADC输入通道
规则组的转换模式
单次转换:在完成一次转换后,进行下一次转换时需要重新触发一次
连续转换:在完成一次转换后,进行下一次转换时无需再触发,自动开始转换
非扫描模式:每一次转换只能转换一个通道
扫描模式:每次转换可以转换16个及以下的通道,按照序列一个一个的转换。16个序列中可以放重复的通道
间断模式:在扫描模式下,每隔几个转换就暂停一次,需要再次触发才能继续转换
单次转换,非扫描模式
连续转换,非扫描模式
单次转换,扫描模式
连续转换,扫描模式
触发控制
EXTSEL:配置寄存器的参数。使用库函数的话给个参数就行
触发源:用于触发AD转换
数据对齐
STM32的ADC外设是12位的,那么转换结果只有12位,但是数据寄存器是16位的,所以数据有两种对齐方式
数据右对齐:低16位写转换出来的数据,高4位补0
一般使用该方式,直接读寄存器就是转换的结果,不需要二次计算
数据左对齐:高16位写转换出来的数据,低4位补0
直接读寄存器的话会比实际值大,对于不需要很高分辨率的情况可以选这种,只读寄存器的高8位,舍弃掉后面4位的精度,相当于8位ADC
转换时间
AD转换的步骤:采样,保持,量化,编码
采样、保持:AD在转换时是需要一小段时间的,需要输入的电压稳定不变才能准确的定位。比如用一个小容量的电容存储一下采样的电压,方便后面的量化、编码
量化、编码:逐次比较
STM32 ADC的总转换时间为:TCONV = 采样时间 + 12.5个ADC周期
采样时间是在保持电压时消耗的时间
ADC周期是从RCC分频过来的ADCCLK,最大频率为14MHz
12位的ADC,所以需要12个周期,还有0.5个周期是做其他事情花的时间
例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期
TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs
当然,也可以对ADCCLK进行超频,大于14MHz,这样转换时间会更快,但是稳定性会下降
校准
不需要详细了解,只需要在初始化ADC的后面加上几条用于校准的代码即可
-
ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差
-
建议在每次上电后执行一次校准
-
启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期
可输出不同电压的电路
ADC库函数
ADC_InitTypeDef参数
typedef struct
{
uint32_t ADC_Mode; /*!< 选择工作模式,独立模式还是双ADC模式 */
FunctionalState ADC_ScanConvMode; /*!< 选择扫描模式或非扫描模式 */
FunctionalState ADC_ContinuousConvMode; /*!< 单次转换还是连续转换 */
uint32_t ADC_ExternalTrigConv; /*!< 选择转换的触发源 */
uint32_t ADC_DataAlign; /*!< 选择数据的对齐,左对齐或右对齐 */
uint8_t ADC_NbrOfChannel; /*!< 扫描模式下每一次转换的通道数 */
}ADC_InitTypeDef;
/* ADC_Mode */
ADC_Mode_Independen // 独立模式
// 下面都是双ADC相关的工作模式
ADC_Mode_RegInjecSimult
ADC_Mode_RegSimult_AlterTrig
ADC_Mode_InjecSimult_FastInterl
ADC_Mode_InjecSimult_SlowInterl
ADC_Mode_InjecSimult
ADC_Mode_RegSimult
ADC_Mode_FastInterl
ADC_Mode_SlowInterl
ADC_Mode_AlterTrig
/* ADC_ScanConvMode */
ENABLE // 扫描模式
DISABLE // 非扫描模式
/* ADC_ContinuousConvMode */
ENABLE // 连续转换模式
DISABLE // 单次转换模式
/* ADC_ExternalTrigConv */
ADC_ExternalTrigConv_T1_CC1 // TIM1_CC1事件
/*!< 适用于ADC1 和 ADC2 */
ADC_ExternalTrigConv_T1_CC2 // TIM1_CC2事件
/*!< 适用于ADC1 和 ADC2 */
ADC_ExternalTrigConv_T2_CC2 // TIM2_CC2事件
/*!< 适用于ADC1 和 ADC2 */
ADC_ExternalTrigConv_T3_TRGO // TIM3_TRGO事件
/*!< 适用于ADC1 和 ADC2 */
ADC_ExternalTrigConv_T4_CC4 // TIM4_CC4事件
/*!< 适用于ADC1 和 ADC2 */
ADC_ExternalTrigConv_Ext_IT11_TIM8_TRGO // EXTI线11/TIM8_TRGO事件
/*!< 适用于ADC1 和 ADC2 */
ADC_ExternalTrigConv_T1_CC3 // TIM1_CC3事件
/*!< 适用于ADC1, ADC2, ADC3 */
ADC_ExternalTrigConv_None // 软件控制
/*!< 适用于ADC1, ADC2, ADC3 */
ADC_ExternalTrigConv_T3_CC1 // TIM3_CC1事件
/*!< 仅适用于ADC3 */
ADC_ExternalTrigConv_T2_CC3 // TIM2_CC3事件
/*!< 仅适用于ADC3 */
ADC_ExternalTrigConv_T8_CC1 // TIM8_CC1事件
/*!< 仅适用于ADC3 */
ADC_ExternalTrigConv_T8_TRGO // TIM8_TRGO事件
/*!< 仅适用于ADC3 */
ADC_ExternalTrigConv_T5_CC1 // TIM5_CC1事件
/*!< 仅适用于ADC3 */
ADC_ExternalTrigConv_T5_CC3 // TIM5_CC3事件
/*!< 仅适用于ADC3 */
/* ADC_DataAlign */
ADC_DataAlign_Right // 数据右对齐
ADC_DataAlign_Left // 数据左对齐
/* ADC_NbrOfChannel */
1~16
函数
// 配置ADCCLK分频器,对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCKL
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2); // 该函数来自rcc.c文件中
// ADC恢复初始化配置
void ADC_DeInit(ADC_TypeDef* ADCx);
// 使用ADC_InitTypeDef结构体中的属性配置ADC
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
// 使用默认值填充ADC_InitTypeDef结构体
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);
// 启动或关闭ADC
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// 启动或关闭DMA
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC的中断输出控制
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规则组通道配置
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);
// 获取AD转换的数据寄存器,读取转换结果的
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
// 双ADC模式下读取转换结果
uint32_t ADC_GetDualModeConversionValue(void);
/* 配置注入组相关函数,Injected */
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);
// ADC温度传感器、内部参考电压控制。开启内部的两个通道
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);
案例
AD单通道转换
使用的函数
// 配置ADCCLK分频器,对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCKL
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2); // 该函数来自rcc.c文件中
// 使用ADC_InitTypeDef结构体中的属性配置ADC
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
// 启动或关闭ADC
void ADC_Cmd(ADC_TypeDef* ADCx, 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规则组通道配置
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
// 获取AD转换的数据寄存器,读取转换结果的
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
// 获取转换完成标志位状态,判断转换是否完成。中断函数外使用
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
// 清除转换完成标志位。中断函数外使用
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
接线图
示例代码
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Delay.h"
int16_t Speed;
int main()
{
/* 开启ADC2和GPIOA的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2 | RCC_APB2Periph_GPIOA, ENABLE);
/* 初始化PA0 */
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入模式
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 对72MHz的PAB2时钟进行分频得到ADCCLK,其中ADCCLK最大频率为14MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 6分频
// 规则组配置。PA0对应ADC2的通道0;通道0排在转换序列的第一位;采样、保持的时间55.5个ADC周期
ADC_RegularChannelConfig(ADC2, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
/* 初始化ADC2 */
ADC_InitTypeDef ADC_InitStruct;
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; // 连续转换
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 使用软件作为AD转换的触发源
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // ADC的工作模式为独立模式,ADC1和ADC2给干各的
ADC_InitStruct.ADC_NbrOfChannel = 1; // 每次转换有几个通道。只有在扫描模式下才有效
ADC_InitStruct.ADC_ScanConvMode = DISABLE; // 非扫描模式
ADC_Init(ADC2, &ADC_InitStruct);
// ADC2开始工作
ADC_Cmd(ADC2, ENABLE);
/* ADC校准 */
ADC_ResetCalibration(ADC2); // 复位ADC2的校准寄存器
while (ADC_GetResetCalibrationStatus(ADC2) == SET); // ADC2的校准寄存器是否复位完成。复位完成后返回RESET
ADC_StartCalibration(ADC2); // ADC2开始进行校准
while (ADC_GetCalibrationStatus(ADC2) == SET); // ADC2是否校准完成。校准完成后返回RESET
// 对ADC2进行软件中断,由于是连续转换模式,所以只需要触发一次即可
ADC_SoftwareStartConvCmd(ADC2, ENABLE);
OLED_Init();
while(1)
{
// AD的规则组是否转换完成,转换完成后返回SET
while(ADC_GetFlagStatus(ADC2, ADC_FLAG_EOC) == RESET);
// ADC_GetConversionValue:读取转换结果。该函数就是直接读取DR寄存器,而读取DR寄存器时会自动清除EOC标志位,所以不用手动清除EOC
OLED_ShowNum(1,1,ADC_GetConversionValue(ADC2), 5);
Delay_s(1);
}
}
AD多通道转换
接线图
示例代码
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Delay.h"
int main()
{
// 开启GPIOA和ADC1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
// 初始化PA0、PA1、PA2、PA3为模拟输入
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 对APB2的时钟6分频作为ADC的时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 初始化ADC1
ADC_InitTypeDef ADC_InitStruct;
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // 单次转换
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 外部触发,软件触发
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // ADC独立工作模式
ADC_InitStruct.ADC_NbrOfChannel = 1; // 占用规则组16个序列中的1个序列
ADC_InitStruct.ADC_ScanConvMode = DISABLE; // 非扫描模式
ADC_Init(ADC1, &ADC_InitStruct);
// 开启ADC1
ADC_Cmd(ADC1, ENABLE);
// 校准ADC
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1) == SET);
// 将用到的四个模拟信号输入通道组成一个列表
uint8_t ADC_Channel[4] = {ADC_Channel_0, ADC_Channel_1, ADC_Channel_2, ADC_Channel_3};
OLED_Init();
while(1)
{
for(uint8_t i = 0; i<4; i++)
{
// 规则组配置
ADC_RegularChannelConfig(ADC1, ADC_Channel[i], 1, ADC_SampleTime_55Cycles5);
// 软件触发转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 检测是否转换完成
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
// ADC_GetConversionValue:读取转换的结果
OLED_ShowNum(i + 1, 1, ADC_GetConversionValue(ADC1), 5);
Delay_ms(100);
}
}
}
标签:TypeDef,转换,void,ADC2,ADC,模数转换,ADCx
From: https://www.cnblogs.com/liuhousheng/p/17988382