In this tutorial, we will see how to use ADCs in MicroPython to perform an analog input for reading sensors and other devices.
Unlike digital inputs, which can only detect two states (HIGH or LOW), analog inputs allow us to measure continuous values, within a specific voltage range and with a certain resolution.
The measurement is performed by an ADC (Analog-to-Digital Converter), which transforms the analog voltage into a digital value that can be processed by the microcontroller.
We can use analog inputs to read sensors that operate via analog signals, such as potentiometers, temperature sensors, light sensors, pressure sensors (or any other magnitude that varies gradually).
Configuring Analog Inputs
In MicroPython, analog inputs are configured using the ADC class from the machine module (which we know well by now).
# Configure the analog pin (GPIO 34 on ESP32)
pot = ADC(Pin(34))
Once the ADC instance is defined, we can use some of its methods. Some common ones are:
| Command | Description |
|---|---|
adc.read() | Reads the analog value on a scale of 0 to 4095 (ESP32) or 0 to 1023 (ESP8266) |
adc.read_u16() | Reads the analog value with 16-bit resolution (0 to 65535) |
adc.atten(attenuation) | Sets the attenuation to adjust the input voltage range |
adc.width(width) | Sets the reading resolution (only on ESP32) |
We can use the atten(attenuation) function to adapt the analog input range. On the ESP32, the available values are:
ADC.ATTN_0DB: 0 to 1.1V.ADC.ATTN_2_5DB: 0 to 1.5V.ADC.ATTN_6DB: 0 to 2.0V.ADC.ATTN_11DB: 0 to 3.3V.
MicroPython allows setting the ADC resolution via the width() method. On the ESP32, the available options are:
WIDTH_9BIT: Range from 0 to 511.WIDTH_10BIT: Range from 0 to 1023.WIDTH_11BIT: Range from 0 to 2047.WIDTH_12BIT: Range from 0 to 4095.
All functions and values may not be available on all board models. Check the information specific to your particular board.
Practical Example
Let’s see it in a simple example. The following code shows how we could read an analog voltage value from a GPIO36 pin (corresponds to an ADC1 on the ESP32).
from machine import ADC, Pin
# ADC pin configuration
adc = ADC(Pin(36)) # GPIO36 or VP
adc.width(ADC.WIDTH_12BIT) # Set resolution to 12 bits (default value)
adc.atten(ADC.ATTN_11DB) # Set input range to 0-3.3V
# Read the analog value
value = adc.read() # Read a value between 0 and 4095
print(f"Analog value read: {value}")
Analog signals often have noise. Use hardware filters (like capacitors) or software filters (averaging readings) to reduce noise. We will see this in the sensor reading tutorial.
Converting Analog Values to Physical Magnitudes
In general, the digital value obtained from the ADC is not useful by itself. We don’t care at all about its value, in ADC units 😜.
What we need is to convert it into a physical magnitude (like voltage, temperature, light, etc). To do this, a conversion formula must be applied.
For example, suppose we want to convert the digital value obtained from the ADC into a real voltage. The conversion formula is as follows:
In a 12-bit ADC with a reference voltage of 3.3V, the formula would be:
In code, this would look like this:
from machine import ADC, Pin
from time import sleep
# Configure the analog pin (GPIO 34 on ESP32)
pot = ADC(Pin(34))
# Configure the reading range (0-4095 for a 12-bit ADC)
pot.atten(ADC.ATTN_11DB) # Full range of 0 to 3.3V
while True:
value = pot.read() # Read the analog value
voltage = (value * 3.3) / 4095 # Convert to voltage
print(f"Voltage: {voltage:.2f} V")
sleep(0.5) # Wait 0.5 seconds
Which would give this output (for example), which is what we need:
Voltage: 1.65 V
Voltage: 0.82 V
Voltage: 2.47 V
...
To convert it to another magnitude, like temperature or light, you will need the sensor’s characteristic equation, which converts voltage values into the measured magnitude.
Practical Examples
Mapping Values
In some cases, it is useful to map the input range (0-4095) to a more meaningful range (for example, 0-100%):
def map(value, in_min, in_max, out_min, out_max):
return (value - in_min) * (out_max - out_min) // (in_max - in_min) + out_min
# Map analog value to percentage
percentage = map(value, 0, 4095, 0, 100)
print(f"Percentage: {percentage}%")
Reading a Potentiometer
A potentiometer is an electronic component that varies its resistance based on the position of a wiper. By connecting it to an analog input, we can measure its position and convert it into a digital value.
- Connect one end of the potentiometer to 3.3V (or 5V, depending on the microcontroller).
- Connect the other end to GND.
- Connect the wiper (center pin) to an analog pin, such as GPIO 34 on the ESP32.
from machine import ADC, Pin
from time import sleep
# Configure the analog pin (GPIO 34 on ESP32)
pot = ADC(Pin(34))
# Configure the reading range (0-4095 for a 12-bit ADC)
pot.atten(ADC.ATTN_11DB) # Full range of 0 to 3.3V
while True:
value = pot.read() # Read the analog value
print(f"Potentiometer value: {value}")
sleep(0.5) # Wait 0.5 seconds
In this example, the read value will vary according to the potentiometer’s position. If you turn the potentiometer, you will see the value change between 0 and 4095.
