(一)AD转换
AD转换是指由模拟信号转换为数字信号,在单片机中集成有ADC芯片,其采用12位逐次比较法来确定数字输出,所以其转换结果为12为二进制,其转换范围为0~2^12 ,其有多种转换方式,这里使用的是单次转换、非扫描模式,这个模式在开启后转换一个通道口且只转换一次,类似的还有单次转换、扫描模式模式,这样就只转换一个ADC通道口,且不断读取数值转换,还有连续转换、扫描模式,这样就会一次转换多个通道口,且不停地循环;同时我们还可以配置规则组和注入组,通常规则组适合常规周期性的数据采集,而注入组则用于应对特殊情况下紧急的数据采集需求;
AD转换可以将指定的端口输入的模拟信号转化为数字信号,其流程为
(二)使用ADC实现对电位器电压的测量
(1)流程
通过流程图,我们知道要完成模数转化,需要完成(1)RCC时钟开启;(2)GPIO对应初始化;(3)设置转换通道;(4)配置ADC模块;(5)打开ADC,校准后工作
(2)ADC模块函数
ADC的所有函数的声明都在以adc.h文件中,可以打开文件查看
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
//通过一个结构体来初始化ADC模块
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
//使能ADC,在配置好ADC后要使用该函数开启其才能工作
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
//配置ADC的中断
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
//重启校准
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
//获取重启校准标志位,未完成为1,完成为0
void ADC_StartCalibration(ADC_TypeDef* ADCx);
//开始校准
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
//获取校准标志位,未完成为1,完成为0
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
//使用软件开始转换
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);
//获取软件开始转换标志位,开始时为1,即刻为0,不能使用这个函数判断转换是否完成
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
//配置规则组通道,规则组最多可以有16个通道
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
//读取转换后的寄存器的值
uint32_t ADC_GetDualModeConversionValue(void);
//读取双通道转换后寄存器的值
void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);
uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);
//注入组的函数
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);
//配置看门狗的函数,看门狗用于配置阈值
void ADC_TempSensorVrefintCmd(FunctionalState NewState);
//配置芯片内置温度传感器的使能
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);
//分别为获取转换完成标志位和清除该标志位
(3)ADC模块代码
这里我们使用PA1口,对应的是
(1)RCC时钟开启
我们使用AD转换器,自然要打开ADC的时钟,还有其对应的输入端口的时钟,和以往不同的是,AD转换器内部使用逐次比较的时候还需要配置一个时钟分频用以逐次比较,其时钟最大频率为14MHz,我们的AD转换时钟为标准72MHz,我们可以选择6分频(12MHz)或8分频(9MHz),这里使用6分频
void adc_rcc_init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //max_freq=14MHz
}
(2)配置对应的模拟输入端口
这里我们使用ADC的1号输入口,其与PA1复用,我们要打开PA1并让其模式为模拟输入模式,我们也要把模拟信号的输入端接在PA1口上
void adc_gpio_init()
{
GPIO_InitTypeDef gpio_init;
gpio_init.GPIO_Mode = GPIO_Mode_AIN;
gpio_init.GPIO_Pin = GPIO_Pin_1;
gpio_init.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &gpio_init);
}
(3)配置ADC通道
上面配置的是PA1口,这里的通道对应选择的是1号通道,可以跳转到定义查看通道配置函数
第一个参数选择的是ADC,这里我们选择ADC1,第二个参数选择通道,这里我们选择通道1,第三个参数选择的是这个通道在常规组中的位置,常规组可以有16个位置,这里我们使用的是单次转换,只有一个通道,放在1号位置中,最后一个参数选择的是转换时间,分别为几个计数周期,这里我们没有什么时间要求,随便选一个
void adc_channel_init()
{
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5);
}
(4)ADC模块初始化
这里可以看一下初始化结构体
第一个参数是选择单通道还是双通道,我们这里选择的是单通道,因此我们选择独立模式;
第二个参数选择的是扫描模式,选择ENABLE则为扫描模式,DISABLE为非扫描模式,我们选择的为非扫描模式;
第三个参数是外部触发源,这里我们选择软件触发,因此我们选择没有外部触发;
第四个参数为选择数据向右对其还是向左对齐,向左对齐主要实现舍弃地位的功能,我们选择直接读取数据,选择向右对齐;
最后一个参数选择的是总共有多少个要转换的通道,主要用于扫描模式,这里只有一个输入且不用扫描模式,因此给1即可;
通过查看参数的配置即可配置ADC的初始化
void adc_adc_init()
{
ADC_InitTypeDef adc_init;
adc_init.ADC_Mode = ADC_Mode_Independent;
adc_init.ADC_ScanConvMode = DISABLE;
adc_init.ADC_ContinuousConvMode = DISABLE;
adc_init.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
adc_init.ADC_DataAlign = ADC_DataAlign_Right;
adc_init.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &adc_init);
}
(5)打开ADC
和定时器一样,我们配置好后要使能ADC
void adc_open()
{
ADC_Cmd(ADC1, ENABLE);
}
(6)ADC校准
我们在使用ADC中最好要先对其校准,以提高转换精度,我们的校准在打开ADC后进行,我们只需要重启校准,在重启校准动作完成后启动校准,等待校准完成即可
void adc_check()
{
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1) == SET);
}
(7)读取转换值
由于我们采用的是单次转换、非扫描模式和软件触发,因此每次要进行AD转换的时候都要用软件来启动转化,等待转换完毕之后读取转换值,按照之前外部中断的套路,在标志位置一之后都是要手动清零的,但是这里不用,因为我们在调用读取转换值的时候已经自动帮我们完成了清零
unsigned int adc_get_value()
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1);
}
这里在转换没有完成的时候标志位为0(RESET),在转换完成后标志位为1(SET),我们使用一个while循环来等待转换的完成
(8)封装与声明
接着我们可以把所有的初始化函数封装在一个函数中供外部调用,并且把初始化函数和读取转换值的函数在头文件声明
void adc_init()
{
adc_rcc_init();
adc_gpio_init();
adc_channel_init();
adc_adc_init();
adc_open();
adc_check();
}
头文件声明
#ifndef __ADC_H__
#define __ADC_H__
void adc_init(void);
unsigned int adc_get_value(void);
#endif
(9)主函数调用
我们调用读取转换值的函数就可以读取到模拟输入量到数字量的转化值了,如果我们还要读取电压值,那也很简单,因为电位器就是一个滑动电阻器,它的电压和转换值成正比,我们转换值的最大值为2^12,电压的最大值为3.3V,我们假设获取的转换值为val,我们只要把val除以2^12再乘以3.3,就可以得到电压值
主函数可以这样调用
#include "stm32f10x.h" // Device header
#include "adc.h"
#include "OLED.h"
#include "Delay.h"
int main()
{
unsigned int value;
float volts;
OLED_Init();
adc_init();
OLED_ShowString(1, 1, "org:");
OLED_ShowString(2, 1, "volts:0.00");
while(1)
{
value = adc_get_value();
OLED_ShowNum(1, 5, value, 5);
volts = 3.3 * value / 4095.0;
OLED_ShowNum(2, 7, volts, 1);
OLED_ShowNum(2, 9, (int)(volts*100)%100, 2);
Delay_ms(200);
}
return 0;
}
这里由于提供的OLED函数中没有显示浮点数的函数,可以把浮点数转化为两个整数来进行显示,一个是整数部分,一个是小数部分,小数部分可以用乘以10^n后再除以10^n的方法提取小数点后n位数
(三)总结
通过一个读取电位器电压的程序,我们了解到了AD转换的流程和相关的代码配置,了解了ADC中单次非连续模式的使用