STM32笔记(1)——ADC模数转换器原理及单双通道转换详解

一.ADC 模数转换器

1.1 ADC、DAC、PWM

ADC(Analog-Digital Converter),意即模拟-数字转换器,简称模数转换器。ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。

DAC:数字到模拟的桥梁(PWM控制灯的亮度和电机旋转的速度,DAC的使用只要是在信号发生器、音频解码芯片等
PWM:数字到模拟的桥梁,例如PWM控制灯的亮度和电机旋转的速度,PWM只有完全导通和完全断开两种状态,在这两种状态都没有功率损耗,故直流电机调速这种大功率的应用场景,使用PWM来等效模拟量,是比DAC更好的选择,PWM电路更简单,更常用。

1.2 12位逐次逼近型ADC,1us转换时间

  • STM32中的ADC是一个12位的逐次逼近型的ADC,最快转换时间1us。“逐次逼近”是ADC的一种工作模式;“12位”指ADC的分辨率,12位AD值的表示范围即0 ∼ 212− 1 ,量化值为0 ∼ 4095 ,位数越高,量化结果越精细,对应ADC的分辨率就越高;转换时间1us对应的频率就是1MHz,这就是STM32中ADC的最快转换频率。
  • STM32中的ADC的输入电压范围为0 ∼ 3.3V,对应的转换结果就是0 ∼ 4095。ADC的输入电压一般要求都是要在芯片供电的正负极之间变化且电压和转换结果都是一一对应的线性关系。
  • 1.3 ADC有18个输入通道

  • 可以测量16个外部信号和2个内部信号源。外部的16个信号源即16个GPIO口(可能来自不同的GPIOx,具体要参考引脚定义),在引脚上直接输入模拟信号即可,不需要额外的测量电路;
  • 2个内部信号源分别是内部温度传感器和内部参考电压,温度传感器可以测量CPU的温度,内部参考电压是一个1.2V左右的内部基准电压,且这个内部基准电压是不随外部供电电压变化的。当芯片的供电电压不是准确的3.3V时,就可以通过这个内部的基准电压进行校准来得到正确的电压值。
  • 采用STM32C8T6只有 ADC1、ADC2,10个外部输入通道,即最多只能测量10个外部引脚的模拟信号。
  • 1.4 规则组和注入组两个转换单元

    普通的AD转换流程为:先启动一次转换,之后读值,依次循环。
    STM32的ADC可以将要转换的通道列为一组,每一次连续转换多个值。

  • 规则组用于常规使用,注入组一般用于突发事件。

  • 一般可以用于测量光线强度、温度这些值
    如果光线高于某个预值、低于某个预值,或者温度高于某个预值、低于某个预值时,就会执行一些操作

  • 模拟开门狗可以监测指定的某些通道,当AD值高于他设定的上域值,或者低于下域值时。就会申请中断,然后就可以在中断函数里,执行相应的操作。

  • 1.5 逐次逼近型ADC

    下图所示的即为逐次逼近型ADC的工作原理图(ADC0809)。STM32中的ADC的结构与此类似

  • IN0~IN7是八个输入通道
    通过配置ADDA~ADDC可以选择一个通道作为信号输入
    通过比较器, DAC逐渐逼近输入信号, DAC的值最终与输入信号十分接近
  • 结构图上方的EOC(End Of Convert)是转换结束信号。该芯片通过START端口控制转换开始,CLOCK控制ADC内部的转换工作频率。VREF(+) 和VREF(-)是DAC的参考电压,定义数据对应的电压范围(255对应3.3V或5V)。通常芯片的工作电压正极VCC 和VREF 相同,接在一起;通常芯片的工作电压负极GND和VREF(-) 相同,接在一起。
  • 1.6 STM32中的ADC框图

    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端口重映射来配置)
    4. 这里ADC的时钟ADCCLK是来自于RCC的APB2时钟。由原理图可得,ADCCLK最大为14MHz,所以ADC预分频器只能选择6分频(得到12MHz)和8分频(得到9MHz)两个值
    5. ADC可以通过DMA请求信号触发DMA转运数据
    6. 模拟看门狗的功能是监测指定的通道。可以设置模拟看门狗的阈值高限(12位)、阈值底限(12位)和指定“看门”的通道。只要通道的电压值超过阈值范围,模拟看门狗就会“乱叫”,申请一个模拟看门狗的中断,之后通向NVIC。
    7. 规则组和注入组在转换完成后会生成一个转换完成的信号。EOC为规则组转换完成的信号,JEOC为注入组转换完成的信号。这两个信号会在状态寄存器中置一个标志位,我们通过读取状态寄存器,就可以知道转换是否完成了。同时这两个标志位也可以通过配置通向NVIC申请中断。

    1.7 ADC基本结构

  • 左边是输入通道16个GPIO口外加2个内部的通道
  • AD数据寄存器:规则组只有1个数据计算器,注入组有4个
  • 触发控制:提供开始转换START信号,触发控制可以选择软件触发和硬件触发,硬件触发主要是来自于定时器,也可以选择外部中断的硬件
  • RCC的ADC时钟CLOCK:推动ADC逐次比较的过程
  • 模拟看门狗:监测转换结果的范围,如果超出设定的预值就通过中断输出控制,向NVIC申请中断
  • 1.8 输入通道

    通道 AD1 AD2 AD3
    通道0 PA0 PA0 PA0
    通道1 PA1 PA1 PA1
    通道2 PA2 PA2 PA2
    通道3 PA3 PA3 PA3
    通道4 PA4 PA4 PF6
    通道5 PA5 PA5 PF7
    通道6 PA6 PA6 PF8
    通道7 PA7 PA7 PF9
    通道8 PB0 PB0 PF10
    通道9 PB1 PB1
    通道10 PC0 PC0 PC0
    通道11 PC1 PC1 PC1
    通道12 PC2 PC2 PC2
    通道13 PC3 PC3 PC3
    通道14 PC4 PC4
    通道15 PC5 PC5
    通道16 温度传感器
    通道17 内部参考电压

    本节课程使用的STM32F103C8T6没有PC0到PC5的引脚,故也就不存在通道10到通道15。

    1.9 ADC的四种转换模式

    单次转换非扫描模式、连续转换非扫描模式、单次转换扫描模式、连续转换扫描模式

    1.9.1单次转换非扫描模式

  • 非扫描的模式下,只有第一个序列1的位置有效
  • 在序列1的位置,我们可以指定想转换的通道,比如通道2,写到序列1的位置然后触发转换,ADC就会对这个通道2进行模数转换,转换完成后,转换结果放在数据计算器里,同时给EOC标志位置1,转换结束
  • 通过判断这个EOC标志位是否转换完成,若完成,就可以在数据寄存器里读结果
  • 若想再启动一次转换,需要再触发一次,转换结束至EOC标志位读结果
  • 若想换一个通道转换,在转换之前,把第一个位置的通道2改成其他通道,然后再启动转换就行了
  • 1.9.2 连续转换非扫描模式

  • 非扫描模式:菜单列表就只用第一个
  • 连续转换:在一次转换结束后不会停止,而是立刻开始下一轮的转换,然后一直持续下去,只需要最开始触发一次,就可以一直转换
  • 读取的时候不用判断是否结束,直接从数据寄存器读即可
  • 1.9.3 单次转换扫描模式

  • 单次转换:每触发一次,转换结束后就会停下来,下次转换需要再触发
  • 扫描模式:会用到这个菜单列表了,通道几(相当于菜)可以任意指定,且可以重复,初始化结构体有个参数表示通道数目(比如7个),说明只需要用多少序列
  • 为了防止数据被覆盖,用DMA及时将数据挪走,7个通道转换完成之后产生EOC信号,转换结束
  • 然后再触发,重新开始
  • 1.9.4 连续转换扫描模式


    一次转换完成后,立刻开始下一次的转换
    在扫描模式下还可有一种模式——间断模式,作用是在扫描过程中每隔几个转换,就暂停一次,需要再次触发才能继续

    1.10 细节之处

    1.10.1 触发控制

    此表为规则组的触发源,也就是上图第2部分,有来自定时器的信号、引脚或定时器的信号(具体是引脚和定时器,需要用AFIO重映射来确定)最后的软件控制位就是软件触发。这些触发信号的选择,可以通过上图1设置表右边的寄存器完成,使用库函数的话只需一个函数给个参数即可。

    1.10.2 数据对齐

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

    1.10.3 转换时间(AD转换很快,一般忽略)

  • AD转换的步骤: 采样, 保持, 量化, 编码
  • STM32 ADC的总转换时间为:
    Tconv = 采样时间 + 12.5个ADC周期
    (花费12个ADC周期进行量化和编码,多余的0.5个周期完成了其他的工作)
  • 最短的转换时间:
    例如:当ADCCLK = 14MHz, 采样时间为1.5个ADC周期
    ​ Tconv = 1.5 + 12.5 = 14个ADC周期 = 1us
  • 采样时间越大,越能避免一些毛刺信号的干扰,不过转换时间也会相应延长
  • 1.10.4 校准

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

    二. 硬件电路

    ADC外围电路的设计给出以下三个电路图:

    图1:电位器产生一个可调的电压的电路

    中间的滑动端可以输出一个0~3.3伏可调的电压输出来,上滑时电压增大,下滑时电压减小,若阻值太小,电阻就会比较费电

    图2:分压方法输出传感器组织的电路

  • 传感器输出电压的电路,例如光敏电阻、热敏电阻、红外接头管、麦克风等都可以等效为一个可变电阻
  • 那电阻阻值得通过和一个固定电阻串联分压,当传感器阻值变大时,下拉作用变弱,输出端受上拉电阻的作用,电压就会升高
  • 固定电阻建议选择和传感器阻值相近的电阻,才可以得到一个位于中间电压区域比较好的输出
  • 此处传感器和固定电阻的位置也调换,输出电压的极性就反过来了
  • 图3:简单的电压转换电路

    想测一个0-5V的VIN电压,到那时ADC只能接收0~3.3V的电压,就可以搭建此类电路。使用电阻分压,上面阻值17K,下面阻值33K,加一起50K,中间的电压就是VIN/50K*33K,得到的电压范围就是0-3.3伏,就可以进入ADC转换了。想要其他范围(如5V、10V)的VIN电压可类似操作,如果电压过高就不建议使用这种电路了,可能比较危险,高电压采集最好使用专用芯片,比如隔离放大器等,做到高低电压隔离保证电路安全。

    三. ADC 常用库函数

    3.1 ADC的RCC时钟配置函数

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

    1.void RCC_ADCCLKConfig(uint32_t RCC_PCLK2)
    

    3.2 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);
    

    3.3 配置的参数

    (1)双ADC工作模式选择,ADC_Mode,其中分别为:

    #define ADC_Mode_Independent                       ((uint32_t)0x00000000)//独立模式
    #define ADC_Mode_RegInjecSimult                    ((uint32_t)0x00010000)//同步规则和同步注入模式
    #define ADC_Mode_RegSimult_AlterTrig               ((uint32_t)0x00020000)//同步规则和交替触发模式
    #define ADC_Mode_InjecSimult_FastInterl            ((uint32_t)0x00030000)//同步注入和快速交叉模式
    #define ADC_Mode_InjecSimult_SlowInterl            ((uint32_t)0x00040000)//同步注入和慢速交叉模式
    #define ADC_Mode_InjecSimult                       ((uint32_t)0x00050000)//同步注入模式
    #define ADC_Mode_RegSimult                         ((uint32_t)0x00060000)//同步规则模式
    #define ADC_Mode_FastInterl                        ((uint32_t)0x00070000)//快速交叉模式
    #define ADC_Mode_SlowInterl                        ((uint32_t)0x00080000)//慢速交叉模式
    #define ADC_Mode_AlterTrig                         ((uint32_t)0x00090000)//交替触发模式
    

    (2)扫描模式选择,ADC_ScanConvMode,ENABLE表示多通道扫描模式,否则为单通道。
    (3)连续转换模式选择,ADC_ContinuousConvMode,ENABLE表示连续转换模式,否则为单次转换模式。
    (4)ADC转换触发方式选择,ADC_ContinuousConvMode,其中ADC_ExternalTrigConv_None为不使用外部触发,即使用软件触发方式。
    (5)ADC转换数据对齐方式选择,ADC_DataAlign,可以左对齐或右对齐
    (6) 顺序进行规则转换的ADC通道的数目设置,ADC_NbrOfChannel,可以设置1~16个通道

    3.4 ADC间断模式配置

    // 下面两个函数用来配置STM32中ADC的间断模式
    // 配置每隔几个通道间断依次
    void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
    // 开启间断模式
    void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
    

    四. 程序示例

    4.1 ADC单通道转换

    1.AD.c(单次转换非扫描)

    #include "stm32f10x.h"                  // Device header
    void AD_Init(void)
    {
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	
    	//配置ADCCLK分频器,对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCLK
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//分频后等于72MHz/6=12MHz
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入(ADC的专属模式)在AIN,GPIO口无效,断开GPIO口,防止GPIO口的输入输出对模拟电压造成干扰
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	//ADC规则组通道配置,给序列的每个位置填写指定的通道,就是填写点菜菜单的过程
    	//第一个参数是ADCx,第二个是ADC指定的通道(通道0-17)
    	//第三个是写在序列几的位置,然后第四个指定通道的采样时间
    	//ADC_SampleTime_55Cycles5表示55.5个ADCCLK的周期
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    	//ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 2, ADC_SampleTime_55Cycles5);
    	//ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 3, ADC_SampleTime_55Cycles5);
    	//通道可以重复,序列不要重复,需要的话可以多写几个,这是填充菜单的方法
    	
    	ADC_InitTypeDef ADC_InitStructure;
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//工作模式,独立模式:ADC1和ADC2各转各的
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
    	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//不使用外部触发转换,即软件触发
    	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//单次  //ENABLE:连续转换
    	ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描
    	ADC_InitStructure.ADC_NbrOfChannel = 1;//总共需要扫描多少个通道
     
    	ADC_Init(ADC1, &ADC_InitStructure);
    	
    	//中断和看门狗如果需要可以在此处定义
    	
    	//ADC上电
    	ADC_Cmd(ADC1, ENABLE);
    	
    	ADC_ResetCalibration(ADC1);//复位校准
    	//为1时,开始复位校准,复位校准完后,该位就会由硬件自动清0
    	while (ADC_GetResetCalibrationStatus(ADC1) == SET);//等待复位校准完成
    	ADC_StartCalibration(ADC1);//开始校准
    	while (ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成
    	/*ADC_SoftwareStartConvCmd(ADC1, ENABLE);	//软件触发(连续转换只需要初始化一次即可,所以软件触发的函数可以挪到初始化函数) */
    }
     
    uint16_t AD_GetValue(void)
    {
    	// 1. 软件触发开启转换
    	ADC_SoftwareStartConvCmd(ADC1, ENABLE);	//软件触发
    	// 2. 等待转换完成(获取标志位状态,等待EOC标志位置1)/*连续转换非扫描:不需要判断标志位while这句可删掉
    	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//转换结束,EOC置1// 转换未完成则等待(55.5T + 12.5T = 68T,结果大概为5.6us)
    	// 3. 读取ADC数据寄存器并返回
    	return ADC_GetConversionValue(ADC1);// 读取之后会自动清除EOC标志位
    }
    

    2、改为连续转换非扫描

    好处:无需不断触发,不需要等待转换完成

  • ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续
  • 连续转换只需要初始化一次即可,所以软件触发的函数可以挪到初始化函数最后ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发 在初始化完成后触发一次即可
  • 且在AD_GetValue函数中,不需要判断标志位
    while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//转换结束,EOC置1 这一句可以删除
    程序如上图所示
  • 3、main.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "AD.h"
    
    /*
    电位器即滑动变阻器,用电位器产生0~3.3V连续变化的模拟电压信号,然后接到STM32的PA0口上,
    之后用STM32内部的ADC读取电压数据,显示在屏幕上                           
    屏幕第一行:模拟数据                                                                                                       
    屏幕第二行:处理过后显示的电压值
    往左拧电位器,AD值减小,对应的电压减小,反之则反
    ADC是12位的,AD结果最大值是4095,也就是2^12-1,对应的电压是3.3V
    GPIO只能读取高低电平 ,而ADC可以对高低电平之间的任意电压进行量化,最终用一个变量表示
    */
    uint16_t ADValue;
    float Voltage;
    int main(void)
    {
    	OLED_Init();
    	AD_Init();
    	OLED_ShowString(1, 1, "ADValue:");
    	OLED_ShowString(2, 1, "Volatge:0.00V");
    
    	while(1)
    	{
    		ADValue = AD_GetValue();
    		Voltage = (float)ADValue/4095*3.3;
    		//数字与电压映射关系,ADValue为uint16类型除以4095会舍弃小数部分,先强转为float
    		OLED_ShowNum(1,9,ADValue,4);
    		//目前的OLED没有显示浮点数的功能,但可用显示整数的借用
    		OLED_ShowNum(2,9,Voltage,1);//显示整数部分
    		OLED_ShowNum(2,11,(uint16_t)(Voltage*100)%100,2);//显示小数部分,浮点数不可%取余所以先强转为整型
    		Delay_ms(100);//限制OLED刷新速度
    
    	}
    	
    }
    

    4.2 ADC多通道转换

    1、思路

    在每次触发转换之前,手动更改一下列表第一个位置的通道
    比如第一次转换,在序列1先写入通道0,之后触发、等待、读值
    第二次转换,在序列1把通道0改成通道1,之后触发、等待、读值
    第三次转换,在序列1改成通道2等等

    2、AD.c

    #include "stm32f10x.h"                  // Device header
    
    //(单次转换非扫描)
    
    void AD_Init(void)
    {
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	
    	//配置ADCCLK分频器,对APB2的72MHz时钟选择2、4、6、8分频,输入到ADCCLK
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//分频后等于72MHz/6=12MHz
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//模拟输入(ADC的专属模式)在AIN,GPIO口无效,断开GPIO口,防止GPIO口的输入输出对模拟电压造成干扰
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	ADC_InitTypeDef ADC_InitStructure;
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//工作模式,独立模式:ADC1和ADC2各转各的
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐
    	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//不使用外部触发转换,即软件触发
    	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//单次  //ENABLE:连续转换
    	ADC_InitStructure.ADC_ScanConvMode = DISABLE;//非扫描
    	ADC_InitStructure.ADC_NbrOfChannel = 1;//总共需要扫描多少个通道
     
    	ADC_Init(ADC1, &ADC_InitStructure);
    	
    	//中断和看门狗如果需要可以在此处定义
    	
    	//ADC上电
    	ADC_Cmd(ADC1, ENABLE);
    	
    	ADC_ResetCalibration(ADC1);//复位校准
    	//为1时,开始复位校准,复位校准完后,该位就会由硬件自动清0
    	while (ADC_GetResetCalibrationStatus(ADC1) == SET);//等待复位校准完成
    	ADC_StartCalibration(ADC1);//开始校准
    	while (ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成
    	/*ADC_SoftwareStartConvCmd(ADC1, ENABLE);	//软件触发(连续转换只需要初始化一次即可,所以软件触发的函数可以挪到初始化函数) */
    }
     
    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这句可删掉
    	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//转换结束,EOC置1// 转换未完成则等待(55.5T + 12.5T = 68T,结果大概为5.6us)
    	// 3. 读取ADC数据寄存器并返回
    	return ADC_GetConversionValue(ADC1);// 读取之后会自动清除EOC标志位
    }
    

    3、main.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "AD.h"
    
    /*
    DO是数字输出
    AO是模拟量输出
    */
    uint16_t AD0,AD1,AD2,AD3;//表示四个ADC输入通道的转换结果的接收变量
    
    int main(void)
    {
    	OLED_Init();
    	AD_Init();
    	OLED_ShowString(1, 1, "AD0:");
    	OLED_ShowString(2, 1, "AD1:");
    	while(1)
    	{
    		AD0 = AD_GetValue(ADC_Channel_0); 
    		AD1 = AD_GetValue(ADC_Channel_1); 
    		//根据需要也可设置映射关系,再进行显示
    		
    		OLED_ShowNum(1,5,AD0,4);
    		OLED_ShowNum(2,5,AD1,4);
    		Delay_ms(100);//限制OLED刷新速度
    
    	}
    	
    }
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32笔记(1)——ADC模数转换器原理及单双通道转换详解

    发表评论