你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32-ADC模块讲解

[复制链接]
STMCU 发布时间:2020-9-21 13:00
一.ADC简介

STM32f103系列有3个ADC,精度为12位,每个ADC最多有16个外部通道。其中ADC1和ADC2都有16个外部通道,ADC3一般有8个外部通道,各通道的A/D转换可以单次、连续、扫描或间断执行,ADC转换的结果可以左对齐或右对齐储存在16位数据寄存器中。ADC的输入时钟不得超过14MHz,其时钟频率由PCLK2分频产生。

二.ADC功能框图讲解

学习STM32开发板上的外设时首先要了解其外设的功能框图,如下:


1_meitu_5.jpg



功能框图可以大体分为7部分,下面一一讲解:


1.电压输入范围

ADC所能测量的电压范围就是VREF- ≤ VIN ≤ VREF+,把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到ADC 的输入电压范围为: 0~3.3V。


2.输入通道

ADC的信号输入就是通过通道来实现的,信号通过通道输入到单片机中,单片机经过转换后,将模拟信号输出为数字信号。STM32中的ADC有着18个通道,其中外部的16个通道已经在框图中标出,如下:


2.png



这16个通道对应着不同的IO口,此外ADC1/2/3 还有内部通道: ADC1 的通道 16 连接到了芯片内部的温度传感器, Vrefint 连接到了通道 17。 ADC2 的模拟通道 16 和 17 连接到了内部的 VSS。


ADC的全部通道如示:


3_meitu_7.jpg



外部的16个通道在转换时又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多有4路(注入通道貌似使用不多),下面简单介绍一下俩种通道:


规则通道
规则通道顾名思义就是,最平常的通道、也是最常用的通道,平时的ADC转换都是用规则通道实现的。
注入通道
注入通道是相对于规则通道的,注入通道可以在规则通道转换时,强行插入转换,相当于一个“中断通道”吧。当有注入通道需要转换时,规则通道的转换会停止,优先执行注入通道的转换,当注入通道的转换执行完毕后,再回到之前规则通道进行转换。


3.转换顺序

知道了ADC的转换通道后,如果ADC只使用一个通道来转换,那就很简单,但如果是使用多个通道进行转换就涉及到一个先后顺序了,毕竟规则转换通道只有一个数据寄存器。多个通道的使用顺序分为俩种情况:规则通道的转换顺序和注入通道的转换顺序。

规则通道转换顺序
规则通道中的转换顺序由三个寄存器控制:SQR1、SQR2、SQR3,它们都是32位寄存器。SQR寄存器控制着转换通道的数目和转换顺序,只要在对应的寄存器位SQx中写入相应的通道,这个通道就是第x个转换。具体的对应关系如下:


4_meitu_8.jpg



通过SQR1寄存器就能了解其转换顺序在寄存器上的实现了:


5_meitu_9.jpg




注入通道转换顺序


和规则通道转换顺序的控制一样,注入通道的转换也是通过注入寄存器来控制,只不过只有一个JSQR寄存器来控制,控制关系如下:


6_meitu_10.jpg



需要注意的是,只有当JL=4的时候,注入通道的转换顺序才会按照JSQ1、JSQ2、JSQ3、JSQ4的顺序执行。当JL<4时,注入通道的转换顺序恰恰相反,也就是执行顺序为:JSQ4、JSQ3、JSQ2、JSQ1。


配置转换顺序的函数如示:



  1. /**
  2.   * @brief  Configures for the selected ADC regular channel its corresponding
  3.   *         rank in the sequencer and its sample time.
  4.   * @param  ADCx: where x can be 1, 2 or 3 to select the ADC peripheral.
  5.   * @param  ADC_Channel: the ADC channel to configure.
  6.   *   This parameter can be one of the following values:
  7.   *     @arg ADC_Channel_0: ADC Channel0 selected
  8.   *     @arg ADC_Channel_1: ADC Channel1 selected
  9.   *     @arg ADC_Channel_2: ADC Channel2 selected
  10.   *     @arg ADC_Channel_3: ADC Channel3 selected
  11.   *     @arg ADC_Channel_4: ADC Channel4 selected
  12.   *     @arg ADC_Channel_5: ADC Channel5 selected
  13.   *     @arg ADC_Channel_6: ADC Channel6 selected
  14.   *     @arg ADC_Channel_7: ADC Channel7 selected
  15.   *     @arg ADC_Channel_8: ADC Channel8 selected
  16.   *     @arg ADC_Channel_9: ADC Channel9 selected
  17.   *     @arg ADC_Channel_10: ADC Channel10 selected
  18.   *     @arg ADC_Channel_11: ADC Channel11 selected
  19.   *     @arg ADC_Channel_12: ADC Channel12 selected
  20.   *     @arg ADC_Channel_13: ADC Channel13 selected
  21.   *     @arg ADC_Channel_14: ADC Channel14 selected
  22.   *     @arg ADC_Channel_15: ADC Channel15 selected
  23.   *     @arg ADC_Channel_16: ADC Channel16 selected
  24.   *     @arg ADC_Channel_17: ADC Channel17 selected
  25.   * @param  Rank: The rank in the regular group sequencer. This parameter must be between 1 to 16.
  26.   * @param  ADC_SampleTime: The sample time value to be set for the selected channel.
  27.   *   This parameter can be one of the following values:
  28.   *     @arg ADC_SampleTime_1Cycles5: Sample time equal to 1.5 cycles
  29.   *     @arg ADC_SampleTime_7Cycles5: Sample time equal to 7.5 cycles
  30.   *     @arg ADC_SampleTime_13Cycles5: Sample time equal to 13.5 cycles
  31.   *     @arg ADC_SampleTime_28Cycles5: Sample time equal to 28.5 cycles       
  32.   *     @arg ADC_SampleTime_41Cycles5: Sample time equal to 41.5 cycles       
  33.   *     @arg ADC_SampleTime_55Cycles5: Sample time equal to 55.5 cycles       
  34.   *     @arg ADC_SampleTime_71Cycles5: Sample time equal to 71.5 cycles       
  35.   *     @arg ADC_SampleTime_239Cycles5: Sample time equal to 239.5 cycles       
  36.   * @retval None
  37.   */
  38. void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime)
  39. {
  40.   函数内容略;
  41. }
复制代码

4.触发源

ADC转换的输入、通道、转换顺序都已经说明了,但ADC转换是怎么触发的呢?就像通信协议一样,都要规定一个起始信号才能传输信息,ADC也需要一个触发信号来实行模/数转换。
其一就是通过直接配置寄存器触发,通过配置控制寄存器CR2的ADON位,写1时开始转换,写0时停止转换。在程序运行过程中只要调用库函数,将CR2寄存器的ADON位置1就可以进行转换,比较好理解。
另外,还可以通过内部定时器或者外部IO触发转换,也就是说可以利用内部时钟让ADC进行周期性的转换,也可以利用外部IO使ADC在需要时转换,具体的触发由控制寄存器CR2决定。
ADC_CR2寄存器的详情如下:


7_meitu_11.jpg



8_meitu_12.jpg


9_meitu_13.jpg



5.转换时间

还有一点,就是转换时间的问题,ADC的每一次信号转换都要时间,这个时间就是转换时间,转换时间由输入时钟和采样周期来决定。


输入时钟
由于ADC在STM32中是挂载在APB2总线上的,所以ADC得时钟是由PCLK2(72MHz)经过分频得到的,分频因子由 RCC 时钟配置寄存器RCC_CFGR 的位 15:14 ADCPRE[1:0]设置,可以是 2/4/6/8 分频,一般配置分频因子为8,即8分频得到ADC的输入时钟频率为9MHz。
采样周期
采样周期是确立在输入时钟上的,配置采样周期可以确定使用多少个ADC时钟周期来对电压进行采样,采样的周期数可通过 ADC采样时间寄存器 ADC_SMPR1 和 ADC_SMPR2 中的 SMP[2:0]位设置,ADC_SMPR2 控制的是通道 0~9, ADC_SMPR1 控制的是通道 10~17。每个通道可以配置不同的采样周期,但最小的采样周期是1.5个周期,也就是说如果想最快时间采样就设置采样周期为1.5.
转换时间
转换时间=采样时间+12.5个周期
12.5个周期是固定的,一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us。



6.数据寄存器

转换完成后的数据就存放在数据寄存器中,但数据的存放也分为规则通道转换数据和注入通道转换数据的。


规则数据寄存器
规则数据寄存器负责存放规则通道转换的数据,通过32位寄存器ADC_DR来存放。


10_meitu_14.jpg

当使用ADC独立模式(也就是只使用一个ADC,可以使用多个通道)时,数据存放在低16位中,当使用ADC多模式时高16位存放ADC2的数据。需要注意的是ADC转换的精度是12位,而寄存器中有16个位来存放数据,所以要规定数据存放是左对齐还是右对齐。

当使用多个通道转换数据时,会产生多个转换数据,然鹅数据寄存器只有一个,多个数据存放在一个寄存器中会覆盖数据导致ADC转换错误,所以我们经常在一个通道转换完成之后就立刻将数据取出来,方便下一个数据存放。一般开启DMA模式将转换的数据,传输在一个数组中,程序对数组读操作就可以得到转换的结果。
DMA的使用之前在一篇博客中介绍过:DMA介绍

注入数据寄存器
注入通道转换的数据寄存器有4个,由于注入通道最多有4个,所以注入通道转换的数据都有固定的存放位置,不会跟规则寄存器那样产生数据覆盖的问题。 ADC_JDRx 是 32 位的,低 16 位有效,高 16 位保留,数据同样分为左对齐和右对齐,具体是以哪一种方式存放,由ADC_CR2 的 11 位 ALIGN 设置。


11_meitu_15.jpg




7.中断
12_meitu_16.jpg


从框图中可以知道数据转换完成之后可以产生中断,有三种情况:
规则通道转换完成中断
规则通道数据转换完成之后,可以产生一个中断,可以在中断函数中读取规则数据寄存器的值。这也是单通道时读取数据的一种方法。
注入通道转换完成中断
注入通道数据转换完成之后,可以产生一个中断,并且也可以在中断中读取注入数据寄存器的值,达到读取数据的作用。
模拟看门狗事件
当输入的模拟量(电压)不再阈值范围内就会产生看门狗事件,就是用来监视输入的模拟量是否正常。
以上中断的配置都由ADC_SR寄存器决定:


13_meitu_17.jpg


当然,在转换完成之后也可以产生DMA请求,从而将转换好的数据从数据寄存器中读取到内存中。


8.电压转换

要知道,转换后的数据是一个12位的二进制数,我们需要把这个二进制数代表的模拟量(电压)用数字表示出来。比如测量的电压范围是0~3.3V,转换后的二进制数是x,因为12位ADC在转换时将电压的范围大小(也就是3.3)分为4096(2^12)份,所以转换后的二进制数x代表的真实电压的计算方法就是:
y=3.3* x / 4096


三.初始化结构体

每个外设的核心就是其对应的初始化结构体了,ADC的初始化结构体如下:



  1. typedef struct
  2. {
  3. uint32_t ADC_Mode; // ADC 工作模式选择
  4. FunctionalState ADC_ScanConvMode; // ADC 扫描(多通道)或者单次(单通道)模式选择
  5. FunctionalState ADC_ContinuousConvMode; // ADC 单次转换或者连续转换选择
  6. uint32_t ADC_ExternalTrigConv; // ADC 转换触发信号选择
  7. uint32_t ADC_DataAlign; // ADC 数据寄存器对齐格式
  8. uint8_t ADC_NbrOfChannel; // ADC 采集通道数
  9. } ADC_InitTypeDef;
复制代码


通过配置初始化结构体来设置ADC的相关信息。



四.单通道电压采集

用这个程序来简单熟练一下ADC的单通道电压采集吧,程序使用了ADC1的通道11,对应的IO口是PC^1,因为博主的开发板上PC ^1引脚没有任何复用,使用中断,在中断中读取转换的电压。



1.头文件

为了提高文件的可移植性,头文件中定义了一些与ADC和中断相关的量,在移植程序的时候只需要修改头文件中的定义即可。



  1. #ifndef __ADC_H
  2. #define __ADC_H

  3. #include "stm32f10x.h"

  4. /* 采用ADC1的通道11  引脚为PC^1 模式必须是模拟输入*/
  5. #define ADC_GPIO_RCC     RCC_APB2Periph_GPIOC
  6. #define ADC_GPIO_PORT    GPIOC
  7. #define ADC_GPIO_PIN     GPIO_Pin_1
  8. #define ADC_GPIO_MODE    GPIO_Mode_AIN  

  9. /* 配置与中断有关的信息 */
  10. #define ADC_IRQn         ADC1_2_IRQn
  11. #define ADC_RCC          RCC_APB2Periph_ADC1


  12. /* 配置ADC初始化结构体的宏定义 */
  13. #define ADCx                          ADC1
  14. #define ADCx_ContinuousConvMode       ENABLE                                  //连续转换模式
  15. #define ADCx_DataAlign                ADC_DataAlign_Right                     //转换结果右对齐
  16. #define ADCx_ExternalTrigConv         ADC_ExternalTrigConv_None      //不使用外部触发转换,采用软件触发
  17. #define ADCx_Mode                     ADC_Mode_Independent                    //只使用一个ADC,独立模式
  18. #define ADCx_NbrOfChannel             1                                      //一个转换通道
  19. #define ADCx_ScanConvMode             DISABLE                                 //禁止扫描模式,多通道时使用

  20. /* 通道信息和采样周期 */
  21. #define ADC_Channel                   ADC_Channel_11
  22. #define ADC_SampleTime                ADC_SampleTime_55Cycles5


  23. /* 函数声明 */
  24. void ADC_COnfig(void);
  25. void ADC_NVIC_Config(void);
  26. void ADC_GPIO_Config(void);
  27. void ADCx_Init(void);

  28. #endif  /* __ADC_H */
复制代码



2.引脚配置函数

首先配置相应的GPIO引脚,毕竟模拟信号是通过GPIO引脚传输到开发板的,注意的是,引脚的模式一定要是模拟输入!



  1. void ADC_GPIO_Config(void)
  2. {
  3.         GPIO_InitTypeDef   GPIO_InitStruct;
  4.         RCC_APB2PeriphClockCmd(ADC_GPIO_RCC,  ENABLE);
  5.        
  6.         GPIO_InitStruct.GPIO_Pin = ADC_GPIO_PIN ;
  7.         GPIO_InitStruct.GPIO_Mode = ADC_GPIO_MODE ;
  8.        
  9.         GPIO_Init(ADC_GPIO_PORT , &GPIO_InitStruct);
  10. }
复制代码

配置引脚就是老套路:声明结构体变量、开启时钟、写入结构体、初始化GPIO



3.NVIC配置函数

因为我们是在转换完成后利用中断,在中断函数中读取数据,所以要首先配置中断函数的优先级,因为程序中只有这一个中断,所以优先级的配置就比较随意。



  1. void ADC_NVIC_Config(void)
  2. {

  3.         NVIC_InitTypeDef NVIC_InitStruct ;
  4.        
  5.         /* 配置中断优先级分组(设置抢占优先级和子优先级的分配),在函数在misc.c */
  6.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1) ;
  7.        
  8.         /* 配置初始化结构体 在misc.h中 */
  9.         /* 配置中断源 在stm32f10x.h中 */
  10.         NVIC_InitStruct.NVIC_IRQChannel = ADC_IRQn ;
  11.         /* 配置抢占优先级 */
  12.         NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1 ;
  13.         /* 配置子优先级 */
  14.         NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1 ;
  15.         /* 使能中断通道 */
  16.         NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE ;
  17.         /* 调用初始化函数 */
  18.         NVIC_Init(&NVIC_InitStruct) ;
  19. }
复制代码

4.ADC配置函数

ADC的配置函数是ADC的精髓,在这个函数中包含的内容有:ADC的初始化结构体配置、配置了时钟分频、配置了通道转换顺序、打开转换中断、进行校准、软件触发ADC采集等。
函数中都有详细的注释:


  1. void ADC_COnfig(void)
  2. {
  3.         ADC_InitTypeDef  ADC_InitStruct;
  4.         RCC_APB2PeriphClockCmd(ADC_RCC,  ENABLE);
  5.        
  6.         /* 配置初始化结构体,详情见头文件 */
  7.         ADC_InitStruct.ADC_ContinuousConvMode = ADCx_ContinuousConvMode  ;
  8.         ADC_InitStruct.ADC_DataAlign = ADCx_DataAlign ;
  9.         ADC_InitStruct.ADC_ExternalTrigConv = ADCx_ExternalTrigConv ;
  10.         ADC_InitStruct.ADC_Mode = ADCx_Mode ;
  11.         ADC_InitStruct.ADC_NbrOfChannel = ADCx_NbrOfChannel ;
  12.         ADC_InitStruct.ADC_ScanConvMode = ADCx_ScanConvMode ;
  13.        
  14.         ADC_Init(ADCx, &ADC_InitStruct);
  15.        
  16.         /* 配置ADC时钟为8分频,即9M */
  17.         RCC_ADCCLKConfig(RCC_PCLK2_Div8);
  18.         /* 配置ADC通道转换顺序和时间 */
  19.         ADC_RegularChannelConfig(ADCx, ADC_Channel, 1, ADC_SampleTime );
  20.         /* 配置为转换结束后产生中断 在中断中读取信息 */
  21.         ADC_ITConfig(ADCx, ADC_IT_EOC,ENABLE);
  22.         /* 开启ADC,进行转换 */
  23.         ADC_Cmd(ADCx, ENABLE );
  24.         /* 重置ADC校准 */
  25.         ADC_ResetCalibration(ADCx);
  26.         /* 等待初始化完成 */
  27.         while(ADC_GetResetCalibrationStatus( ADCx))
  28.                 /* 开始校准 */
  29.                 ADC_StartCalibration(ADCx);
  30.         /* 等待校准完成 */
  31.         while (ADC_GetCalibrationStatus(ADCx));
  32.         /* 软件触发ADC转换 */
  33.         ADC_SoftwareStartConvCmd(ADCx, ENABLE);
  34.        
  35. }
复制代码


5.中断函数

在中断函数中进行读取数据,将数据存放在变量result中,此处使用关键字extern声明,代表变量result已经在其他文件中定义.



  1. extern uint16_t resurt;

  2. void ADC1_2_IRQHandler(void)
  3. {
  4.     /* 判断产生中断请求 */
  5.         while(ADC_GetITStatus(ADCx, ADC_IT_EOC) == SET)
  6.                 resurt=ADC_GetConversionValue(ADCx);
  7.         /* 清除中断标志 */
  8.         ADC_ClearITPendingBit(ADCx, ADC_IT_EOC);
  9. }
复制代码

6.主函数

主函数负责接收转换的值,并将其转换为电压值,然后通过串口打印在计算机上,便于调试。
变量result是主函数中的全局变量,注意最后的结果应该转换为浮点型。




收藏 3 评论2 发布时间:2020-9-21 13:00

举报

2个回答
乐天乐 回答时间:2020-9-21 16:39:22
非常详细,注入通道和规则通道可以设定成同一路吗?就是平时是规则转换,一唤酣就进入注入通道?
乐天乐 回答时间:2020-9-21 16:43:23
同一通道为什么分布成ADC1\ADC2\ADC3三个位置?

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版