蓝桥嵌入式之 ADC电压采集与显示

工程代码可见Github<传送门>


1个留待补充的程序:


“为了确保不产生丢失数据的情况,开启 DMA 传输模式,将数据传输到内存中”尝试编程

一、ADC入门

1、基础知识

模数转换器(Analog To Digital Converter)简称 ADC(也可以写成 A/D),是指将连续变化的模拟信号转换为离散的数字信号的器件。 ADC 分为积分型、逐次逼近型、并行/串行比较型、Σ-Δ型等多种类型,STM32F103 自带的 ADC 属于逐次逼近型。

逐次逼近型 ADC 与天平称物重非常相似,从高位到低位逐位比较。首先从最重的砝码开始试放,与被称物体行进比较,若物体重于砝码,则该砝码保留,否则移去,然后用次重砝码继续比较,照此一直到最小一个砝码为止,将所有留下的砝码重量相加,就得此物体的重量。逐次逼近型 A/D 转换器,就是将输入模拟信号与不同的参考电压作多次比较,使转换所得的数字量在数值上逐次逼近输入模拟量对应值。


2、STM32F103RBT6的ADC时钟及转换时间

蓝桥板载STM32F103RBT6拥有2路12位(0~4096)ADC,ADC挂载在APB2总线上,且ADC最大时钟不超过14MHz。所以当APB2总线设置为72M,有必要对其分频再用于ADC,分频用到的库函数是:void ADC_ADCCLKConfig(u32 RCC_ADCCLKSource);

ADC的转换时间 = 采样时间 + 12.5周期

采样时间和实际电路有着莫大的关系,但是对于蓝桥的板子而言,对于转换速度没有太大要求我们一般设置为:ADC_SampleTime_239Cycles5239.5个周期即可。

3、ADC输入通道
通道 ADC1 ADC2
通道0 PA0 PA0
通道1 PA1 PA1
通道2 PA2 PA2
通道3 PA3 PA3
通道4 PA4 PA4
通道5 PA5 PA5
通道6 PA6 PA6
通道7 PA7 PA7
通道8 PB0 PB0
通道9 PB1 PB1
通道10 PC0 PC0
通道11 PC1 PC1
通道12 PC2 PC2
通道13 PC3 PC3
通道14 PC4 VSS
通道15 PC5 VSS
通道16 内部温度传感器 VSS
通道17 参考电压Vref VSS

注意到了两个ADC模块,共用了一些通道,不同ADC应用不同通道时,可以同时进行采样和转换,但不可以对相同通道同时采样。

板子对应ADC部分原理图
蓝桥嵌入式之 ADC电压采集与显示_第1张图片

PB0管脚可复用为ADC_IN8
在这里插入图片描述

ADC1和ADC2两个通道的都欧克,所以本博客选用了ADC1。

4、ADC的注入组和规则组

在使用 ADC 外部通道时,可以设定为规则组和注入组。规则组就是设定好转换顺序后,按照规则正常转换;注入组类似于中断,可以插队,当触发信号触发注入组通道时,优先转换注入组,转换完后再继续转换规则组;如果正在转换规则通道期间,注入通道被触发,当前规则组转换被复位,注入通道序列被以单次扫描方式转换,完成转换后恢复上次被中断的规则通道转换。规则组最多可以使用 16 个通道,注入组最多可以使用 4 个通道。

蓝桥板子也没有用到多么复杂,我们就直接使用规则组就好了。

5、ADC的单通道和多通道

当“转换组”只有一个通道转换时称之为单通道模式,当有多个通道按顺序转换时称之为多通道模式或者扫描模式。

蓝桥板子,使用单通道即可。

6、ADC的单次转换和连续转换

当规则组或注入组的通道按照设定顺序执行一次采转换后即停止工作,这种模式称之为单次转换模式;如果执行完一次转换后,ADC 没有停止,而是立即启动新一轮转换,这种模式称之为连续转换模式。

蓝桥板子,使用单次转换即可。

7、触发源

触发启动转换:触发启动转换分为两种方式,分别是软件触发和外部事件触发(外部是相对于 ADC 外设来讲),其中外部事件触发又分为定时器触发和外部触发(这里的外部指的是芯片外部信号)。

蓝桥板子,使用软件触发即可

8、转换后的数据储存

①、规则组存储

在独立 ADC 模式下,规则组通道转换完成后,转换后的数据被存储在对应的 ADC 规则数据寄存器 ADC_DR 的低 16 位。

双 ADC 模式是 ADC1 和 ADC2 同时采集某些参数,比如要获取瞬时功率需要同时采集电压和电流参数才能准确计算结果,这种场合就必须使用双 ADC 模式。这种模式下 ADC1所对应的 ADC_DR 的高 16 位存储 ADC2 的规则数据,低 16 位存储 ADC1 的规则数据。

由于 ADC 的精度是 12 位,因此在将 ADC 的 12 位数据存入 16 位的数据寄存器中时,可以通过 ADC_CR2 寄存器的 ALIGN 位来选择数据是左对齐还是右对齐。

规则通道最多有 16 个通道,但规则数据寄存器只有一个,因此当使用多通道转换时,前一个转换的通道数据,会被后一个通道转换的数据覆盖掉,因此理论上必须在后一个通道转换完毕之前就把数据取走

为了确保不产生丢失数据的情况,开启 DMA 传输模式,将数据传输到内存中是一个好办法,这个到时候作为扩展例程写。

②、注入组存储

ADC 注入组最多有 4 个通道,每个通道都有对应的数据寄存器 ADC_JDRx(x=1…4),ADC_JDRx 是 32 位的,高 16 位保留,低 16 位用来保存数据,选择数据是左对齐还是右对齐都由 ALIGN 决定。

9、中断处理

如果打开相应的中断,有三种情况可以进入中断。
(1)规则通道转换完成中断

  1. 转换数据被存储在 16 位的 ADC_DR 寄存器中。
  2. EOC(转换结束)标志被置位。
  3. 如果设置了 EOCIE 位,则产生中断。

(2)注入通道转换完成中断

  1. 转换数据被存储在 16 位的 ADC_DRJx 寄存器中。
  2. JEOC(注入转换结束)标志被置位。
  3. 如果设置了 JEOCIE 位,则产生中断。

(3)模拟看门狗中断
如果开启了模拟看门狗中断,并且设置ADC低阈值ADC_LTR和高阈值ADC_HTR,
当采集到的电压高于高阈值或者低于低阈值时,就会产生模拟看门狗中断。

关于ADC中断这部分,这里也没有具体涉及到,等日后做题做到了再补充吧。目前也只用到了查询的方式。

10、ADC校准

ADC 有一个内置自校准模式,可以大幅减小由于内部电容的变化而造成的精准度误差。通过设置 ADC_CR2 寄存器的 CAL 位启动校准,一旦校准结束,CAL 位被硬件复位。建议在每次上电时执行一次 ADC 校准,启动校准前,ADC 必须处于上电状态(ADON=‘1’)(言外之意必须先使能ADC),至少超过两个 ADC 时钟周期。

校准代码是固定的,参考下面的代码即可。

11、 使能通道触发转换

规则组和注入组触发方式方式分为软件触发和外部触发,其中外部触发包括定时器触发和外部信号触发。如果设置了规则组或注入组触发方式,还需要使能相应触发,保证在触发到来时启动转换。

规则组软件触发相应函数为void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

12、转换结束标志位

看手册介绍
在这里插入图片描述
可以由软件清除或者读取数据寄存器清除!

13、总结配置流程

1、初始化 ADC1 通道引脚(内部温度传感器和参照电压通道不需要进行引脚初始化)
2、配置 ADC1 中断优先级(如果使用 ADC 中断,本节未使用)
3、使能 ADC1 外设时钟
4、设置 ADC1 预分频系数
5、复位 ADC1
6、配置 ADC1 初始化结构体
7、设置规则组转换顺序和采样时间
8、使能 ADC1 中断(本节未使用)
9、使能 ADC1 外设
10、校准 ADC1
11、使能 ADC1 软件触发或外部触发转换
12、编写中断服务函数(如果使用 ADC 中断,本节未使用)

二、主要代码

main.c

/*******************************************************************************
* 文件名:main.c
* 描  述:
* 作  者:CLAY
* 版本号:v1.0.0
* 日  期: 2019年1月27日
* 备  注:ADC转换实现电压测量
*         
*******************************************************************************
*/

#include "config.h"
#include "led.h"
#include "key.h"
#include "timer.h"
#include "beep.h"
#include "lcd.h"
#include "stdio.h"
#include "rtc.h"
#include "adc.h"

u8 ADC_Flag = 0;
u8 string[20];
float adc_val;

int main(void)
{

	STM3210B_LCD_Init();
	LCD_Clear(Blue);
	LCD_SetBackColor(Blue);
	LCD_SetTextColor(White);
	
	LEDInit();
	KeyInit();
	BeepInit();
	TIM2Init(2000, 72);//定时2ms
	
	ADC1Init();
	
	while(1)
	{	
		KeyDriver();
		if(ADC_Flag )
		{
			ADC_Flag = 0;
			adc_val = Get_ADC(8) * 3.3 / 4096;
			sprintf((char*)string,"ADC_VAL : %.2f    ",adc_val);
			LCD_DisplayStringLine(Line2, string);
		
		}
	}
}

void KeyAction(int code)
{
	if(code == 1)//按下B1,切换灯状态,蜂鸣器鸣叫0.1s
	{
		GPIOC->ODR ^= (1<<8);//PC8不断取反
		GPIOD->ODR |= (1<<2);//PD2置1,使能573锁存器
		GPIOD->ODR &= ~(1<<2);//PD2清0,关闭573锁存器
		Beep(100);
	}
	else if(code == 2)
	{
		Beep(-1);
	}
	else if(code == 3)
	{
		Beep(0);
	}
	else if(code == 4)
	{
		
	}
}



adc.c

#include "adc.h"

void ADC1_IOInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能PB口时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//选中PB0引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化IO
}

void ADC_Cali(void)
{
	ADC_ResetCalibration(ADC1);//使能复位校准
	while(ADC_GetResetCalibrationStatus( ADC1));//等待复位校准结束
	ADC_StartCalibration( ADC1);//开启AD校准
	while(ADC_GetCalibrationStatus(ADC1));//等待校准结束
}

void ADC1Init(void)
{
	ADC_InitTypeDef ADC_InitStructure;
	
	ADC1_IOInit();//ADC的GPIO配置
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//使能ADC时钟,APB2总线
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//ADC最大时钟不超过14M,这里尽心了对APB2时钟进行6分频=12M
	ADC_DeInit(ADC1);//复位ADC
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC独立模式
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;//通道模式选择单通道
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//转换模式选择单次转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//规则组触发转换方式选择转换由软件触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据格式选择右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 1;//通道数目为1
	ADC_Init(ADC1,&ADC_InitStructure);//初始化结构体
	
	ADC_Cmd(ADC1, ENABLE);//使能ADC
	ADC_Cali();//ADC校准
}

u16 Get_ADC(u8 channel)
{
	u16 temp;
	
	ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_239Cycles5);//ADC采样时间239.5周期 总转换时间=采样时间+12.5周期
	ADC_SoftwareStartConvCmd( ADC1,ENABLE);//使能ADC1软件触发转换,触发一次ADC转换
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == 0);//等待ADC转换完毕
	temp = ADC_GetConversionValue( ADC1);//获取转换结果,注意此时已经自动清除EOC位
	ADC_SoftwareStartConvCmd( ADC1,DISABLE);//失能ADC转换
	
	return temp;
}




adc.h

#ifndef _ADC_H
#define _ADC_H


#include "config.h"

void ADC1Init(void);
u16 Get_ADC(u8 channel);

#endif


stm32f10x_it.c

extern u8 ADC_Flag;

void TIM2_IRQHandler(void)
{
	static u16 tmr500ms = 0;
	
	if(TIM_GetITStatus(TIM2, TIM_FLAG_Update))
	{
		TIM_ClearITPendingBit(TIM2, TIM_FLAG_Update);
		
		tmr500ms++;
		
		KeyScan();
		BeepScan(2);//2ms扫描
		if(tmr500ms >= 250)
		{
			tmr500ms = 0;
			ADC_Flag = 1;
		}
	}
}

三、注意事项

定时器设定500ms读取一次AD值

你可能感兴趣的:(蓝桥杯之嵌入式)