The first step in programming embedded devices is to perform digital input/output. Digital input/output refers to reading parallel data. The same logical signals are utilized for switching and control operations. MicroPython, an embedded firmware, essentially includes libraries to control digital input/output and other hardware functions. MicroPython can be uploaded and run on a variety of hardware platforms, i.e., microcontrollers. These microcontrollers are called ports in the MicroPython terminology. Every port has its own hardware features, built-in peripherals, and pin configuration. Despite, MicroPython supporting various microcontroller systems that are all different from each other, it has a universal syntax for all hardware functionalities. In this article, we will discuss how we can read digital input and generate digital output using MicroPython. As we will be testing our MicroPython codes on ESP8266 and ESP32, we will familiarize ourselves with both boards and show how digital input/output works.
Even simple digital input/output operations involve sound technical know-how. To learn the technical aspects of digital input in microcontrollers and have some in-depth knowledge of LEDs, check out our article on digital input in Arduino. To learn more about switches and technical aspects of digital output in microcontrollers, we recommend going through our article on digital output in Arduino. It is expected that you are familiar with What MicroPython is. And yes, before you try hands-on digital input/output in ESP8266/ESP32 using MicroPython, you must upload the MicroPython firmware in ESP8266/ESP32 and get ready with uPyCraft/Thonny IDE on your computer.
MicroPython machine library
MicroPython provides the machine module for several specific functions related to microcontroller hardware. The module contains classes for controlling digital input/output, controlling output signals from external devices, pulse width modulation, analog to digital conversion, controlling ADC peripherals, UART, SPI, I2C, I2S, Timer, RTC, Watchdog timer, and managing SD card. The module is designed to directly access different hardware blocks of various microcontrollers without any restriction. Apart from classes related to specific hardware functions, the module provides methods for enabling/disabling interrupts, power management, reset, and bootloader. Usually, only the required classes of the module are imported into a MicroPython script to keep the code short and clean.
PIN class in machine library
The PIN class of the machine module is designed to control general-purpose input/output (microcontroller GPIO). This class is imported in a MicroPython script using the following statement.
from machine import Pin
The pin class contains methods to set the pin mode and get and set the logic levels of digital pins. Each port (supported microcontroller) has a different pin configuration and pin assignment. A physical pin of a port is defined by an identifier, i.e., a variable. The identifier is an instantiation of the Pin object. A specific physical pin of a port/microcontroller is specified by a number, string, or tuple id in the pin object instantiation. The id is specific to a given port/microcontroller. The pin instantiation is done by the Pin() method of the class. This method is defined as follows.
class machine.Pin(id, mode=- 1, pull=- 1, *, value=None, drive=0, alt=- 1)
The parameters of the Pin() method are described as follows.
id: The mandatory parameter identifies a specific physical pin on a given port/microcontroller. The id may be provided as a number, string, or tuple.
mode: It specifies the pin mode of the specified pin. The mode is specified by the following constants – Pin.IN, Pin.OUT, Pin.OPEN_DRAIN, Pin.ALT, Pin.ALT_OPEN_DRAIN, Pin.ANALOG. If mode is set to Pin.IN, the pin is configured to digital input. To external devices, this pin is in a high impedance state. If mode is set to Pin.OUT, the pin is configured to digital output. If mode is set to Pin.OPEN_DRAIN, the pin is configured to open-drain output. It means that if the pin is set to 0, it is active at LOW. If set to 1, it goes in a high impedance state where it is in a floating state until pulled down or pulled up by an external circuit. Not all ports/microcontrollers support open-drain output. If mode is set to Pin.ALT, the pin is configured to alternate functions like SPI, I2C, or UART functions assigned to it in a given port. The Pin.ALT_OPEN_DRAIN mode is the same as Pin.ALT except that it configures the pin open-drain. If the mode is set to Pin.ANALOG, the pin is configured to analog input.
pull: It specifies the internal pull-up/pull-down when the pin is configured as input. It is specified by the following constants – None, Pin.PULL_UP, and Pin.PULL_DOWN. If the pull is set to None, neither pull-up nor pull-down resistor is enabled. If the pull is set to Pin.PULL_UP, pull-up resister is enabled at the specified pin. If the pull is set to Pin.PULL_DOWN, pull-down resister is enabled at the specified pin.
value: It specifies the signal level of the pin if its mode is set to OUT or OPEN_DRAIN. If set to 0, the output signal is LOW. If set to 1, the output signal is set to HIGH.
drive: It specifies the output power or output current carrying capability of the given pin. It can be set to Pin.DRIVE_0, Pin.DRIVE_1, Pin.DRIVE_2 or Pin.DRIVE_3, corresponding to ascending output power as mentioned in the table below.
For example, the following statement sets pin 2 of a given port to digital output.
p2 = Pin(2, Pin.OUT)
Similarly, the following statement sets pin 1 of a given port to a digital input with internal pull-up enabled.
p1 = Pin(1, Pin.IN, Pin.PULL_UP)
Unlike typical microcontroller programming, MicroPython allows you to change the mode and other parameters of a pin in the mid of the program. This is done by re-initializing the pin using Pin.init() method. This method is defined as follows.
Pin.init(mode=- 1, pull=- 1, *, value=None, drive=0, alt=- 1)
The Pin.init() method parameters are specified as the same as in Pin() method. The mode of a pin object can also be explicitly set by calling Pin.mode() method. If Pin.mode() is called without arguments, it returns the current mode of the given pin. Similarly, Pin.pull() method lets set or get the pin pull state, and Pin.drive() lets get or set the pin drive strength.
The digital logic at a given pin is obtained or set using the Pin.value() method. It is defined as follows.
Pin.value([x])
If called without arguments, it returns the digital logic level of the pin. If the pin is set to Pin.IN, it returns the actual input value currently present on the pin. If the pin is set to Pin.OUT, it returns an undefined value. If the pin is set to Pin.OPEN_DRAIN, returns a value of undefined if set to 0, otherwise returns the actual input value currently present at the pin if set to 1. If Pin.value() is supplied with an argument, it must be a boolean, i.e., true/1 or false/0. If the given pin is set as Pin.OUT, the value passed is immediately set in the output buffer. If the pin is set as Pin.IN, the value is stored in the output buffer and becomes active as the pin is set to Pin.OUT or Pin.OPEN_DRAIN. Until then, the pin remains in a high impedance state. If the pin is set to Pin.OPEN_DRAIN, a value of 0/false sets the pin to a low voltage state while a value of 1/true sets the pin to a high-impedance state where an external circuit drives the pin. Pin.__call__() method is similar to Pin.value() except that it is faster than Pin.value() method.
The output at a pin can also be set to 1 by calling Pin.high() or Pin.on() methods. Similarly, the output at a pin can be set to 0 by calling Pin.low() or Pin.off() methods. The Pin.irq() method is for handling the pin interruptions.
Some methods also have some port-specific parameters. For example, in ESP32, the hold parameter in Pin() and Pin.init() methods let enable the pad hold feature of ESP32. If the hold is set to True, the pin configuration is held, and any further changes are not applied. Any pin configurations set after setting hold to True are applied only when the hold is set to False. If the hold is again set to True, any recent configuration changes are applied and the pin is set to hold again.
ESP8266 features and pin configuration
We will be running our MicroPython codes on ESP8266 and ESP32 boards. ESP8266 has 17 GPIO, one SPI and HSPI interface, one I2C, two UART, one I2S input and output interface with DMA, and a 10-bit ADC. The I2C is implemented in software that can be multiplexed over any GPIO. ESP8266 is available in a variety of breakout boards. How different pins and peripherals are exposed differs among different ESP8266 boards. The most popular ESP8266 boards are ESP8266-01 and ESP8266-12E. The pinout of ESP8266-01 is shown in the image below.
The ESP8266-12E chip has the following pinout.
The ESP8266-12E breakout board has the following pinout.
Digital input/output in ESP8266
ESP8266 GPIOs are 3V3 compatible and can sink or source a maximum of 40 mA. As we can see, GPIO6, GPIO7, GPIO8, and GPIO11 are not available for input/output in the ESP-12E breakout board, which is commonly available as a NodeMCU kit. The GPIO6 is exposed as ADC0 in the breakout board. The GPIO6~GPIO11 is connected to the Flash chip. That is why these are not recommended to use for digital input/output. We are then left with GPIO0~GPIO5 and GPIO12~GPIO16.
The GPIO0 is used for setting the ESP8266 in bootloader mode. That is why GPIO0 can be used only after burning the MicroPython firmware in ESP8266. Similarly, GPIO16 is used to wake up the ESP8266 from a deep sl; therefore, it must be avoided for digital input/output if the deep sleep mode is utilized in the embedded application.
While assembling an external circuit with ESP8266, the boot behavior of its GPIO pins needs to be considered. When ESP8266 boots up, the board uses GPIO0, GPIO1, GPIO2, GPIO3, GPIO9, GPIO10, GPIO15, and GPIO16. The GPIO1, GPIO3, GPIO9, GPIO10, and GPIO16 are pulled HIGH at the boot up while other GPIOs, except for GPIO4 and GPIO5, output a LOW signal. Therefore, if any relay is driven in the circuit attached to ESP8266, only GPIO4 and GPIO5 are suitable to connect the relays. All other GPIOs can result in autonomous relay operations due to the odd behavior of GPIOs with the relay driving circuit during the bootup. It should also be noted that the boot will fail if GPIO0, GPIO1, and GPIO2 are externally pulled low in the circuit. The boot will also fail if GPIO15 is externally pulled high in the circuit. Therefore, the following precautions must be taken while assembling an external circuit with ESP8266.
- Any relay driver if connected to the circuit must only be interfaced at GPIO4 and GPIO5.
- The GPIO0, GPIO1, and GPIO2 should never be pulled low by the external circuit.
- The GPIO15 should never be pulled high by the external circuit.
- The GPIO16 should not be used for digital I/O if the deep sleep mode of ESP8266 is utilized in the embedded application.
- The GPIO0 should be avoided if ESP8266 needs boot loading several times during the application development.
Finally, it must be remembered that the I2C interface in ESP8266 is available on GPIO4 (I2C SDA) and GPIO5 (I2C SCL) in the NodeMCU kit. The SPI interface is available at GPIO12 (MISO), GPIO13 (MOSI), GPIO14 (SCK) and GPIO15 (CS). The PWM is available in all GPIO, while the interrupts are available at all GPIOs except GPIO16.
ESP32 features and pin configuration
ESP32 is a superior Wi-Fi development board compared to ESP8266. It has 39 GPIOs, of which 34 GPIO are normal digital input/output. It comes packed with 18 ADC channels, 16 PWM output channels, 3 UART, 3 SPI interfaces, 2 I2C interfaces, 2 DAC, 2 I2S interfaces, and 10 capacitive sensing GPIOs. The bare ESP-WROOM-32 chip has the following pin diagram.
The ESP32-DevKit V1 has the following pin diagram.
The ESP32-DevKit V1 has the following pin diagram.
Digital input/output in ESP32
ESP32 GPIOs are 3V3 compatible and can source or sink a maximum of 40 mA. However, the development board can also be powered by a 5V supply, as it has an onboard voltage regulator. There are 39 GPIOs in ESP32. These GPIO6~GPIO11 are connected to internal SPI flash; therefore, these should be best avoided. The GPIO28~GPIO31 are not exposed on the development board. The GPIO34, GPIO35, GPIO36, and GPIO39 are input only. Rest all GPIOs can be used for both digital input and output.
Like ESP8266, ESP32 also shows some odd behavior at some of its GPIO during boot up. The GPIO1, GPIO3, GPIO5, GPIO6~GPIO11, GPIO14, and GPIO15 are pulled HIGH during boot up, and the GPIO0, GPIO5 GPIO14, and GPIO15 output PWM signal during boot up. During boot up, the GPIO1 is engaged for debugging the output. Therefore, if interfaced in the circuit, any relay driver should not be connected to these pins. Otherwise, the relay driving circuit will exhibit autonomous behavior due to the odd state of these GPIOs while boot up.
It should also be remembered that UART is available at the following pins.
There are two I2C channels in ESP32. The default I2C pins in ESP32 are GPIO21 and GPIO22. However, the software I2C can be implemented on any GPIO.
There are 3 SPI channels in ESP32, of which one is reserved for communication with flash memory and two channels are available for use. These two channels are hardware SPI (HSPI) and virtual SPI (VSPI). The SPI channels are available at the following pins.
There are 16 PWM channels in ESP32. These can be multiplexed to output PWM output at all pins except GPIO34~GPIO39. The pin interruptions are available at all GPIOs. The capacitive GPIOs are GPIO4 (T0), GPIO0 (T1), GPIO2 (T2), GPIO15 (T3), GPIO13 (T4), GPIO12 (T5), GPIO14 (T6), GPIO27 (T7), GPIO33 (T8) and GPIO32 (T9). The RTC GPIOs are GPIO36 (RTC_GPIO0), GPIO39 (RTC_GPIO3), GPIO34 (RTC_GPIO4), GPIO35 (RTC_GPIO5), GPIO25 (RTC_GPIO6), GPIO26 (RTC_GPIO7), GPIO33 (RTC_GPIO8), GPIO32 (RTC_GPIO9), GPIO4 (RTC_GPIO10), GPIO0 (RTC_GPIO11), GPIO2 (RTC_GPIO12), GPIO15 (RTC_GPIO13), GPIO13 (RTC_GPIO14), GPIO12 (RTC_GPIO15), GPIO14 (RTC_GPIO16) and GPIO27 (RTC_GPIO17).The ADC channels are multiplexed to GPIO36 (ADC1_CH0), GPIO37 (ADC1_CH1), GPIO38 (ADC1_CH2), GPIO39 (ADC1_CH3), GPIO32 (ADC1_CH4), GPIO33 (ADC1_CH5), GPIO34 (ADC1_CH6), GPIO35 (ADC1_CH7), GPIO4 (ADC2_CH0), GPIO0 (ADC2_CH1), GPIO2 (ADC2_CH2), GPIO15 (ADC2_CH3), GPIO13 (ADC2_CH4), GPIO12 (ADC2_CH5), GPIO14 (ADC2_CH6), GPIO27 (ADC2_CH7), GPIO25 (ADC2_CH8) and GPIO26 (ADC2_CH9). The DAC channels are available at GPIO25 (DAC1) and GPIO26 (DAC2).
Driving a LED by switch on ESP8266/ESP32
Now equipped with the knowledge of digital input/output in MicroPython and familiar with pin configuration and features of ESP8266/ESP32, we can drive components like LEDs by ESP8266/ESP32 and control their switching using push buttons. Let us switch ON/OFF a LED by a tactile switch on ESP8266 using MicroPython code.
Components required
- ESP8266 x1
- LED x1
- 330Ω resistor x2
- Push button x1
- Connecting wires/jumper wires
- Breadboard
Circuit connections
Connect the anode of LED with the GPIO5 of ESP8266 and connect its cathode with ground via a series resistor of 330 ohm. Connect push button at GPIO4 of ESP8266 pulled down by a resistor of 330 ohm.
Note that the MicroPython firmware must be uploaded in the ESP8266. The Python code can be written and uploaded to ESP8266 using uPyCraft or Thonny IDE.
Python code
from machine import Pin
from time import sleep
led = Pin(5, Pin.OUT)
button = Pin(4, Pin.IN)
while True:
led.value(button.value())
sleep(0.5)
Working
The script begins with importing the Pin class of the machine module and the sleep class of the time module. The GPIO5 is set as digital output to drive LED. The GPIO4 is set as a digital input with a push button connected in a pulled low configuration. In the while loop, the output at the GPIO5 is set to the input at GPIO4. The GPIO4 is pulled low by default, so the LED remains off. Whenever the push button is pressed, the GPIO4 goes HIGH, a HIGH signal is passed at GPIO5 simultaneously, and the LED starts glowing.
Result
[Link to demonstration video ESP04-DV]
You may also like:
Filed Under: Featured, Python, Tutorials