前言:

为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长。



github:my github


注:博客所涉及的关于 stm32 的代码,均在仓库【stm32f013_study】下,包括底层驱动和应用测试代码。
本文设计的文件包含:
(1)drvadc.c:ADC 驱动实现
(2)app_adc.c:ADC 功能测试代码
(3)头文件:
drvuadc.h :ADC;
app_adc.h :ADC应用测试;



1. STM32 ADC 简介

STM32 拥有 1~3 个 ADC(STM32F101/102 系列只有 1 个 ADC),这些 ADC 可以独立使用,也可以使用双重模式(提高采样率)。 STM32 的 ADC 是 12 位逐次逼近型的模拟数字转换器。它有 18 个通道,可测量 16 个外部和 2 个内部信号源。各通道的 A/D 转换可以单次、连续、扫描或间断模式执行。 ADC 的结果可以左对齐或右对齐方式(12位)存储在 16 位数据寄存器中。 模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。

STM32 的 ADC 最大的转换速率为 1Mhz,也就是转换时间为 1us(在 ADCCLK = 14M,采样周期为 1.5 个 ADC 时钟下得到),不要让 ADC 的时钟超过 14M,否则将导致结果准确度下降。STM32 将 ADC 的转换分为 2 个通道组:规则通道组和注入通道组。规则通道相当于你正常运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你的执行的。同这个类似,注入通道的转换可以打断规则通道的转换, 在注入通道被转换完成之后,规则通道才得以继续转换。

STM32 的 ADC 在单次转换模式下,只执行一次转换,该模式可以通过 ADC_CR2 寄存器的 ADON 位(只适用于规则通道)启动,也可以通过外部触发启动(适用于规则通道和注入通道),这是 CONT 位为 0。以规则通道为例,一旦所选择的通道转换完成,转换结果将被存在 ADC_DR 寄存器中,EOC(转换结束)标志将被置位,如果设置了 EOCIE,则会产生中断。然后 ADC 将停止,直到下次启动。

2. 规则通道的单次转换

2.1. 相关 ADC 寄存器

2.2.1. ADC 控制寄存器(ADC_CR1ADC_CR2

a. ADC_CR1

ADC_CR1 的 SCAN 位,该位用于设置扫描模式,由软件设置和清除,如果设置为 1,则使用扫描模式,如果为 0,则关闭扫描模式。在扫描模式下,由 ADC_SQRx 或 ADC_JSQRx 寄存器选中的通道被转换。如果设置了 EOCIE 或 JEOCIE,只在最后一个通道转换完毕后才会产生 EOC 或 JEOC 中断

ADC_CR1[19: 16] 用于设置 ADC 的操作模式,详细的对应关系如下图:

b. ADC_CR2

该寄存器的各位描述如图:

ADON 位用于开关 AD 转换器。CONT 位用于设置是否进行连续转换,我们使用单次转换,CONT 位必须为 0。 CAL 和 RSTCAL 用于 AD 校准。 ALIGN 用于设置数据对齐,使用右对齐,该位设置为 0。

EXTSEL[2: 0] 用于选择启动规则转换组转换的外部事件

这里使用的是软件触发(SWSTART),所以设置这 3 个位为 111。 ADC_CR2 的 SWSTART 位用于开始规则通道的转换,我们每次转换(单次转换模式下)都需要向该位写 1AWDEN 为用于使能温度传感器和 Vrefint。

2.2 ADC 采样事件寄存器(ADC_SMPR1 和 ADC_SMPR2

这两个寄存器用于设置通道 0~17 的采样时间,每个通道占用 3 个位。ADC_SMPR1 的各位描述如图:

ADC_SMPR2 的各位描述如下图:

对于每个要转换的通道,采样时间建议尽量长一点,以获得较高的准确度,但是这样会降低 ADC 的转换速率。 ADC 的转换时间可以由以下公式计算:

Tcovn = 采样时间 + 12.5 个周期

其中:Tcovn:为总转换时间,采样时间是根据每个通道的 SMP 位的设置来决定的。例如,当 ADCCLK = 14Mhz 的时候,并设置 1.5 个周期的采样时间,则得到: Tcovn = 1.5 + 12.5 =14 个周期 = 1us。

2.3 ADC 规则序列寄存器(ADC_SQR1~3)

该寄存器总共有 3 个,这几个寄存器的功能都差不多,这里仅介绍一下 ADC_SQR1,该寄存器的各位描述如图 :

L[3: 0] 用于存储规则序列的长度,我们这里只用了 1 个,所以设置这几个位的值为 0。其他的 SQ13~16 则存储了规则序列中第 13~16 通道的编号(编号范围: 0~17)。另外两个规则序列寄存器同 ADC_SQR1 大同小异,要说明一点的是:是单次转换,所以只有一个通道在规则序列里面,这个序列就是 SQ1,通过 ADC_SQR3 的最低 5位(也就是 SQ1) 设置。

2.4 ADC 规则数据寄存器(ADC_DR)

规则序列中的 AD 转化结果都将被存在这个寄存器里面,而注入通道的转换结果被保存在 ADC_JDRx 里面。 ADC_DR 的各位描述如图 :

该寄存器的数据可以通过 ADC_CR2 的 ALIGN 位设置左对齐还是右对齐。在读取数据的时候要注意。

2.5 ADC 状态寄存器(ADC_SR)

该寄存器保存了 ADC 转换时的各种状态。该寄存器的各位描述如图:

这里用到的是 EOC 位,我们通过判断该位来决定是否此次规则通道的 AD 转换已经完成,如果完成我们就从 ADC_DR 中读取转换结果,否则等待转换完成。

3. STM32 的单次转换模式设置步骤

1) 开启 PA 口和 ADC1 时钟,设置 PA1 为模拟输入

STM32F103RCT6 的 ADC1 通道 1 在 PA1 上,所以,我们先要使能 PORTA 的时钟,然后设置 PA1 为模拟输入。 使能 GPIOA 和 ADC 时钟用RCC_APB2PeriphClockCmd 函数,设置 PA1 的输入方式,使用 GPIO_Init 函数即可。下面为 STM32 的 ADC 通道与 GPIO 对应表:

2)复位 ADC1,同时设置 ADC1 分频因子。

开启 ADC1 时钟之后,我们要复位 ADC1,将 ADC1 的全部寄存器重设为缺省值之后我们就可以通过 RCC_CFGR 设置 ADC1 的分频因子。分频因子要确保 ADC1 的时钟(ADCCLK)不要超过 14Mhz。 这个我们设置分频因子位 6,时钟为 72 / 6 = 12MHz。库函数的实现方是:

RCC_ADCCLKConfig(RCC_PCLK2_Div6);

ADC 时钟复位的方法是:

ADC_DeInit(ADC1);

3)初始化 ADC1 参数,设置 ADC1 的工作模式以及规则序列的相关信息。

在设置完分频因子之后,就可以开始 ADC1 的模式配置了,设置单次转换模式、触发方式选择、数据对齐方式等都在这一步实现。同时,我们还要设置 ADC1 规则序列的相关信息。

这里只有一个通道,并且是单次转换的,所以设置规则序列中通道数为 1。这些在库函数中是通过函数 ADC_Init 实现的,其定义:

void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);

第一个参数:指定 ADC 号。

第二个参数:跟其他外设初始化一样,同样是通过设置结构体成员变量的值来设定参数。

typedef struct
{uint32_t ADC_Mode;FunctionalState ADC_ScanConvMode;FunctionalState ADC_ContinuousConvMode;uint32_t ADC_ExternalTrigConv;uint32_t ADC_DataAlign;uint8_t ADC_NbrOfChannel;
} ADC_InitTypeDef;

参数 ADC_Mode :用来设置 ADC 的模式.ADC 的模式非常多,包括独立模式,注入同步模式等等,这里我们选择独立模式,参数为 ADC_Mode_Independent

参数 ADC_ScanConvMode :用来设置是否开启扫描模式,因为我们的实验是单通道单次转换,所以这里我们选择不开启值 DISABLE 即可。

参数 ADC_ContinuousConvMode: 用来设置是否开启连续转换模式,因为是单次转换模式,所以我们选择不开启连续转换模式, DISABLE 即可。

参数 ADC_ExternalTrigConv: 是用来设置启动规则转换组转换的外部事件,这里我们选择软件触发,选择值为 ADC_ExternalTrigConv_None 即可。

参数 DataAlign:用来设置 ADC 数据对齐方式是左对齐还是右对齐,这里选择右对齐方式ADC_DataAlign_Right

参数 ADC_NbrOfChannel :用来设置规则序列的长度, 我们实验只开启一个通道,所以值为 1 即可。

初始化范例:

 ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_PCLK_ADC_CHL, ENABLE );     //使能ADC1通道时钟RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14MADC_DeInit(ADC_CHL);  //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值ADC_InitStructure.ADC_Mode                   = ADC_Mode_Independent;    //ADC工作模式:ADC1和ADC2工作在独立模式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数据右对齐ADC_InitStructure.ADC_NbrOfChannel            = 1;   //顺序进行规则转换的ADC通道的数目ADC_Init(ADC_CHL, &ADC_InitStructure);   //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   

5)使能 ADC 并校准

在设置完了以上信息后,就使能 AD 转换器,**执行复位校准和 AD 校准,注意这两步是必须的!**不校准将导致结果很不准确。

使能指定的 ADC 的方法是:

ADC_Cmd(ADC1, ENABLE); //使能指定的 ADC1

执行复位校准的方法是:

ADC_ResetCalibration(ADC1);

执行 ADC 校准的方法是:

ADC_StartCalibration(ADC1); //开始指定 ADC1 的校准状态

记住,每次进行校准之后要等待校准结束。这里是通过获取校准状态来判断是否校准是否结束。

复位校准和 AD 校准的等待结束方法:

while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束while(ADC_GetCalibrationStatus(ADC1)); //等待校 AD 准结束

6)读取 ADC 值。

在上面的校准完成之后, ADC 就算准备好了。接下来我们要做的就是设置规则序列 1 里面的通道,采样顺序,以及通道的采样周期,然后启动 ADC 转换。在转换结束后,读取 ADC 转换结果值。

这里设置规则序列通道以及采样周期的函数是:

void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel,
uint8_t Rank, uint8_t ADC_SampleTime);

我们这里是规则序列中的第 1 个转换,同时采样周期为 239.5,所以设置为:

ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );

软件开启 ADC 转换的方法是:

ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能指定的 ADC1 的软件转换启动功能

开启转换之后,就可以获取转换 ADC 转换结果数据,方法是:

ADC_GetConversionValue(ADC1);

同时在 AD 转换中,我们还要根据状态寄存器的标志位来获取 AD 转换的各个状态信息。库函数获取 AD 转换的状态信息的函数是:

FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG)

比如我们要判断 ADC1 的转换是否结束,方法是:

while(!ADC_GetFlagStatus(ADC1,  ADC_FLAG_EOC ));//等待转换结束

通过以上几个步骤的设置,我们就能正常的使用 STM32 的 ADC1 来执行 AD 转换操作了。

注:说明一下 ADC 的参考电压, MiniSTM32 开发板使用的是STM32F103RCT6,该芯片没有外部参考电压引脚, ADC 的参考电压直接取自 VDDA,也就是 3.3V,即输入电压范围为:0~3.3V 。

4. 例程分析

宏定义:

//GPIO 部分
#define RCC_PCLK_ADC_GPIO           RCC_APB2Periph_GPIOA
#define PORT_ADC_IN                 GPIOA
#define ADC_IN_PIN                  GPIO_Pin_1//ADC 部分
#define RCC_PCLK_ADC_CHL            RCC_APB2Periph_ADC1
#define ADC_CHL                     ADC1

例程实现:

//---------------------------------------------------------------------------------------------------------------------------------------------
//  函 数 名: Adc_GpioConfig
//  功能说明: ADC输入采集引脚配置
//  形    参: 无
//  返 回 值: 无
//  日    期: 2020-03-11
//  备    注:
//  作    者: 霁风AI
//---------------------------------------------------------------------------------------------------------------------------------------------
void Adc_GpioConfig(void)
{   GPIO_InitTypeDef gpio_init;RCC_APB2PeriphClockCmd(RCC_PCLK_ADC_GPIO, ENABLE );    //使能ADC1通道时钟//PA1 作为模拟通道输入引脚                         gpio_init.GPIO_Pin = ADC_IN_PIN;    //ADC输入引脚gpio_init.GPIO_Mode = GPIO_Mode_AIN;      //模拟输入引脚GPIO_Init(PORT_ADC_IN, &gpio_init);
}//---------------------------------------------------------------------------------------------------------------------------------------------
//  函 数 名: Adc_Config
//  功能说明: ADC输入采集功能配置
//  形    参: 无
//  返 回 值: 无
//  备    注:
//  日    期: 2020-03-11
//  作    者: 霁风AI
//---------------------------------------------------------------------------------------------------------------------------------------------
void  Adc_Config(void)
{   ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_PCLK_ADC_CHL, ENABLE );     //使能ADC1通道时钟RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14MADC_DeInit(ADC_CHL);  //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值ADC_InitStructure.ADC_Mode                   = ADC_Mode_Independent;    //ADC工作模式:ADC1和ADC2工作在独立模式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数据右对齐ADC_InitStructure.ADC_NbrOfChannel            = 1;   //顺序进行规则转换的ADC通道的数目ADC_Init(ADC_CHL, &ADC_InitStructure);   //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   ADC_Cmd(ADC_CHL, ENABLE);   //使能指定的ADC1ADC_ResetCalibration(ADC_CHL);   //使能复位校准  while(ADC_GetResetCalibrationStatus(ADC_CHL));    //等待复位校准结束ADC_StartCalibration(ADC_CHL);     //开启AD校准while(ADC_GetCalibrationStatus(ADC_CHL));   //等待校准结束// ADC_SoftwareStartConvCmd(ADC1, ENABLE);     //使能指定的ADC1的软件转换启动功能}//---------------------------------------------------------------------------------------------------------------------------------------------
//  函 数 名: Adc_Init
//  功能说明: ADC初始化
//  形    参: 无
//  返 回 值: 无
//  备    注:
//  日    期: 2020-03-11
//  作    者: 霁风AI
//---------------------------------------------------------------------------------------------------------------------------------------------
void Adc_Init(void)
{Adc_GpioConfig();  //IO初始化Adc_Config();    //ADC功能配置
}//---------------------------------------------------------------------------------------------------------------------------------------------
//  函 数 名: Get_AdcConvertVal
//  功能说明: 输出ADC转换结果
//  形    参:     _ucChl:ADC 采集通道
//  返 回 值: 采集数据结果
//  备    注:
//  日    期: 2020-03-11
//  作    者: 霁风AI
//---------------------------------------------------------------------------------------------------------------------------------------------
uint16_t Get_AdcConvertVal(uint8_t _ucChl)
{//设置指定ADC的规则组通道,一个序列,采样时间ADC_RegularChannelConfig(ADC_CHL, _ucChl, 1, ADC_SampleTime_239Cycles5);    //ADC1,ADC通道,采样时间为239.5周期                   ADC_SoftwareStartConvCmd(ADC_CHL, ENABLE);      //使能指定的ADC1的软件转换启动功能    while(!ADC_GetFlagStatus(ADC_CHL, ADC_FLAG_EOC));   //等待转换结束return ADC_GetConversionValue(ADC_CHL);     //返回最近一次ADC1规则组的转换结果
}//---------------------------------------------------------------------------------------------------------------------------------------------
//  函 数 名: Get_Adc_Average
//  功能说明: 转换出 ADC 采集数据
//  形    参:     _ucChl:ADC 通道号
//              _ucTimes:采集次数
//  返 回 值: 转换数据
//  备    注:
//  日    期: 2020-03-11
//  作    者: 霁风AI
//---------------------------------------------------------------------------------------------------------------------------------------------
uint16_t Get_AdcAverage(uint8_t _ucChl, uint8_t _ucTimes)
{uint32_t ulTmpVal = 0;uint8_t i = 0;for(i = 0; i < _ucTimes; i++){ulTmpVal += Get_AdcConvertVal(_ucChl);delay_ms(5);}return ulTmpVal / _ucTimes;
}

结果测试:

//---------------------------------------------------------------------------------------------------------------------------------------------
//  函 数 名: App_AdcTest
//  功能说明: ADC单通道采样测试
//  形    参: 无
//  返 回 值: 无
//  备    注:
//  日    期: 2020-03-11
//  作    者: 霁风AI
//---------------------------------------------------------------------------------------------------------------------------------------------
void App_AdcTest(void)
{uint16_t usAdcVal = 0;float fTmpVal = 0.0;usAdcVal = Get_AdcAverage(ADC_Channel_1, 10);//INPUT VOLTAGE = (ADC Value / ADC Resolution) * Reference VoltagefTmpVal = (float)usAdcVal * (3.3 / 4096);    //12位ADCprintf("voltage is %04fv.\r\n", fTmpVal);delay_ms(250);
}

ADC 结果:


改变输入电压,ADC 采集的电压对应发生改变。


参考:

原子 STM32 教程—库函数

STM32学习笔记一一ADC相关推荐

  1. STM32学习笔记一一触摸屏

    前言: 为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长. 1. 简介 1.1 电阻式触摸屏 电阻式触摸屏利用压力感应进行触点检测控制 ...

  2. STM32学习笔记(七) ADC模数转换测电平(普通和DMA模式)

    嵌入式系统在微控制领域(温度,湿度,压力检测,四轴飞行器)中占据着重要地位,这些功能的实现是由微处理器cpu(如stm32)和传感器以及控制器共同完成的,而连接他们,使它们能够互相正常交流的正是本小节 ...

  3. STM32学习笔记一一TFTLCD 显示

    前言: 为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长. 1.TFTLCD 简介 TFT-LCD 即薄膜晶体管液晶显示器.TFT-L ...

  4. STM32学习笔记一一UCOSII(1)

    前言: 为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长. 1. 简介 UCOSII 是一个可以基于 ROM 运行的.可裁减的.抢占式 ...

  5. STM32学习笔记(13)——模数转换ADC

    这个月一直忙于准备考试,已经考完一半科目了,偷闲写了这篇文章.因为还没考完试,估计还得咕一段时间了. STM32学习笔记(13)--模数转换ADC 第一部分:ADC功能框图 一.输入电压 二.输入通道 ...

  6. STM32学习笔记(八)丨ADC模数转换器(ADC单、双通道转换)

    本篇文章包含的内容 一.ADC 模数转换器 1.1 ADC简介 1.2 逐次逼近型ADC工作原理 1.3 STM32中的ADC基本结构 1.4 STM32中ADC的输入通道 1.5 STM32中的AD ...

  7. 《STM32学习笔记》4——核心功能电路与编程(下)

    接上文,文中的图片,大多数来自视频的截图(来自洋桃电子). 欢迎大家批评指正! STM32学习笔记-专栏 文章目录 一.蜂鸣器驱动 1.蜂鸣器介绍 2.蜂鸣器电路 3.蜂鸣器程序 二. MIDI 音乐 ...

  8. STM32学习笔记(15)——SPI协议

    STM32学习笔记(15)--SPI协议 一.SPI协议简介 1. 物理层 2. 协议层 (1) 通讯的开始与停止 (2)时钟极性CPOL.时钟相位CPHA 二.STM32的SPI外设 1. 通讯引脚 ...

  9. STM32学习笔记(三)丨中断系统丨EXTI外部中断(对射式红外传感器计次、旋转编码器计次)

    本篇文章包含的内容 一.中断系统 1.1 中断的定义 1.2 中断优先级 1.3 中断的嵌套 1.4 STM32中的中断系统 1.4.1 STM32的中断资源 1.4.2 嵌套中断向量控制器 NVIC ...

最新文章

  1. HTTP/3 来了 !HTTP/2 还没怎么用起来呢,先一起扫个盲吧!
  2. 深入理解 C 指针阅读笔记 -- 第四章
  3. JDK源码重新编译——支持eclipse调试JDK源码--转载
  4. Linux 环境下安装 Golang
  5. 弥勒市召开智慧城市建设规划设计征求意见会
  6. SAP BTP Kyma Runtime dashboard 打开报缺少缺陷的错误消息该如何解决
  7. Redis:23---info命令总结
  8. Storm精华问答 | Kafka在Storm中的角色是什么?
  9. 中序线索树和后序线索树
  10. [抄]外部奖励对内在动机的侵蚀
  11. Maven_1 安装配置
  12. 2020年“深圳杯”数学建模挑战赛C题-无线可充电传感器网络充电路线规划
  13. 错误变惊喜,10个有趣的404页面设计(转)
  14. 计算机管理 没有初始化,win7系统电脑新增的硬盘没有初始化的解决方法
  15. 判断四边形凹凸性及凹点
  16. MAtlab wavefront,这是张小飞的阵列信号的全部MATLAB代码 传统的高分辨波达方向(DOA)估计算法中 - 下载 - 搜珍网...
  17. 15000字看完lululemon增长攻略
  18. 回归方程有效性的检查
  19. 知乎 | 大家都见过哪些让你虎躯一震的代码?
  20. c语言编计分秒时的程序,用c语言编写程序,用于c51单片机四位共阴数码管显示,显示分秒的计时器。...

热门文章

  1. 函数模板和普通函数区别
  2. 全球人口密度大致分布
  3. 软考学院九老师简介:国家软考(中高级认证)培训
  4. jquery iframe 父子互操作
  5. JS/JQuery整齐的照片墙:展示很多宽高不同照片,让每一行中的所有照片高度一样,所有的行的宽度一样
  6. AST:Audio Spectrogram Transformer
  7. What Is An NFT? Non-Fungible Tokens Explained
  8. WPF学习之X名称空间详解
  9. 恒星物联 窨井盖安全监测系统方案 智慧井盖 井盖状态监测
  10. iPhone 忘记了密码怎么办?