hack4electronics.com

Guide to Using ESP32 ADC (Analog to Digital Converter) with ESP-IDF

The Analog to Digital Converter (ADC) is a critical component in microcontrollers, enabling them to interface with the analog world. The ESP32, a popular microcontroller, boasts a 12-bit ADC with multiple input channels, making it a versatile choice for a variety of applications. This guide will explore the features of the ESP32 ADC, provide detailed programming instructions using ESP-IDF, and address common issues and advanced topics to help you get the most out of your ADC applications.

What is an ADC?

An ADC converts analog signals into digital data that microcontrollers can process. This capability is essential for applications such as reading sensor data, audio processing, and data acquisition systems. The ESP32’s ADC is particularly robust, offering 12-bit resolution and multiple input channels, allowing it to handle complex tasks efficiently.

Features of the ESP32 ADC

ESP32 ADC can read analog values between 0-3.3V. ADC converts analog signals into digital values. The measured voltage signal is given a value between 0 and 4095. Accordingly, a value of 0 equals to 0V, while the maximum value 4095 equals to 3.3V. Any intermediate related values will likewise be assigned in the appropriate manner. There is a tiny drawback, though. The ADC module is not particularly sensitive to value changes. Its nature is non-linear. Very close values will therefore match up with similar voltages. For instance, the voltage readings 3.3V and 3.2V will both be represented as the ADC value of 4095.

ESP32 has two 12-bit ADCs (ADC 1 & ADC 2) and supports a maximum of 18 analog channels.

  • Resolution: 12-bit, providing 4096 different levels of precision.
  • Voltage Range: 0 to 3.3V
  • Assigned Value: 0-4095
  • Analog Pins: GPIO0, GPIO2, GPIO4, GPIO12, GPIO13, GPIO14, GPIO15, GPIO 25, GPIO 26, GPIO27, GPIO32, GPIO33, GPIO34, GPIO35, GPIO36, and GPIO39
  • Changing Resolution: The resolution can be changed if needed.

The ESP32 board has 8 channels for ADC1 but the DEVKIT V1 only supports 6 of them. ADC2 has 10 analog channels. The analog pins of both of these channels are listed below:

ADC1 Pins

  • ADC1_CH0 : GPIO 36
  • ADC1_CH1 : GPIO 37 (NOT AVAILABLE)
  • ADC1_CH2 : GPIO 38 (NOT AVAILABLE)
  • ADC1_CH3 : GPIO 39
  • ADC1_CH4 : GPIO 32
  • ADC1_CH5 : GPIO 33
  • ADC1_CH6 : GPIO 34
  • ADC1_CH7 : GPIO 35

ADC2 Pins

  • ADC2_CH0 : GPIO 4
  • ADC2_CH1 : GPIO 0
  • ADC2_CH2 : GPIO 2
  • ADC2_CH3 : GPIO 15
  • ADC2_CH4 : GPIO 13
  • ADC2_CH5 : GPIO 12
  • ADC2_CH6 : GPIO 14
  • ADC2_CH7 : GPIO 27
  • ADC2_CH8 : GPIO 25
  • ADC2_CH9 : GPIO 26

The following diagram shows the analog pins for ESP32 DEVKIT V1:

ESP32 ESP-IDF ADC APIs

Before we move ahead, let us discuss some important functions that are required to access ADC channels of ESP32.

ESP-IDF provides driver/adc.h and esp_adc_cal.h libraries that are required for the ADC driver and ADC calibration respectively. For this project, we will be required to set the ADC input voltage range, ADC calibration, ADC bit width, obtain ADC value, and convert it to voltage reading. Let’s see how it will be accomplished.

Setting Up the Development Environment

To start programming the ADC on ESP32, you need to set up the ESP-IDF (Espressif IoT Development Framework). Follow these steps:

  1. Install ESP-IDF: Download and install the latest version of ESP-IDF from the official website.
  2. Set Up the Environment: Configure your development environment as per the ESP-IDF setup guide.
  3. Connect the ESP32: Ensure your ESP32 board is connected to your computer via USB.

Basic ADC Reading Program

Here’s a simple program to read values from an ADC channel on the ESP32:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/adc.h"
#include "esp_system.h"
#include <stdio.h>

void app_main() {
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0);

    while (1) {
        int adc_reading = adc1_get_raw(ADC1_CHANNEL_0);
        printf("ADC Reading: %d\n", adc_reading);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

Explanation of the Code

  • Include Headers: Necessary headers for FreeRTOS, ADC driver, and ESP system functions.
  • Configuration: Set the ADC width to 12 bits and configure the channel attenuation.
  • Reading Values: Continuously read from the ADC channel and print the values.

Reading Multiple ADC Channels

To read from multiple ADC channels, you can configure additional channels similarly:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/adc.h"
#include "esp_system.h"
#include <stdio.h>

void app_main() {
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0);
    adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_0);

    while (1) {
        int adc_reading_0 = adc1_get_raw(ADC1_CHANNEL_0);
        int adc_reading_3 = adc1_get_raw(ADC1_CHANNEL_3);
        printf("ADC Reading Channel 0: %d, Channel 3: %d\n", adc_reading_0, adc_reading_3);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

Handling ESP32 ADCCalibration and Attenuation

Calibration and attenuation are crucial for accurate ADC readings:

  • Calibration: Use the built-in calibration functions to minimize errors. Calibration involves compensating for the inherent inaccuracies in the ADC readings due to manufacturing variances and environmental factors.
  • Attenuation: Adjust the attenuation to accommodate higher input voltages, up to 3.3V. Attenuation helps in extending the input range of the ADC channels by reducing the input voltage before it reaches the ADC.

Implementing ESP32 ADC Calibration in ESP32

To ensure accurate ESP32 ADC readings, you can use the ESP32’s built-in calibration API. Here’s an example of how to implement calibration:

#include "esp_adc_cal.h"

void check_efuse() {
    //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");
    }
}

void app_main() {
    check_efuse();

    //Characterize ADC
    esp_adc_cal_characteristics_t *adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
    esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_0, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars);

    //Configure ADC
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0);

    while (1) {
        uint32_t adc_reading = adc1_get_raw(ADC1_CHANNEL_0);
        uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars);
        printf("ADC Reading: %d, Voltage: %dmV\n", adc_reading, voltage);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

Advanced Topics

  • Using ADC with DMA: Direct Memory Access (DMA) allows efficient data transfer without CPU intervention, ideal for high-speed applications. By using DMA, you can offload data handling tasks from the CPU, making your application more efficient.
  • Low-Power Applications: Optimize ADC usage in low-power modes to extend battery life in portable devices. Implementing low-power strategies involves minimizing the ADC’s active time and using sleep modes effectively.

Implementing ESP32 ADC with DMA

Here’s an example of how to set up ADC with DMA on the ESP32:

#include "driver/adc.h"
#include "esp_log.h"

#define ADC_BUFFER_SIZE 1024

uint16_t adc_buffer[ADC_BUFFER_SIZE];

void app_main() {
    //Configure ADC
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0);

    //Initialize DMA
    adc_digi_pattern_config_t adc_pattern = {
        .atten = ADC_ATTEN_DB_0,
        .channel = ADC1_CHANNEL_0,
        .bit_width = 12
    };
    adc_digi_config_t dig_cfg = {
        .conv_limit_en = 1,
        .conv_limit_num = ADC_BUFFER_SIZE,
        .pattern_num = 1,
        .adc_pattern = &adc_pattern,
        .dma_eof_num = ADC_BUFFER_SIZE,
        .adc_eof_num = ADC_BUFFER_SIZE
    };
    adc_digi_initialize(&dig_cfg);

    while (1) {
        adc_digi_read(adc_buffer, ADC_BUFFER_SIZE, portMAX_DELAY);
        for (int i = 0; i < ADC_BUFFER_SIZE; i++) {
            ESP_LOGI("ADC_DMA", "ADC Reading: %d", adc_buffer[i]);
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

The ESP32’s ADC is a powerful feature that opens up numerous possibilities for interfacing with the analog world. By understanding its capabilities, setting up the development environment, and following best practices for programming and calibration, you can harness the full potential of the ESP32’s ADC for your projects. Whether you’re working on simple sensor readings or complex data acquisition systems, this guide provides the foundation you need to get started and succeed.

Leave a Reply

Your email address will not be published. Required fields are marked *