ESP32 ADC Tutorial – Read Analog Voltage in Arduino

Previous Tutorial Previous Tutorial Tutorial 5 Next Tutorial Next Tutorial
ESP32 ADC Tutorial (analogRead) – Arduino
ESP32 Course Home Page 🏠

 

In this tutorial, you’ll learn about ESP32 ADC and how to read analog input channels in Arduino Core. But first of all, you’ll get an introduction to what’s an ADC and how it works in most microcontrollers on a hardware level. Then, we’ll investigate the ESP32 ADC Hardware peripheral and check the features it does have, ADC error sources, calibration, and much more.

Then, we’ll move to the Arduino Core libraries that implement drivers for the ESP32 ADC peripheral and how to use its API functions, like analogRead(). Without further ado, let’s get right into it!

ESP32 ADC Analog Read Arduino Example Tutorial - Calibration And Filtering


Requirements For This Tutorial

Prior Knowledge

  • Nothing

Software Tools

Hardware Components

You can either get the complete course kit for this series of tutorials using the link down below. Or just refer to the table for the exact components to be used in practical LABs for only this specific tutorial.


Analog To Digital Converter (ADC)

 

An ADC (analog to digital converter) is an electronic circuit that’s usually integrated into different microcontrollers or comes in as a dedicated IC. We typically use an ADC in order to measure/read the analog voltage from different sources or sensors.

Most parameters and variables are analog in nature and the electronic sensors that we use to capture this information are also analog. Just like temperature, light, pressure, and more other sensors are all analog. Therefore, we need to read the analog voltage value using our digital microcontrollers. And this is when we start learning about the ADC peripheral and try to configure it so as to get this task done.

This is an in-depth article (tutorial) for ADC, how it works, different types of ADC, error sources, sampling, and much more. Consider checking it out if it’s your first time to learn about the ADC.


ESP32 ADC Hardware

 

In this section, I’ll give you an introduction to the hardware capabilities for the ESP32 LED PWM peripheral, how it works, and what kind of features it has. So you can use it in an efficient way depending on the specific application requirements you have.

ESP32 ADC Pins (Channels)

The ESP32 has 2 x 12-Bit SAR (Successive Approximation Register) ADC hardware peripherals that can read 18 different analog input channels.

The ADC firmware driver API supports ADC1 (8 channels, attached to GPIOs 32 – 39), and ADC2 (10 channels, attached to GPIOs 0, 2, 4, 12 – 15, and 25 – 27). However, the usage of ADC2 has some restrictions for the application:

  1. ADC2 is used by the Wi-Fi driver. Therefore the application can only use ADC2 when the Wi-Fi driver has not been started.
  2. Some of the ADC2 pins are used as strapping pins (GPIO 0, 2, 15) thus cannot be used freely.

For the exact pinout for our DevKit board’s ADC analog input pins, refer to this ESP32 devkit board pinout.

ESP32 DEVKIT v1 DOIT Pinout - Getting Started

(if it’s not clear, right-click and open it in a new tab for a larger view)

ESP32 ADC Features

Each one of the ESP32’s ADCs has its own logic controller units. Those control units are shown in the diagram below. They support each ADC to operate in multiple modes to achieve certain design goals (high-performance or low-power consumption).

ESP32 ADC Arduino Analog Input Tutorial - Internal Diagram

1- ADC-RTC

ADC-RTC is controlled by the RTC controller and is suitable for low-frequency sampling operations.

2- ADC-DMA

ADC-DMA is controlled by a digital controller and is suitable for high-frequency continuous sampling actions.

Those are the ESP32’s ADC features as stated by its datasheet:

  • Two SAR ADCs, with simultaneous sampling and conversion
  • Up to five SAR ADC controllers for different purposes (e.g. high performance, low power, or PWDET / PKDET)
  • Up to 18 analog input pads
  • 12-bit, 11-bit, 10-bit, 9-bit configurable resolution
  • DMA support (available on one controller)
  • Multiple channel-scanning modes (available on two controllers)
  • Operation during deep sleep (available on one controller)
  • Controlled by a ULP coprocessor (available on two controllers)

ESP32 ADC Voltage Range

The ESP32 ADC analog input pins are 3.3v tolerant with a peak input voltage of 3.3v. So it’s the maximum voltage a pin can experience under any circumstances. However, you can still do some signal conditioning outside the microcontroller board to accept different input ranges.

But at the ADC analog input pin, the voltage should always be in the range (0 – 3.3v) regardless of what you’re doing externally. Maybe using a voltage divider network to read up to 6.6v inputs (you lose half the resolution by doing this), just to name a use case.

ESP32 ADC Attenuation

The default ADC voltage is for attenuation 0 dB and listed in the table below. By setting higher attenuation it is possible to read higher voltages. Due to ADC characteristics, the most accurate results are obtained within the “suggested range” shown in the following table.

SoC Attenuation (dB) Suggested Voltage Range (mV)
ESP32 0 100 ~ 950
2.5 100 ~ 1250
6 150 ~ 1750
11 150 ~ 2450
ESP32-S2 0 0 ~ 750
2.5 0 ~ 1050
6 0 ~ 1300
11 0 ~ 2500

In the ESP32 Arduino Core ADC driver, you’ll find 2 functions dedicated to set or change the ADC analog input channel’s attenuation value. They are listed down below.

  • analogSetAttenuation(): Sets the attenuation for all ADC channels
  • analogSetPinAttenuation(): Sets the attenuation for a specific analog channel pin

We’ll be mostly using the first function, or just will ignore this feature altogether. Why? because its default value is -11dB attenuation which does already give us the maximum operating voltage range for the analog inputs (0 – 3.3v).

ESP32 ADC Resolution

As we’ve stated in the features of the ESP32 ADC, it’s got a programmable resolution that can be changed if you want to. By default, it’s a 12-Bit ADC. Which gives you readings in the range (0 – 4095). However, you can change the resolution bits by using the following function from Arduino Core ADC Driver.

The minimum resolution you can get is 9-Bit, at which the ADC conversion range is (0 – 511). You change it if you want, but in most applications, we’ll also leave this feature without changing the default 12-Bit resolution. I had to mention it just in case you need to use it in a particular project or something.

ESP32 ADC Arduino Analog Read Tutorial

ESP32 ADC Voltage Reference

The ADC reference voltage (VREF) varies between different ESP32 chips. By design, the ADC reference voltage is 1100 mV, however, the true reference voltage can range from 1000 mV to 1200 mV amongst different ESP32s.

With that being said, the VREF setting has its own contribution to drift the ADC readings away from being ideal. There are so many other reasons and sources for ADC errors as we’ll see in the next sections. But (VREF) setting is one of them. The function down below can be used to change the default VREF pin if the device is not already calibrated.

It’s extremely important to guarantee a stable VREF voltage to get consistent readings with the ADC. We’ll address this shortly after to calibrate the ADC using the Espressif VREF calibration function.

ESP32 ADC Clock

The SAR ADC does the conversion on multiple clock cycles depending on the resolution of conversion. The faster the clock rate is, the faster the ADC will finish every single A/D conversion process.

You can actually control the FCLK for the ADC by changing the clock frequency division factor. Dividing by a larger number will result in a slower clock rate and vice versa. The Arduino Core ADC Driver has a function to change the ADC clock rate as shown below.

The fastest conversion time for the ADC we can get (when clockDiv = 1). While the slowest option is when (clockDiv = 255).

ESP32 ADC Errors

Generally speaking, there are so many different sources of error when it comes to A/D conversion. Not only in ESP32, but it’s just how ADCs work in most microcontrollers. Usually, you’ll find one or more “App Note” from the manufacturer to guide you through calibrating and characterizing the ADC performance to get better results.

ESP32 ADC Linearity

PCBgogo Ad

Let’s start with the ADC linearity error. An ideal ADC should be linear in response but in practice, you’ll find out non-linearity in the characteristics curve as you can see in the figure below.

ESP32 ADC Linearity

ESP32 ADC Noise

As stated by Espressif in their documentation, ” The ESP32 ADC can be sensitive to noise leading to large discrepancies in ADC readings. To minimize noise, users may connect a 0.1uF capacitor to the ADC input pad in use. Multi-sampling may also be used to further mitigate the effects of noise”.

You should be careful before considering to add that capacitor. It’s going to attenuate all the high-frequency components in your analog signal. If you’re measuring a near-DC signal like a temperature sensor or something that’s physically slow to change, then it’s ok. Otherwise, I’d advise against adding any capacitance on the ADC input lines.

Instead, I usually incorporate an active buffering for ADC inputs in most of my designs. However, you should also be careful when doing this. Going cheap on the op-amp will get you more trouble, noise, bandwidth limitation, and end up defeating the purpose of doing such buffering thing.

A great advantage of having an active buffering (like a voltage follower op-amp config.) is that it reduces the ADC channels cross-coupling while the ADC is switching from channel to channel, the internal ADC’s sampling capacitor will end up picking some measurement noise due to this. Having an active buffer will eliminate this sort of error.

ESP32 ADC Noise Reduction With Buffer

ESP32 ADC VREF Offset

As we’ve earlier stated, the VREF is by design around 1.1v but it does vary from chip to chip causing a non-negligible source of error. For this, we’ll be using the calibration method provided by Espressif and see the results. It’s going to be the last LAB in this tutorial, so stick around.

ESP32 ADC Calibration 

The ESP32 Arduino Core ADC driver’s API provides functions to correct for differences in measured voltages caused by variation of ADC reference voltages (VREF) between ESP32 chips.

Correcting ADC readings using this API involves characterizing one of the ADCs at a given attenuation to obtain a characteristics curve (ADC-Voltage curve) that takes into account the difference in ADC reference voltage.

The characteristics curve is in the form of y = coeff_a * x + coeff_b and is used to convert ADC readings to voltages in mV. Calculation of the characteristics curve is based on calibration values which can be stored in eFuse or provided by the user.

We’ll be using this function to characterize the ADC parameters.

Then, we’ll call the ADC read calibrated results functions or use the characteristics to get the results on our own without using the functions below. Especially because they’ll give you the ADC results (in mV) which you might not be interested in.

ESP32 ADC Sampling Rate

If you’re not familiar with the terminology, the ADC sampling rate is a measure of the ADC speed. In other words, how many samples (A/D conversions) can the ADC achieve per second.

There is no clear statement for how fast this ADC can go. Some users have reported getting decent results with a timer interrupt @ up to 10kHz. While in DMA mode, it can get much faster but also didn’t get any exact figures. We’ll be doing our own testing using ESP IDF (not Arduino) in the future and I’ll check this parameter in one way or another.

 


ESP32 ADC Analog Read (in Arduino)

 

In this section, I’ll give you a step-by-step approach to what to do in order to read any ADC analog input pin.

Step1– Decide on the ADC analog channel that you’re going to use

let it be GPIO 35 for example

Step2– Call the analog read function to get the raw result

Optionally you can apply and kind of digital filtering, calibration, or whatever you need before using the results as-is. I’d highly recommend doing a multi-sampling function to average the last-N readings (where N = 5 or more samples).

 


Components For This Tutorial’s LABs

 

QTY. Component Name Buy Links
1 ESP32 Devkit v1 DOIT Board

or Any Other ESP32 Dev Board

Amazon.com  –  eBay.com  –  Banggood.com
2 BreadBoard Amazon.com –  eBay.com –  Banggood.com
1 Resistors Kit Amazon.com / Amazon.com  –  eBay.com  –  Banggood.com
1 Jumper Wires Pack Amazon.comAmazon.com –  eBay.comeBay.com –  Banggood.com
1 LEDs Kit Amazon.comAmazon.com –  eBay.com –  Banggood.com
1 Potentiometers Amazon.comAmazon.com –  eBay –  Banggood.com
1 Micro USB Cable Amazon.com  –  eBay.com –  Banggood.com

*Affiliate Disclosure: When you click on links in this section and make a purchase, this can result in this site earning a commission. Affiliate programs and affiliations include, but are not limited to, the eBay Partner Network (EPN) and Amazon.com, Banggood.com. This may be one of the ways to support this free platform while getting your regular electronic parts orders as usual at no extra cost to you.


ESP32 ADC + PWM LED Dimmer – LAB

 

LAB Number 9
LAB Name ESP32 ADC (Analog Read Potentiometer) LED Dimmer
  • Define & Attach The PWM GPIO pin
  • Configure The PWM Channel (frequency & resolution)
  • Read The ADC input channel (Potentiometer)
  • Write the ADC_Result to the PWM duty cycle output pin (LED)

Connection Diagram

ESP32 ADC LED Dimmer Potentiometer Arduino

ESP32 ADC + Potentiometer LED Dimmer – Code Example

The code example down below does the following: We start with defining & Attaching The PWM GPIO pin. The pin I’ll be using a PWM pin is GPIO5 in this example.

Then, we’ll be configuring the PWM Channel’s frequency (1kHz) & resolution (12-Bits to be similar to the ADC_Result). And in the main loop() function, I’ll be reading the ADC analog input (potentiometer – GPIO35 pin), and write the result to the PWM duty cycle.

The Full code Listing

Choose the board, COM port, hold down the BOOT button, click upload and keep your finger on the BOOT button pressed. When the Arduino IDE starts sending the code, you can release the button and wait for the flashing process to be completed. Now, the ESP32 is flashed with the new firmware.

Demo Video For The Result

ESP32 ADC Potentiometer LED Dimmer Arduino Code Example

Click The image to watch the demo video on YouTube

 


ESP32 ADC Calibration – LAB

 

LAB Number 10
LAB Name ADC Calibration Procedure

 

ESP32 ADC Calibration – Code Example

In this example, I’ve used the ADC Calibration functions from Arduino Core ADC APIs. For this LAB, you’ll need a pretty accurate DMM (digital multi-meter). Then, I’ve picked an analog input channel pin (GPIO35).

You’ll have to connect the potentiometer to that analog channel pin and tweak the pot until you get 2v (or any other value). Just make sure the DMM is reading an exact value (2v or any other value).

Now, read the ADC with analogRead() function without calibration or whatsoever and note down that value. For me, it was 1.85v, while the DMM is reading an exact 2v.

Then, I’ve incorporated the ADC calibration functions in my code as shown below. The result was 1.99v which is very close to the DMM reading. But keep in mind that this calibration procedure has an offset (dead-band) on both ends of the spectrum (in my test that was 0.14v near-zero end and near 3.3v end).

 


ESP32 ADC Noise Reduction – LAB

 

LAB Number 11
LAB Name ADC Noise Reduction By Multi-Sampling & Moving Average Digital Filtering

 

ESP32 ADC Noise Reduction By Multi-Sampling & Moving Average Digital Filtering – Code Example

Another type of error that you’ll probably experience is the noise and fluctuations in readings. This can be reduced by placing a small capacitor on the ADC input pin (Hardware solution). Or by applying a simple digital filter like the moving average filter (Software solution). Both will work in the same way but I’d go for the multi-sampling and averaging solution.

This is an example code for doing multi-sampling and averaging for a specific ADC channel. You can change the Filter_Length to whatever you want but note that the larger it gets, the slower the response of the filter gets (it increases the phase lag effect but it gets more smooth as it attenuates more frequencies).

Demo Video For The Result

ESP32 ADC Noise Reduction Moving Average Filter Arduino Example

Click The image to watch the demo video on YouTube

Note that: the blue curve is the Raw ADC Readings, the red curve is the Filtered ADC Readings.


Some ESP32 ADC Useful Driver APIs

 

adcAttachPin(pin): Attach an analog input pin to the ADC

analogRead(pin): Get the ADC Value for the specified pin.

analogSetWidth(bits): Sets the ADC resolution (in bits). Default is 12-bit but range is 9 to 12.

analogSetAttenuation(attenuation): Set the attenuation for all channels. Default is 11db but possible values are 0db, 2_5db, 6db, and 11db.

analogSetPinAttenuation(pin, attenuation): Set the attenuation for a particular pin.

analogSetClockDiv(clockDiv): Set the divider for the ADC clock.

analogSetVRefPin(pin): Set pin to be used for ADC calibration if ESP32 is not already calibrated. Possible pins are 25, 26 or 27.

analogReadMilliVolts(pin): Reads an analog channel, converts the raw value to voltage (in mV) and return that voltage value.


ESP32 ADC Concluding Remarks

 

ESP32 ADC hardware peripheral can be used in so many applications as we’ll see in the future tutorials. I’ll keep updating this series of tutorials by adding more applications and techniques that may help you in your projects. Drop me a comment if you’ve got any questions or suggestions, I’ll be glad to help!

My final thought about this peripheral that it’s OK to work with in most applications. But I’d not recommend it for safety-critical applications where human health, life, or money is involved. Just do yourself a favor and get a 1.5$ uC from Microchip or STM that has a “well-documented ADC” + DMA + SPI. This would be the best combination for a Wi-Fi/BLE-enabled application.

Don’t get me wrong on this, it may be a personal or biased opinion. But I’d not risk designing a critical measurement system based on this. The ESP32 is still a great option and we can make use of the Wi-Fi/BLE stack but off-load the ADC functionality to another chip.

Especially if you’re designing your own board with buffering/signal conditioning being done by other ICs on the board. It’d be a good decision to have also another uC acting as a slave ADC device. It totally depends on the application, at the of the day It’s your call and you’ve to decide on this!

Related Tutorials Based On ESP32 ADC

  • ESP32 Motor Speed Control
  • ESP32 LM35 Temperature Sensor Interfacing
  • ESP32 LDR Light Sensor Interfacing
  • ESP32 Joystick Interfacing
  • And More…

Learn More About ADC in General

 

You can also check the ESP32 Course Home Page 🏠  for more ESP32 tutorials divided into sections based on categories. This may be helpful for you in case of searching for a specific tutorial or application.


Did you find this helpful? If yes, please consider supporting this work and sharing these tutorials!

 

Stay tuned for the upcoming tutorials and don’t forget to SHARE these tutorials. And consider SUPPORTING this work to keep publishing free content just like this!

 

 

ESP32 Course Home Page 🏠 
Previous Tutorial Previous Tutorial Tutorial 5 Next Tutorial Next Tutorial
Share This Page With Your Network!

Khaled Magdy

I'm an embedded systems engineer doing both Software & Hardware. I'm an EE guy who studied Computer Engineering, But I'm also passionate about Computer Science. I love reading, writing, creating projects and Technical training. A reader by day a writer by night, it's my lifestyle. You can view my profile or follow me via contacts.

You may also like...

6 Responses

  1. Samit Hasan says:

    I have a code for counting samples of ADC.I’m attaching the code here.

    Code:

    #include
    #include
    #include

    const uint16_t samples = 10000;
    uint8_t clockDiv =255;

    void setup() {
    Serial.begin(115200);
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_11);
    adc_set_clk_div(clockDiv); //doesn’t work
    }

    void loop() {

    const uint16_t samples = 10000;
    auto t1 = std::chrono::system_clock::now();

    for (uint32_t i=0; i
    adc_buf[i]=adc1_get_raw(ADC1_CHANNEL_3);
    }
    auto t2 = std::chrono::system_clock::now();

    std::chrono::duration diff = t2 – t1;
    auto ms = std::chrono::duration_cast(diff).count();
    //For seeing output if needed
    //for (int j=0; j
    // Serial.print(adc_buf[j]);Serial.print(” “);
    // }

    Serial.println();
    Serial.printf(“Samples: %u\n”, samples);
    Serial.printf(“Duration: %llums\n”, ms);
    Serial.printf(“KSPS: %0.4f”, static_cast(samples) / ms);
    Serial.println();

    }

    The problem is that no clock divider is changing the sampling rate .. My device showing about 20ksps.
    Although if I change CPU frequency from the ArduinoIDE settings the code showing sampling rate as low as 1ksps .But why does the adc_set_clk_div(clockDiv) not working.Could you kindly look over this problem ?

    • Khaled Magdy says:

      Hi Samit!
      Well, i’ve looked into this while writing this tutorial.
      Your procedure for measuring the adc sampling rate is logically correct.
      However, the adc read function is a non-blocking routine. Which means, reading the timer before and after that function will not give you the adc conversion time.
      There was a function in the adc driver that was a blocking routine which waits until conversion completion then returns. But they’ve removed it for some reason back in 2019 or something.
      I know it can be strange a little bit, but the adc function we have is working asynchronously in a non-blocking way.
      This shows you that changing the clock div won’t change anything at all. While changing cpu frequency will change the speed it goes from time capture before and after adc read. This is logical too!

      Hope this helps.

      Kind regards,

      Khaled M.

      • Samit Hasan says:

        Thanks a lot for your explanation. Yeah it’s look kinda weird . However then could you suggest any other way of measuring the ADC sampling frequency as I need to know and be able to change it’s ADC sampling frequency for my project. Thanks in advance.

        • Khaled Magdy says:

          I think switching to espressif IDF can help you get more control over the ADC operation. But with a little bit more complex workflow. I’ll be doing this in the future here on my website.
          For now, you can search online if you want to take this route!

  2. Samit Hasan says:

    Also as mentioned ADC-DMA can be used for faster sampling ,could you kindly demonstarate with example how to store ADC values in DMA and read it. Hoping to see more about DMAs. And yes, you have created an excellent website,your contents are so well explained and brought all together in a
    so much organized way.Looking forward to seeing lots of videos and projects from here .

  1. June 1, 2021

    […] knapp 2000 Messwerte gesammelt und daraus ein Diagramm erstellt, das sich so auch an vielen anderen Stellen im Web findet, die sich mit den Analog-Digital-Konvertern des ESP32 […]

Leave a Reply

%d bloggers like this: