STM32——ADC模数转换器

news/2024/6/3 19:27:43 标签: stm32, 单片机, 嵌入式硬件

文章目录

  • 一、ADC模数转化器
    • ADC简介
    • 逐次逼近型ADC
    • ADC框图
  • 二、ADC基本结构
  • 三、触发转换控制
  • 四、输入通道
  • 五、规则组的四种转换模式
    • 单次转换,非扫描模式
    • 连续转换,非扫描模式
    • 单次转换,扫描模式
    • 连续转换,扫描模式
  • 六、数据对齐
  • 七、转换时间(可编程的通道采样时间)
  • 八、校准
  • 九、硬件电路
  • 十、电压值在抖动的解决方案:
  • 十一、AD单通道
    • 电路设计
    • 关键代码
  • 十二、AD多通道
    • 电路设计
    • 关键代码

一、ADC模数转化器

ADC简介

  • ADC(Analog-Digital Converter)模拟-数字转换器
  • ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
  • ADC其实是一个电压表将引脚输入的高低电平间的任意电压进行量化,放在数据寄存器里面
  • 12位【分辨率】逐次逼近型ADC,1us转换时间【转化频率1MHZ】
  • 输入电压范围:0~ 3.3V,转换结果范围:0~4095【2^12-1】
  • 18个输入通道,可测量16个外部和2个内部信号源【内部温度传感器和参考电压VREFINT=1.2V】
  • 规则组和注入组两个转换单元
  • 模拟看门狗自动监测输入电压范围【应用于中断】
  • STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道
  • PWM也属于数字信号到模拟信号的桥梁,在某些方面优于DAC,没有功率损耗,DAC应用于信号发生器,音频解码,PWM用于控制电机速度

逐次逼近型ADC

通过ADC0809外挂芯片了解逐次逼近型ADC
在这里插入图片描述

  • 电压比较器,未知编码电压与已知编码电压比较,采用二分法比较,二分法选取的电压值恰好是二进制的位权,所以8位只要分8次就可以找到对应编码电压,8位的二级制最大值是255
  • 电压比较器比较未知编码电压与已知编码电压,大于时调小DAC的模拟电压,反之调大,DAC的输出数据就是未知编码电压
  • DAC参考电压决定ADC参考电压,因为两者相互比较,通常ADC的供电电压(Vcc)和DAC参考电压(VREF+)连接在一起
  • DAC内部是由加权电阻网络实现
  • EOC输出转换结束信号
  • CLOCK为ADC时钟,因为比较是需要时钟推动
  • START信号:触发转化的信号源,如定时器,外部中断等

ADC框图

在这里插入图片描述

  • 常规情况使用规则组,突发事件的注入组
  • 开启注入通道,可以同时接收4个通道的输入,经过逐次比较后输出到四个数据寄存器,不会有覆盖问题,而规则通道会有覆盖问题,只能存放一个通道的值在一个数据寄存器中【配合DMA实现,避免数据被覆盖】
  • VREF+和VREF-为参考电压,是进行逐次比较的已知编码电压
  • VDDA和VSSA是供电引脚,是内部模拟部分的电源,比如ADC,RC振荡器,锁相环等
  • ADCCLK:来自ADC预分频器,通过RCC时钟树可以知道最大是频率是14MHZ,只能选择6分频【12MHZ】或8分频【9MHZ】
    在这里插入图片描述
  • EOC是规则组的完成信号,JEOC是注入组的完成信号,通过读取状态标志位了解是否完成转换。可以使能中断功能,由NVIC管理外设ADC中断
  • ADC触发方式有软件触发和硬件触发,此处是硬件触发,包含定时器触发,外部中断引脚触发,规则组合注入组都不一样。
    • 定时器中断可以触发ADC、DAC转换,但是频繁进入中断会消耗资源,通过定时器的更新事件映射到TRGO输出,此时就是通过硬件自动触发ADC转换

二、ADC基本结构

在这里插入图片描述

关键函数

ADC_RegularChannelConfig()//模拟多路开关函数
ADC_SoftwareStartConvCmd()//软件触发函数,转换开启的函数
ADC_Cmd()//开关控制
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//ADCCLK函数在rcc.h文件里面
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//等待转换结果的完成,通过判断EOC标志位

ADC输入引脚必须配置为模拟输入
在这里插入图片描述

  • 在模拟输入模式下,会断开GPIO口,GPIO口输入的电压失效,防止对模拟输入造成影响

三、触发转换控制

规则组合注入组触发控制的方式:
在这里插入图片描述
规则组触发控制的方式

在这里插入图片描述

四、输入通道

stm32f103c8t6的10个输入通道
在这里插入图片描述

  • ADC1和ADC2通道的引脚相同,可以用于组成双ADC模式:配合组成同步模式,交叉模式(ADC1和ADC2交叉对一个通道进行采样,提高采样频率)

五、规则组的四种转换模式

单次转换,非扫描模式

在这里插入图片描述

连续转换,非扫描模式

在这里插入图片描述

单次转换,扫描模式

在这里插入图片描述

  • 扫描模式只有在列表里面所有的通道都转换完成才会发出EOC信号

连续转换,扫描模式

在这里插入图片描述

六、数据对齐

因为数据寄存器16位,而ADC寄存器是12位,所以涉及数据对齐

数据右对齐:

在这里插入图片描述

数据左对齐:
在这里插入图片描述

  • 数据左移四次,等效于乘以16
  • 左对齐的好处是当不需要12位的精度而只需要8位精度时,采用左对齐,然后只取出前8位即可

七、转换时间(可编程的通道采样时间)

AD转换的步骤:采样,保持,量化,编码

  • 采样保持:闭合采样开关,存储需要量化的电压
  • 量化编码:比较器比较电压

STM32 ADC的总转换时间为:

  • TCONV = 采样(保持)时间 + 12.5个ADC周期【ADCCLK】

  • 例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期

    • TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs
//采样保持时间在这个函数的最后一个参数里面设置
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);//配置通道选择器
//ADCCLK设置
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//ADCCLK函数在rcc.h文件里面
//参数
@arg RCC_PCLK2_Div6: ADC clock = PCLK2/6//6分频为12MHZ
@arg RCC_PCLK2_Div8: ADC clock = PCLK2/8//8分频为9MHZ

八、校准

  • ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差
  • 建议在每次上电后执行一次校准
  • 启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期

固定代码

ADC_ResetCalibration(ADC1);//校准寄存器
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//获取所选ADC重置校准寄存器状态。当校准完成会变为RESET,此时跳出循环
ADC_StartCalibration(ADC1);//启动选定的ADC校准过程。
while (ADC_GetCalibrationStatus(ADC1) == SET);//获取所选ADC校准状态。

九、硬件电路

在这里插入图片描述

  • 传感器输出电压电路输出引脚是AO
  • 电压转换电路中输入的电压范围为0-5V,通过串联两个电阻将电压转化为0-3.3V

十、电压值在抖动的解决方案:

当设定了阈值与电压值进行比较时会遇到抖动导致功能无法正常实现,可以采用如下方法:

  1. 采用迟滞比较方式:设置两个阈值,优于一个阈值的方式,在两个阈值中间抖动不会影响
  2. 采用滤波:均值滤波
  3. 裁剪分辨率

十一、AD单通道

电路设计

在这里插入图片描述

关键代码

AD.c

#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);//ADCCLK函数在rcc.h文件里面
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//ADC输入引脚必须配置为模拟输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//ADC12_IN0
	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_Mode = ADC_Mode_Independent;//独立通道or双通道
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//指定ADC数据对齐是左对齐还是右对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//触发方式,外部触发None表示采用软件触发
	//设置成单次转换非扫描
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//连续转换or单次转换
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;//扫描模式or非扫描模式
	ADC_InitStructure.ADC_NbrOfChannel = 1;//指定ADC通道数量,在扫描模式下才有
	ADC_Init(ADC1, &ADC_InitStructure);
	
	ADC_Cmd(ADC1, ENABLE);
	
	//校准固定代码
	ADC_ResetCalibration(ADC1);//校准寄存器
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);//获取所选ADC重置校准寄存器状态。当校准完成会变为RESET,此时跳出循环
	ADC_StartCalibration(ADC1);//启动选定的ADC校准过程。
	while (ADC_GetCalibrationStatus(ADC1) == SET);//获取所选ADC校准状态。
	
	//ADC_SoftwareStartConvCmd(ADC1, ENABLE);//当选择连续单通道时可以将触发函数写在初始化函数内部,在while循环里面就不会频繁触发。此时EOC标志位也可以不用判断是否置1,直接返回数据寄存器里面的ADC转换结果值即可
}

uint16_t AD_GetValue(void)
{
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);//启用或禁用所选ADC的软件触发
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//等待转换结果的完成,
	return ADC_GetConversionValue(ADC1);//返回常规通道的最后ADCx转换结果数据。读取DR寄存器会自动清除EOC标志位,不用手动清除
}

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();
	AD_Init();
	
	OLED_ShowString(1, 1, "ADValue:");
	OLED_ShowString(2, 1, "Volatge:0.00V");
	
	while (1)
	{
		ADValue = AD_GetValue();
		Voltage = (float)ADValue / 4095 * 3.3;
		
		OLED_ShowNum(1, 9, ADValue, 4);
		//showNum显示的是整数,通过计算获取小数部分
		OLED_ShowNum(2, 9, Voltage, 1);
		OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2);
		
		Delay_ms(100);
	}
}

十二、AD多通道

电路设计

在这里插入图片描述

关键代码

AD多通道采集一般采用扫描模式,在本案例中可以用单次转换非扫描模式,在每次触发转换之前,更改列表第一个序列的通道

AD.c

#include "stm32f10x.h"                  // Device header

void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, 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_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
		
	ADC_InitTypeDef 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_Cmd(ADC1, ENABLE);
	
	ADC_ResetCalibration(ADC1);
	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);//第三个参数表示序列1
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);//切换序列上的通道后在开启转换
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
	return ADC_GetConversionValue(ADC1);
}

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();
	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);
	}
}

参考视频:江科大自化协


http://www.niftyadmin.cn/n/25440.html

相关文章

A1038 Recover the Smallest Number (30)

Powered by:NEFU AB-IN Link 文章目录A1038 Recover the Smallest Number (30)题意思路代码A1038 Recover the Smallest Number (30) 题意 Given a collection of number segments, you are supposed to recover the smallest number from them. For example, given { 32, 321…

综合项目 旅游网【2. 优化servlet】没有指定的js文件读不到文件 错误

优化servlet目的减少Servlet的数量,现在是一个功能一个Servlet,将其优化为一个模块一个Servlet,相当于在数据库中一张表对应一个Servlet,在Servlet中提供不同的方法,完成用户的请求。如何解决测试时控制台中文乱码&…

字符串、列表、元组、字典的常用方法

字符串常用方法: 函数作用endswith/startswith是否以**开始/结束,返回true或falsefind若找到,返回在字符串中的下标,若没找到,返回-1capitalize把首字母大写isalnum判断是否是字母和数字isalpha判断是否是字母isdigit…

1. 【prometheus 学习】架构Architecture

prometheus是开源的系统监控及告警系统,很多企业、互联网公司应用prometheus,搭配可视化的grafana,实现对系统的全面度量。 prometheus应用的场景: 1)对于数据准确率要求不高,可以粗略反映监控数据走势的场…

成功拿下百度、蔚来测开offer,他为什么这么优秀(附面试经验分享)

大家好,我是洋子 想进大厂工作相信是很多同学的梦想,测开岗技术要求相对较低,进大厂也相对容易 但随着2022年裁员不断,测开岗招聘要求也有所变难,2023年已经来到,我们能做的就是提前做规划准备,多学知识,增加自己拿到offer的概率 这篇文章的主人公是一名24届的同学,…

【Java编程进阶】Java抽象类与接口详解

推荐学习专栏:Java 编程进阶之路【从入门到精通】,从入门到就业精通,买不了吃亏,买不了上当!! 文章目录1. 抽象类2.接口3. 抽象类和接口对比4. 总结Java基础教程系列文章1. 抽象类 前面说到,Ja…

04_机器学习相关笔记——特征工程(4)

04_机器学习相关笔记——特征工程(4) 主体模型,用于从文本库中发现有代表性的主题,得到每个主题上面词的分布特性,并且能够计算出每篇文章的主题分布。词嵌入,是一类经词向量化的模型统称,核心…

ArcGIS Pro脚本工具(16)——要素类转txt坐标文件

之前介绍过txt坐标文件如何转为GIS要素类 ArcGIS Pro脚本工具(8)——txt坐标文件转shp_学学GIS的博客-CSDN博客_txt转shp国土部门给过来的数据经常需要转换,比如土地报批和高标准农田的数据经常给一个txt文件过来,不能直接在GIS软…