-
Notifications
You must be signed in to change notification settings - Fork 4
/
sparkfun_serlcd.py
578 lines (478 loc) · 20.2 KB
/
sparkfun_serlcd.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2021 Gaston Williams
#
# SPDX-License-Identifier: MIT
# The MIT License (MIT)
#
# Copyright (c) 2019 Gaston Williams
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER 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.
"""
`sparkfun_serlcd`
================================================================================
CircuitPython driver library for the Sparkfun Serial LCD displays
* Author(s): Gaston Williams
* Based on the Arduino library for the Sparkfun SerLCD displays
Written by Gaston Williams, August 22, 2018
* Based on sample code provided with the SparkFun Serial OpenLCD display.
The original LiquidCrystal library was written by David A. Mellis and
modified by Limor Fried @ Adafruit and the OpenLCD code was written by
Nathan Seidle @ SparkFun.
Implementation Notes
--------------------
**Hardware:**
* This is library is for the SparkFun Serial LCD displays
* SparkFun sells these at its website: www.sparkfun.com
* Do you like this library? Help support SparkFun. Buy a board!
* 16x2 SerLCD Black on RGB https://www.sparkfun.com/products/14072
* 16x2 SerLCD RGB on Black https://www.sparkfun.com/products/14073
* 20x4 SerLCD Black on RGB https://www.sparkfun.com/products/14074
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
"""
# imports__version__ = "0.0.0-auto.0"
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/fourstix/Sparkfun_CircuitPython_SerLCD.git"
# imports
from time import sleep
from micropython import const
# public constants
DEFAULT_I2C_ADDR = const(0x72)
"""Default I2C address for SerLCD"""
# private constants
_MAX_ROWS = const(4)
_MAX_COLS = const(20)
# Character to reset display Splash Screen to default
_DEFAULT_SPLASH_SCREEN = const(0xFF)
# OpenLCD command characters
_SPECIAL_COMMAND = const(254)
_SETTING_COMMAND = const(0x7C)
# OpenLCD commands
# 45, -, the dash character: command to clear and home the display
_CLEAR_COMMAND = const(0x2D)
# Command to change the contrast setting
_CONTRAST_COMMAND = const(0x18)
# Command to change the i2c address
_ADDRESS_COMMAND = const(0x19)
# 43, +, the plus character: command to set backlight RGB value
_SET_RGB_COMMAND = const(0x2B)
# 46, ., command to enable system messages being displayed
_ENABLE_SYSTEM_MESSAGE_DISPLAY = const(0x2E)
# 47, /, command to disable system messages being displayed
_DISABLE_SYSTEM_MESSAGE_DISPLAY = const(0x2F)
# 48, 0, command to enable splash screen at power on
_ENABLE_SPLASH_DISPLAY = const(0x30)
# 49, 1, command to disable splash screen at power on
_DISABLE_SPLASH_DISPLAY = const(0x31)
# 10, Ctrl+j, command to save current text on display as splash
_SAVE_CURRENT_DISPLAY_AS_SPLASH = const(0x0A)
# Show firmware version
_SHOW_VERSION_COMMAND = const(0x2C)
# Software reset of the system
_RESET_COMMAND = const(0x08)
# special commands
_LCD_RETURNHOME = const(0x02)
_LCD_ENTRYMODESET = const(0x04)
_LCD_DISPLAYCONTROL = const(0x08)
_LCD_CURSORSHIFT = const(0x10)
_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)
_LCD_DISPLAYOFF = const(0x00)
_LCD_CURSORON = const(0x02)
_LCD_CURSOROFF = const(0x00)
_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)
# private functions
def _map_range(value, in_min, in_max, out_min, out_max):
"""Map an integer value from a range into a value in another range."""
result = (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
return int(result)
# abstract base class
class Sparkfun_SerLCD:
"""Abstract base class for Sparkfun AVR-Based Serial LCD display.
Use the appropriate driver communcation subclass Sparkfun_SerLCD_I2C()
for I2C, Sparkfun_SerLCD_SPI() for SPI or Sparkfun_SerLCD_UART for UART.
"""
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-public-methods
def __init__(self):
self._display_control = _LCD_DISPLAYON | _LCD_CURSOROFF | _LCD_BLINKOFF
self._display_mode = _LCD_ENTRYLEFT | _LCD_ENTRYSHIFTDECREMENT
self._begin()
def command(self, command):
# pylint: disable=line-too-long
"""
Send a command to the display.
**Command cheat sheet:**
* ASCII / DEC / HEX
* '|' / 124 / 0x7C - Put into setting mode
* Ctrl+c / 3 / 0x03 - Change width to 20
* Ctrl+d / 4 / 0x04 - Change width to 16
* Ctrl+e / 5 / 0x05 - Change lines to 4
* Ctrl+f / 6 / 0x06 - Change lines to 2
* Ctrl+g / 7 / 0x07 - Change lines to 1
* Ctrl+h / 8 / 0x08 - Software reset of the system
* Ctrl+i / 9 / 0x09 - Enable/disable splash screen
* Ctrl+j / 10 / 0x0A - Save currently displayed text as splash
* Ctrl+k / 11 / 0x0B - Change baud to 2400bps
* Ctrl+l / 12 / 0x0C - Change baud to 4800bps
* Ctrl+m / 13 / 0x0D - Change baud to 9600bps
* Ctrl+n / 14 / 0x0E - Change baud to 14400bps
* Ctrl+o / 15 / 0x0F - Change baud to 19200bps
* Ctrl+p / 16 / 0x10 - Change baud to 38400bps
* Ctrl+q / 17 / 0x11 - Change baud to 57600bps
* Ctrl+r / 18 / 0x12 - Change baud to 115200bps
* Ctrl+s / 19 / 0x13 - Change baud to 230400bps
* Ctrl+t / 20 / 0x14 - Change baud to 460800bps
* Ctrl+u / 21 / 0x15 - Change baud to 921600bps
* Ctrl+v / 22 / 0x16 - Change baud to 1000000bps
* Ctrl+w / 23 / 0x17 - Change baud to 1200bps
* Ctrl+x / 24 / 0x18 - Change the contrast. Follow Ctrl+x with number 0 to 255. 120 is default.
* Ctrl+y / 25 / 0x19 - Change the TWI address. Follow Ctrl+x with number 0 to 255. 114 (0x72) is default.
* Ctrl+z / 26 / 0x1A - Enable/disable ignore RX pin on startup (ignore emergency reset)
* '+' / 43 / 0x2B - Set RGB backlight with three following bytes, 0-255
* ',' / 44 / 0x2C - Display current firmware version
* '-' / 45 / 0x2D - Clear display. Move cursor to home position.
* '.' / 46 / 0x2E - Enable system messages (ie, display 'Contrast: 5' when changed)
* '/' / 47 / 0x2F - Disable system messages (ie, don't display 'Contrast: 5' when changed)
* '0' / 48 / 0x30 - Enable splash screen
* '1' / 49 / 0x31 - Disable splash screen
* / 128-157 / 0x80-0x9D - Set the primary backlight brightness. 128 = Off, 157 = 100%.
* / 158-187 / 0x9E-0xBB - Set the green backlight brightness. 158 = Off, 187 = 100%.
* / 188-217 / 0xBC-0xD9 - Set the blue backlight brightness. 188 = Off, 217 = 100%.
* For example, to change the baud rate to 115200 send 124 followed by 18.
"""
data = bytearray()
data.append(_SETTING_COMMAND)
data.append(command & 0xFF)
self._write_bytes(data)
# Wait a bit longer for special display commands
sleep(0.010)
def clear(self):
"""Clear the display"""
self.command(_CLEAR_COMMAND)
def home(self):
"""Send the cursor home"""
self._special_command(_LCD_RETURNHOME)
def write(self, message):
"""Write a character string to the display."""
# Value -> String -> Bytes
text = str(message).encode()
self._write_bytes(text)
def set_cursor(self, col, row):
"""Set the cursor position."""
row_offsets = [0x00, 0x40, 0x14, 0x54]
# keep variables in bounds
# row cannot be less than 0
row = max(0, row)
# row cannot be greater than max rows
row = min(row, _MAX_ROWS - 1)
# send the command
self._special_command(_LCD_SETDDRAMADDR | (col + row_offsets[row]))
def create_character(self, location, charmap):
"""Create a customer character
location - character number 0 to 7
charmap - bytes for character as 8 x 5 bit map"""
# There are only 8 locations 0-7
location &= 0x07
data = bytearray()
# Send request to create a customer character
data.append(_SETTING_COMMAND)
data.append(27 + location)
for i in range(8):
# Only the lowest 5 bits are used
data.append(charmap[i] & 0x1F)
self._write_bytes(data)
# This takes a bit longer
sleep(0.050)
def write_character(self, location):
"""Write a customer character to the display
location - character number 0 to 7"""
# There are only locations 0-7
location &= 0x07
self.command(35 + location)
def set_backlight(self, rgb):
"""Set the backlight with 24-bit RGB value."""
red = (rgb >> 16) & 0x0000FF
green = (rgb >> 8) & 0x0000FF
blue = rgb & 0x0000FF
self.set_backlight_rgb(red, green, blue)
def set_backlight_rgb(self, red, green, blue):
"""Set the backlight with byte values for r, g, b"""
# map the byte value range to backlight command range
r_value = 128 + _map_range(red, 0, 255, 0, 29)
g_value = 158 + _map_range(green, 0, 255, 0, 29)
b_value = 188 + _map_range(blue, 0, 255, 0, 29)
# send commands to the display to set backlights
data = bytearray()
# Turn display off to hide confirmation messages
self._display_control &= ~_LCD_DISPLAYON
data.append(_SPECIAL_COMMAND)
data.append(_LCD_DISPLAYCONTROL | self._display_control)
# Set the red, green and blue values
data.append(_SETTING_COMMAND)
data.append(r_value)
data.append(_SETTING_COMMAND)
data.append(g_value)
data.append(_SETTING_COMMAND)
data.append(b_value)
# Turn display back on and end
self._display_control |= _LCD_DISPLAYON
data.append(_SPECIAL_COMMAND)
data.append(_LCD_DISPLAYCONTROL | self._display_control)
# Send data
self._write_bytes(data)
# This one is a bit slow
sleep(0.050)
def set_fast_backlight(self, rgb):
"""Set the backlight color by a 24-bit value in one pass."""
# Convert from hex triplet to byte values
red = (rgb >> 16) & 0x0000FF
green = (rgb >> 8) & 0x0000FF
blue = rgb & 0x0000FF
self.set_fast_backlight_rgb(red, green, blue)
def set_fast_backlight_rgb(self, red, green, blue):
"""Set the backlight color in one pass."""
# Mask values into 0-255 range
red &= 0x00FF
green &= 0x00FF
blue &= 0x00FF
# Send commands to the display to set backlights
data = bytearray()
data.append(_SETTING_COMMAND)
# Send the set RGB character '+' or plus
data.append(_SET_RGB_COMMAND)
data.append(red)
data.append(green)
data.append(blue)
self._write_bytes(data)
sleep(0.010)
def display(self, value):
"""Turn the display on and off quickly."""
if bool(value):
self._display_control |= _LCD_DISPLAYON
self._special_command(_LCD_DISPLAYCONTROL | self._display_control)
else:
self._display_control &= ~_LCD_DISPLAYON
self._special_command(_LCD_DISPLAYCONTROL | self._display_control)
def cursor(self, value):
"""Turn the underline cursor on and off."""
if bool(value):
self._display_control |= _LCD_CURSORON
self._special_command(_LCD_DISPLAYCONTROL | self._display_control)
else:
self._display_control &= ~_LCD_CURSORON
self._special_command(_LCD_DISPLAYCONTROL | self._display_control)
def blink(self, value):
"""Turn the blink cursor on and off."""
if bool(value):
self._display_control |= _LCD_BLINKON
self._special_command(_LCD_DISPLAYCONTROL | self._display_control)
else:
self._display_control &= ~_LCD_BLINKON
self._special_command(_LCD_DISPLAYCONTROL | self._display_control)
def system_messages(self, enable):
"""Enable or disable the printint of messages like 'UART: 57600' or 'Contrast: 5'"""
if bool(enable):
# Send the set '.' character
self.command(_ENABLE_SYSTEM_MESSAGE_DISPLAY)
else:
# Send the set '/' character
self.command(_DISABLE_SYSTEM_MESSAGE_DISPLAY)
sleep(0.010)
def autoscroll(self, enable):
"""Turn autoscrolling on and off."""
if bool(enable):
self._display_mode |= _LCD_ENTRYSHIFTINCREMENT
self._special_command(_LCD_ENTRYMODESET | self._display_mode)
else:
self._display_mode &= ~_LCD_ENTRYSHIFTINCREMENT
self._special_command(_LCD_ENTRYMODESET | self._display_mode)
sleep(0.010)
def set_contrast(self, value):
"""Set the display contrast."""
data = bytearray()
data.append(_SETTING_COMMAND)
data.append(_CONTRAST_COMMAND)
data.append(value & 0x00FF)
self._write_bytes(data)
sleep(0.010)
def set_i2c_address(self, new_address):
"""Change the I2C Address. 0x72 is the default.
Note that this change is persistent. If anything goes wrong
you may need to do a hardware reset to unbrick the display.
byte new_addr - new i2c address"""
# Mask new address to byte
new_address &= 0x00FF
# Transmit to device on old address
data = bytearray()
# Send contrast command
data.append(_SETTING_COMMAND)
data.append(_ADDRESS_COMMAND) # 0x19
data.append(new_address)
self._write_bytes(data)
# Update our own address so we can still talk to the display
self._change_i2c_address(new_address)
# This may take awhile
sleep(0.050)
def scroll_display_left(self, count=1):
"""Scroll the display to the left"""
self._special_command(
_LCD_CURSORSHIFT | _LCD_DISPLAYMOVE | _LCD_MOVELEFT, count
)
def scroll_display_right(self, count=1):
"""Scroll the display to the right"""
self._special_command(
_LCD_CURSORSHIFT | _LCD_DISPLAYMOVE | _LCD_MOVERIGHT, count
)
def move_cursor_left(self, count=1):
"""Move the cursor to the left"""
self._special_command(_LCD_CURSORSHIFT | _LCD_CURSORMOVE | _LCD_MOVELEFT, count)
def move_cursor_right(self, count=1):
"""Scroll the display to the right"""
self._special_command(
_LCD_CURSORSHIFT | _LCD_CURSORMOVE | _LCD_MOVERIGHT, count
)
def splash_screen(self, enable):
"""Enable or disable the splash screem."""
if bool(enable):
self.command(_ENABLE_SPLASH_DISPLAY)
else:
self.command(_DISABLE_SPLASH_DISPLAY)
sleep(0.010)
def save_splash_screen(self):
"""Save the current display as the splash screem."""
self.command(_SAVE_CURRENT_DISPLAY_AS_SPLASH)
sleep(0.010)
def left_to_right(self):
"""Set the text to flow from left to right. This is the direction
that is common to most Western languages."""
self._display_mode |= _LCD_ENTRYLEFT
self._special_command(_LCD_ENTRYMODESET | self._display_mode)
def right_to_left(self):
"""Set the text to flow from right to left."""
self._display_mode &= ~_LCD_ENTRYLEFT
self._special_command(_LCD_ENTRYMODESET | self._display_mode)
def show_version(self):
"""Show the firmware version on the display."""
self.command(_SHOW_VERSION_COMMAND)
def reset(self):
"""Perform a software reset on the dislay."""
self.command(_RESET_COMMAND)
def default_splash_screen(self):
"""Result to the default splash screen"""
# Clear the display
self.clear()
# put the default charater
self._put_char(_DEFAULT_SPLASH_SCREEN)
# Wait a bit
sleep(0.200)
self.save_splash_screen()
# abstract methods
def _write_bytes(self, data):
pass
def _change_i2c_address(self, addr):
pass
# private functions
def _begin(self):
"""Initialize the display"""
data = bytearray()
# Send special command character
data.append(_SPECIAL_COMMAND)
# Send the display command
data.append(_LCD_DISPLAYCONTROL | self._display_control)
# Send special command character
data.append(_SPECIAL_COMMAND)
# Send the entry mode command
data.append(_LCD_ENTRYMODESET | self._display_mode)
# Put LCD into setting mode
data.append(_SETTING_COMMAND)
# Send clear display command
data.append(_CLEAR_COMMAND)
self._write_bytes(data)
sleep(0.050)
def _special_command(self, command, count=1):
"""Send a special command to the display. Used by other functions."""
data = bytearray()
data.append(_SPECIAL_COMMAND)
for _ in range(count):
data.append(command & 0xFF)
self._write_bytes(data)
# Wait a bit longer for special display commands
sleep(0.050)
def _put_char(self, char):
"""Send a character byte directly to display, no encoding"""
data = bytearray()
data.append(char & 0xFF)
self._write_bytes(data)
# concrete subclass for I2C
class Sparkfun_SerLCD_I2C(Sparkfun_SerLCD):
"""Driver subclass for Sparkfun Serial Displays over I2C communication"""
def __init__(self, i2c, address=DEFAULT_I2C_ADDR):
# pylint: disable=import-outside-toplevel
import adafruit_bus_device.i2c_device as i2c_device
self._i2c_device = i2c_device.I2CDevice(i2c, address)
self._i2c = i2c
super().__init__()
def _write_bytes(self, data):
with self._i2c_device as device:
device.write(data)
def _change_i2c_address(self, addr):
# pylint: disable=import-outside-toplevel
import adafruit_bus_device.i2c_device as i2c_device
self._i2c_device = i2c_device.I2CDevice(self._i2c, addr)
# concrete subclass for SPI
class Sparkfun_SerLCD_SPI(Sparkfun_SerLCD):
"""Driver subclass for Sparkfun Serial LCD display over SPI communication"""
def __init__(self, spi, cs):
# pylint: disable=import-outside-toplevel
import adafruit_bus_device.spi_device as spi_device
self._spi_device = spi_device.SPIDevice(spi, cs)
super().__init__()
def _write_bytes(self, data):
with self._spi_device as device:
# pylint: disable=no-member
device.write(data)
def _change_i2c_address(self, addr):
# No i2c address change for SPI
pass
# concrete subclass for UART
class Sparkfun_SerLCD_UART(Sparkfun_SerLCD):
"""Driver subclass for Sparkfun Serial LCD display over Serial communication"""
def __init__(self, uart):
self._uart = uart
super().__init__()
def _write_bytes(self, data):
self._uart.write(data)
def _change_i2c_address(self, addr):
# No i2c address change for UART
pass