AD单通道&AD多通道
ADC
- 什么是ADC?
①ADC(Analog-Digital Converter)模拟-数字转换器,ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁.
②12位逐次逼近型ADC,1us转换时间
③输入电压范围:0-3.3v,转换结果范围:0~4095
④18个输入通道,可测量16个外部和2个内部信号源
⑤规则组和注入组两个转换单元
⑥STM32F103C8T6 的ADC资源:ADC1、ADC2,10个外部输入通道 - ADC的基本结构如图:

- ADC通道与引脚对应关系如图:
值得注意的是:STM32只有10个外部输入通道,即PA0到PB1
- ADC规则组的转换模式有四种:
①单次转换,非扫描:

②连续转换,非扫描:

③单次转换,扫描:

④连续转换,扫描:

⑤各个转换模式的EOC变化如图:

AD单通道
-
接线图如下:
电位器相当于滑动变阻器,往右拧阻值减小,往左拧阻值增大.

-
复制OLED显示屏的项目并新增在Hardware文件夹下新增AD.c和AD.h文件
AD.c代码:
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
//ADC都是总线APB2上的设备
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//ADCCLK配置,ADC最大时钟频率是14MHZ,RCC_PCLK2_Div6表示6分频,使用的内部时钟是72MHZ,6分频后是12MHZ
//配置ADCCLK是为了驱动ADC内部有规律的逐次比较
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
//使用参数GPIO_Mode_AIN是因为模数转换需要未经任何“加工”的原始模拟电压
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
//GPIO_Speed(速度)参数在配置为模拟输入模式时是不起作用的,但是程序上仍需要填写一个合法值
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置ADC规则组通道模式.ADC_Channel_0即通道0(因为使用PA0输入信号,根据引脚定义表对应为ADC_Channel_0)
//1即序列1,这两个参数表明将ADC通道0放在序列1上进行采样操作
//ADC_SampleTime_55Cycles5表示采样时间为55.5个ADCCLK周期
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
//结构体初始化ADC
ADC_InitTypeDef ADC_InitStructure;
//转换模式,连续转换或者单次转换,DISABLE是单次转换
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
//ADC是12bit的,而数据寄存器是16bit的,ADC_DataAlign指定ADC数据在数据寄存器是左对齐还是右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//外部触发转换选择,指定外部触发器的触发源,这里使用内部软件触发,所有填参数ADC_ExternalTrigConv_None
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
//选择ADC是独立转换还是双ADC模式,ADC_Mode_Independent是独立转换模式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
//指定ADC在扫描模式下一次扫描几个通道,仅在扫描模式下生效
ADC_InitStructure.ADC_NbrOfChannel = 1;
//扫描模式,连续扫描模式或者单次扫描模式,DISABLE表示单次扫描模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_Init(ADC1,&ADC_InitStructure);
//ADC上电
ADC_Cmd(ADC1,ENABLE);
//上电后校准,大幅减小因 ADC 内部电容器组变化造成的精度误差,提高转换准确性
//复位校准
ADC_ResetCalibration(ADC1);
//获取复位校准状态,由ADC_ResetCalibration(ADC1);设置为校准状态为1,硬件校准完毕后将校准状态置0
while(ADC_GetResetCalibrationStatus(ADC1) == SET);
//开始校准
ADC_StartCalibration(ADC1);
//等待校准完成
while(ADC_GetCalibrationStatus(ADC1) == SET);
}
uint16_t AD_GetValue(void)
{
//软件触发转换
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
//查看规则组是否转换完成ADC_FLAG_EOC = RESET表示未转换完成,EOC为规则组或注入组转换完成标志位,时间为采样时间+转换时间
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);
//将数据寄存器的值返回,该函数读取完寄存器的值会自动清除标志位
return ADC_GetConversionValue(ADC1);
}
值得注意的是:
①ADC对模拟电压转换到数字电压是采用逐次逼近法的方式,这就要采用一个时钟频率来控制逐次逼近法的规律性比较,所以要开启ADC的时钟,即RCC_ADCCLKConfig(RCC_PCLK2_Div6);,参数RCC_PCLK2_Div6表示6分频,ADC时钟最大频率是14MHZ,而STM32的内部时钟是72MHZ,所以要对其进行分频操作,ADCCLK支持2 4 6 8 四种分频操作,采用6分频为72MHZ/6=12MHZ正好在14MHZ范围内.
②在初始化GPIO操作时,GPIO的模式GPIO_Mode 应选择为GPIO_Mode_AIN模式,使用参数GPIO_Mode_AIN是因为模数转换需要未经任何“加工”的原始模拟电压,具体原因如下图:
③配置ADC规则组通道模式函数ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);,参数ADC_Channel_0含义是ADC的通道0,由于使用的是PA0引脚,根据引脚定义图,PA0引脚对应的是ADC_Channel_0.1的含义是序列1,这两个参数表明将ADC通道0放在序列1上进行采样操作.ADC_SampleTime_55Cycles5表示采样时间为55.5个ADCCLK周期
④ADC上电后要进行先复位再校准.为什么要这样?
答:目的是为了确保ADC模块能够从一个已知且稳定的初始状态开始计算校准系数,从而获得最高的转换精度。简单来说,这个固定的顺序 复位校准 -> AD校准的核心原因是:复位校准是准备工作,确保“从零开始”。AD校准是核心计算,负责“计算误差”并“存储修正值”。跳过复位校准直接进行AD校准,就像没有清零的计算器直接做复杂的数学运算,结果很可能是不正确的。
⑤ADC_SoftwareStartConvCmd(ADC1, ENABLE);函数的核心作用是通过软件方式触发指定的ADC(此处为ADC1)开始一次模数转换。该函数设置ADC控制寄存器中的软件启动位(SWSTART),一旦SWSTART位被设置,ADC模块便会立即开始对当前选中的通道进行转换,硬件会在转换开始后立刻自动将SWSTART位清零。因此,ADC_GetSoftwareStartConvStatus函数并不能用来判断转换是否结束,它仅仅反映SWSTART位的瞬时状态.
⑥ADC_Init();是“定规矩”,而 ADC_SoftwareStartConvCmd();才是“下命令开始干活”.
⑦ADC的连续转换是指按序列号大小从小到大扫描待处理的序列,每次处理完毕单个序列号都会有EOC信号产生,扫描模式是指按序列号扫描完毕到最后一组才产生EOC信号.个人感觉扫描模式只是单纯的控制不在每一组数据处理完后就产生EOC的,而是等序列里的所有待处理的数据处理完毕才产生一个EOC,另外说一下EOC,EOC是ADC状态寄存器(如STM32的ADC_SR)中的一个硬件标志位(通常对应寄存器的第1位,即BIT1)。当ADC完成对规则组(Regular Group)中某一个通道的模数转换后,硬件会自动将该位置1(表示转换结束).
- 所以ADC规则组的初始化流程如图:
main.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t ADValue;
float Voltage;
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
AD_Init();
OLED_ShowString(1,1,"ADValue:");
OLED_ShowString(2,1,"Voltage:0.00v");
while (1)
{
ADValue = AD_GetValue();
//获取电压值,4095是ADC的最大量程
Voltage = (float)AD_GetValue() / 4095 * 3.3;
OLED_ShowNum(1,9,ADValue,5);
OLED_ShowNum(2,9,Voltage,1);
OLED_ShowNum(2,11,(uint16_t)(Voltage*100)%100,2);
Delay_ms(100);
}
}
这的注意的是:
①在代码Voltage = (float)AD_GetValue() / 4095 * 3.3;是为了AD_GetValue() / 4095 是在获取比例(ADC的最大量程是4095),然后乘以3.3(电源电压)来计算电压.
①在代码OLED_ShowNum(2,11,(uint16_t)(Voltage100)%100,2);,Voltage100可能是小数,而小数是没有办法取余的.
AD多通道
- 依旧是采用单次转换,非扫描模式,因为扫描模式还要用DMA,我还没学到,感觉也可以使用多次转换,非扫描模式.
- 接线图如下:

- 复制AD单通道工程并修改AD.c代码
AD.c代码:
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
//ADC都是总线APB2上的设备
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//ADCCLK配置,ADC最大时钟频率是14MHZ,RCC_PCLK2_Div6表示6分频,使用的内部时钟是72MHZ,6分频后是12MHZ
//配置ADCCLK是为了驱动ADC内部有规律的逐次比较
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
//使用参数GPIO_Mode_AIN是因为模数转换需要未经任何“加工”的原始模拟电压
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
//GPIO_Speed(速度)参数在配置为模拟输入模式时是不起作用的,但是程序上仍需要填写一个合法值
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置ADC规则组通道模式.ADC_Channel_0即通道0(因为使用PA0输入信号,根据引脚定义表对应为ADC_Channel_0)
//1即序列1,这两个参数表明将ADC通道0放在序列1上进行采样操作
//ADC_SampleTime_55Cycles5表示采样时间为55.5个ADCCLK周期
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
//结构体初始化ADC
ADC_InitTypeDef ADC_InitStructure;
//转换模式,连续转换或者单次转换,DISABLE是单次转换
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
//ADC是12bit的,而数据寄存器是16bit的,ADC_DataAlign指定ADC数据在数据寄存器是左对齐还是右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//外部触发转换选择,指定外部触发器的触发源,这里使用内部软件触发,所有填参数ADC_ExternalTrigConv_None
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
//选择ADC是独立转换还是双ADC模式,ADC_Mode_Independent是独立转换模式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
//指定ADC在扫描模式下一次扫描几个通道,仅在扫描模式下生效
ADC_InitStructure.ADC_NbrOfChannel = 1;
//扫描模式,连续扫描模式或者单次扫描模式,DISABLE表示单次扫描模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_Init(ADC1,&ADC_InitStructure);
//ADC上电
ADC_Cmd(ADC1,ENABLE);
//上电后校准,大幅减小因 ADC 内部电容器组变化造成的精度误差,提高转换准确性
//复位校准
ADC_ResetCalibration(ADC1);
//获取复位校准状态,由ADC_ResetCalibration(ADC1);设置为校准状态为1,硬件校准完毕后将校准状态置0
while(ADC_GetResetCalibrationStatus(ADC1) == SET);
//开始校准
ADC_StartCalibration(ADC1);
//等待校准完成
while(ADC_GetCalibrationStatus(ADC1) == SET);
}
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
//软件触发转换
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
//查看规则组是否转换完成ADC_FLAG_EOC = RESET表示未转换完成,EOC为规则组或注入组转换完成标志位,时间为采样时间+转换时间
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);
//将数据寄存器的值返回,该函数读取完寄存器的值会自动清除标志位
return ADC_GetConversionValue(ADC1);
}
相较于ADC单通道工程,代码变化如下:
①外接设备增多,修改GPIO引脚参数为GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
②在函数uint16_t AD_GetValue(uint8_t ADC_Channel);增加代码ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);**这个函数是STM32库中用来配置ADC规则组转换通道的核心函数,它告诉ADC哪个引脚需要转换、转换的顺序以及采样时间。**第二个参数ADC_Channel是通过外部传参来保证测量各个引脚的.
main.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD0,AD1,AD2,AD3;
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
AD_Init();
OLED_ShowString(1,1,"AD0:");
OLED_ShowString(2,1,"AD1:");
OLED_ShowString(3,1,"AD2:");
OLED_ShowString(4,1,"AD3:");
while (1)
{
AD0 = AD_GetValue(ADC_Channel_0);
AD1 = AD_GetValue(ADC_Channel_1);
AD2 = AD_GetValue(ADC_Channel_2);
AD3 = AD_GetValue(ADC_Channel_3);
OLED_ShowNum(1,5,AD0,4);
OLED_ShowNum(2,5,AD1,4);
OLED_ShowNum(3,5,AD2,4);
OLED_ShowNum(4,5,AD3,4);
Delay_ms(100);
}
}
效果图

1360

被折叠的 条评论
为什么被折叠?



