MaixPy #3: Maixduino Analog-To-Digital Converter (ADC)
An ADC converts a continuous-time and continuous-amplitude analog signal to a discrete-time and discrete-amplitude digital signal. The conversion involves quantization of the input, so it necessarily introduces a small amount of error or noise. Furthermore, instead of continuously performing the conversion, an ADC does the conversion periodically, sampling the input, limiting the allowable bandwidth of the input signal. [1]
In this tutorial, I am going to talk about the ADC function of the Maixduino and do one experiment: using a LDR (light dependent resistor) to light up an LED based on the brightness conditions.
What Will We Need
- Maixduino development board: https://s.click.aliexpress.com/e/_DBd6fTF
- LDR (Light dependent resistor) and a LED
- 2 Resistors (1×86Ω and 1x10KΩ)
- Breadboard and some jumper wires
Maixduino ADC
Introduction
The AI module does not have the ability to process analog signals, so we have to implement communication between the Sipeed M1 AI module and the ESP32 that is available on the board. There are 6 pins available for ADC operations on the board, and the communication is done via SPI. This interface is also used for WIFI functions.
Here’s a link to the bigger schematic here (Sipped official site).
Reading an analog value with the ESP32 from the Maixduino board means you can measure varying voltage levels between 0 V and 3.3 V. The voltage measured is then assigned a value between 0 and 4095, in which 0 V corresponds to 0, and 3.3 V corresponds to 4095. [4]
The pins have a 12 bit resolution in normal mode (without attenuation). The 4095 value comes from 2^12=4096, but it starts at zero, so we have the 0-4095 range.
In theory, the range of the ADC pins should be linear, but it isn’t. It is non-linear. That is what we are going to see next.
ESP32 ADC Problems
I have to say that the Esp32 ADC is not perfect. There are a lot of forums discussing these details, and almost everyone has negative things to say about them. The basic fact is that there are two aspects to the problem: the noise and the calibration. These are things that should be minimized when the boards are being made and these are one of them. It makes no sense for me to have to connect a bypass capacitor and calibrate the pins using a reference voltage. All of this can be seen on the official Espressif site. Next, I will state the main problems:
- Noise
- ADC Calibration: This is a very pretty official calibration graph from Espressif.
- ADC behaviour in practice: As we can see, it’s not so pretty. The ESP32 can’t distinguish 3.3 V from 3.2 V. You’ll get the same value for both voltages: 4095. And in the lower range, the same for 0 V and 0.1 V, you’ll get the same value: 0.
Source: https://github.com/espressif/arduino-esp32/issues/92
Maixduino ADC Pins
Of the 6 available pins, some of them have special characteristics. The schematic of the Maixduino Esp32 doesn’t specify if all the functions of the original ESP32 board are available. My research led me to a site where people like Rui Santos (randomnerdtutorials.com) have studied the esp32 extensivly. Assuming that the ADC pins of the Maixduino have all the functions, we can say that:
- IO36 (AI Module) <–>A5 (Esp32) = Sensor_VP (Special pins) [Input Only]
- IO39 (AI Module) <–>A4 (Esp32) = Sensor_VN (Special pins) [Input Only]
- IO34 (AI Module)< –>A3 (Esp32) = Normal pin [Input Only]
- IO35 (AI Module) <–>A2 (Esp32) = Normal pin [Input Only]
- IO32 (AI Module) <–>A1 (Esp32) = Capacitive touch GPIOs [Input/Output]
- IO33 (AI Module) <–>A0 (Esp32) = Capacitive touch GPIOs [Input/Output]
Maixduino original schematic link (Github)
In depth, the caracteristics of the pins in here (Technical Reference Manual for SP32)
SPI Comunication
The Serial Peripheral Interface (SPI) is a synchronous serial communication interface specification used for short-distance communication, primarily in embedded systems. The interface was developed by Motorola in the mid-1980s and has become a de facto standard. Typical applications include Secure Digital cards and liquid crystal displays.[2]
The SPI bus specifies four logic signals:
- SCLK: Serial Clock (output from master)
- MOSI: Master Out Slave In (data output from master)
- MISO: Master In Slave Out (data output from slave)
- CS /SS: Chip/Slave Select (often active low, output from master to indicate that data is being sent)
In the communication between the AI Module (Sipeed M1) and the ESP32, we have the usual four pins and two extra.
- SCLK: IO27 (AI Module)<–>Esp32 GPIO18
- MOSI: IO28 (AI Module)<–>Esp32 GPIO14
- MISO: IO26 (AI Module)<–>Esp32 GPIO23
- CS: IO25 (AI Module)<–>Esp32 GPIO5
- ESP32_EN: IO8 (AI Module)<–>Esp32 CHP_PU (PIN3)
- ESP32_READY: IO9 (AI Module)<–>Esp32 GPIO25
Maixduino ADC Code (Original)
Import the modules:
import time, network from Maix import GPIO from fpioa_manager import fm
Make the pin connections for the SPI communication, creating a class wifi:
class wifi():
# IO map for ESP32 on Maixduino
fm.register(25,fm.fpioa.GPIOHS10)#cs
fm.register(8,fm.fpioa.GPIOHS11)#rst
fm.register(9,fm.fpioa.GPIOHS12)#rdy
print("Use Hareware SPI for other maixduino")
fm.register(28,fm.fpioa.SPI1_D0, force=True)#mosi
fm.register(26,fm.fpioa.SPI1_D1, force=True)#miso
fm.register(27,fm.fpioa.SPI1_SCLK, force=True)#sclk
nic = network.ESP32_SPI(cs=fm.fpioa.GPIOHS10, rst=fm.fpioa.GPIOHS11, rdy=fm.fpioa.GPIOHS12, spi=1)
print("ESP32_SPI firmware version:", wifi.nic.version())
Create a loop to display all the pin values and the ADC on the serial terminal:
# get ADC0 ADC1 ADC2 adc = wifi.nic.adc((0,1,2)) print(adc) while True: try: # get ADC0~5 adc = wifi.nic.adc() except Exception as e: print(e) continue for v in adc: print("%04d" %(v), end=" ") print(': adc')
Maixduino ADC Code (My Code)
I almost broke my brain trying to figure out how to access just one pin of the ADC. To do that, we have to add a comma in the code ((0,)). This is my code to access the A0 pin of the Maixduino:
I made a little change, converted the raw values from the range 0-4095 to the voltage range of 0-3.3 Volt witch i think is better for visualization in the serial terminal. We just have to divide the voltage (3.3 V) for 4096 and it will give us 0.000806.
import time, network, utime from Maix import GPIO from fpioa_manager import fm class wifi(): # IO map for ESP32 on Maixduino fm.register(8,fm.fpioa.GPIOHS11)# ESP32_EN fm.register(9,fm.fpioa.GPIOHS12)# ESP32_READY fm.register(28,fm.fpioa.SPI1_D0, force=True)# MOSI fm.register(26,fm.fpioa.SPI1_D1, force=True)# MISO fm.register(27,fm.fpioa.SPI1_SCLK, force=True)# SCLK fm.register(25,fm.fpioa.GPIOHS10)# CS nic = network.ESP32_SPI(cs=fm.fpioa.GPIOHS10, rst=fm.fpioa.GPIOHS11, rdy=fm.fpioa.GPIOHS12, spi=1) while True: adc = wifi.nic.adc((0,)) #Accessing the A0 pin of the maixduino adc0=adc[0]*0.000806 #Convertion to volt #print(adc) print(adc0) #Print the voltage values on the serial terminal utime.sleep_ms(150)
A Tuple in Python (Extra):
- Tuples are used to store multiple items in a single variable.
- Tuple is one of 4 built-in data types in Python used to store collections of data, the other 3 are List, Set, and Dictionary, all with different qualities and usage.
- A tuple is a collection which is ordered and unchangeable.
- Tuples are written with round brackets. [5]
To access just one item, we have to use the [,]:
thistuple = ("apple",) print(type(thistuple)) #NOT a tuple thistuple = ("apple") print(type(thistuple))
Maixduino ADC Experiment With LDR and LED
Introduction
For this experience, we are going to use an LDR (Light-Dependent Resistor) and a LED. The objective is to light up the LED when we cover the LDR.
An L.D.R. is an electrical component whose resistance varies according to the luminosity of the place where it is inserted. When the component is covered, its resistance increases, staying at that of the MegaOhm (MΩ) range, and when the opposite level occurs, that is, when the brightness of the place is high, its resistance decreases.
Maixduino LDR Experiment
Calculations
I am going to use the raw values of the ESP32 ADC without any modification in terms of noise or calibration procedures.
The calculations are straight-forward because I’m lazy. In the LDR, we use the principle of voltage divider, and one of the characteristics is that if we have two resistences of the same value, the voltage output is half the value. That is what I am going to use here. The steps are as follows:
- Measure with a multimeter the resistance of the LDR in normal brightness conditions. For me, it gave approximately 10KΩ.
- So, the other resistance will also be 10KΩ.
- In the end, for Vin = 3.3 V we should have a Vout = 1.65 V approximately.
Calculations:
So in this connection, as LDR resistance increases (meaning light decreases, the voltage increases, and vice versa).
Circuit
Final Code
import time, network, utime from Maix import GPIO, from fpioa_manager import fm #IO map of the PIN2 PIN2 = 21 fm.register(PIN2, fm.fpioa.GPIO0) pin2=GPIO(GPIO.GPIO0, GPIO.OUT) class wifi(): # IO map for ESP32 on Maixduino fm.register(8,fm.fpioa.GPIOHS11)#en fm.register(9,fm.fpioa.GPIOHS12)#rdy fm.register(28,fm.fpioa.SPI1_D0, force=True)#mosi fm.register(26,fm.fpioa.SPI1_D1, force=True)#miso fm.register(27,fm.fpioa.SPI1_SCLK, force=True)#sclk fm.register(25,fm.fpioa.GPIOHS10)#cs nic = network.ESP32_SPI(cs=fm.fpioa.GPIOHS10, rst=fm.fpioa.GPIOHS11, rdy=fm.fpioa.GPIOHS12, spi=1) while True : adc = wifi.nic.adc((0,)) adc0=adc[0]*0.000806 #convertion to volt #print(adc) values in the 0-4095 range print(adc0) #values in the 0-3.3V range utime.sleep_ms(50) #if adc[0] > 3000: if statement in the 0-4095 range if adc0 > 2.45: #if statement in the 0-3.3V range pin2.value(1) else: pin2.value(0)
Maixduino Experiment Materials
References
[1] https://en.wikipedia.org/wiki/Analog-to-digital_converter
[2] https://en.wikipedia.org/wiki/Serial_Peripheral_Interface
[3] https://wiki.sipeed.com/soft/maixpy/en/
[4] https://randomnerdtutorials.com/esp32-adc-analog-read-arduino-ide/
[5] https://www.w3schools.com/python/python_tuples.asp
[6] https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html