wechat:嵌入式工程师成长日记
具体功能实现
打开开关后,OLED上显示模拟值(0-4095)和电压值(0-3.3),通过调节电位器,电压值和模拟值都在不断发生变化。与此同时模拟值和电压值是映射关系,一一对应。
STM32F103RCT6原理图
器件
STM32F103RCT6,OLED屏,若干杜邦线 ,电位器,小型螺丝刀(用来旋转电位器),面包板
电位器接线图:(以STM32F103C8T6为例)
前后展示图
刚启动时:
旋转电位器后
知识介绍
ADC介绍
ADC可以将引脚上连续变化的模拟电压转化为数字量,12位ADC是一种逐次逼近型模拟数字转换器,转换时间为1us。其多达18个通道,可测量16个外部和2个内部信号源。
各通道AD转换可以单次,连续,扫描或间断模式执行。最后的数据存储在16为数据寄存器中。
输入电压范围:0-3.3V
转换结果范围:0-4095
转换单元:规则组和注入组
ADC资源:ADC1,ADC2,10个外部输入通道
模拟看门狗
可以自动检测模拟电压范围,可以检测指定通道,当AD值高于设定的上阈值和下阈值时,会自动申请中断,然后交给用户进行处理(多次中断对于系统的程序有一定影响)
作用:对外设信号起监控作用。
比如在做信号处理的时候,对于一些快速的处理和保护,最常规的方法是直接在采集中断中比较设定的最大值和最小值
数据对齐
数据可以左对齐或者是右对齐。对于规则组通道,不需要减去偏移值,因此只有12位有效数据,但是数据寄存器是16位。(一般采用右对齐)
左移一次相当于乘以2
数据校准
ADC有个内置的自校准模式,校准可以大幅减少因内部电容器组的变化而造成的准精度误差,校准期间,每个电容器中都会计算一个误差修正码,用于消除随后的转化中每个电容器产生的误差
建议在每一次上电后都执行一次校准
启动校准前,ADC必须处于关电状态至少超过两个ADC时钟周期
外围电路设计
第一个是利用滑动变阻器,当滑动变阻器往下的时候,电压减小;往上滑时,电压增大。
第二个是利用传感器模块,可以等效为可变电阻,利用串联分压来反映电压变化情况
对应电位器接线图:电位器中间引脚接PA0,左右引脚分别接地和接正极
AD转化
转化步骤:采样,保持,量化,编码
总转化时间:TCONV = 采样时间+12.5个ADC周期
ADC周期是RCC六分频来的14Mhz
最快转化时间:当ADCCLK=14Mhz时,采样时间为1.5个ADC周期
TCONV = 1.5+12.5=14周期=1us
规则组的四种模式
①单次转换非扫描模式:
下图是ADC对通道2进行模数转换,一段时间转换完成后,结果会放在数据寄存器中,同时EOC标志位置1,最后在数据寄存器中读取结果。
(EOC:转换结束位,该位在通道组转换结束时设置,由软件清楚或由读取ADC_DR时清除。
其中0:转换未完成;1:转换完成)
②连续转换非扫描:
在一次转换后不会停止,而是立刻开始下一轮的转换,一直持续下去,直接从数据寄存器中读AD值
优点:开始转换时无需等待,因为一直在转换,无需手动转换。
③单次转换扫描:
扫描模式用到序列列表,每个通道可以任意指定,并且可以重复。但是规则组单次只能传输一个通道的数据,所以为了防止数据被覆盖,需要用DMA对数据进行转运,当通道全部转换完成后,产生EOC信号。
④连续转换扫描:
和非连续扫描模式同理,但是一次转换完成后立刻开始下一轮转换
主函数代码(C语言)KEIL5实现
//main.c
#include "stm32f10x.h"
#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);
OLED_ShowNum(2,9,Voltage,1);
OLED_ShowNum(2,11,(unsigned int)(Voltage*100) % 100,2);
Delay_ms(100);
}
}
//AD.c
#include "stm32f10x.h"
void AD_Init(void)
{
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_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_NbrOfChannel=1;
ADC_InitStructure.ADC_ScanConvMode=DISABLE;
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()
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);
return ADC_GetConversionValue(ADC1);
}
问题解答
Q:
为什么在配置ADCCLKConfig要选择六分频
A:
下图是RCC的时钟树,上面写着ADCCLK最大是14Mhz,其中stm32f10x系列的最大时钟频率是72Mhz,也就是最大只能是6分频,72/6=12Mhz,所以虽然始终书上说最大是14Mhz,但是最大可以达到的却只有12Mhz。
Q:
如何在上电时执行一次ADC校准?
A:
代码如下:
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1)==SET);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1)==SET);
首先进行复位校准,然后获取复位校准状态,然后ADC开始转化,最后获取ADC转化状态