快速上手STM32 ADC模块:速成笔记

文章目录

  • 一、什么是ADC
  • 二、ADC的用途
  • 三、STM32F103ZET6的ADC
  • 3.1 ADC通道对应引脚
  • 3.2ADC时钟
  • 3.3 ADC工作模式
  • 3.4 ADC转换时间
  • 3.5 ADC校准
  • 3.6 ADC转换结果与实际电压的换算
  • 四、ADC配置步骤
  • 五、ADC配置程序
  • 5.1 ADC初始化程序
  • 5.2 软件触发AD转换
  • 5.3 读取AD转换结果
  • 六、实战项目
  • 6.1 ADC初始化
  • 6.2 main函数
  • 七、拓展
  • 7.1 定时器触发ADC采集
  • 7.2 ADC采集交流信号
  • 7.3 计算交流信号有效值
  • 一、什么是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如下

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

    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。
  • 扫描模式
  • 3.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配置程序

    5.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 V\r\n",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;
    	GPIO_InitTypeDef GPIO_InitStructure;
    	
    	// 开启时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
    	
    	// 初始化GPIO
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   // 复用推挽输出
    	GPIO_Init(GPIOB,&GPIO_InitStructure);
    	
    	// 初始化定时器参数
    	TIM_TimeBaseInitStructure.TIM_Period = per;   // 自动装载值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;   // 分频系数
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;   // 设置向上计数模式
    	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);	
    	
    	// 初始化PWM参数
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;   // 比较输出模式
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;   // 输出极性
    	TIM_OCInitStructure.TIM_Pulse = 500;   // 脉冲宽度
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   // 输出使能
    	TIM_OC4Init(TIM4,&TIM_OCInitStructure);   // 输出比较通道2初始化
    	
    	TIM_OC4PreloadConfig(TIM4,TIM_OCPreload_Enable);   // 使能TIMx在 CCR2 上的预装载寄存器
    	TIM_ARRPreloadConfig(TIM4,ENABLE);   // 使能预装载寄存器
    	
    	TIM_Cmd(TIM4,ENABLE);   // 使能定时器
    }
    
    

    ADC配置程序如下,触发源选择TIM4的CH4,使能外部触发。

    /*
     *==============================================================================
     *函数名称: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);
    	// 规则通道配置
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);
    	
    	// 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_T4_CC4;   // TIM2通道2触发
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;   // 右对齐	
    	ADC_InitStructure.ADC_NbrOfChannel = 1;   // 1个转换在规则序列中 也就是只转换规则序列1 
    	ADC_Init(ADC1, &ADC_InitStructure);   // ADC初始化
    	
    	// 使能外部触发
    	ADC_ExternalTrigConvCmd(ADC1, ENABLE);
    	
    	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的软件转换启动功能
    }
    

    main函数如下

    u16 gAdcAdValue = 0;   // 存储AD值
    float gAdcVol = 0;   // 实际电压值
    
    int main(void)
    {
    	Med_Mcu_Iint();   // 系统初始化
    	
    	while(1)
      {
    		gAdcAdValue = ADC_GetConversionValue (ADC1);   // 获取转换结果
    		gAdcVol = (gAdcAdValue / 0xFFF) * 3.3;   // 计算实际电压
    		printf ("Vol=%.1f V\r\n",gAdcVol);   // 串口打印结果
    		
    		delay_ms (500);   // 防止打印过快
    	}
    }
    

    初始化定PWM时,程序为

    TIM4_CH4_PWM_Init(1000,71);   // 初始化TIM4的通道4
    

    分频系数为71 + 1,自动重装载值为1000,也就是1KHz的方波,也就是触发AD转换的频率为1KHz,与占空比无关。

    7.2 ADC采集交流信号

    ADC能够采集的电压范围是0~3.3V,也就是说负电压无法采集。比如,需要采集下图中的一个交流信号
    交流信号图
    其位于0以下的部分是无法采集的。因此,再利用STM32采集交流信号时,在交流信号输入ADC引脚前,给交流信号增加一个直流偏置,将交流信号的最低点抬升到0以上,之后再输入ADC引脚。

    7.3 计算交流信号有效值

    ADC可以用于电流监测,实时监测主线路中的电流。当然,硬件方面需要搭配电流互感线圈,通过采集互感线圈两端的电压,来监测主线路电流。由于一般都是交流信号,所以需要计算有效值。

    根据我们所学的知识,计算交流信号有效值常用两种方法。一种是峰峰值除以根号2,另一种是计算均方根得到有效值。通常我们采用计算均方根的方法来计算有效值。因为如果用峰峰值除以根号2去计算有效值,峰峰值很容易不准确。如果在某一个时刻,由于环境干扰或者硬件问题,导致突然出现了一个很大的值,会导致计算结果与实际偏差较大。关于为什么计算均方根可以得到交流信号的有效值,这里就不做介绍了,只给出部分程序设计。由于博主目前身边没有合适的设备验证,因此仅供参考。

    假设需要计算一个50Hz交流信号的的有效值,在其输入到ADC采集引脚之前,增加一个稳定的1.65V的偏置。ADC的采样频率为1KHz,也就是一个正弦波的周期可以采集20个点。假设采集到的AD值存储到一个数组中,计算有效值的程序设计如下

    int gAdcAdValue[20];   // 存储采样结果AD值的数组
    int gAdcValidValue = 0;   // 有效值
    
    void Med_Adc_ValidValueCal (void)
    {
    	int tempVar = 0;   // 循环变量
    	int squarSum = 0;   // 平方和
    	
    	// 求平方和
    	for (tempVar = 0;tempVar < 20;tempVar ++)
    	{
    		// 减去直流偏置
    		gAdcAdValue[tempVar] = gAdcAdValue[tempVar] - 2048;
    		// 计算平方和
    		squarSum = squarSum + gAdcAdValue[tempVar] * gAdcAdValue[tempVar];
    	}
    	
    	// 求平均
    	squarSum = squarSum / 20;
    	
    	// 开根号得到均方根(有效值)
    	gAdcValidValue = sqrt (squarSum);
    }
    

    在进行程序设计时需要注意不要超出数据类型范围。在实际应用时肯定会存在误差,这里也简单介绍一下误差消除方法。目前用到的有两种方法,第一种是分段矫正,在不同的区间内,误差满足线性关系时可以使用。另一种是按比例矫正,这种方法常用于误差随着测量值的增大而增大的情况。在计算出有效值后,减去或者加上一定比例的计算值来做矫正。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 快速上手STM32 ADC模块:速成笔记

    发表评论