一、什么是ADC
ADC(Analogto-Digital Converter)模拟数字转换器,是将模拟信号转换成数字信号的一种外设。比如某一个电阻两端的是一个模拟信号,单片机无法直接采集,此时需要ADC先将短租两端的电压这个模拟信号转化成数字信号,单片机才能够进行处理。
二、ADC的用途
ADC具有将模拟信号转换成数字信号的能力,比如将模拟的电压转换成数字信号,单片机进行处理。可以用作温度监测或者电流监测等方面,用途极广。
三、STM32F103ZET6的ADC
根据中文参考手册介绍,STM32F103ZET6单片机有3个12位ADC,共有18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。ADC的输入时钟不得超过14MHz,它是由PCLK2经分频产生。
3.1 ADC通道对应引脚
STM32F103ZET6的ADC各通道对应IO如下
3.2ADC时钟
ADC输入时钟ADC_CLK由APB2分频产生,最大值是14MHz。库函数提供了设置分频因子的函数
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2)
可选的分频因子有
#define RCC_PCLK2_Div2 ((uint32_t)0x00000000)
#define RCC_PCLK2_Div4 ((uint32_t)0x00004000)
#define RCC_PCLK2_Div6 ((uint32_t)0x00008000)
#define RCC_PCLK2_Div8 ((uint32_t)0x0000C000)
APB2总线时钟为72MHz,而ADC的最大工作频率为14MHz,所以,分频因子一般设置为6,这样ADC的输入时钟频率为12MHz。
3.3 ADC工作模式
根据中文参考手册介绍,STM32F1的ADC有三种工作模式
• 单次转换模式 单次转换模式下,ADC只执行一次转换。该模式既可通过设置ADC_CR2寄存器的ADON位(只适用于规则通道)启动也可通过外部触发启动(适用于规则通道或注入通道),这时CONT位为0。
• 连续转换模式 在连续转换模式中,当前面ADC转换一结束马上就启动另一次转换。此模式可通过外部触发启动或通过设置ADC_CR2寄存器上的ADON位启动,此时CONT位是1。
• 扫描模式
33.4 ADC转换时间
ADC的总转换时间与时钟频率有关,总转换时间 = 采样时间 + 12.5个周期。其中,采样时间最短为1.5个周期,也就是最短转换时间为14个时钟周期。使用软件触发时,可选择的采样时间如下
#define ADC_SampleTime_1Cycles5 ((uint8_t)0x00)
#define ADC_SampleTime_7Cycles5 ((uint8_t)0x01)
#define ADC_SampleTime_13Cycles5 ((uint8_t)0x02)
#define ADC_SampleTime_28Cycles5 ((uint8_t)0x03)
#define ADC_SampleTime_41Cycles5 ((uint8_t)0x04)
#define ADC_SampleTime_55Cycles5 ((uint8_t)0x05)
#define ADC_SampleTime_71Cycles5 ((uint8_t)0x06)
#define ADC_SampleTime_239Cycles5 ((uint8_t)0x07)
3.5 ADC校准
使能ADC后,需要对ADC进行校准。使用库函数开发时,提供了ADC校准的函数
ADC_ResetCalibration(ADC1);//重置指定的ADC的校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1));//获取ADC重置校准寄存器的状态
ADC_StartCalibration(ADC1);//开始指定ADC的校准状态
while(ADC_GetCalibrationStatus(ADC1));//获取指定ADC的校准程序
3.6 ADC转换结果与实际电压的换算
获取到的AD转换结果并不是实际电压,如果想要得到实际电压,需要经过换算。上面介绍了,STM32的ADC为12位,也就是AD值取值范围为0~4095。采集电压范围为0到3.3V。AD值与实际电压之间存在比例关系。
实际电压 = (AD值 / 4095) * 3.3。单位为伏特(V)
四、ADC配置步骤
• 使能GPIO时钟和ADC时钟,设置引脚为模拟输入
• 设置ADC的分频因子
• 初始化ADC参数,包括ADC工作模式,规则序列等
• 使能ADC并校准
• 触发AD转换,读取AD转换值
五、ADC配置程序
55.1 ADC初始化程序
这里以配置ADC1的通道1为例,给出ADC的配置例程,分频因子设置为6,单次转换模式,软件触发。
/*
*==============================================================================
*函数名称:ADC1_Init
*函数功能:初始化ADCx
*输入参数:无
*返回值:无
*备 注:无
*==============================================================================
*/
void ADC1_Init(void)
{
// 结构体定义
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1,ENABLE);
// 设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// GPIO配置
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; //ADC1通道1
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; // 模拟输入
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// ADC参数配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
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个转换在规则序列中 也就是只转换规则序列1
ADC_Init(ADC1, &ADC_InitStructure); // ADC初始化
ADC_Cmd(ADC1, ENABLE); // 开启AD转换器
// ADC校准
ADC_ResetCalibration(ADC1); // 重置指定的ADC的校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); // 获取ADC重置校准寄存器的状态
ADC_StartCalibration(ADC1); // 开始指定ADC的校准状态
while(ADC_GetCalibrationStatus(ADC1)); // 获取指定ADC的校准程序
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能或者失能指定的ADC的软件转换启动功能
}
5.2 软件触发AD转换
库函数开发,配置为软件触发时,可以通过下面的函数触发AD转换
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState)
5.3 读取AD转换结果
库函数提供了用于读取AD转换结果的函数
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx)
这里给出另一个函数,用于软件触发AD转换并读取转换结果
/*
*==============================================================================
*函数名称:Get_ADC_Value
*函数功能:读取某一规则通道AD值
*输入参数:ch:规则通道ADC_Channel_x;times:读取次数
*返回值:无
*备 注:该函数配置好后,返回的结果是N次后的平均值
*==============================================================================
*/
u16 Get_ADC_Value(u8 ch,u8 times)
{
u32 temp_val = 0;
u8 t;
// 设置指定ADC的规则组通道,一个序列,采样时间
// ADC1,ADC通道,239.5个周期,提高采样时间可以提高精确度
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5);
for(t=0;t< times;t++)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能指定的ADC1的软件转换启动功能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC )); // 等待转换结束
temp_val+=ADC_GetConversionValue(ADC1);
delay_ms(5);
}
return temp_val/times;
}
六、实战项目
用ADC1的通道1采集某电阻两端电压(由于普中核心板没有可供采集的电阻,可以直接将采集引脚接到3.3V查看一下结果),将结果通过串口打印到电脑。其中关于串口的配置就不再做介绍,给出ADC的配置和main函数。
6.1 ADC初始化
/*
*==============================================================================
*函数名称:ADC1_Init
*函数功能:初始化ADCx
*输入参数:无
*返回值:无
*备 注:无
*==============================================================================
*/
void ADC1_Init(void)
{
// 结构体定义
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1,ENABLE);
// 设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// GPIO配置
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; //ADC1通道1
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; // 模拟输入
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// ADC参数配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
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个转换在规则序列中 也就是只转换规则序列1
ADC_Init(ADC1, &ADC_InitStructure); // ADC初始化
ADC_Cmd(ADC1, ENABLE); // 开启AD转换器
// ADC校准
ADC_ResetCalibration(ADC1); // 重置指定的ADC的校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); // 获取ADC重置校准寄存器的状态
ADC_StartCalibration(ADC1); // 开始指定ADC的校准状态
while(ADC_GetCalibrationStatus(ADC1)); // 获取指定ADC的校准程序
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能或者失能指定的ADC的软件转换启动功能
}
6.2 main函数
u16 gAdcAdValue = 0; // 存储AD值
float gAdcVol = 0; // 实际电压值
int main(void)
{
Med_Mcu_Iint(); // 系统初始化
while(1)
{
gAdcAdValue = Get_ADC_Value (ADC_Channel_1,10); // 获取转换结果
gAdcVol = (gAdcAdValue / 0xFFF) * 3.3; // 计算实际电压
printf ("Vol=%.1f Vrn",gAdcVol); // 串口打印结果
delay_ms (500); // 防止打印过快
}
}
七、拓展
7.1 定时器触发ADC采集
根据中文参考手册介绍,ADC可以通过定时器触发AD转换(只有PWM的上升沿可以触发AD转换)。触发方式有以下几种
• TIM1_CH1 :定时器 1 的通道 1 的 PWM 触发
• TIM1_CH2 : 定时器 2 的通道 2 的 PWM 触发
• TIM1_CH3: 定时器 1 的通道 3 的 PWM 触发
• TIM2_CH2 : 定时器 2 的通道 2 的 PWM 触发
• TIM3_TRGO: 定时器 3 触发
• TIM4_CH4 : 定时器 4 的通道 4 的 PWM 触发
ADC外部触发方式
这里以TIM4的通道4触发ADC采集为例,给出程序配置。
首先是定时器PWM的配置,不对引脚进行重映射。
/*
*==============================================================================
*函数名称:TIM4_CH4_PWM_Init
*函数功能:初始化定时器4的PWM通道4
*输入参数:per:自动重装载值;psc:预分频系数
*返回值:无
*备 注:无
*==============================================================================
*/
void TIM4_CH4_PWM_Init (u16 per,u16 psc)
{
// 结构体定义
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;