From 2a73750034263012e067f156b9b5724a80ea2013 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Sat, 10 Jun 2017 16:56:11 -0400 Subject: [PATCH 1/3] Minimal functionality: backlight on, no cursor, no shifting, etc. --- lcd/i2c_pcf8574_interface.py | 24 ++--------- lcd/lcd.py | 80 ++++-------------------------------- 2 files changed, 10 insertions(+), 94 deletions(-) diff --git a/lcd/i2c_pcf8574_interface.py b/lcd/i2c_pcf8574_interface.py index adcc622..763aa01 100644 --- a/lcd/i2c_pcf8574_interface.py +++ b/lcd/i2c_pcf8574_interface.py @@ -24,16 +24,13 @@ import board from adafruit_bus_device.i2c_device import I2CDevice -from .lcd import LCD_4BITMODE, LCD_BACKLIGHT, LCD_NOBACKLIGHT, PIN_ENABLE +from .lcd import PIN_ENABLE, LCD_BACKLIGHT MICROSECOND = 1e-6 MILLISECOND = 1e-3 class I2CPCF8574Interface: - # Bit values to turn backlight on/off. Indexed by a boolean. - _BACKLIGHT_VALUES = (LCD_NOBACKLIGHT, LCD_BACKLIGHT) - def __init__(self, address): """ CharLCD via PCF8574 I2C port expander. @@ -47,8 +44,6 @@ def __init__(self, address): """ self.address = address - self._backlight_pin_state = LCD_BACKLIGHT - self.i2c = busio.I2C(board.SCL, board.SDA) self.i2c_device = I2CDevice(self.i2c, self.address) self.data_buffer = bytearray(1) @@ -56,26 +51,13 @@ def __init__(self, address): def deinit(self): self.i2c.deinit() - @property - def data_bus_mode(self): - return LCD_4BITMODE - - @property - def backlight(self): - return self._backlight_pin_state == LCD_BACKLIGHT - - @backlight.setter - def backlight(self, value): - self._backlight_pin_state = _BACKLIGHT_VALUES[value] - self._i2c_write(self._backlight_pin_state) - # Low level commands def send(self, value, rs_mode): """Send the specified value to the display in 4-bit nibbles. The rs_mode is either ``_RS_DATA`` or ``_RS_INSTRUCTION``.""" - self._write4bits(rs_mode | (value & 0xF0) | self._backlight_pin_state) - self._write4bits(rs_mode | ((value << 4) & 0xF0) | self._backlight_pin_state) + self._write4bits(rs_mode | (value & 0xF0) | LCD_BACKLIGHT) + self._write4bits(rs_mode | ((value << 4) & 0xF0) | LCD_BACKLIGHT) def _write4bits(self, value): """Pulse the `enable` flag to process value.""" diff --git a/lcd/lcd.py b/lcd/lcd.py index 4788841..b5cd38f 100644 --- a/lcd/lcd.py +++ b/lcd/lcd.py @@ -26,16 +26,12 @@ _LCD_RETURNHOME = const(0x02) _LCD_ENTRYMODESET = const(0x04) _LCD_DISPLAYCONTROL = const(0x08) -_LCD_CURSORSHIFT = const(0x10) _LCD_FUNCTIONSET = const(0x20) _LCD_SETCGRAMADDR = const(0x40) _LCD_SETDDRAMADDR = const(0x80) # Flags for display entry mode -_LCD_ENTRYRIGHT = const(0x00) _LCD_ENTRYLEFT = const(0x02) -_LCD_ENTRYSHIFTINCREMENT = const(0x01) -_LCD_ENTRYSHIFTDECREMENT = const(0x00) # Flags for display on/off control _LCD_DISPLAYON = const(0x04) @@ -45,18 +41,10 @@ _LCD_BLINKON = const(0x01) _LCD_BLINKOFF = const(0x00) -# Flags for display/cursor shift -_LCD_DISPLAYMOVE = const(0x08) -_LCD_CURSORMOVE = const(0x00) -_LCD_MOVERIGHT = const(0x04) -_LCD_MOVELEFT = const(0x00) - # Flags for function set -_LCD_8BITMODE = const(0x10) LCD_4BITMODE = const(0x00) _LCD_2LINE = const(0x08) _LCD_1LINE = const(0x00) -_LCD_5x10DOTS = const(0x04) _LCD_5x8DOTS = const(0x00) # Flags for backlight control @@ -72,32 +60,21 @@ PIN_READ_WRITE = const(0x2) PIN_REGISTER_SELECT = const(0x1) -class CursorMode: - HIDE = const(_LCD_CURSOROFF | _LCD_BLINKOFF) - LINE = const(_LCD_CURSORON | _LCD_BLINKOFF) - BLINK = const(_LCD_CURSOROFF | _LCD_BLINKON) - MICROSECOND = 1e-6 MILLISECOND = 1e-3 class LCD(object): - def __init__(self, interface, num_cols=20, num_rows=4, char_height=8): + def __init__(self, interface, num_cols=20, num_rows=4): """ Character LCD controller. :param interface: Communication interface, such as I2CInterface :param num_rows: Number of display rows (usually 1, 2 or 4). Default: 4. :param num_cols: Number of columns per row (usually 16 or 20). Default 20. - :param char_height: Some 1 line displays allow a font height of 10px. - Allowed: 8 or 10. Default: 8. """ self.interface = interface - if char_height not in (8, 10): - raise ValueError('The ``char_height`` argument should be either 8 or 10.') - self.char_height = char_height - self.num_rows = num_rows self.num_cols = num_cols @@ -105,35 +82,22 @@ def __init__(self, interface, num_cols=20, num_rows=4, char_height=8): self._row_offsets = (0x00, 0x40, self.num_cols, 0x40 + self.num_cols) # Setup initial display configuration - displayfunction = self.interface.data_bus_mode | _LCD_5x8DOTS + displayfunction = LCD_4BITMODE | _LCD_5x8DOTS if self.num_rows == 1: displayfunction |= _LCD_1LINE elif self.num_rows in (2, 4): # LCD only uses two lines on 4 row displays displayfunction |= _LCD_2LINE - if self.char_height == 10: - # For some 1 line displays you can select a 10px font. - displayfunction |= _LCD_5x10DOTS - # Choose 4 or 8 bit mode + # Assume 4-bit mode self.command(0x03) time.sleep(4.5*MILLISECOND) self.command(0x03) time.sleep(4.5*MILLISECOND) self.command(0x03) - if self.interface.data_bus_mode == LCD_4BITMODE: - # Hitachi manual page 46 - time.sleep(100*MICROSECOND) - self.command(0x02) - elif self.interface.data_bus_mode == _LCD_8BITMODE: - # Hitachi manual page 45 - self.command(0x30) - time.sleep(4.5*MILLISECOND) - self.command(0x30) - time.sleep(100*MICROSECOND) - self.command(0x30) - else: - raise ValueError('Invalid data bus mode: {}'.format(self.interface.data_bus_mode)) + # Hitachi manual page 46 + time.sleep(100*MICROSECOND) + self.command(0x02) # Write configuration to display self.command(_LCD_FUNCTIONSET | displayfunction) @@ -144,9 +108,7 @@ def __init__(self, interface, num_cols=20, num_rows=4, char_height=8): time.sleep(50*MICROSECOND) # Configure display mode. Define internal fields. - self._display_mode = _LCD_DISPLAYON - self._cursor_mode = CursorMode.HIDE - self.command(_LCD_DISPLAYCONTROL | self._display_mode | self._cursor_mode) + self.command(_LCD_DISPLAYCONTROL | _LCD_DISPLAYON | _LCD_CURSOROFF | _LCD_BLINKOFF) time.sleep(50*MICROSECOND) self.clear() @@ -154,19 +116,6 @@ def __init__(self, interface, num_cols=20, num_rows=4, char_height=8): def close(self): self.interface.deinit() - def set_backlight(self, value): - self.interface.backlight = value - - def set_display_enabled(self, value): - self._display_mode = _LCD_DISPLAYON if value else _LCD_DISPLAYOFF - self.command(_LCD_DISPLAYCONTROL | self._display_mode | self._cursor_mode) - time.sleep(50*MICROSECOND) - - def set_cursor_mode(self, value): - self._cursor_mode = value - self.command(_LCD_DISPLAYCONTROL | self._display_mode | self._cursor_mode) - time.sleep(50*MICROSECOND) - def cursor_pos(self): """The cursor position as a 2-tuple (row, col).""" return (self._row, self._col) @@ -212,16 +161,6 @@ def home(self): self._col = 0 time.sleep(2*MILLISECOND) - def shift_display(self, amount): - """Shift the display. Use negative amounts to shift left and positive - amounts to shift right.""" - if amount == 0: - return - direction = _LCD_MOVERIGHT if amount > 0 else _LCD_MOVELEFT - for i in range(abs(amount)): - self.command(_LCD_CURSORSHIFT | _LCD_DISPLAYMOVE | direction) - time.sleep(50*MICROSECOND) - def create_char(self, location, bitmap): """Create a new character. @@ -253,11 +192,6 @@ def create_char(self, location, bitmap): >>> lcd.create_char(0, smiley) """ - if not (0 <= location <= 7): - raise ValueError('Only locations 0-7 are valid.') - if len(bitmap) != 8: - raise ValueError('Bitmap should have exactly 8 rows.') - # Store previous position save_row = self._row save_col = self._col From c0d30d7d47e52c8b2b8cc7c41c053d427d208399 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Mon, 15 Oct 2018 17:30:17 -0400 Subject: [PATCH 2/3] Code cleanup; add proper delays so it works on faster processors (e.g., M4) --- lcd/i2c_pcf8574_interface.py | 15 ++++----- lcd/lcd.py | 64 ++++++++++++++++++++---------------- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/lcd/i2c_pcf8574_interface.py b/lcd/i2c_pcf8574_interface.py index 763aa01..0cf28a0 100644 --- a/lcd/i2c_pcf8574_interface.py +++ b/lcd/i2c_pcf8574_interface.py @@ -18,19 +18,17 @@ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -import time +"""Low-level interface to PCF8574.""" import busio import board +import microcontroller from adafruit_bus_device.i2c_device import I2CDevice from .lcd import PIN_ENABLE, LCD_BACKLIGHT -MICROSECOND = 1e-6 -MILLISECOND = 1e-3 - class I2CPCF8574Interface: - + """Write to PCF8574.""" def __init__(self, address): """ CharLCD via PCF8574 I2C port expander. @@ -49,6 +47,7 @@ def __init__(self, address): self.data_buffer = bytearray(1) def deinit(self): + """Done using this interface.""" self.i2c.deinit() # Low level commands @@ -63,12 +62,12 @@ def _write4bits(self, value): """Pulse the `enable` flag to process value.""" with self.i2c_device: self._i2c_write(value & ~PIN_ENABLE) - time.sleep(MICROSECOND) + microcontroller.delay_us(1) self._i2c_write(value | PIN_ENABLE) - time.sleep(MICROSECOND) + microcontroller.delay_us(1) self._i2c_write(value & ~PIN_ENABLE) # Wait for command to complete. - time.sleep(100*MICROSECOND) + microcontroller.delay_us(100) def _i2c_write(self, value): self.data_buffer[0] = value diff --git a/lcd/lcd.py b/lcd/lcd.py index b5cd38f..273022c 100644 --- a/lcd/lcd.py +++ b/lcd/lcd.py @@ -20,6 +20,7 @@ import time from micropython import const +import microcontroller # Commands _LCD_CLEARDISPLAY = const(0x01) @@ -60,27 +61,26 @@ PIN_READ_WRITE = const(0x2) PIN_REGISTER_SELECT = const(0x1) -MICROSECOND = 1e-6 -MILLISECOND = 1e-3 - class LCD(object): def __init__(self, interface, num_cols=20, num_rows=4): """ Character LCD controller. - + :param interface: Communication interface, such as I2CInterface :param num_rows: Number of display rows (usually 1, 2 or 4). Default: 4. :param num_cols: Number of columns per row (usually 16 or 20). Default 20. """ self.interface = interface - + self.num_rows = num_rows self.num_cols = num_cols - + + self._row = self._col = 0 + # get row addresses (varies based on display size) self._row_offsets = (0x00, 0x40, self.num_cols, 0x40 + self.num_cols) - + # Setup initial display configuration displayfunction = LCD_4BITMODE | _LCD_5x8DOTS if self.num_rows == 1: @@ -89,27 +89,30 @@ def __init__(self, interface, num_cols=20, num_rows=4): # LCD only uses two lines on 4 row displays displayfunction |= _LCD_2LINE + # Wait at least 40ms on power-up + time.sleep(0.050) # Assume 4-bit mode self.command(0x03) - time.sleep(4.5*MILLISECOND) + time.sleep(0.0045) self.command(0x03) - time.sleep(4.5*MILLISECOND) + time.sleep(0.0045) self.command(0x03) # Hitachi manual page 46 - time.sleep(100*MICROSECOND) + microcontroller.delay_us(150) self.command(0x02) + time.sleep(0.003) # Write configuration to display self.command(_LCD_FUNCTIONSET | displayfunction) - time.sleep(50*MICROSECOND) + microcontroller.delay_us(50) # Configure entry mode. Define internal fields. self.command(_LCD_ENTRYMODESET | _LCD_ENTRYLEFT) - time.sleep(50*MICROSECOND) + microcontroller.delay_us(50) # Configure display mode. Define internal fields. self.command(_LCD_DISPLAYCONTROL | _LCD_DISPLAYON | _LCD_CURSOROFF | _LCD_BLINKOFF) - time.sleep(50*MICROSECOND) + microcontroller.delay_us(50) self.clear() @@ -121,18 +124,18 @@ def cursor_pos(self): return (self._row, self._col) def set_cursor_pos(self, row, col): - if not (0 <= row < self.num_rows): - raise ValueError('row should be in range 0-{}'.format(self.num_rows - 1)) - if not (0 <= col < self.num_cols): - raise ValueError('col should be in range 0-{}'.format(self.num_cols - 1)) + if not 0 <= row < self.num_rows: + raise ValueError('row not in range 0-{}'.format(self.num_rows - 1)) + if not 0 <= col < self.num_cols: + raise ValueError('col not in range 0-{}'.format(self.num_cols - 1)) self._row = row self._col = col self.command(_LCD_SETDDRAMADDR | self._row_offsets[row] + col) - time.sleep(50*MICROSECOND) + microcontroller.delay_us(50) - def print(self, string): + def print(self, *strings): """ - Write the specified unicode string to the display. + Write the specified unicode strings to the display. A newline ('\n') will advance to the left side of the next row. Lines that are too long automatically continue on next line. @@ -140,18 +143,19 @@ def print(self, string): supported. """ - for char in string: - if char == '\n': - # Advance to next row, at left side. Wrap around to top row if at bottom. - self.set_cursor_pos((self._row + 1) % self.num_rows, 0) - else: - self.write(ord(char)) + for string in strings: + for char in string: + if char == '\n': + # Advance to next row, at left side. Wrap around to top row if at bottom. + self.set_cursor_pos((self._row + 1) % self.num_rows, 0) + else: + self.write(ord(char)) def clear(self): """Overwrite display with blank characters and reset cursor position.""" self.command(_LCD_CLEARDISPLAY) - time.sleep(2*MILLISECOND) + time.sleep(0.003) self.home() def home(self): @@ -159,7 +163,7 @@ def home(self): self.command(_LCD_RETURNHOME) self._row = 0 self._col = 0 - time.sleep(2*MILLISECOND) + time.sleep(0.002) def create_char(self, location, bitmap): """Create a new character. @@ -200,6 +204,7 @@ def create_char(self, location, bitmap): self.command(_LCD_SETCGRAMADDR | location << 3) for row in bitmap: self.interface.send(row, _RS_DATA) + microcontroller.delay_us(50) # Restore cursor pos self.set_cursor_pos(save_row, save_col) @@ -207,10 +212,12 @@ def create_char(self, location, bitmap): def command(self, value): """Send a raw command to the LCD.""" self.interface.send(value, _RS_INSTRUCTION) + microcontroller.delay_us(50) def write(self, value): """Write a raw character byte to the LCD.""" self.interface.send(value, _RS_DATA) + microcontroller.delay_us(50) if self._col < self.num_cols - 1: # Char was placed on current line. No need to reposition cursor. self._col += 1 @@ -220,4 +227,3 @@ def write(self, value): self._col = 0 self.set_cursor_pos(self._row, self._col) - From 0a4da76a7fcd01e5eaf35fa14c304e0329736fe0 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Wed, 19 May 2021 09:04:42 -0400 Subject: [PATCH 3/3] add i2c arg to constructor; fix brightness setter --- lcd/i2c_pcf8574_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lcd/i2c_pcf8574_interface.py b/lcd/i2c_pcf8574_interface.py index 0cf28a0..923770d 100644 --- a/lcd/i2c_pcf8574_interface.py +++ b/lcd/i2c_pcf8574_interface.py @@ -29,7 +29,7 @@ class I2CPCF8574Interface: """Write to PCF8574.""" - def __init__(self, address): + def __init__(self, i2c, address): """ CharLCD via PCF8574 I2C port expander. @@ -42,7 +42,7 @@ def __init__(self, address): """ self.address = address - self.i2c = busio.I2C(board.SCL, board.SDA) + self.i2c = i2c self.i2c_device = I2CDevice(self.i2c, self.address) self.data_buffer = bytearray(1)