MaixPy #8: Maixduino 1602 LCD | I2C MicroPython

In this experiment, an LCD based on the Hitachi HD44780 controller was used. In this type of screen, there are liquid crystals inside that when they are passed through by a current, the crystal becomes opaque, blocking the light (backlight) that tries to pass. Therefore, the various combinations of specific crystals on and off  make it possible for us to visualize on the screen what we want. Although we say that a 16×2 screen has 2 rows and 16 columns, the display block of basic characters is 5×8 pixels.


Changed by: []

In this tutorial, I am going to interface an LCD display via I2C with the Maixduino and do some experiments.

What Will We Need

  • Maixduino Board
  • 1602 LCD Display
  • I2C Adaptor
  • Logic Level Converter


  • MaixPy IDE

Maixduino I2C Comunication (Simplified)


There are other wires on the I2C Bus that constitute the circuit, but the most important ones are:

  • SCL: The clock line is responsible for the clock on the bus.
  • SDA: The data line is responsible for the transmission of the data.

These wires are connected to all the devices on the bus. In the circuit, there are also two pull-up resistors.

The resistors need to be in the circuit because the transistors are open-drain, which means that when they are activated, they are pulled to the ground (0 volts). The pull-up resistor ensures that the lines remain at the VCC voltage, which is normally 3.3-5 Volt.


I2C Master / Slave

On the bus, the devices can be either masters or slaves, but the first one (the master) is the one that gives the orders that are given via the SCL clock line. In our case, the Master will be the Maixduino Board, and the Slave, the 1602 LCD Display. 

Basic I2C Algorithm

This is a very basic I2C algorithm. There is much more to explore, like the R/W and ACK bits functions. We can write and read from a slave, but the last procedure is a bit more complex (not much). In this case, we only want to send commands to the display, so there is no need to complicate things. To write to a slave device, the algorithm is as follows:

1. Begin by sending a start sequence.This is a unique sequence in which the SDA line is allowed to change to low while the SCL is high.

2. Send the I2C address of the slave with the R/W bit low (even address). Every slave has an address that can be used for communication.

3. Send the internal register number to which you wish to write. Especify were in the memory of the slave (1602 LCD display), we what to write.

4. Send the data byte. Send the info that we want to: display on/off, show text, etc.

5. [Optional: send any additional data bytes]

6. Execute the stop sequence.(Special sequence)

The AI module of the Maixduino board has the ability to implement I2C communication. There are 2 pins available for I2C operations on the board. We can see them highlighted in the schematic below.

Here’s a link to the bigger schematic here (Sipped official site).

Maixduino I2C Pins

The pins used for I2C communication by the Ai Module of the Maixduino board are as follows:

  • IO30 I2C1_SCL (AI Module) <–>SCL PIN of the Maixduino Board
  • IO31 I2C1_SCA (AI Module) <–>SCA PIN of the Maixduino Board

Maixduino original schematic link (Github)

Maixduino I2C Converter Module for LCD

The Maixduno and other microcontrollers have a limited number of digital ports. When connecting an LCD, for example, there will be a few ports left for us to finish our project. With this module, it will only occupy two pins on our Maixduino to use a 16×2, 20×4 LCD or similar.

Maixduino Logic Level Converter

It is not good to use voltage levels higher than 3.3 on the Maixduino board pins, so it is necessary (recommended) to use a component that can do this conversion.

The circuit in this tutorial works without the converter with an external power supply at +5V powering the LCD display, but i do not recommend it. I used a multimeter to measure the voltage in the Maixduino’s pins (SDL/SDA), and it read around 3.6 volts.“Not great, not terrible.”

This happens because the LCD needs a supply voltage higher than 3.3 v to work properly. We can see that in the datasheet electrical characteristics:

The converter, powered by 5 and 3.3 Volt, allows the conversion of signals to be done either from 5->3.3 Volts or from 3.3->5 Volts.


It looks like a rats’ nest, I know, but it works

Maixduino I2C Device Search Code

If everything goes according to plan, we need to check the addresses of the devices on the i2c bus. For that, there is a simple code provided by Sipeed.

Double check the pins if the address doesn´t show 

This code is important for us because we need to find out the address of the “slave” to initialize the device in the main 1602 control code. In my case, it gave a [39] that in hexadecimal is [0x27]

from machine import I2C
i2c = I2C(I2C.I2C0, freq=100000, scl=30, sda=31) # hardware i2c
#i2c = I2C(I2C.I2C3, freq=100000, scl=30, sda=31) # software i2c
#[39] --> 0x27 in Hex
devices = i2c.scan()


With a device plugged in

Without a device being plugged in

Maixduino LCD 1602 Code

I found this code on Github. It is a work by a guy named bluejazzCHN. He picked up the work from another guy and made the changes that made it possible to run with the Maixduino board. In the end, i also made my one change, so the final version should be like a million. The code is simple but contains the most important functions.

We can clear the display:


And show a message:

lcd.message("String",Show on line 1 or 2)

I also created a simple loop with a counter that does, well… nothing good:

while 1:
    lcd.message("Counter: {}" .format(count),1)
    if count == 0 :

Extra: str.format()

Basic syntax for the str.format() method:

"template string {}".format(arguments)
  • Inside the template string, we can use {} which act as placeholders for the arguments.
  • The arguments are values that will be displayed in the string.

In my example, the argument is the variable count and values will change inside the {}


  • Check the device address
  • Check the SCL and SDA pins of the Maixduino
  • Double check all of this in the final code (in bold)

Final Code

from machine import I2C
import time, utimeclass LCD:
def __init__(self,i2c_addr = 0x27,backlight = True,scl=30,sda=31):#device constants
self.I2C_ADDR = i2c_addr
self.LCD_WIDTH = 16 # max. characters per line
self.LCD_CHR = 1 #mode – sending data
self.LCD_CMD = 0 #mode – sending command
self.LCD_LINE_1 = 0x80 # lcd ram addr for line one
self.LCD_LINE_2 = 0xC0 # lcd ram addr for line twoif backlight:
self.LCD_BACKLIGHT = 0x08 #lcd onelse:
self.LCD_BACKLIGHT = 0x00 #lcd offself.ENABLE = 0b00000100 # enable bit , E RW RS 三个由高到低构成了控制位,所以enbale bit:0b00000100# Timing constants
self.E_PULSE = 0.0001
self.E_DELAY = 0.0001
self.E_CYCLE = 0.0002
self.i2c = I2C(I2C.I2C0, mode=I2C.MODE_MASTER,freq=100000, scl=scl, sda=sda)#initialise display
self.lcd_byte(0x33,self.LCD_CMD) # 110011 initialise
self.lcd_byte(0x32,self.LCD_CMD) # 110010 Initialise
self.lcd_byte(0x06,self.LCD_CMD) # 000110 Cursor move direction
self.lcd_byte(0x0C,self.LCD_CMD) # 001100 Display On,Cursor Off, Blink Off
self.lcd_byte(0x28,self.LCD_CMD) # 101000 Data length, number of lines, font size 4 line mode
#self.lcd_byte(0x38,self.LCD_CMD) # 101000 Data length, number of lines, font size 8 line modeself.lcd_byte(0x01,self.LCD_CMD) # 000001 Clear displaydef lcd_byte(self,bits,mode):
#send byte to data pins
#bits = data
#mode = 1 for data,0 for command##four line mode:bits_high = mode | (bits & 0xF0) | self.LCD_BACKLIGHT
bits_low = mode | (bits<<4 & 0xF0) | self.LCD_BACKLIGHT
#print(bits_high,’–‘,bits_low)#high bits
self.toggle_enable(bits_high)#low bits
self.toggle_enable(bits_low)def toggle_enable(self, bits):
self.i2c.writeto(self.I2C_ADDR,bytearray([(bits | self.ENABLE)]))time.sleep(self.E_PULSE)
self.i2c.writeto(self.I2C_ADDR,bytearray([(bits | ~self.ENABLE)]))time.sleep(self.E_DELAY)def message(self, string, line = 1, position = None):
# display message string on LCD line 1 or 2
if line == 1:
lcd_line = self.LCD_LINE_1
elif line == 2:
lcd_line = self.LCD_LINE_2
raise ValueError(‘line number must be 1 or 2’)#print(‘string length :’,len(string),string)# set where will String be displayed at
self.lcd_byte(lcd_line, self.LCD_CMD)for i in range(len(string)):
self.lcd_byte(ord(string[i]), self.LCD_CHR)def clear(self):
# clear LCD display
self.lcd_byte(0x01, self.LCD_CMD)############### Usefull part of the code ###################lcd = LCD(0x27,scl=30,sda=31)
#lcd.message(“TiagoTech”,2)while 1:
lcd.message(“Counter: {}” .format(count),1)
if count == 0 :











[9] How To Mechatronics Video