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

​  本次课程采用单片机型号为STM32F103C8T6。
​  课程链接:江科大自化协 STM32入门教程


  往期笔记链接:
  STM32学习笔记(一)丨建立工程丨GPIO 通用输入输出
  STM32学习笔记(二)丨STM32程序调试丨OLED的使用
  STM32学习笔记(三)丨中断系统丨EXTI外部中断
  STM32学习笔记(四)丨TIM定时器及其应用(定时中断、内外时钟源选择)
  STM32学习笔记(五)丨TIM定时器及其应用(输出比较丨PWM驱动呼吸灯、舵机、直流电机)
  STM32学习笔记(六)丨TIM定时器及其应用(输入捕获丨测量PWM波形的频率和占空比)
  STM32学习笔记(七)丨TIM定时器及其应用(编码器接口丨用定时器实现编码器测速)


一、ADC 模数转换器

1.1 ADC简介

  ADC(Analog-Digital Converter),意即模拟-数字转换器,简称模数转换器。ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁与ADC相对应,从数字电路到模拟电路的桥梁即DAC(Digital-Analog Convertor),数模转换器。但是STM32中没有DAC外设,所以这里仅作了解即可。

  DAC不是唯一可以实现将数字量转换为模拟量功能的外设,PWM波形同样实现了用数字量对模拟量进行编码的操作。PWM只有完全导通和完全断开两种状态,故其不存在静态功耗。所以在直流电机调速等大功率的引用场景,使用PWM来对模拟量进行编码是比使用DAC的更好的选择。目前DAC的应用场景通常在波形发生领域,例如信号发生器、音频解码芯片等。

  STM32中的ADC是一个12位的逐次逼近型的ADC,最快转换时间1us。“逐次逼近”是ADC的一种工作模式;“12位”指ADC的分辨率,12位AD值的表示范围即 0 ∼ 2 12 − 1 0\sim2^{12}-1 02121,量化值为 0 ∼ 4095 0\sim4095 04095,位数越高,量化结果约精细,对应ADC的分辨率就越高;转换时间1us对应的频率就是1MHz,这就是STM32中ADC的最快转换频率。
  STM32中的ADC的输入电压范围为 0 ∼ 3.3 V 0\sim3.3V 03.3V,对应的转换结果就是 0 ∼ 4095 0\sim4095 04095。ADC的输入电压一般要求都是要在芯片供电的正负极之间变化的。且电压和转换结果都是一一对应的线性关系。
  STM32中的ADC有18个输入通道,可以测量16个外部信号和2个内部信号源。外部的16个信号源即16个GPIO口(可能来自不同的GPIOx,具体要参考引脚定义),在引脚上直接输入模拟信号即可,不需要额外的测量电路;2个内部信号源分别是内部温度传感器和内部参考电压,温度传感器可以测量CPU的温度,内部参考电压是一个1.2V左右的内部基准电压,且这个内部基准电压是不随外部供电电压变化的。当芯片的供电电压不是准确的3.3V时,就可以通过这个内部的基准电压进行校准来得到正确的电压值。
  STM32还拥有两个增强功能:拥有规则组注入组两个转换单元。普通的AD转换流程为:先启动一次转换,之后读值,依次循环。STM32的ADC可以将要转换的通道列为一组,每一次连续转换多个值。规则组用于常规使用,注入组一般用于突发事件。
  STM32中还拥有一个模拟看门狗(AWD),可以自动监测输入电压范围。STM32的ADC一般可以用于测量光线强度、温度。这里的模拟看门狗实现的功能是:监测温度(光强),当其高于某个阈值,或低于某个阈值时,申请中断,之后可以在中断函数中执行相应的操作。

  本节课程使用的STM32F103C8T6的ADC资源为:ADC1、ADC2,10个外部输入通道,即最多只能测量10个外部引脚的模拟信号。上文中提及的16个外部信号源指这个系列最多拥有16个外部信号源。每个芯片型号对应的通道数量可以参考手册。

1.2 逐次逼近型ADC工作原理

  下图所示的即为逐次逼近型ADC的工作原理图(ADC0809)。STM32中的ADC的结构与此类似,学习下面的结构可以帮助我们理解STM32中ADC的工作原理。
在这里插入图片描述

  ADC0809是一个独立的8位逐次逼近型ADC芯片。它拥有IN0~IN7,8个输入通道,通过地址锁存器和译码器电路实现对通道的选择,且每一次转换通道选择开关只能转换一个通道的信号。ADC转换的速度非常快,从信号转换开始到结束只需要几个us的时间,如果想转换多路信号,不必设计多个ADC,只需要在每一次转换前通过多路选择开关选择要转换的通路即可。 STM32的ADC拥有18个输入通道,与这里的8个输入通道的结构相对应。
  ADC内部拥有一个DAC模块,其内部是通过加权电阻网络实现模数转换,可以将逐次逼近寄存器SAR的值转换为对应的模拟电压值,将其电压值再与待测电压相比较,比较结果控制SAR中存储的值,直到DAC输出的电压与外部通道输入的电压近似相等,DAC输入的数据就是外部电压的编码数据了。为了最快找到未知编码的电压,通常使用二分法进行查找。且使用二分法查找未知电压的编码的好处在于:每次选择比较的值( 2 n 2^{n} 2n)恰好为对应二进制数字的每一位的权数,判断过程相当于,从高位到底位依次判断为1还是为0的过程。要找到未知编码的电压,8位ADC需要判断8次,12位ADC需要判断12次。转换结束后,ADC的输入数据就是未知电压的编码,通过8位数字输出端口(D0~D7)进行输出
  结构图上方的EOC(End Of Convert)是转换结束信号。该芯片通过START端口控制转换开始,CLOCK控制ADC内部的转换工作频率。 V R E F ( + ) V_{REF(+)} VREF(+) V R E F ( − ) V_{REF(-)} VREF()是DAC的参考电压,定义数据对应的电压范围(255对应3.3V或5V)。通常芯片的工作电压正极 V c c V_{cc} Vcc V R E F ( + ) V_{REF(+)} VREF(+)相同,接在一起;通常芯片的工作电压负极 G N D GND GND V R E F ( − ) V_{REF(-)} VREF()相同,接在一起。

1.3 STM32中的ADC基本结构

在这里插入图片描述
  STM32中ADC的结构框图如上图所示。其“模拟至数字转换器”模块的工作模式与ADC0809在原理上完全相同。不同点有以下几点:

  1. 普通的ADC多路开关一般只选中一个,STM32的ADC可以同时选中多个通道进行转换,规则组最多同时选中16个通道,注入组一次最多可以选中4个通道。(以餐厅点菜模型为例,普通模式为每次点一个菜,做好菜后上菜;STM32可以做到每次列出一个菜单,规则组一次最多可以列16个菜,注入组一次最多可以列4个菜,做好后依次上菜)

  2. STM32中的ADC的转换结果会被存储在对应的数据寄存器中。对于规则组通道,其只有一个数据寄存器(餐桌上只能摆一个菜),后转换的数据会将之前转换的数据覆盖,之前转换的数据就会丢失。对于规则组通道,要想实现同时转换的功能,最好配合DMA来将转换后的数据及时转运,就可以保证转换的数据不会丢失了。对于注入组通道,它拥有4个数据寄存器(餐厅的VIP坐席,餐桌上一次可以摆四个菜)。对于注入组而言,就不用担心数据覆盖的问题了。一般情况下,使用规则组和DMA就可以满足大部分的使用需求。(这里只讲解规则组使用,注入组自行了解即可)

  3. 结构图的左下角为触发转换信号,对应ADC0809的START信号。STM32的触发转换信号来源有两种:软件触发和硬件触发。硬件触发信号可以来自于定时器的各个通道、定时器TRGO主模式的输出,外部中断EXTI。下表列出了ADC1和ADC2的触发源。(其中EXTI线11/TIM8_YRGO事件的选择需要使用AFIO端口重映射来配置)
    在这里插入图片描述

      定时器可以通过主模式输出TRGO控制ADC、DAC等外设,用于通过硬件电路自动触发转换。ADC经常需要过一个固定的时间段转换一次,例如可以每隔1ms转换一次。通过定时器,每隔1ms申请一次中断,在中断函数中手动通过软件触发开启一次转换也可以实现功能,但是频繁进中断会对主程序造成一定的影响。并且在不同的中断之间,由于优先级的不同,有可能会使某些中断不能及时得到响应。如果触发ADC的中断没有及时得到响应,那么ADC的转换频率就肯定会受影响了。所以对于这种需要频繁进中断,但是在中断函数中只完成了简单工作的情况,一般都会有硬件电路的支持

  4. 在STM32中, V R E F ( + ) V_{REF(+)} VREF(+)一般和 V D D A V_{DDA} VDDA(ADC模块的正极供电引脚)接在一起, V R E F ( − ) V_{REF(-)} VREF()一般和 V S S A V_{SSA} VSSA(ADC模块的负极供电引脚)接在一起。本课程使用的芯片没有单独的 V R E F ( + ) V_{REF(+)} VREF(+) V R E F ( − ) V_{REF(-)} VREF()的引脚,它在芯片内部就已经和对应引脚连接在一起了。( V D D A V_{DDA} VDDA V S S A V_{SSA} VSSA是STM32模拟部分的电源,例如ADC、RC振荡器、锁相环等,在套件中的最小系统板中已经将 V D D A V_{DDA} VDDA与3.3V、 V S S A V_{SSA} VSSA与GND相连接了)。

  5. 这里ADC的时钟ADCCLK是来自于RCC的APB2时钟。由原理图可得,ADCCLK最大为14MHz,所以ADC预分频器只能选择6分频(得到12MHz)和8分频(得到9MHz)两个值

  6. ADC可以通过DMA请求信号触发DMA转运数据。(这部分内容会在下一小节STM32学习笔记(九)中涉及)

  7. 模拟看门狗的功能是监测指定的通道。可以设置模拟看门狗的阈值高限(12位)、阈值底限(12位)和指定“看门”的通道。只要通道的电压值超过阈值范围,模拟看门狗就会“乱叫”,申请一个模拟看门狗的中断,之后通向NVIC。

  8. 规则组和注入组在转换完成后会生成一个转换完成的信号。EOC为规则组转换完成的信号,JEOC为注入组转换完成的信号。这两个信号会在状态寄存器中置一个标志位,我们通过读取状态寄存器,就可以知道转换是否完成了。同时这两个标志位也可以通过配置通向NVIC申请中断。

在这里插入图片描述

1.4 STM32中ADC的输入通道

  由ADC的内部结构可知,STM32的ADC对应16个输入通道。这16个输入通道对应的GPIO端口如下表所示:
在这里插入图片描述
  上表展示了ADC通道和引脚复用之间的连接关系,这个对应关系也可以参考引脚定义表。可以看到,只有ADC1拥有温度传感器和内部参考电压的采样通道。ADC1和ADC2的引脚完全相同,ADC3有些是存在变化的。本节课程使用的STM32F103C8T6没有PC0到PC5的引脚,故也就不存在通道10到通道15。

  参考引脚定义表可以看到,STM32的ADC1和ADC2的引脚是相同的。这样的设计是为双ADC模式服务的。关于双ADC模式的内容比较复杂,这里仅作简单了解即可。双ADC模式,即ADC1和ADC2同时工作,二者可以配合为同步模式、交叉模式等多种不同的工作模式。以交叉模式为例,ADC1和ADC2交叉对同一个通道进行采样,这样就可以进一步提高采样率。

1.5 STM32中的ADC的四种转换模式

  STM32中的ADC的转换模式有以下四种:

  • 单次转换非扫描模式
    在这里插入图片描述

  • 连续转换非扫描模式
    在这里插入图片描述

  • 单次转换扫描模式
    在这里插入图片描述

  • 连续转换扫描模式
    在这里插入图片描述

  ADC的16个序列就相当于一个“菜单”,在序列中可以填入不同的通道。在非扫描模式下,这个“菜单”就只对存放在序列1的通道起作用, 这时“菜单”中可以选中一组的方式就退化为简单地选中一个的模式了。 在每次转换开始前可以对序列中的通道进行更改。在扫描模式下,“菜单”将发挥作用。每次转换开始后,依次对定义的通道进行转换,并且将转换结果放在数据寄存器中。这里为了方式数据被覆盖导致丢失,就需要使用DMA及时将数据移走。
  在单次转换模式中,需要手动触发转换。 ADC就会对序列中的通道进行转换,转换完成后,将转换结果储存在数据寄存器中,同时将EOC标志位置1,转换过程结束。在连续转换模式中,一次转换完成后不会停止,而是立刻开始下一轮的转换,并持续下去。这样就可以在最开始时触发一次,之后就可以一直转换了。这个模式的好处在于:开始转换后不需要等待,因为其一直都在转换,所以也不需要手动开启转换了,也不用判断转换是否结束。需要读取AD值时,直接在数据寄存器中读取即可。
  在扫描模式的情况下,还可以使用间断模式。它的作用是在扫描的过程中,每隔几次转换就暂停,需要再次触发才能继续。该模式仅作了解即可。

1.6 使用ADC时的一些细节讨论

1.6.1 数据对齐

在这里插入图片描述
  STM32中的ADC是12位的,但是数据寄存器拥有16位,故存在数据对齐的问题。数据右对齐,即作为转换结果的12位数据向右靠,高位补0;数据左对齐,即作为转换结果的12位数据向左靠,低位补0。在使用时通常使用数据右对齐,这样在读取时直接读取寄存器即可。如果选择左对齐直接读取,得到的数据会比实际的数据大16倍。当对分辨率的要求不高时(对电压仅作大概的判断即可)可以采用左对齐,将数据寄存器的高8位取出,就相当于舍弃了转换结果的4位的精度,12位的ADC退化位为8位的ADC。

1.6.2 转换时间

  AD的转换时间是一个很短的时间,如果不需要极高的转换频率,那么转换时间是可以忽略的。那么转换时间具体是多少呢?
  AD转换的步骤分别是:采样、保持、量化、编码。其中采样和保持可以看作一个过程,量化和编码可以看作一个过程。量化和编码实际上就是ADC逐次比较的过程,一般ADC的位数越多,所花费的时间就越长。采样和保持是为了保证在量化和编码的过程中输入电压的变化不会过大。在量化和编码之前,需要添加采样-保持电路,即需要设置一个采样开关,打开开关一段时间来收集电压(可以用一个小容量的电容来存储这个电压),存储完成之后断开开关,再进行之后的AD转换。这样就可以保证在量化和编码器件始终保持电压基本不变。这个采样时间是比较长的。所以AD转换所花费的时间可以分为:采样-保持电路的采样时间 + 量化和编码花费的时间。

  • STM32 ADC总转换时间 = 采样时间 + 12.5个ADC周期

  ADC的采样时间可以在程序中进行配置。之后花费12个ADC周期进行量化和编码,多余的0.5个周期完成了其他的工作。
  最短的转换时间:当ADCCLK = 14MHz,采样时间为1.5个ADC周期时:
T C O N V = ( 1.5 + 12.5 ) ⋅ T A D C = 14 T A D C = 1 μ s T_{CONV}=(1.5 + 12.5)\cdot T_{ADC} = 14T_{ADC}=1\mu s TCONV=(1.5+12.5)TADC=14TADC=1μs
  当然,可以通过设置将ADC的转换频率超过14MHz,这样ADC就会工作在超频状态下。超频时转换时间可能会更短,不过电路的稳定性将无法保证。

1.6.1 ADC校准

  ADC有一个固定的内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。建议在每次上电后执行依次校准,且启动校准前,ADC必须处于关电状态超过至少两个ADC周期
  ADC的校准详细过程不需要掌握,在使用时在ADC初始化最后加几行固定的代码即可。

二、ADC 实际应用示例

2.1 ADC 常用库函数

  1. ADC的RCC时钟配置函数

  该配置函数定义存放在stm32f10x_rcc.h文件中,用来配置ADCCLK分频器。它可以对APB2的72MHz时钟选择2、4、6、8分频,输出到ADCCLK。

void RCC_ADCCLKConfig(uint32_t RCC_PCLK2)
  1. ADC设置
// 恢复ADC缺省配置
void ADC_DeInit(ADC_TypeDef* ADCx);
// ADC初始化
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
// ADC配置结构体初始化
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);

// ADC上电工作函数,即开关控制函数
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// ADC开启DMA输出信号
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// ADC中断输出控制函数
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);

// 下面4个函数用于ADC工作前的校准操作,在ADC初始化完成后依次调用即可
// ADC复位校准
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
// ADC获取复位校准状态
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
// ADC开始校准
void ADC_StartCalibration(ADC_TypeDef* ADCx);
// ADC获取开始校准状态
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);

// ADC软件触发转换,给CR2的SWSTART置1(开始转换后立即自动清0)
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC获取软件触发状态,获取CR2的SWSTART(开始转换规则通道)位
// 不能用它判断转换是否结束,一般不用,了解即可
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);

// ADC规则组通道配置,给转换序列的每个位置填写指定的通道
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);

// ADC外部触发转换控制(是否允许外部触发转换)
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

// ADC获取转换值,获取AD转换的数据寄存器
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

// ADC获取双模式转换值,读取双ADC模式下ADC的转换结果
uint32_t ADC_GetDualModeConversionValue(void);

// ADC温度传感器、内部参考电压控制,开启内部的两个转换通道
void ADC_TempSensorVrefintCmd(FunctionalState NewState);

// 下面的函数与操作标志位寄存器状态有关
// ADC获取标志位状态,可通过获取EOC标志位判断转换是否结束
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);
  1. 模拟看门狗配置(本节暂不涉及,需要可以了解)
// 对模拟看门狗进行配置
// 是否启动模拟看门狗
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);
  1. 注入组相关配置函数(本节暂不涉及,需要可以了解)
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);
  1. ADC间断模式配置
// 下面两个函数用来配置STM32中ADC的间断模式
// 配置每隔几个通道间断依次
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
// 开启间断模式
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

2.2 ADC 单通道转换

在这里插入图片描述

  • AD.c
#include "stm32f10x.h"                  // Device header

/**
  * @brief  ADC初始化函数(软件触发,且这里不使用模拟看门狗和中断)
  * @param  无
  * @retval 无
  */
void AD_Init(void)
{
    
    
	// 1. RCC开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);	// ADCCLK = 72MHz / 6 = 12MHz
	
	// 2. 配置GPIO
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// 3. 将指定的GPIO端口接入规则组列表中
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);	// 把通道0填入序列1中,通道的采样周期是55.5个ADCCLK的周期
	
	// 4. 配置ADC
	ADC_InitTypeDef ADC_InitStruct;
	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;						// ADC模式(独立模式或双ADC模式):独立模式						
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;					// ADC数据对齐:右对齐
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	// ADC外部触发源选择:不使用外部源触发(这里使用软件触发)
	ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;					// ADC连续转换模式:单次转换
	ADC_InitStruct.ADC_ScanConvMode = DISABLE;							// ADC扫描模式:非扫描
	ADC_InitStruct.ADC_NbrOfChannel = 1;								// 扫描模式下通道的数量
	ADC_Init(ADC1, &ADC_InitStruct);
	
	/*	中断和模拟看门狗在此配置	*/
	
	// 5. 开关控制
	ADC_Cmd(ADC1, ENABLE);
	
	// 6. 对ADC进行校准
	ADC_ResetCalibration(ADC1);								// 复位校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);		// 等待复位校准完成
	ADC_StartCalibration(ADC1);								// 开始校准
	while (ADC_GetCalibrationStatus(ADC1) == SET);			// 等待校准完成
}

/**
  * @brief  ADC结果读取函数(软件触发)
  * @param  无
  * @retval 转换之后的结果
  */
uint16_t AD_GetValue(void)
{
    
    
	// 1. 软件触发开启转换
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	
	// 2. 等待转换完成(获取标志位状态,等待EOC标志位置1)
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);		// 转换未完成则等待(55.5T + 12.5T = 68T,结果大概为5.6us)
	
	// 3. 读取ADC数据寄存器并返回
	return ADC_GetConversionValue(ADC1);	// 读取之后会自动清除EOC标志位
}


  • main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

uint16_t AD_Value;
float Voltage;

int main()
{
    
    
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1, 1, "AD_Value:");
	OLED_ShowString(2, 1, "Voltage:0.00V");
	
	while(1)
	{
    
    
		AD_Value = AD_GetValue();
		Voltage = (float)AD_Value / 4095 * 3.3;			// 整数除以小数会舍弃小数部分
		
		OLED_ShowNum(1, 10, AD_Value, 4);
		OLED_ShowNum(2, 9, Voltage, 1);								// 显示整数部分
		OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2);	// 显示小数部分
		
		Delay_ms(100);
	}
}

2.3 ADC 多通道转换

在这里插入图片描述

  • AD.c
#include "stm32f10x.h"                  // Device header

/**
  * @brief  ADC初始化函数(单次转换非扫描实现多通道转换,软件触发,且这里不使用模拟看门狗和中断)
  * @param  无
  * @retval 无
  */
void AD_Init(void)
{
    
    
	// 1. RCC开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);	// ADCCLK = 72MHz / 6 = 12MHz
	
	// 2. 配置GPIO
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// 3. 将指定的GPIO端口接入规则组列表中
	/*	这里要在每一次转换前都更改转换列表中要转换的GPIO端口	*/
	
	// 4. 配置ADC
	ADC_InitTypeDef ADC_InitStruct;
	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;						// ADC模式(独立模式或双ADC模式):独立模式						
	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;					// ADC数据对齐:右对齐
	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	// ADC外部触发源选择:不使用外部源触发(这里使用软件触发)
	ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;					// ADC连续转换模式:单次转换
	ADC_InitStruct.ADC_ScanConvMode = DISABLE;							// ADC扫描模式:非扫描
	ADC_InitStruct.ADC_NbrOfChannel = 1;								// 扫描模式下通道的数量
	ADC_Init(ADC1, &ADC_InitStruct);
	
	/*	中断和模拟看门狗在此配置	*/
	
	// 5. 开关控制
	ADC_Cmd(ADC1, ENABLE);
	
	// 6. 对ADC进行校准
	ADC_ResetCalibration(ADC1);								// 复位校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);		// 等待复位校准完成
	ADC_StartCalibration(ADC1);								// 开始校准
	while (ADC_GetCalibrationStatus(ADC1) == SET);			// 等待校准完成
}

/**
  * @brief  ADC结果读取函数(单次转换非扫描实现多通道转换,软件触发)
  * @param  无
  * @retval 转换之后的结果
  */
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
    
    
	ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);	// 把通道作为参数填入序列1中,通道的采样周期是55.5个ADCCLK的周期
	
	// 1. 软件触发开启转换
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	
	// 2. 等待转换完成(获取标志位状态,等待EOC标志位置1)
	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);		// 转换未完成则等待(55.5T + 12.5T = 68T,结果大概为5.6us)
	
	// 3. 读取ADC数据寄存器并返回
	return ADC_GetConversionValue(ADC1);	// 读取之后会自动清除EOC标志位
}


  • main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

uint16_t AD0, AD1, AD2, AD3;

int main()
{
    
    
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1, 1, "AD0:");
	OLED_ShowString(2, 1, "AD1:");
	OLED_ShowString(3, 1, "AD2:");
	OLED_ShowString(4, 1, "AD3:");

	
	while(1)
	{
    
    
		AD0 = AD_GetValue(ADC_Channel_0);
		AD1 = AD_GetValue(ADC_Channel_1);
		AD2 = AD_GetValue(ADC_Channel_2);
		AD3 = AD_GetValue(ADC_Channel_3);
		
		OLED_ShowNum(1, 5, AD0, 4);
		OLED_ShowNum(2, 5, AD1, 4);
		OLED_ShowNum(3, 5, AD2, 4);
		OLED_ShowNum(4, 5, AD3, 4);

		Delay_ms(100);
	}
}


  原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、机器学习方面的学习笔记~

猜你喜欢

转载自blog.csdn.net/weixin_62179882/article/details/128767241
今日推荐