本文的大部分内容来自B站up主 江协科技, 此文只供本人学习记录用途, 侵删
一、ADC
- ADC(Analog-Digital Converter)模拟-数字转换器
- ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
- 12位逐次逼近型ADC,1us转换时间
- 输入电压范围:03.3V,转换结果范围:04095
- 18个输入通道,可测量16个外部和2个内部信号源
- 规则组和注入组两个转换单元
- 模拟看门狗自动监测输入电压范围
- STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道
二、ADC8090 原理
这里选用和stm32内部ADC相同原理(逐次逼近)的ADC0809进行讲解
-
模拟信号输入 IN0~IN7:IN0-IN7 为八路模拟电压输入线,加在模拟开关上,通过 ADDA、ADDB、ADDC三个地址译码来选通
-
ALE为地址锁存允许输入线,高电平有效。当 ALE线为高电平时,ADDA、ADDB和 ADDC三条地址线上地址信号得以锁存, 经译码器控制八路模拟开关通路工作
-
START 为“启动脉冲”输入线,上升沿清零,下降沿启动 ADC0809工作,最小脉冲宽度与 ALE信号相同。
-
EOC 为转换结束输出线,该线高电平表示 A/D 转换已结束,数字量已锁入“三态输出锁存器” ,常用来作为中断请求信号。
-
OE为“输出允许”线,高电平有效。ADC0809接到此信号时,其三态输出端与 CPU数据总线接通,后者可将数据取走。
-
D0-D7为数字量输出线, D7为最高位, D0为最低位。
-
START 为“启动脉冲”输入线,上升沿清零,下降沿启动 ADC0809工作,最小脉冲宽度与 ALE信号相同。
-
EOC为转换结束输出线,该线高电平表示 A/D 转换已结束,数字量已锁入“三态输出锁存器” ,常用来作为中断请求信号。
-
OE为“输出允许”线,高电平有效。ADC0809接到此信号时,其三态输出端与 CPU数据总线接通,后者可将数据取走。
-
D0-D7 为数字量输出线, D7为最高位, D0为最低位。
-
CLOCK 为时钟线, 驱动ADC芯片工作
-
VREF+和VREF-为校准电压, 一般直接接VCC, GND
-
作为8位ADC, 都会拿DAC输出的电压和进来的信号比较(, DAC模拟量和输入模拟量进入运算放大器, 比较大小(用二分法),假设进来的模拟量转化成数字量是31,那么比较过程如下:
注意,这里实际在运放中比较的是模拟量, 用数字量表达只是为了方便 ! ! ! ! !
比较: 1 2 3 4 5 6 7
DAC: 64 32 16 24 28 30 31
输入: 31 31 31 31 31 31 31
只需要经过八次以内的比较, 就能得到准确的数字量
三、STM32中的ADC
规则组和注入组:注入组这里不涉及, 只讲规则组, ADC0809转换过程是: 一个信号, 采集, MCU取走,而32中, 可以是一堆信号, 依次采集, 去数据寄存器取, 这堆待采集的信号, 或者说引脚, 就放在规则组这个菜单上, 不过桌子只有一张, 所以每次新的信号进来,都会覆盖原来的, 这时候我们只能进中断先拿走信号, 或者用DMA自动搬运走,再接着采集
ADC的时钟 来源于APB2, 可以选择2 4 6 8分频, 不过尴尬的是, 最大只能支持14MHz, 于是2 4分频都超过了14 只能用6 和 8了,对应 12MHz ,9MHz
开始采集的信号触发信号源(同0809的START): 可以软件开始,也可以配置硬件触发源, 如果要用定时器每1ms采集一次, 如果中断里开始, 会浪费硬件资源, 一般都会配置TRGO(定时器主模式)让他自己开始
四、注意事项
-
校准
ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差
建议在每次上电后执行一次校准, 不理解没关系, 代码是固定的
-
数据对齐
由于32中的外设寄存器是16位的, 但这是个12位的ADC, 数据对齐就很重要, 一般都是右对齐, 取出来再计算转换, 但如果用的8位模式, 左对齐直接取出高八位, 就直接是数据
-
转换模式
在规则组这个菜单上,
单次/多次转换 决定ADC是转换完就停止 还是一直转换下去
扫描/非扫描模式 决定ADC是只转换序列1 还是扫描转换完所有序列
于是延伸出, 以下四种转换模式
单次转换, 非扫描模式
多次转换, 非扫描模式
单次转换, 扫描模式
多次转换, 扫描模式
五、常用配置代码
手动单次转换代码:
#include "stm32f10x.h" // Device header
void AD_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//开启时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC时钟分频
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(ADC12_IN0)模拟输入
ADC_RegularChannelConfig(ADC1,ADC_Channel_0 , 1, ADC_SampleTime_55Cycles5);
/*
ADC规则组通道配置
ADC1 没啥好说
ADC_Channel_0 选择通道0
1 代表这个通道在规则组的序列1
ADC_SampleTime_55Cycles5 采样时间为55.5个CLK周期
*/
ADC_InitTypeDef ADC_InitStructure;
ADC_StructInit(&ADC_InitStructure);//先初始化用不到的参数
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None ;
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;
ADC_InitStructure.ADC_ScanConvMode=DISABLE;
ADC_InitStructure.ADC_NbrOfChannel=1;
ADC_Init(ADC1, &ADC_InitStructure);
/*
ADC_Mode_Independent: ADC为独立模式, 其他参数都为双ADC
ADC_DataAlign_Right: 数据右对齐
ADC_ExternalTrigConv_None: 关闭外部触发, 这里用软件触发
ADC_ContinuousConvMode = DISABLE 关闭连续模式
ADC_ScanConvMode = DISABLE 关闭扫描模式
ADC_NbrOfChannel=1 规则组通道的数目
*/
ADC_Cmd(ADC1,ENABLE);//使能ADC
//启动校准, 就这固定四句, 原理我没仔细研究
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1)==SET);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1)==SET);
}
uint16_t AD_Get(void){
//软件触发ADC
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
//等待转换完成
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);
//返回转换结果
return ADC_GetConversionValue(ADC1);
}
定时器触发单次转换代码:
void Tim3_Init(void){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//开启TIM2时钟
TIM_InternalClockConfig(TIM3);//选择内部时钟源
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//设置时钟分割(采样滤波)
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=10000-1;//ARR
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;//PSC
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化时基单元
//!!! 选择主模式, 把更新映射到TRGO(答一下TRGO就高电平一下)
TIM_SelectOutputTrigger(TIM3,TIM_TRGOSource_Update);
//初始化会更新中断标志位,这里手动清除
TIM_ClearFlag(TIM3, TIM_FLAG_Update);
TIM_Cmd(TIM3, ENABLE);
}
void AD_Init(void){
Timer_Init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
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_ExternalTrigConvCmd(ADC1, ENABLE);//外部触发源使能
ADC_InitTypeDef ADC_InitStructure;
ADC_StructInit(&ADC_InitStructure);
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
//这里把外部触发源, 设置到TIM3的TRGO上
ADC_InitStructure.ADC_ExternalTrigConv= ADC_ExternalTrigConv_T3_TRGO;
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;
ADC_InitStructure.ADC_ScanConvMode=DISABLE;
ADC_InitStructure.ADC_NbrOfChannel=1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1)==SET);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1)==SET);
}
//这里不用软件触发了, 每次定时器更新, 就会自动进行AD转换
uint16_t AD_Get(void){
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);
return ADC_GetConversionValue(ADC1);
}
对于多路ADC, 没有DMA, 用中断搬实在小丑, 所以留到下节再给代码