ADC
ADC 是 “Analog - to - Digital Converter” 的缩写,即模拟数字转换器
简介
单片机只有高电平和低电平两种状态,想要将模拟信号(光,声音等)转为数字信号,来反映模拟信号的大小,需要借助ADC来实现,ADC读取引脚的电压,将读到的值准换成一个数据,并存到寄存器里。
输入电压范围,一般是单片机的输入电压的正负极,这个芯片是0~3.3V
12位逐次逼近,0~4095
16个外部,就是16个引脚,可以通过引脚进行输入电压,进行读取,属于这个系列芯片拥有的最多通道数目,但是STM32F103C8T6只有十个外部通道,两个ADC内部通道
其中2个内部,内部温度传感器和内部参考电压(1.2V基准电压固定不变,无论输入电压是多少),
1)可以通过温度传感器读取CPU的温度,
2)内部参考电压可以用来校准,当电源供电不等于3.3V,可能就会出现误差,假如输入的电压为3.0V,那么寄存器的最大值4095对应的就是3V,如果电源输入是3.3V,那么4095就是3.3V,所以可以通过这个电压值进行校准
1微妙转换时间,说明ADC转换的频率是1MHZ,如果对数据读取的频率有更高的要求,就需要考虑其他单片机。
规则组和注入组:普通的ADC是启动一次,转换,读值,...然后再启动、转换、读值
32单片机可以进行分组,列一个组,一次性启动一个组,进行多次转换,这就叫做规则组。规则组是常规使用的,注入组是用于突发情况的
看门狗自动监测,通常用于测试温度和光度,并且当温度或者光度超过某一个阈值,就会经行对应的操作,说白了看门狗就是用来监测指定的一个通道,超出阈值的范围就会申请中断,可以再中断里头执行一些操作。
ADC0809
这里介绍的是ADC0809,由于以前的单片机功能不是很强大,想要实现ADC需要外挂ADC模块ADC0809是以前一个外挂ADC设备,通过学习这个,了解原理
8个信号输入通道,输入一个位置编码的电压
DAC,给定一个电压,输出对应的编码,通过比较器,进行比较,如果比较之后DAC输出的数据比较大,就降低DAC的电压,反之就调高,对于ADC0809,有八位所存缓冲器(就是将电压转换为编码的八位二进制数),
这个比较的过程就是这八位从高到低依次确定的过程,最多八次就能够确定电压的编码。对于stm32F03C8T6有12位,最多12次就能确定输入电压的编码啦~
为什么要设计两个ADC呢?
这里涉及到双ADC模式,双ADC可以进一步提高对同一个通道的数据采样频率,也可以分开测试两个通道,(了解即可)
工作模式
连续转换扫描,单次转换扫描,连续转换非扫描,单次转换非扫描
所以在初始化ADC时,需要两个参数,来确定1.转换(连续/单次)和 是否为扫描模式
1)单次转换,非扫描模式
在非扫描模式下,只有序列1有效,将选择的通道填入序列1,开始进行转换,转换完成后会置标记为EOC为1,一次转换完成,就可以读取寄存器的数据啦~,想要在进行第二次转换,就需要再次重新手动触发,单次转换是不会进行下一次转换的,如果想要转换另一个通道的数据,就需要提前更换序列填入的通道,在进行转换。
2)连续转换,非扫描模式
连续转换,就是会一致转换,当想要读取AD值时,直接读取寄存器的数据就可以了,不需要对标记为进行判断
3)单次转换,扫描模式
因为是单次转换,在每次完成一次扫描之后,需要手动进行下一次扫描,而且和前面第一种介绍的模式不一样,同时扫描模式,就会用到菜单(多个序列),所以在进行扫描模式下,在初始化ADC的时候就要多配置一个参数,参数表示用到的序列数目,并且每个序列需要指定特定的通道,,通道也可以重复。
详细过程:
假设用到7个序列,在触发之后,就会依次从序列1到7经行AD转换,每读取一次,就会放到寄存器里边,为了防止寄存器的数据被覆盖,就需要结合DMA来进行数据运转,完成7个通道转换完成之后,EOC才会置1
4)连续转换,扫描模式
在 3 模式基础上,不需要手动进行后面的触发,触发一次后会自动进行下一次转换
5)(了解)在扫描模式下,还有一种间断模式,这种模式就是,在读取完几个通道后,会停止扫描,需要手动触发,
(由于篇幅过多,再加这个模式,可能会太多太乱了,所以江科大也没有过多赘述)
触发源
红色圈是ADC的触发源
有内部时钟源,也有EXTI线11/TIM_8 TRGO事件,在使用这个的时候,需要通过AFIO重映射,来选择具体是 外部引脚,还是内部时钟源 来作为触发源
ACD数据对其问题
由于ADC转换的数据是12位的,但是寄存器是16位的,就要解决数据对其问题
有两种数据存放的方法
1)将数据放到寄存器的低八位——右对齐(较常用)
在读寄存器的时候,可以直接读出来,就是ADC的值,读取范围12位 0~4095
2)将数据放到寄存器的高八位——左对齐
这样,读出来的值就会比原来的大16倍,(二进制左移一位相当于*2,这里左移了4位相当于*16)
左对其可以解决精度问题,如果不需要0~4095的精度,可以使用较低的精度,就可以舍弃后面的几位,直接读取前面几位就可以啦!
ADC校准
校准使用的时候实际是要进行两次校准
1)ADC寄存器校准
2)ADC校准(上面截图)
ADC代码实现
单道
一、ADC初始化
void InitADC(void){
//开启ADC和内部时钟,配置ADC1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//内部时钟6分频,
// 配置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);
//配置序列号对应的通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//单次转换
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//右对齐
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//不适用外部中断,直接软件触发
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//单独模式,只是用一个ADC
ADC_InitStructure.ADC_NbrOfChannel=1;//选择通道1
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//不使用扫描模式
ADC_Init(ADC1,&ADC_InitStructure);//ADC初始化
ADC_Cmd(ADC1,ENABLE);//使能ADC
ADC_ResetCalibration(ADC1);//开启ADC寄存器重置校准,
while( ADC_GetResetCalibrationStatus( ADC1));//确保寄存器的数据已经重置 返回set表示正在重置,reset表示重置完成
ADC_StartCalibration(ADC1);//ADC较准
while(ADC_GetCalibrationStatus( ADC1));//确保ADC校准完成 返回set表示正在重置,reset表示重置完成
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开始软件触发ADC
}
1)先开启内部时钟源,和ADC时钟
还要进行ADC时钟配置(分频,最大是14MHZ)
//开启ADC和内部时钟,配置ADC1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//内部时钟6分频,
2)GPIO,看好通道和GPIO的对应关系,并且进行通道配置
我用的是PA0,对应通道0,在进行配置序列号对应的通道时,第二个参数是通道0
// 配置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);
//配置序列号对应的通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
3)配置ADC模块,并且使能ADC
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//单次转换
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//右对齐
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//不适用外部中断,直接软件触发
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//单独模式,只是用一个ADC
ADC_InitStructure.ADC_NbrOfChannel=1;//选择通道1
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//不使用扫描模式
ADC_Init(ADC1,&ADC_InitStructure);//ADC初始化
ADC_Cmd(ADC1,ENABLE);//使能ADC
4)配置完成后进行校准,校准是在初始化进行校准,不是每次调用前校准,上面ADC校准提到建议每次上电时,校准,
- 校准耗时:ADC 校准需要一定时间,因为它涉及到 ADC 内部的硬件操作和计算,以确定并补偿偏移和增益误差。在初始化时进行校准,可避免每次启动 ADC 都花费额外时间,使系统在后续运行中能更高效地进行模数转换。
- 稳定性:在正常使用环境下,ADC 的误差特性在短时间内相对稳定。只要系统的环境条件(如温度、电源电压等)没有发生显著变化,一次校准的结果可以在一段时间内保持有效。因此,在初始化时完成校准足以保证 ADC 在后续运行中的准确性。
ADC_ResetCalibration(ADC1);//开启ADC寄存器重置校准,
while( ADC_GetResetCalibrationStatus( ADC1));//确保寄存器的数据已经重置 返回set表示正在重置,reset表示重置完成
ADC_StartCalibration(ADC1);//ADC较准
while(ADC_GetCalibrationStatus( ADC1));//确保ADC校准完成 返回set表示正在重置,reset表示重置完成
ADC使用
uint16_t ADC_GetValue(void){
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开始软件触发ADC
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));//判断是否已经完成一次转换的标志,EOC置一时,表示已经完成
return ADC_GetConversionValue(ADC1);//返回寄存器的值
}
main
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "ADC.h"
int main(void){
OLED_Init();
InitADC();
uint16_t value;
OLED_ShowString(1,1,"value:");
while(1){
value=ADC_GetValue();
OLED_ShowNum(1,7,value,4);
Delay_ms(500);
}
}
多道
更改初始化GPIO,匹配通道代代码,移到调用ADC前更换,提前更换通道,在进行ADC转换
void InitADC(void){
//开启ADC和内部时钟,配置ADC1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//内部时钟6分频,
// 配置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);
//配置序列号对应的通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//单次转换
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//右对齐
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//不适用外部中断,直接软件触发
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//单独模式,只是用一个ADC
ADC_InitStructure.ADC_NbrOfChannel=1;//选择通道1
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//不使用扫描模式
ADC_Init(ADC1,&ADC_InitStructure);//ADC初始化
ADC_Cmd(ADC1,ENABLE);//使能ADC
ADC_ResetCalibration(ADC1);//开启ADC寄存器重置校准,
while( ADC_GetResetCalibrationStatus( ADC1));//确保寄存器的数据已经重置 返回set表示正在重置,reset表示重置完成
ADC_StartCalibration(ADC1);//ADC较准
while(ADC_GetCalibrationStatus( ADC1));//确保ADC校准完成 返回set表示正在重置,reset表示重置完成
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开始软件触发ADC
}
在上面的基础上,改一下这个函数,将原先ADC初始化的序列匹配通道代码调下面
将通道作为参数,
uint16_t ADC_GetValue( uint8_t ADC_Channel ){
ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开始软件触发ADC
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));//判断是否已经完成一次转换的标志,EOC置一时,表示已经完成
return ADC_GetConversionValue(ADC1);//返回寄存器的值
}
主函数调用时,记得给这个参数
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "ADC.h"
int main(void){
OLED_Init();
Init_ADC();
uint16_t value0;
uint16_t value1;
uint16_t value2;
uint16_t value3;
OLED_ShowString(1,1,"value0:");
OLED_ShowString(2,1,"value1:");
OLED_ShowString(3,1,"value2:");
OLED_ShowString(4,1,"value:");
while(1){
value0=ADC_GetValue(ADC_Channel_0);
value1=ADC_GetValue(ADC_Channel_1);
value2=ADC_GetValue(ADC_Channel_2);
value3=ADC_GetValue(ADC_Channel_3);
OLED_ShowNum(1,8,value0,4);
OLED_ShowNum(2,8,value1,4);
OLED_ShowNum(3,8,value2,4);
OLED_ShowNum(4,8,value3,4);
Delay_ms(500);
}
}