16x2 LCD Display

../_images/lcd_i2c.png

16x2 LCD Display with I2C Driver.

LCD Displays can be configured for I2C or Parallel communication with a microcontroller. When configured with an I2C driver, much of the following is abstracted away for the user. Contrast, brightness, and pinouts are much less complicated in this approach at the cost of price and speed.

../_images/lcd.png

16x2 LCD Display without I2C Driver.

Using parallel communication, the LCD is wired to the microcontroller with either four-bit or eight bit communication 1. A potentiometer should be connected for contrast control of the LCD, and a 500 ohm resistor for brightness control. If the user desires adaptive brightness control, a photoresistor and 1k-ohm resistor can be connected in parallel.

../_images/lcd_pinout.png

16x2 LCD Display Pinout

Using 4-bit parallel communication, a nibble of data is sent to the display simultaneously. 8-bit parallel will communicate a full byte simultaneously. Regardless of method, a full byte of data is then sent to the device in a process called bit-banging. This splits a byte of data to send two nibbles in series for 4-bit communication, ordered by the upper four bits and then the lower four bits.

../_images/lcd_nibble_wiring.png

Wiring for parallel nibble communication.

class src.pico_devboard.LCD.LCD(enable_pin: int, reg_select_pin: int, data_pins: list)

The LCD class is meant to abstract the LCD driver further and streamline development.

CMD_MODE = 0
DATA_MODE = 1

Have the cursor start blinking.

clear()

Clears the LCD Screen.

Good to perform on occasion but produces flashing on screen when triggered consistently.

cursor_off()

Turn the cursor off.

cursor_on()

Have the cursor on, Good for debugging.

go_to(column, row)

Move the cursor to a specific column and row.

Parameters
  • column (int) – Column index, 0-15. May be higher on different size LCDs

  • row (int) – Row index, 0-1. May be higher on different size LCDs

Returns

None

home()

Return the Cursor to the starting position.

Functionally the same as using the go_to function and specifying (0, 0) coordinates.

init()

Initializes the LCD for communication.

print(string)

Write a string on to the LCD.

Wrapper of the string function with more descriptive naming.

Parameters

string (str) – String desired to be written.

strobe()

Flashes the enable line and provides wait period.

write(command, mode)

Sends data to the LCD module.

Parameters
  • command (str) – Information packet being sent to the LCD.

  • mode (int) – Mode of operation for the LCD, either command mode (1) or data mode (0)

Returns

None

Code

  1# -----------------------------------------
  2#                 NOTES 
  3# -----------------------------------------
  4"""
  5Dieter Steinhauser
  65/2023
  7Parallel LCD functions, Configured for 4-bit communication.
  8"""
  9
 10# -----------------------------------------
 11#               IMPORTS
 12# -----------------------------------------
 13
 14from machine import Pin
 15import time
 16utime = time
 17
 18# -----------------------------------------
 19#          CONSTANTS AND VARIABLES
 20# -----------------------------------------
 21
 22# CONSTANTS
 23# ----------------------
 24
 25LCD_CMD = 0
 26LCD_DATA = 1
 27
 28# ----------------------
 29# GPIO Wiring: Legacy Structure
 30# ----------------------
 31
 32# EN = Pin(0, Pin.OUT) # Enable Pin
 33# RS = Pin(1, Pin.OUT) # Register Select
 34
 35# PINS = [2, 3, 4, 5]  
 36# Pin numbers for the upper nibble, does the below assignment in configure method.
 37# D4 = Pin(2, Pin.OUT)
 38# D5 = Pin(3, Pin.OUT)
 39# D6 = Pin(4, Pin.OUT)
 40# D7 = Pin(5, Pin.OUT)
 41 
 42# list that gets populated with pinout objects for data line.
 43# DATA_BUS = []
 44
 45# -----------------------------------------
 46#                 METHODS
 47# -----------------------------------------
 48
 49# def Configure():
 50#     """Creates the data bus object from the pin list"""
 51
 52#     for index in range(4):
 53#        DATA_BUS.append(Pin(PINS[index], Pin.OUT))
 54
 55# # -----------------------------------------
 56
 57# def lcd_strobe():
 58#     """Flashes the enable line and provides wait period."""
 59
 60#     EN.value(1)
 61#     utime.sleep_ms(1)
 62
 63#     EN.value(0)
 64#     utime.sleep_ms(1)
 65
 66# # -----------------------------------------
 67 
 68# def lcd_write(command, mode):
 69#     """Sends data to the LCD module. """
 70
 71#     # determine if writing a command or data
 72#     data = command if mode == 0 else ord(command)
 73
 74#     # need upper nibble for first loop. lower nibble can use data directly.
 75#     upper = data >> 4
 76    
 77#     # write the upper nibble
 78#     for index in range(4):
 79#         bit = upper & 1
 80#         DATA_BUS[index].value(bit)
 81#         upper = upper >> 1
 82
 83#     # strobe the LCD, sending the nibble
 84#     RS.value(mode)
 85#     lcd_strobe()
 86
 87#     # write the lower nibble
 88#     for index in range(4):
 89#         bit = data & 1
 90#         DATA_BUS[index].value(bit)
 91#         data = data >> 1
 92
 93#     # Strobe the LCD, sending the nibble 
 94#     RS.value(mode)
 95#     lcd_strobe()
 96#     utime.sleep_ms(1)
 97#     RS.value(1)
 98
 99# # -----------------------------------------
100
101# def lcd_clear():
102#     """Clear the LCD Screen."""
103
104#     lcd_write(0x01, 0)
105#     utime.sleep_ms(5)
106
107# # -----------------------------------------
108
109# def lcd_home():
110#     """Return the Cursor to the starting position."""
111
112#     lcd_write(0x02, 0)
113#     utime.sleep_ms(5)
114
115# # -----------------------------------------
116
117
118# def lcd_cursor_blink():
119#     """Have the cursor start blinking."""
120
121#     lcd_write(0x0D, 0)
122#     utime.sleep_ms(1)
123
124# # -----------------------------------------
125
126# def lcd_cursor_on():
127#     """Have the cursor on, Good for debugging."""
128
129#     lcd_write(0x0E, 0)
130#     utime.sleep_ms(1)
131
132# # -----------------------------------------
133
134# def lcd_cursor_off():
135#     """Turn the cursor off."""
136
137#     lcd_write(0x0C, 0)
138#     utime.sleep_ms(1)
139
140# # -----------------------------------------
141
142# def lcd_puts(string):
143#     """Write a string on to the LCD."""
144
145#     for element in string:
146#        lcd_putch(element)
147
148# # -----------------------------------------
149
150# def lcd_putch(c):
151#     """Write a character on to the LCD."""
152#     lcd_write(c, 1)
153
154# # -----------------------------------------
155
156# def lcd_goto(column, row):
157    
158    
159#     if row == 0:
160#         address = 0
161
162#     if row == 1:
163#         address = 0x40
164
165#     if row == 2:
166#         address = 0x14
167
168#     if row == 3:
169#         address = 0x54
170
171#     address = address + column
172#     lcd_write(0x80 | address, 0)
173
174# # -----------------------------------------
175
176# def lcd_init():
177    
178#     # Configure the pins of the device.
179#     Configure()
180#     utime.sleep_ms(120)
181
182#     # clear values on data bus.
183#     for index in range(4):
184#         DATA_BUS[index].value(0)
185#     utime.sleep_ms(50)
186
187#     # initialization sequence.
188#     DATA_BUS[0].value(1)
189#     DATA_BUS[1].value(1)
190#     lcd_strobe()
191#     utime.sleep_ms(10)
192
193#     lcd_strobe()
194#     utime.sleep_ms(10)
195
196#     lcd_strobe()
197#     utime.sleep_ms(10)
198
199#     DATA_BUS[0].value(0)
200#     lcd_strobe()
201#     utime.sleep_ms(5)
202
203#     lcd_write(0x28, 0)
204#     utime.sleep_ms(1)
205
206#     lcd_write(0x08, 0)
207#     utime.sleep_ms(1)
208
209#     lcd_write(0x01, 0)
210#     utime.sleep_ms(10)
211
212#     lcd_write(0x06, 0)
213#     utime.sleep_ms(5)
214
215#     lcd_write(0x0C, 0)
216#     utime.sleep_ms(10)
217
218
219# -----------------------------------------
220#                 LCD Class:
221# -----------------------------------------
222
223
224class LCD:
225    """The LCD class is meant to abstract the LCD driver further and streamline development."""
226
227    CMD_MODE = 0
228    DATA_MODE = 1
229
230    def __init__(self, enable_pin: int, reg_select_pin: int, data_pins: list) -> None:
231        """
232        Object initialization.
233
234        :param enable_pin: integer value of the enable pin desired.
235        :type enable_pin: int
236        :param reg_select_pin: integer value of the reg_select pin desired.
237        :type reg_select_pin: int
238        :param data_pins: list of integer values for the data pins desired in communication.
239        :type data_pins: list[int]
240        """
241
242        self.enable_pin = Pin(enable_pin, Pin.OUT)
243        self.reg_select_pin = Pin(reg_select_pin, Pin.OUT)
244        self._data_pins = data_pins
245        self.data_bus = []
246        
247        # Configure the pins of the device.
248        self._configure()
249        utime.sleep_ms(120)
250
251    # -----------------------------------------    
252
253    def _configure(self):
254        """Creates the data bus object from the pin list. """
255
256        # Configure the pins of the device.
257        for element in self._data_pins:
258            self.data_bus.append(Pin(element, Pin.OUT))
259
260    # -----------------------------------------
261
262    def init(self):
263        """Initializes the LCD for communication."""
264
265        # clear values on data bus.
266        for index in range(4):
267            self.data_bus[index].value(0)
268        utime.sleep_ms(50)
269
270        # initialization sequence.
271        self.data_bus[0].value(1)
272        self.data_bus[1].value(1)
273        self.strobe()
274        utime.sleep_ms(10)
275
276        self.strobe()
277        utime.sleep_ms(10)
278
279        self.strobe()
280        utime.sleep_ms(10)
281
282        self.data_bus[0].value(0)
283        self.strobe()
284        utime.sleep_ms(5)
285
286        self.write(0x28, 0)
287        utime.sleep_ms(1)
288
289        self.write(0x08, 0)
290        utime.sleep_ms(1)
291
292        self.write(0x01, 0)
293        utime.sleep_ms(10)
294
295        self.write(0x06, 0)
296        utime.sleep_ms(5)
297
298        self.write(0x0C, 0)
299        utime.sleep_ms(10)
300
301    # -----------------------------------------
302
303    def strobe(self):
304        """
305        Flashes the enable line and provides wait period.
306        """
307
308        self.enable_pin.value(1)
309        utime.sleep_ms(1)
310
311        self.enable_pin.value(0)
312        utime.sleep_ms(1)
313
314    # -----------------------------------------
315    
316    def write(self, command, mode):
317        """
318        Sends data to the LCD module.
319
320        :param command: Information packet being sent to the LCD.
321        :type command: str
322        :param mode: Mode of operation for the LCD, either command mode (1) or data mode (0)
323        :type mode: int
324        :return: None
325        """
326
327        # determine if writing a command or data
328        data = command if mode == 0 else ord(command)
329
330        # need upper nibble for first loop. lower nibble can use data directly.
331        upper = data >> 4
332        
333        # write the upper nibble
334        for index in range(4):
335            bit = upper & 1
336            self.data_bus[index].value(bit)
337            upper = upper >> 1
338
339        # strobe the LCD, sending the nibble
340        self.reg_select_pin.value(mode)
341        self.strobe()
342
343        # write the lower nibble
344        for index in range(4):
345            bit = data & 1
346            self.data_bus[index].value(bit)
347            data = data >> 1
348
349        # Strobe the LCD, sending the nibble 
350        self.reg_select_pin.value(mode)
351        self.strobe()
352        utime.sleep_ms(1)
353        self.reg_select_pin.value(1)
354
355    # -----------------------------------------
356
357    def clear(self):
358        """
359        Clears the LCD Screen.
360
361        Good to perform on occasion but produces flashing on screen when triggered consistently.
362        """
363
364        self.write(0x01, 0)
365        utime.sleep_ms(5)
366
367    # -----------------------------------------
368
369    def home(self):
370        """
371        Return the Cursor to the starting position.
372
373        Functionally the same as using the go_to function and specifying (0, 0) coordinates.
374        """
375
376        self.write(0x02, 0)
377        utime.sleep_ms(5)
378
379    # -----------------------------------------
380
381
382    def blink(self):
383        """
384        Have the cursor start blinking.
385        """
386
387        self.write(0x0D, 0)
388        utime.sleep_ms(1)
389
390    # -----------------------------------------
391
392    def cursor_on(self):
393        """
394        Have the cursor on, Good for debugging.
395        """
396
397        self.write(0x0E, 0)
398        utime.sleep_ms(1)
399
400    # -----------------------------------------
401
402    def cursor_off(self):
403        """
404        Turn the cursor off.
405        """
406
407        self.write(0x0C, 0)
408        utime.sleep_ms(1)
409
410    # -----------------------------------------
411
412    def print(self, string):
413        """
414        Write a string on to the LCD.
415
416        Wrapper of the string function with more descriptive naming.
417
418        :param string: String desired to be written.
419        :type string: str
420
421        """
422
423        for element in string:
424            self._putch(element)
425
426    # -----------------------------------------
427
428    def _putch(self, c):
429        """
430        Write a character on to the LCD.
431
432        Protected because the method is less intuitive compared to the print.
433
434        :param c: ASCII character.
435        :type c: str
436        :return: None
437        """
438        self.write(c, 1)
439
440    # -----------------------------------------
441
442    def _puts(self, string):
443        """
444        Write a string on to the LCD.
445
446        :param string: String desired to be written.
447        :type string: str
448        :return: None
449        """
450
451        for element in string:
452            self._putch(element)
453
454    # -----------------------------------------
455    def go_to(self, column, row):
456        """
457        Move the cursor to a specific column and row.
458
459        :param column: Column index, 0-15. May be higher on different size LCDs
460        :type column: int
461        :param row: Row index, 0-1. May be higher on different size LCDs
462        :type row: int
463        :return: None
464        """
465        if row == 0:
466            address = 0
467
468        if row == 1:
469            address = 0x40
470
471        if row == 2:
472            address = 0x14
473
474        if row == 3:
475            address = 0x54
476
477        address = address + column
478        self.write(0x80 | address, 0)
479
480 
481# -----------------------------------------
482#              END OF FILE
483# -----------------------------------------

References

1

“Sitronix ST7066U - Crystalfontz,” crystalfontz. [Online]. Available: https://www.crystalfontz.com/controllers/Sitronix/ST7066U/438. [Accessed: 03-Oct2022].