diff --git a/README.md b/README.md index 5d06b7d..9f4542a 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,8 @@ Giuseppe Cassibba wrote up a [tutorial](https://peppe8o.com/using-i2c-lcd-displa | pyb_i2c_grove_rgb_lcd_test.py | Pyboard test using Grove I2C RGB LCD | | pyb_i2c_lcd.py | Pyboard PCF8574 I2C HAL | | pyb_i2c_lcd_test.py | Pyboard test using PCF8574 backpack | - +| i2c_micropython_lcd.py | Micropython I2C | +| i2c_micropython_lcd_test.py | Micropython I2C test using ESP32 | The files which end in **_test.py** are examples which show how the corresponding file is used. diff --git a/lcd/i2c_micropython_lcd.py b/lcd/i2c_micropython_lcd.py new file mode 100644 index 0000000..5ec5d64 --- /dev/null +++ b/lcd/i2c_micropython_lcd.py @@ -0,0 +1,89 @@ +"""Implements a HD44780 character LCD connected via PCF8574 on I2C.""" + +from lcd_api import LcdApi +from machine import I2C +import time + +# The PCF8574 has a jumper selectable address: 0x20 - 0x27 +DEFAULT_I2C_ADDR = 0x27 + +# Defines shifts or masks for the various LCD line attached to the PCF8574 + +MASK_RS = 0x01 +MASK_RW = 0x02 +MASK_E = 0x04 +SHIFT_BACKLIGHT = 3 +SHIFT_DATA = 4 + + +class I2cLcd(LcdApi): + """Implements a HD44780 character LCD connected via PCF8574 on I2C.""" + + def __init__(self, i2c, i2c_addr, num_lines, num_columns): + self.i2c_addr = i2c_addr + self.i2c = i2c + self.i2c.writeto(self.i2c_addr, bytearray([0])) + time.sleep(0.020) # Allow LCD time to powerup + # Send reset 3 times + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + time.sleep(0.005) # need to delay at least 4.1 msec + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + time.sleep(0.001) + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + time.sleep(0.001) + # Put LCD into 4 bit mode + self.hal_write_init_nibble(self.LCD_FUNCTION) + time.sleep(0.001) + LcdApi.__init__(self, num_lines, num_columns) + cmd = self.LCD_FUNCTION + if num_lines > 1: + cmd |= self.LCD_FUNCTION_2LINES + self.hal_write_command(cmd) + + def hal_write_init_nibble(self, nibble): + """Writes an initialization nibble to the LCD. + + This particular function is only used during initialization. + """ + byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA + self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E])) + self.i2c.writeto(self.i2c_addr, bytearray([byte])) + + def hal_backlight_on(self): + """Allows the hal layer to turn the backlight on.""" + self.i2c.writeto(self.i2c_addr, bytearray([1 << SHIFT_BACKLIGHT])) + + def hal_backlight_off(self): + """Allows the hal layer to turn the backlight off.""" + self.i2c.writeto(self.i2c_addr, bytearray([0])) + + def hal_write_command(self, cmd): + """Writes a command to the LCD. + + Data is latched on the falling edge of E. + """ + byte = ((self.backlight << SHIFT_BACKLIGHT) | + (((cmd >> 4) & 0x0f) << SHIFT_DATA)) + self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E])) + self.i2c.writeto(self.i2c_addr, bytearray([byte])) + byte = ((self.backlight << SHIFT_BACKLIGHT) | + ((cmd & 0x0f) << SHIFT_DATA)) + self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E])) + self.i2c.writeto(self.i2c_addr, bytearray([byte])) + if cmd <= 3: + # The home and clear commands require a worst + # case delay of 4.1 msec + time.sleep(0.005) + + def hal_write_data(self, data): + """Write data to the LCD.""" + byte = (MASK_RS | + (self.backlight << SHIFT_BACKLIGHT) | + (((data >> 4) & 0x0f) << SHIFT_DATA)) + self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E])) + self.i2c.writeto(self.i2c_addr, bytearray([byte])) + byte = (MASK_RS | + (self.backlight << SHIFT_BACKLIGHT) | + ((data & 0x0f) << SHIFT_DATA)) + self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E])) + self.i2c.writeto(self.i2c_addr, bytearray([byte])) diff --git a/lcd/i2c_micropython_lcd_test.py b/lcd/i2c_micropython_lcd_test.py new file mode 100644 index 0000000..fe5b69c --- /dev/null +++ b/lcd/i2c_micropython_lcd_test.py @@ -0,0 +1,65 @@ +"""Implements a HD44780 character LCD connected via PCF8574 on I2C.""" + +from i2c_micropython_lcd import I2cLcd +import machine +import time + +# The PCF8574 has a jumper selectable address: 0x20 - 0x27 +DEFAULT_I2C_ADDR = 0x27 + +def test_main(): + """Test function for verifying basic functionality.""" + sda = machine.Pin(18) + scl = machine.Pin(19) + i2c = machine.I2C(scl=scl, sda=sda, freq=100000) + + lcd = I2cLcd(i2c, DEFAULT_I2C_ADDR, 4, 20) + lcd.blink_cursor_on() + lcd.putstr("It Works!\nSecond Line") + time.sleep(3) + lcd.clear() + + # custom characters: battery icons - 5 wide, 8 tall + lcd.custom_char(0, bytearray([0x0E,0x1B,0x11,0x11,0x11,0x11,0x11,0x1F])) # 0% Empty + lcd.custom_char(1, bytearray([0x0E,0x1B,0x11,0x11,0x11,0x11,0x1F,0x1F])) # 16% + lcd.custom_char(2, bytearray([0x0E,0x1B,0x11,0x11,0x11,0x1F,0x1F,0x1F])) # 33% + lcd.custom_char(3, bytearray([0x0E,0x1B,0x11,0x11,0x1F,0x1F,0x1F,0x1F])) # 50% + lcd.custom_char(4, bytearray([0x0E,0x1B,0x11,0x1F,0x1F,0x1F,0x1F,0x1F])) # 66% + lcd.custom_char(5, bytearray([0x0E,0x1B,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F])) # 83% + lcd.custom_char(6, bytearray([0x0E,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F])) # 100% Full + lcd.custom_char(7, bytearray([0x0E,0x1F,0x1B,0x1B,0x1B,0x1F,0x1B,0x1F])) # ! Error + for i in range(8): + lcd.putchar(chr(i)) + time.sleep(3) + lcd.clear() + lcd.blink_cursor_off() + + count = 0 + while True: + lcd.move_to(0, 0) + lcd.putstr("Time: " + str(time.time())) + time.sleep(1) + count += 1 + if count % 10 == 3: + print("Turning backlight off") + lcd.backlight_off() + if count % 10 == 4: + print("Turning backlight on") + lcd.backlight_on() + if count % 10 == 5: + print("Turning display off") + lcd.display_off() + if count % 10 == 6: + print("Turning display on") + lcd.display_on() + if count % 10 == 7: + print("Turning display & backlight off") + lcd.backlight_off() + lcd.display_off() + if count % 10 == 8: + print("Turning display & backlight on") + lcd.backlight_on() + lcd.display_on() + +if __name__ == "__main__": + test_main() \ No newline at end of file