0x08 ADC

本文的大部分内容来自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进行讲解

image-20231101191706721

  • 模拟信号输入 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自动搬运走,再接着采集

image-20231101194101754

ADC的时钟 来源于APB2, 可以选择2 4 6 8分频, 不过尴尬的是, 最大只能支持14MHz, 于是2 4分频都超过了14 只能用6 和 8了,对应 12MHz ,9MHz

image-20231101200652717

开始采集的信号触发信号源(同0809的START): 可以软件开始,也可以配置硬件触发源, 如果要用定时器每1ms采集一次, 如果中断里开始, 会浪费硬件资源, 一般都会配置TRGO(定时器主模式)让他自己开始

image-20231101194750795

四、注意事项

  1. 校准

    ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差

    建议在每次上电后执行一次校准, 不理解没关系, 代码是固定的

  2. 数据对齐

    由于32中的外设寄存器是16位的, 但这是个12位的ADC, 数据对齐就很重要, 一般都是右对齐, 取出来再计算转换, 但如果用的8位模式, 左对齐直接取出高八位, 就直接是数据

    image-20231101195435257

  3. 转换模式

    在规则组这个菜单上,

    单次/多次转换 决定ADC是转换完就停止 还是一直转换下去

    扫描/非扫描模式 决定ADC是只转换序列1 还是扫描转换完所有序列

    于是延伸出, 以下四种转换模式

    单次转换, 非扫描模式

    image-20231101195930209

    多次转换, 非扫描模式

image-20231101200250409

单次转换, 扫描模式

image-20231101200405409

多次转换, 扫描模式

image-20231101200434625

五、常用配置代码

手动单次转换代码:

#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, 用中断搬实在小丑, 所以留到下节再给代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值