Wiki:知识&笔记 Wiki:知识&笔记
首页
  • 学习笔记

    • 《JavaScript教程》笔记
  • 嵌入式

    • STM32
  • 技术文档
归档
GitHub (opens new window)
首页
  • 学习笔记

    • 《JavaScript教程》笔记
  • 嵌入式

    • STM32
  • 技术文档
归档
GitHub (opens new window)
  • 基础

  • 底层&寄存器

  • HAL库外设

    • HAL库函数
    • HAL结构
    • GPIO各种点亮LED
    • 定时器
    • PWM控制LED颜色
    • 按键外部中断
    • ADC使用
      • ADC
      • 原理介绍
        • 比较与转换
      • 电路结构
      • 实践配置
        • CubeMX
        • 用到的库函数
        • 算法
        • 流程
    • 串口与DMA
    • FLASH 读写
    • IIC通信
    • SPI 通信
    • CAN
  • STM32
  • HAL库外设
2020-08-28
ADC
原理介绍
比较与转换
电路结构
实践配置
CubeMX
用到的库函数
算法
流程

ADC使用

# ADC

ADC(Analog to Digital Converter)是模数转换功能。在单片机中传输的信号均为数字信号,通过离散的高低电平表示数字逻辑的1和0,但是在现实的物理世界中只存在模拟信号,即连续变化的信号。将这些连续变化的信号——比如热,光,声音,速度通过各种传感器转化成连续的电信号,再通过ADC功能将连续的模拟信号转化成离散的数字信号给单片机进行处理。

本节将使用 ADC 测量 STM32 的电源电压与温度

# 原理介绍

ADC 的工作流程主要有三步:

  • 采样:是指对某一时刻的模拟电压进行采集
  • 比较:是指将采样的电压在比较电路中进行比较
  • 转换:是指将比较电路中结果转换成数字量

# 比较与转换

采样是比较简单的一步,而比较与转换的方法,STM32 采用的是逐次逼近法,在STM32F4中是12位逐次逼近型ADC (SAR-ADC) ,下面以一个信号在3位逼近法中的比较过程为例讲解比较过程

首先你要知道的是,在3位逼近法中,可以认为ADC在未转换之前的值是一个3位二进制数,这3位二进制数字存储的数值取决于这3位比较的出来的值:

采样到模拟信号的值之后:

  • 首先与内部参考电压 Vref 的 1/2 进行比较。发现大于其值,则将第一个标志位记为1;反之为0
    • 由于大于 1/2 Vref 值,所以下一个比较的值为 1/2 + 1/4 = 3/4 Vref
  • 然后与Verf 的 3/4 进行比较。发现小于其值,则将第二个标志位记为0
    • 由于小于 3/4 Vref 值,所以下一个比较的值为 3/4 - 1/8 = 5/8 Vref
  • 然后与Vref 的 5/8 进行比较。发现小于其值,则将第三个标志位记为0

所以输出的的结果为100,其对照的结果为 1/2 Vref。如果是12位逼近的方法,这样的过程需要经过12次,输出一串12位的二进制数,然后转化为数值,其完整流程如下:

一般 ADC 的位数越多则转换精度越高,但与此同时转换的速度也会变慢。此外,STM32 内部有一个校准电压VREFINT ,电压为1.2 V,当供电电压不为 3.3 V,可以使用内部的 VREFINT 通道采集1.2 V电压作为 Vref,以提高精度

明确两个概念

VREFINT 是 ADC 的内部参考电压,而 Vref 是在 ADC 进行读取的时候采取的标准电压值

一般来说STM32的ADC采用 Vcc 作为 Vref,但为了防止 Vcc 存在波动较大导致 Vref 不稳定,进而导致采样值的比较结果不准确,STM32 可以通过内部已有的参照电压 VREFINT 来进行校准,接着以 VREFINT 为参照来比较 ADC 的采样值,从而获得比较高的精度,VREFINT的电压为1.2 V

至于如何校准,请见下面的算法

# 电路结构

在开发板中有一个用于读取电池电压使用的电阻分压电路。由于电池提供的电源是24 V的高电压,而单片机引脚的耐压只有0~3.3 V,所以需要通过分压电路进行处理,并使用滤波和二极管限幅电路进行保护

该电路使用一个200 kΩ的电阻与一个22 kΩ的电阻进行分压,由图可见,ADC 采样接口的点位与两个电阻中间的电位相同,由于一点接地,得到其电位大概为:

可以得到分压后的电压大约2.38 V,然后将该电压送至次级电路,在次级电路中,首先通过一个100 nF的电容进行滤波,使输出的电压更加稳定,接着用二极管保护电路将电压限制在3.3 V和0 V之间,当电压大于3.3 V时,二极管正向导通,电压被限制在3.3 V,当产生负压(电压小于0 V)时,二极管正向导通,输出点接地电压被限制在0 V

# 实践配置

# CubeMX

首先配置打开 ADC1 的读取 Vref 与温度的两个通道与 ADC3 读取电池电压的通道8即可:

随后将两个 ADC 的设置均调为:将Vrefint Channel勾选,用于读取内部参考电压。ADC 在 CubeMX 中的设置如图采样频率设置为 PCLK2/4,采样位数为12位,数据设置为右对齐,其余均保持默认。随后由于用于读取电压的 ADC3 通道8需要一个引脚,所以对应的 PF10 亮起,而 ADC1 的两个通道均在内部,不需要引脚

这几项 ADC 设置的功能如下:

名称 功能
Clock Prescaler 设置采样时钟频率
Resolution 设置采样精度
Data Alignment 设置数据对齐方式
Scan Conversion Mode 扫描转换模式开启/关闭
Continuous Conversion Mode 连续转换模式开启/关闭
Discontinuous Conversion Mode 非连续转换模式开启/关闭
DMA Continuous Requests DMA连续启动开启/关闭
End of Conversion Selection 每个通道转换结束后发送EOC标志/所有通道转换结束后发送EOC标志

# 用到的库函数

第一个是HAL_ADC_ConfigChannel,它设置 ADC 通道的各个属性值(包括转换通道,序列排序,采样时间等),相当于初始化。它的参数2是一个结构体,会比较复杂,详细请见这里;且在实例中发现每次使用 ADC 进行采样时,最好将这个函数重新初始化并设置参数,接收到返回值为HAL_OK正常时即可正常使用

第二个是HAL_ADC_Start,它的作用是开启 ADC 的采样。它的参数是 ADC 的句柄指针,也为接收到返回值为HAL_OK时正常工作,详细信息请见这里

第三个是HAL_ADC_PollForConversion,它的作用是等待ADC转换结束。它的两个参数就是等待回传的 ADC 句柄指针与最大等待时间,返回值亦是HAL_OK时正常工作,详细信息请见这里

第四个是HAL_ADC_GetValue,它的作用是获取ADC值。它的参数是要取值的 ADC 的句柄指针,虽然一个 ADC 有很多通道,但是在使用此函数之前的HAL_ADC_ConfigChannel函数已经确定了要取的是哪一个端口的值,其详细信息请见这里

# 算法

首先介绍一下,如何通过 VREFINT 对 Vcc 的电压校准:

通过一个函数对比标准的1.2 V的电压进行多次采样,并计算其平均值,接着将其与 ADC 采出的数据值做对比,得到(采集到的电压的)单位数字电压对应的模拟电压值voltage_vrefint_proportion,其计算公式如下,设采样得到的数字值为average_adc

这一步其实类似于消除测量误差的过程,ADC 测量标准的电压出现误差,则将总共的标准值除以总共的测量值得到的将会是一个测量到的1 V相当于消除测量误差后的多少伏特

接着通过 ADC 对经过了分压电路的电池电压值进行采样,将该采样结果与voltage_vrefint_proportion相乘,就得到了取值范围在 0-3.3 V 之间的 ADC 采样值,由于这个采样值是分压后的结果,需要反向计算出电压的值。分压的电阻值为 200 kΩ 和 22 kΩ,由于,乘以这个值之后就可以得到电池的电压值

# 流程

我们定义了几个函数他们的关系如下:

首先是init_vrefint_reciprocal,就是之前提到的消除误差的作用的函数,他要使用在while(1)之前来为整个程序提供校准值voltage_vrefint_proportion这个全局变量

void init_vrefint_reciprocal(void)
{
    uint8_t i = 0;
    uint32_t total_adc = 0;
    for(i = 0; i < 200; i++)
    {
        total_adc += adcx_get_chx_value(&hadc1, ADC_CHANNEL_VREFINT);
    }

    voltage_vrefint_proportion = 200 * 1.2f / total_adc;
}
1
2
3
4
5
6
7
8
9
10
11

然后是get_battery_voltage,它的作用就是测量电压、计算精确值的主函数,它通过调用adcx_get_chx_value来取得 ADC3 的测量值,随后与voltage_vrefint_proportion相乘,得到精确值

fp32 get_battery_voltage(void)
{
    fp32 voltage;
    uint16_t adcx = 0;

    adcx = adcx_get_chx_value(&hadc3, ADC_CHANNEL_8);
    //(22K Ω + 200K Ω)  / 22K Ω = 10.090909090909090909090909090909
    voltage =  (fp32)adcx * voltage_vrefint_proportion * 10.090909090909090909090909090909f;

    return voltage;
}
1
2
3
4
5
6
7
8
9
10
11

而adcx_get_chx_value就是取得 ADC3 的测量值的函数,他调用了前面的几个库函数来读取 ADC3 的值,关于sConfig这个结构体应该怎样填写,请前往其定义查看参数的注释

static uint16_t adcx_get_chx_value(ADC_HandleTypeDef *ADCx, uint32_t ch)
{
    static ADC_ChannelConfTypeDef sConfig = {0};
    sConfig.Channel = ch;  // ch就是传进来的ADC的某个端口名字(的端口号)
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;  //ADC_SAMPLETIME_3CYCLES;

    if (HAL_ADC_ConfigChannel(ADCx, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }

    HAL_ADC_Start(ADCx);

    HAL_ADC_PollForConversion(ADCx, 10);
    return (uint16_t)HAL_ADC_GetValue(ADCx);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

顺带一提,这是一个static类型的函数,它具体作用请见此处,为什么这么用呢,因为这几个自己编写函数是在一个bsp_adc.c的自写源文件中,我们只希望这个源文件中的函数调用它,而在main.c中不能直接调用

至于温度的测量则是大同小异,与电压测量几乎相同,希望你可以认真的阅读示例代码 (opens new window),把这个方法学会!

编辑 (opens new window)
上次更新: 2020/11/18, 13:11:00
按键外部中断
串口与DMA

← 按键外部中断 串口与DMA→

Theme by Vdoing | Copyright © 2020-2025 Jack :) | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式