ESP32 ESP-IDF ADC监测电池电压(带校正)

陈拓 2022/06/24-2022/06/24

1. 概述

  • 此示例显示如何配置ADC1并读取连接到GPIO引脚的电压。
  • 引脚功能

在本例中,我们使用默认的ADC_UNIT_1,我们电池供电的应用中将ESP32开发板的电源连接到GPIO34,以监测电池电压。如果在应用程序中选择了其他ADC单元,则需要更改GPIO引脚(请参阅《ESP32技术参考手册》第4.11章)。

ESP32有2个12位的ADC,共18个通道。要注意的是ADC2和wifi共用引脚,wifi的优先级更高,所以ADC2只有在WIFI模块不用的情况下才能使用。

ESP32的18个ADC通道如下:

ADC1有8个通道:GPIO32 - GPIO39

ADC2有10个通道:GPIO0、GPIO2、GPIO4、GPIO12-GPIO15、GOIO25-GPIO27

每个ADC单元支持两种工作模式,单次采样模式和连续采样(DMA)模式。单次采样模式适用于低频采样操作,连续采样模式适用于高频连续采样动作。

本例为单次采样模式。

  • 官方例程国内镜像

https://gitee.com/EspressifSystems/esp-idf/tree/master/examples/peripherals/adc/single_read/adc

2. 开发环境

《用乐鑫国内Gitee镜像搭建ESP32开发环境》

https://zhuanlan.zhihu.com/p/348106034

https://blog.csdn.net/chentuo2000/article/details/113424934?spm=1001.2014.3001.5501

3. 修改代码

  • 克隆官方例程

将官方例子项目复制到ESP-IDF开发工具之外:

cd ~/esp

cp -r ~/esp/esp-idf/examples/peripherals/adc/single_read/adc ~/esp/

  • 项目树

cd adc

tree

  • 衰减与测量范围

不同的衰减倍数对应不同的检测电压范围。

ADC默认满量程电压为1.1V。要读取更高的电压(最高为引脚最大电压,通常为3.3V),需要将该ADC通道的信号衰减设置为>0dB

VDD_A3.3V时:

-- 0dB衰减(ADC_ATTEN_0)满量程电压为1.1V

-- 2.5dB衰减(ADC_ATTEN_DB_2_5)满量程电压为1.5V

-- 6dB衰减(ADC_ATTEN_6)满量程电压为2.2V

-- 11dB衰减(ADC_ATTEN_11)满量程电压为3.9 V(见以下注释)

注释:

满量程电压是对应于最大读数的电压(取决于ADC1配置的位宽度,该值为:4095表示12位,2047表示11位,1023表示10位,511表示9位。)

注释:

11dB衰减时,最大电压受VDD_A限制,而非满量程电压。

由于ADC特性,可在以下近似电压范围内获得最准确的结果:

-- 0dB衰减(ADC_ATTEN_DB_0) 100~950 mV

-- 2.5dB衰减(ADC_ATTEN_DB_2_5) 100~1250 mV

-- 6dB衰减(ADC_ATTEN_DB_6) 150~1750 mV

-- 11dB衰减(ADC_ATTEN_DB_11) 150~2450 mV

为了2450mV以上电压测量更准确我们使用了

ESP32在电池供电时用ULP监测电池电压》

https://zhuanlan.zhihu.com/p/523358887

https://blog.csdn.net/chentuo2000/article/details/125094853?spm=1001.2014.3001.5502

一文中相关的校正代码。

  • eFuse校正

ADC1是带有出厂校准的,所以计算结果有eFuse Vref: Supported

  • 修改代码

adc1_example_main.c

/* ADC1 Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_adc_cal.h"

#define DEFAULT_VREF    1100        //Use adc2_vref_to_gpio() to obtain a better estimate
#define NO_OF_SAMPLES   64          // 多重采样次数

static esp_adc_cal_characteristics_t *adc_chars;
#if CONFIG_IDF_TARGET_ESP32
static const adc_channel_t channel = ADC_CHANNEL_6;     //GPIO34 if ADC1, GPIO14 if ADC2
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
#elif CONFIG_IDF_TARGET_ESP32S2
static const adc_channel_t channel = ADC_CHANNEL_6;     // GPIO7 if ADC1, GPIO17 if ADC2
static const adc_bits_width_t width = ADC_WIDTH_BIT_13;
#endif
static const adc_atten_t atten = ADC_ATTEN_DB_11;
static const adc_unit_t unit = ADC_UNIT_1;

// voltage to raw data struct
typedef struct {
    float vol;
    uint32_t raw;
} vol_to_raw_t;

/* Set thresholds, approx. 2.00V - 3.30V */
#define VAL_TO_RAW_NUM   14
static const vol_to_raw_t g_v0l_to_raw[VAL_TO_RAW_NUM] = {
    {2.00, 3123},
    {2.10, 3207},
    {2.20, 3301},
    {2.27, 3361},
    {2.35, 3434},
    {2.43, 3515},
    {2.52, 3598},
    {2.60, 3682},
    {2.68, 3745},
    {2.76, 3825},
    {2.80, 3867},
    {2.90, 3891},
    {3.00, 3986},
    {3.30, 4095},
};

static void check_efuse(void)
{
#if CONFIG_IDF_TARGET_ESP32
    //Check if TP is burned into eFuse
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
        printf("eFuse Two Point: Supported\n");
    } else {
        printf("eFuse Two Point: NOT supported\n");
    }
    //Check Vref is burned into eFuse
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
        printf("eFuse Vref: Supported\n");
    } else {
        printf("eFuse Vref: NOT supported\n");
    }
#elif CONFIG_IDF_TARGET_ESP32S2
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
        printf("eFuse Two Point: Supported\n");
    } else {
        printf("Cannot retrieve eFuse Two Point calibration values. Default calibration values will be used.\n");
    }
#else
#error "This example is configured for ESP32/ESP32S2."
#endif
}


static void print_char_val_type(esp_adc_cal_value_t val_type)
{
    if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
        printf("Characterized using Two Point Value\n");
    } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
        printf("Characterized using eFuse Vref\n");
    } else {
        printf("Characterized using Default Vref\n");
    }
}

static float ulp_adc_raw_to_vol(uint32_t adc_raw)
{
    if (adc_raw < g_v0l_to_raw[0].raw
            || adc_raw > g_v0l_to_raw[VAL_TO_RAW_NUM - 1].raw) {
        return 0;
    }

    float ret_vol = 0;
    for (int i = 0; i < VAL_TO_RAW_NUM - 1; i++) {
        if (g_v0l_to_raw[i].raw <= adc_raw && g_v0l_to_raw[i + 1].raw >= adc_raw) {
            uint32_t raw_dif = g_v0l_to_raw[i + 1].raw - g_v0l_to_raw[i].raw;
            float vol_dif    = g_v0l_to_raw[i + 1].vol - g_v0l_to_raw[i].vol;
            ret_vol = g_v0l_to_raw[i].vol + (float)(adc_raw - g_v0l_to_raw[i].raw) / (float)raw_dif * vol_dif;
            break;
        }
    }
    return ret_vol;
}

void app_main(void)
{
    //Check if Two Point or Vref are burned into eFuse
    check_efuse();

    //Configure ADC
    if (unit == ADC_UNIT_1) {
        adc1_config_width(width);
        adc1_config_channel_atten(channel, atten);
    } else {
        adc2_config_channel_atten((adc2_channel_t)channel, atten);
    }

    //Characterize ADC
    adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
    esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_chars);
    print_char_val_type(val_type);

    //Continuously sample ADC1
    while (1) {
        uint32_t adc_reading = 0;
        //Multisampling
        for (int i = 0; i < NO_OF_SAMPLES; i++) {
            if (unit == ADC_UNIT_1) {
                adc_reading += adc1_get_raw((adc1_channel_t)channel);
            } else {
                int raw;
                adc2_get_raw((adc2_channel_t)channel, width, &raw);
                adc_reading += raw;
            }
        }
        adc_reading /= NO_OF_SAMPLES; // 取多重采样的平均值
        //Convert adc_reading to voltage in mV
        uint32_t voltage = 0;
        if(adc_reading < 3123) { // 如果电压小于2.00, 3123
            voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars);
        } else {
            float voltage_f = ulp_adc_raw_to_vol(adc_reading) * 1000;
            voltage = (uint32_t)voltage_f;
        }
        printf("Raw: %d\tVoltage: %dmV\n", adc_reading, voltage);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

4. 构建项目

  • 刷新esp-idf环境

get_idf

  • 设定目标芯片

idf.py set-target esp32

  • 配置项目

idf.py menuconfig

1) 将闪存设置为4MB

保存,退出。

  • 编译项目

idf.py build

  • 烧写项目

查看USB转串口的COM口号:

烧写:

idf.py -p /dev/ttyS3 -b 115200 flash

  • 启用监视器

idf.py monitor -p /dev/ttyS3

(Ctrl+]可以退出监视器程序)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晨之清风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值