工程代码可见Github<传送门>
1个留待补充的程序:
“为了确保不产生丢失数据的情况,开启 DMA 传输模式,将数据传输到内存中”尝试编程
模数转换器(Analog To Digital Converter)简称 ADC(也可以写成 A/D),是指将连续变化的模拟信号转换为离散的数字信号的器件。 ADC 分为积分型、逐次逼近型、并行/串行比较型、Σ-Δ型等多种类型,STM32F103 自带的 ADC 属于逐次逼近型。
逐次逼近型 ADC 与天平称物重非常相似,从高位到低位逐位比较。首先从最重的砝码开始试放,与被称物体行进比较,若物体重于砝码,则该砝码保留,否则移去,然后用次重砝码继续比较,照此一直到最小一个砝码为止,将所有留下的砝码重量相加,就得此物体的重量。逐次逼近型 A/D 转换器,就是将输入模拟信号与不同的参考电压作多次比较,使转换所得的数字量在数值上逐次逼近输入模拟量对应值。
蓝桥板载STM32F103RBT6拥有2路12位(0~4096)ADC,ADC挂载在APB2总线上,且ADC最大时钟不超过14MHz。所以当APB2总线设置为72M,有必要对其分频再用于ADC,分频用到的库函数是:void ADC_ADCCLKConfig(u32 RCC_ADCCLKSource);
ADC的转换时间 = 采样时间 + 12.5周期
采样时间和实际电路有着莫大的关系,但是对于蓝桥的板子而言,对于转换速度没有太大要求我们一般设置为:ADC_SampleTime_239Cycles5
239.5个周期即可。
通道 | 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应用不同通道时,可以同时进行采样和转换,但不可以对相同通道同时采样。
PB0管脚可复用为ADC_IN8
ADC1和ADC2两个通道的都欧克,所以本博客选用了ADC1。
在使用 ADC 外部通道时,可以设定为规则组和注入组。规则组就是设定好转换顺序后,按照规则正常转换;注入组类似于中断,可以插队,当触发信号触发注入组通道时,优先转换注入组,转换完后再继续转换规则组;如果正在转换规则通道期间,注入通道被触发,当前规则组转换被复位,注入通道序列被以单次扫描方式转换,完成转换后恢复上次被中断的规则通道转换。规则组最多可以使用 16 个通道,注入组最多可以使用 4 个通道。
蓝桥板子也没有用到多么复杂,我们就直接使用规则组就好了。
当“转换组”只有一个通道转换时称之为单通道模式,当有多个通道按顺序转换时称之为多通道模式或者扫描模式。
蓝桥板子,使用单通道即可。
当规则组或注入组的通道按照设定顺序执行一次采转换后即停止工作,这种模式称之为单次转换模式;如果执行完一次转换后,ADC 没有停止,而是立即启动新一轮转换,这种模式称之为连续转换模式。
蓝桥板子,使用单次转换即可。
触发启动转换:触发启动转换分为两种方式,分别是软件触发和外部事件触发(外部是相对于 ADC 外设来讲),其中外部事件触发又分为定时器触发和外部触发(这里的外部指的是芯片外部信号)。
蓝桥板子,使用软件触发即可
①、规则组存储
在独立 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 决定。
如果打开相应的中断,有三种情况可以进入中断。
(1)规则通道转换完成中断
(2)注入通道转换完成中断
(3)模拟看门狗中断
如果开启了模拟看门狗中断,并且设置ADC低阈值ADC_LTR和高阈值ADC_HTR,
当采集到的电压高于高阈值或者低于低阈值时,就会产生模拟看门狗中断。
关于ADC中断这部分,这里也没有具体涉及到,等日后做题做到了再补充吧。目前也只用到了查询的方式。
ADC 有一个内置自校准模式,可以大幅减小由于内部电容的变化而造成的精准度误差。通过设置 ADC_CR2 寄存器的 CAL 位启动校准,一旦校准结束,CAL 位被硬件复位。建议在每次上电时执行一次 ADC 校准,启动校准前,ADC 必须处于上电状态(ADON=‘1’)(言外之意必须先使能ADC),至少超过两个 ADC 时钟周期。
校准代码是固定的,参考下面的代码即可。
规则组和注入组触发方式方式分为软件触发和外部触发,其中外部触发包括定时器触发和外部信号触发。如果设置了规则组或注入组触发方式,还需要使能相应触发,保证在触发到来时启动转换。
规则组软件触发相应函数为void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
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值