Skip to content

Commit 58f90bf

Browse files
committed
Add support for custom characters
1 parent 1f43dbf commit 58f90bf

File tree

3 files changed

+139
-0
lines changed

3 files changed

+139
-0
lines changed

README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,112 @@ and thought I would include it here, since it's related to the LCDs these driver
117117

118118
The circuit allows for digitally controlling the contrast via PWM and also controlling
119119
the backlight brightness via PWM.
120+
121+
Custom characters
122+
=================
123+
124+
The HD44780 displays come with 3 possible CGROM font sets. Japanese, European and Custom.
125+
Test which you have using:
126+
127+
```
128+
lcd.putchar(chr(247))
129+
```
130+
131+
If you see Pi (π), you have a Japanese A00 ROM.
132+
If you see a division sign (÷), you have a European A02 ROM.
133+
134+
Characters match ASCII characters in range 32-127 (0x20-0x7F) with a few exceptions:
135+
136+
* 0x5C is a Yen symbol instead of backslash
137+
* 0x7E is a right arrow instead of tilde
138+
* 0x7F is a left arrow instead of delete
139+
140+
Only the ASCII characters are common between the two ROMs 32-125 (0x20-0x7D)
141+
Refer to the HD44780 datasheet for the table of characters.
142+
143+
The first 8 characters are CGRAM or character-generator RAM.
144+
You can specify any pattern for these characters.
145+
146+
To design a custom character, start by drawing a 5x8 grid.
147+
I use dots and hashes as it's a lot easier to read than 1s and 0s.
148+
Draw pixels by replacing dots with hashes.
149+
Where possible, leave the bottom row unpopulated as it may be occupied by the underline cursor.
150+
151+
Happy Face (where .=0, #=1)
152+
153+
```
154+
.....
155+
.#.#.
156+
.....
157+
..#..
158+
.....
159+
#...#
160+
.###.
161+
.....
162+
```
163+
164+
To convert this into a bytearray for the custom_char() method, you need to add each row of 5 pixels to least significant bits of a byte (the right side).
165+
166+
```
167+
Happy Face (where .=0, #=1)
168+
..... == 0b00000 == 0x00
169+
.#.#. == 0b01010 == 0x0A
170+
..... == 0b00000 == 0x00
171+
..#.. == 0b00100 == 0x04
172+
..... == 0b00000 == 0x00
173+
#...# == 0b10001 == 0x11
174+
.###. == 0b01110 == 0x0E
175+
..... == 0b00000 == 0x00
176+
```
177+
178+
Next, add each byte from top to bottom to a new byte array and pass to custom_char() with location 0-7.
179+
180+
```
181+
happy_face = bytearray([0x00,0x0A,0x00,0x04,0x00,0x11,0x0E,0x00])
182+
lcd.custom_char(0, happy_face)
183+
```
184+
185+
`custom_char()` does not print anything to the display. It only updates the CGRAM.
186+
To display the custom characters, use putchar() with chr(0) through chr(7).
187+
188+
```
189+
lcd.putchar(chr(0))
190+
lcd.putchar(b'\x00')
191+
```
192+
193+
Characters are displayed by reference.
194+
Once you have printed a custom character to the lcd, you can overwrite the custom character and all visible instances will also update.
195+
This is useful for drawing animations and graphs, as you only need to print the characters once and then can simply modify the custom characters in CGRAM.
196+
197+
Examples:
198+
199+
```
200+
# smiley faces
201+
happy = bytearray([0x00,0x0A,0x00,0x04,0x00,0x11,0x0E,0x00])
202+
sad = bytearray([0x00,0x0A,0x00,0x04,0x00,0x0E,0x11,0x00])
203+
grin = bytearray([0x00,0x00,0x0A,0x00,0x1F,0x11,0x0E,0x00])
204+
shock = bytearray([0x0A,0x00,0x04,0x00,0x0E,0x11,0x11,0x0E])
205+
meh = bytearray([0x00,0x0A,0x00,0x04,0x00,0x1F,0x00,0x00])
206+
angry = bytearray([0x11,0x0A,0x11,0x04,0x00,0x0E,0x11,0x00])
207+
tongue = bytearray([0x00,0x0A,0x00,0x04,0x00,0x1F,0x05,0x02])
208+
209+
# icons
210+
bell = bytearray([0x04,0x0e,0x0e,0x0e,0x1f,0x00,0x04,0x00])
211+
note = bytearray([0x02,0x03,0x02,0x0e,0x1e,0x0c,0x00,0x00])
212+
clock = bytearray([0x00,0x0e,0x15,0x17,0x11,0x0e,0x00,0x00])
213+
heart = bytearray([0x00,0x0a,0x1f,0x1f,0x0e,0x04,0x00,0x00])
214+
duck = bytearray([0x00,0x0c,0x1d,0x0f,0x0f,0x06,0x00,0x00])
215+
check = bytearray([0x00,0x01,0x03,0x16,0x1c,0x08,0x00,0x00])
216+
cross = bytearray([0x00,0x1b,0x0e,0x04,0x0e,0x1b,0x00,0x00])
217+
retarrow = bytearray([0x01,0x01,0x05,0x09,0x1f,0x08,0x04,0x00])
218+
219+
# battery icons
220+
battery0 = bytearray([0x0E,0x1B,0x11,0x11,0x11,0x11,0x11,0x1F])) # 0% Empty
221+
battery1 = bytearray([0x0E,0x1B,0x11,0x11,0x11,0x11,0x1F,0x1F])) # 16%
222+
battery2 = bytearray([0x0E,0x1B,0x11,0x11,0x11,0x1F,0x1F,0x1F])) # 33%
223+
battery3 = bytearray([0x0E,0x1B,0x11,0x11,0x1F,0x1F,0x1F,0x1F])) # 50%
224+
battery4 = bytearray([0x0E,0x1B,0x11,0x1F,0x1F,0x1F,0x1F,0x1F])) # 66%
225+
battery5 = bytearray([0x0E,0x1B,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F])) # 83%
226+
battery6 = bytearray([0x0E,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F])) # 100% Full
227+
battery7 = bytearray([0x0E,0x1F,0x1B,0x1B,0x1B,0x1F,0x1B,0x1F])) # ! Error
228+
```

lcd/i2c_lcd_test.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,26 @@
99
def test_main():
1010
"""Test function for verifying basic functionality."""
1111
lcd = I2cLcd(1, DEFAULT_I2C_ADDR, 2, 16)
12+
lcd.blink_cursor_on()
1213
lcd.putstr("It Works!\nSecond Line")
1314
time.sleep(3)
1415
lcd.clear()
16+
17+
# custom characters: battery icons - 5 wide, 8 tall
18+
lcd.custom_char(0, bytearray([0x0E,0x1B,0x11,0x11,0x11,0x11,0x11,0x1F])) # 0% Empty
19+
lcd.custom_char(1, bytearray([0x0E,0x1B,0x11,0x11,0x11,0x11,0x1F,0x1F])) # 16%
20+
lcd.custom_char(2, bytearray([0x0E,0x1B,0x11,0x11,0x11,0x1F,0x1F,0x1F])) # 33%
21+
lcd.custom_char(3, bytearray([0x0E,0x1B,0x11,0x11,0x1F,0x1F,0x1F,0x1F])) # 50%
22+
lcd.custom_char(4, bytearray([0x0E,0x1B,0x11,0x1F,0x1F,0x1F,0x1F,0x1F])) # 66%
23+
lcd.custom_char(5, bytearray([0x0E,0x1B,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F])) # 83%
24+
lcd.custom_char(6, bytearray([0x0E,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F])) # 100% Full
25+
lcd.custom_char(7, bytearray([0x0E,0x1F,0x1B,0x1B,0x1B,0x1F,0x1B,0x1F])) # ! Error
26+
for i in range(8):
27+
lcd.putchar(chr(i))
28+
time.sleep(3)
29+
lcd.clear()
30+
lcd.blink_cursor_off()
31+
1532
count = 0
1633
while True:
1734
lcd.move_to(0, 0)

lcd/lcd_api.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Provides an API for talking to HD44780 compatible character LCDs."""
22

3+
import time
34

45
class LcdApi:
56
"""Implements the API for talking with HD44780 compatible character LCDs.
@@ -151,6 +152,18 @@ def putstr(self, string):
151152
for char in string:
152153
self.putchar(char)
153154

155+
def custom_char(self, location, charmap):
156+
"""Write a character to one of the 8 CGRAM locations, available
157+
as chr(0) through chr(7).
158+
"""
159+
location &= 0x7
160+
self.hal_write_command(self.LCD_CGRAM | (location << 3))
161+
time.sleep_us(40)
162+
for i in range(8):
163+
self.hal_write_data(charmap[i])
164+
time.sleep_us(40)
165+
self.move_to(self.cursor_x, self.cursor_y)
166+
154167
def hal_backlight_on(self):
155168
"""Allows the hal layer to turn the backlight on.
156169

0 commit comments

Comments
 (0)