Most sensors have an analog output. Few sensors come with an integrated controller and can transmit output measurements via a digital protocol. This is why analog to digital conversion is one of the basic features that every microcontroller/gate has. The other basic features include digital input/output, analog output, PWM generation and serial data communication.
With MicroPython, it is useful to read analog voltages from sensors on supported microcontroller boards/ports. Compared to other popular microcontroller ecosystems like Arduino, MicroPython even allows you to access the internal components of the ADC peripherals of supported boards. You can't just read analog voltages; you can even configure ADC peripherals with custom settings. This article will discuss ADC modules in MicroPython and examine how MicroPython firmware is used to read analog signals in the ESP8266 and ESP32.
Machine Module
MicroPython consists of several hardware-specific modules. One of these modules is the machine. This module consists of several classes that are written to control digital input/output, control output signals from external devices, pulse width modulation, analog to digital conversion, control ADC, UART, SPI, I2C, I2S, Timer peripherals, RTC, Watchdog timer and manage SD card. There is an ADC class on the machine module to control analog-to-digital conversions – an additional class, ADCBlock, written for custom configuration and management of ADC peripherals.
ADC class in machine module
The ADC class is responsible for providing an interface for analog-to-digital converters on MicroPython ports. It provides clean, straightforward functions that allow you to configure ADC settings and retrieve ADC readings as discrete values or direct voltages. This module is imported into a MicroPython script using the following statement.
of import of ADC machine
After importing the ADC class from the machine module, you must instantiate an ADC object. This is done by calling the machine.ADC method with an assignment to a variable. The machine.ADC method has the following prototype.
class machine.ADC(id, *, sample_ns, atten)
The ADC method takes three arguments – id, sample_ns and atten, of which sample_ns and atten are optional parameters. The ID is the identification of the ADC source. It can be an ADC channel or an ADC pin. ADC source identification depends on the specific port. It is specified by an integer representing the ADC channel or by a PIN object. The sample_ns parameter is the sampling time in nanoseconds. The atten parameter specifies the input attenuation.
Once the ADC object is defined with the help of the ADC constructor, it can be modified later by calling the ADC.init method. The method has the following prototype.
ADC.init(*, sample_ns, attention)
The ADC class provides two methods for reading ADC input as integer values. It takes sample_ns and atten as arguments. The id parameter is not allowed because the ADC object identifier cannot be changed.
ADC.ler : This method takes the analog reading and returns a value from 0 to 1023. The returned value is a proportional representation of the detected voltage scaled so that the minimum value is 0 and the maximum value is 1023. The method is always called without any arguments.
ADC.read_u16 : This method takes the analog reading and returns a value from 0 to 65535. The returned value is a proportional representation of the detected voltage scaled so that the minimum value is 0 and the maximum value is 65535. The method is always called without any arguments.
The ADC class of machine modules also provides a method that directly returns the detected voltage expressed in microvolts. This method is ADC.read_uv . Whether the returned value is calibrated or not depends on the specific port.
The ADC class also provides a method that allows you to change the resolution of the ADC. This method is ADC.width . The number of bits is required according to the ADC resolution to be set. The availability of this method depends on the specific port. It can only be used on ports where the resolution of the ADC channels is configurable.
ADC block class in machine module
MicroPython provides an additional class for more precise control of the ADC peripheral. This class is ADCBlock. Whether this class is available or not depends on the specific port. It is only available for ports where multiple ADC channels are associated with the ADC peripheral. If the selected port has an ADC peripheral that has several channels associated with it, an object of the ADCBlock class must be instantiated instead of the ADC class. This class is imported into a MicroPython script using the following instructions.
importing the ADCBlock machine
An ADCBlock object can be instantiated by calling the machine.ADCBlock method. This method has the following prototype.
class machine.ADCBlock(id, *, bits)
The constructor takes two parameters – id and bits. The id is an integer or string that specifies the ADC peripheral. The parameter bits specify the resolution of the ADC in bits. The previous or default resolution is set for the ADC peripheral if the bits argument is not passed. You can modify the ADCBlock object later by calling the ADCBlock.init method. The method has the following prototype.
ADCBlock.init(*,bits)
The ADCBlock.init method only allows you to reconfigure the ADC peripheral resolution. Another important method of the ADCBlock class is connect . It has the following prototype.
ADCBlock.connect(source)
ADCBlock.connect(channel)
ADCBlock.connect(channel, source)
The ADCBlock.connect method connects to an ADC channel of the specified ADC peripheral. The method takes one or two arguments. If only the source is specified, the ADC object will be connected to the default channel of the specified ADC peripheral. The origin must be specified as a Pin object. If only one channel is specified, the selected channel will be configured for sampling with a default pin. The channel must be specified as an integer. If channel and source are specified, the assigned pin object will be connected to the specified channel of the ADC peripheral.
Once the ADCBlock object is instantiated and wired, ADC readings can be obtained by calling the read , read_u16 , or read_uv methods on the object. Following is a valid example of analog voltage reading using the ADCBlock class.
#ADCBlock with peripheral ID and 12-bit resolution
block = ADCBlock(id, bits=12)
# connect channel 4 to the provided pin
adc = block.connect(4, pin)
# reads analog voltage in microvolts
val = adc.read_uv
Analog to digital conversion in ESP8266
ESP8266 has only one analog input. It is available on a dedicated pin called A0. The associated ADC peripheral has a fixed resolution of 10 bits. Therefore, analog readings range from 0 to 1023. Although the ADC pin on the ESP8266 can tolerate voltages up to 3.3 V, the input voltage applied for measurement must be between 0 V and 1.0 V.
For ESP8266, an ADC object can be instantiated in MicroPython using the ADC class. As there is only one analog input, the object must be defined using the following instruction.
adc = ADC(0)
Only 10-bit resolution is allowed, so ADC reading can be achieved by just calling the ADC.read method as follows.
adc.ler
Analog to digital conversion in ESP32
ESP32 has two SAR ADC peripherals. These peripherals are called ADC1 and ADC2. They are identified as ADC block 1 and ADC block 2. ADC1 has 8 ADC channels and ADC2 has 10 ADC channels. Pins 32~39 are connected to the ADC channels of ADC1 (block 1). Pins 0, 2, 4, 12, 13, 14~15 and 25~27 are connected to the ADC channels of ADC2 (block 2).
The default resolution of ESP32 ADC peripherals is 12 bits. Therefore, analog readings can range from 0 to 4095. However, the resolution of both ADC peripherals is configurable and can be set to a lower resolution such as 10-bit, 9-bit, or 8-bit. The analog input pins and associated ADC channels on the ESP32 are listed in the table below.
Both ADC and ADCBlock classes can be used for analog-to-digital conversion in ESP32. The ADC or ADCBlock object must be instantiated to access the ADC on the ESP32. The resolution of the instantiated ADC channel can be set by calling the ADC.width or ADCBlock.init methods. If the ADCBlock object is instantiated, it can be connected to a channel or pin object. In ESP32, arbitrary connections between ADC and GPIO channels are not allowed. Therefore, the ADCBlock.connect method will not accept both parameters. If both arguments are passed, ADCBlock.connect will raise an error if the given ADC channel is not bound to the specified pin. The id in the ADCBlock method can be 1 or 2 and the resolution can be 9~12. The ADC pins on the ESP32 can tolerate voltages up to 3.3V. However, because the internal voltage reference is set to 1.1 V, they can only read analog voltages up to 1.1 V. The minimum voltage that both peripherals can detect is 100 mV. All voltages below 100 mV are read as 0. It is important to note that ADC2 is shared with Wi-Fi, so the analog voltage of the pins associated with ADC2 cannot be read if Wi-Fi is active.
Analog readings can be obtained by calling the ADC.read or ADC.read_uv methods. Since the ESP32's ADC peripherals read analog voltages below 100 mV as 0, the readings tend to be non-linear as they are close to the internal reference voltage, i.e. 1.1V, ADC.read_uv is recommended to obtain ADC readings. If the ADC resolution is set to 11 or 12 bits, the ADC.read_u16 method must be used.
Controlling the LED blink rate with a potentiometer
Now equipped with the knowledge of MicroPython's ADC and ADCBlock classes and their application on the ESP8266 and ESP32 boards, we will now design a simple application demonstrating the use of analog-to-digital conversion. In this application, we will control the blinking rate of an LED by taking the analog voltage as input to a potentiometer.
Required components
- ESP8266x1
- 5mm LED x1
- 330Ω resistance x1
- 1K pot x1
- Breadboard x1
- Connecting wires/jumper wires
Circuit Connections
Connect the GPIO5 of the ESP8266 to the LED anode. Connect the cathode of the LED with a 330 Ohm series resistor and ground the other terminal of the resistor. Take a potentiometer and connect the fixed terminals 1 and 3 with ground and 3.3V respectively. Connect variable terminal 2 of the potentiometer to analog input A0 of the ESP8266. DC supply voltage and ground can be supplied to the circuit from the 3V3 output and one of the ground pins of the ESP8266 respectively.
Note that the MicroPython firmware must already be loaded on the ESP8266. Learn more about uploading MicroPython firmware to the ESP8266 and ESP32.
MicroPython Script
Import Pin Machine, ADC
of time matter sleep
pot = ADC(0)
led = Pin (5, Pin.OUT)
while True:
pot_value=pot.read
print(pot_value)
led.on
sleep(pot_value/256)
led.off
sleep(pot_value/256)
How it works
The ESP8266 reads analog voltage from a potentiometer on its single analog input pin, A0. The reading voltage is printed in the uPyCraft IDE console. The same analog reading is divided by 256 and passed as a time interval between turning the LED light on and off.
The code
The code starts by importing the Pin and ADC classes from the machine and the sleep classes from the timing modules. The ADC is initialized by calling the ADC method, creating an ADC object called 'pot'. The GPIO5 pin to which the LED is connected is defined as digital output by calling the Pin method. In an infinite while loop, the ADC reading of the potentiometer is read by calling the read method and is stored in a 'pot_value' variable. The same value is printed to the console log by calling the print function. The LED is turned on by calling the method on the LED pin object. A delay of pot_value/256 is invoked by calling the sleep method. Then the LED is turned off by canceling the method on the LED pin object, and the same delay is provided. This repeats indefinitely, so the blink rate of the LED is controlled by the real-time analog input of the ESP8266.
Result