diff --git a/README.md b/README.md index 32e9724..cdaf904 100644 --- a/README.md +++ b/README.md @@ -117,3 +117,111 @@ and thought I would include it here, since it's related to the LCDs these driver The circuit allows for digitally controlling the contrast via PWM and also controlling the backlight brightness via PWM. + +Custom characters +================= + +The HD44780 displays come with 3 possible CGROM font sets. Japanese, European and Custom. +Test which you have using: + +``` +lcd.putchar(chr(247)) +``` + +If you see Pi (π), you have a Japanese A00 ROM. +If you see a division sign (÷), you have a European A02 ROM. + +Characters match ASCII characters in range 32-127 (0x20-0x7F) with a few exceptions: + +* 0x5C is a Yen symbol instead of backslash +* 0x7E is a right arrow instead of tilde +* 0x7F is a left arrow instead of delete + +Only the ASCII characters are common between the two ROMs 32-125 (0x20-0x7D) +Refer to the HD44780 datasheet for the table of characters. + +The first 8 characters are CGRAM or character-generator RAM. +You can specify any pattern for these characters. + +To design a custom character, start by drawing a 5x8 grid. +I use dots and hashes as it's a lot easier to read than 1s and 0s. +Draw pixels by replacing dots with hashes. +Where possible, leave the bottom row unpopulated as it may be occupied by the underline cursor. + +``` +Happy Face (where .=0, #=1) +..... +.#.#. +..... +..#.. +..... +#...# +.###. +..... +``` + +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). + +``` +Happy Face (where .=0, #=1) +..... == 0b00000 == 0x00 +.#.#. == 0b01010 == 0x0A +..... == 0b00000 == 0x00 +..#.. == 0b00100 == 0x04 +..... == 0b00000 == 0x00 +#...# == 0b10001 == 0x11 +.###. == 0b01110 == 0x0E +..... == 0b00000 == 0x00 +``` + +Next, add each byte from top to bottom to a new byte array and pass to custom_char() with location 0-7. + +``` +happy_face = bytearray([0x00,0x0A,0x00,0x04,0x00,0x11,0x0E,0x00]) +lcd.custom_char(0, happy_face) +``` + +`custom_char()` does not print anything to the display. It only updates the CGRAM. +To display the custom characters, use putchar() with chr(0) through chr(7). + +``` +lcd.putchar(chr(0)) +lcd.putchar(b'\x00') +``` + +Characters are displayed by reference. +Once you have printed a custom character to the lcd, you can overwrite the custom character and all visible instances will also update. +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. + +Examples: + +``` +# smiley faces +happy = bytearray([0x00,0x0A,0x00,0x04,0x00,0x11,0x0E,0x00]) +sad = bytearray([0x00,0x0A,0x00,0x04,0x00,0x0E,0x11,0x00]) +grin = bytearray([0x00,0x00,0x0A,0x00,0x1F,0x11,0x0E,0x00]) +shock = bytearray([0x0A,0x00,0x04,0x00,0x0E,0x11,0x11,0x0E]) +meh = bytearray([0x00,0x0A,0x00,0x04,0x00,0x1F,0x00,0x00]) +angry = bytearray([0x11,0x0A,0x11,0x04,0x00,0x0E,0x11,0x00]) +tongue = bytearray([0x00,0x0A,0x00,0x04,0x00,0x1F,0x05,0x02]) + +# icons +bell = bytearray([0x04,0x0e,0x0e,0x0e,0x1f,0x00,0x04,0x00]) +note = bytearray([0x02,0x03,0x02,0x0e,0x1e,0x0c,0x00,0x00]) +clock = bytearray([0x00,0x0e,0x15,0x17,0x11,0x0e,0x00,0x00]) +heart = bytearray([0x00,0x0a,0x1f,0x1f,0x0e,0x04,0x00,0x00]) +duck = bytearray([0x00,0x0c,0x1d,0x0f,0x0f,0x06,0x00,0x00]) +check = bytearray([0x00,0x01,0x03,0x16,0x1c,0x08,0x00,0x00]) +cross = bytearray([0x00,0x1b,0x0e,0x04,0x0e,0x1b,0x00,0x00]) +retarrow = bytearray([0x01,0x01,0x05,0x09,0x1f,0x08,0x04,0x00]) + +# battery icons +battery0 = bytearray([0x0E,0x1B,0x11,0x11,0x11,0x11,0x11,0x1F])) # 0% Empty +battery1 = bytearray([0x0E,0x1B,0x11,0x11,0x11,0x11,0x1F,0x1F])) # 16% +battery2 = bytearray([0x0E,0x1B,0x11,0x11,0x11,0x1F,0x1F,0x1F])) # 33% +battery3 = bytearray([0x0E,0x1B,0x11,0x11,0x1F,0x1F,0x1F,0x1F])) # 50% +battery4 = bytearray([0x0E,0x1B,0x11,0x1F,0x1F,0x1F,0x1F,0x1F])) # 66% +battery5 = bytearray([0x0E,0x1B,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F])) # 83% +battery6 = bytearray([0x0E,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F])) # 100% Full +battery7 = bytearray([0x0E,0x1F,0x1B,0x1B,0x1B,0x1F,0x1B,0x1F])) # ! Error +``` diff --git a/lcd/i2c_lcd_test.py b/lcd/i2c_lcd_test.py index ba092bd..820bfac 100644 --- a/lcd/i2c_lcd_test.py +++ b/lcd/i2c_lcd_test.py @@ -9,9 +9,26 @@ def test_main(): """Test function for verifying basic functionality.""" lcd = I2cLcd(1, DEFAULT_I2C_ADDR, 2, 16) + 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) diff --git a/lcd/lcd_api.py b/lcd/lcd_api.py index 70338a1..38b60da 100644 --- a/lcd/lcd_api.py +++ b/lcd/lcd_api.py @@ -1,5 +1,6 @@ """Provides an API for talking to HD44780 compatible character LCDs.""" +import time class LcdApi: """Implements the API for talking with HD44780 compatible character LCDs. @@ -151,6 +152,18 @@ def putstr(self, string): for char in string: self.putchar(char) + def custom_char(self, location, charmap): + """Write a character to one of the 8 CGRAM locations, available + as chr(0) through chr(7). + """ + location &= 0x7 + self.hal_write_command(self.LCD_CGRAM | (location << 3)) + time.sleep_us(40) + for i in range(8): + self.hal_write_data(charmap[i]) + time.sleep_us(40) + self.move_to(self.cursor_x, self.cursor_y) + def hal_backlight_on(self): """Allows the hal layer to turn the backlight on.