|
| 1 | +from time import sleep |
| 2 | +from Adafruit_I2C import Adafruit_I2C |
| 3 | + |
| 4 | +# Code based on https://gist.github.com/ufux/6094977 |
| 5 | +# Massaged to use Adafruit_I2C library and commands. |
| 6 | +# Communication from expander to display: high nibble first, then low nibble |
| 7 | +# Communication via i2c to the PCF 8547: bits are processed from highest to lowest (send P7 bit first) |
| 8 | + |
| 9 | +class Adafruit_PCF8547LCD: |
| 10 | + #initializes objects and lcd |
| 11 | + |
| 12 | + # LCD Commands |
| 13 | + LCD_CLEARDISPLAY = 0x01 |
| 14 | + LCD_RETURNHOME = 0x02 |
| 15 | + LCD_ENTRYMODESET = 0x04 |
| 16 | + LCD_DISPLAYCONTROL = 0x08 |
| 17 | + LCD_CURSORSHIFT = 0x10 |
| 18 | + LCD_FUNCTIONSET = 0x20 |
| 19 | + LCD_SETCGRAMADDR = 0x40 |
| 20 | + LCD_SETDDRAMADDR = 0x80 |
| 21 | + |
| 22 | + # Flags for display on/off control |
| 23 | + LCD_DISPLAYON = 0x04 |
| 24 | + LCD_DISPLAYOFF = 0x00 |
| 25 | + LCD_CURSORON = 0x02 |
| 26 | + LCD_CURSOROFF = 0x00 |
| 27 | + LCD_BLINKON = 0x01 |
| 28 | + LCD_BLINKOFF = 0x00 |
| 29 | + |
| 30 | + # Flags for display entry mode |
| 31 | + LCD_ENTRYRIGHT = 0x00 |
| 32 | + LCD_ENTRYLEFT = 0x02 |
| 33 | + LCD_ENTRYSHIFTINCREMENT = 0x01 |
| 34 | + LCD_ENTRYSHIFTDECREMENT = 0x00 |
| 35 | + |
| 36 | + # Flags for display/cursor shift |
| 37 | + LCD_DISPLAYMOVE = 0x08 |
| 38 | + LCD_CURSORMOVE = 0x00 |
| 39 | + LCD_MOVERIGHT = 0x04 |
| 40 | + LCD_MOVELEFT = 0x00 |
| 41 | + |
| 42 | + # flags for function set |
| 43 | + LCD_8BITMODE = 0x10 |
| 44 | + LCD_4BITMODE = 0x00 |
| 45 | + LCD_2LINE = 0x08 |
| 46 | + LCD_1LINE = 0x00 |
| 47 | + LCD_5x10DOTS = 0x04 |
| 48 | + LCD_5x8DOTS = 0x00 |
| 49 | + |
| 50 | + # flags for backlight control |
| 51 | + LCD_BACKLIGHT = 0x08 |
| 52 | + LCD_NOBACKLIGHT = 0x00 |
| 53 | + |
| 54 | + EN = 0b00000100 # Enable bit |
| 55 | + RW = 0b00000010 # Read/Write bit |
| 56 | + RS = 0b00000001 # Register select bit |
| 57 | + |
| 58 | + ''' |
| 59 | + new pinout: |
| 60 | + ----------- ----------- |
| 61 | + 0x80 P7 - - D7 |
| 62 | + 0x40 P6 - - D6 |
| 63 | + 0x20 P5 - - D5 |
| 64 | + 0x10 P4 - - D4 |
| 65 | + ----------- ----------- |
| 66 | + 0x08 P3 - - BL Backlight ??? |
| 67 | + 0x04 P2 - - EN Starts Data read/write |
| 68 | + 0x02 P1 - - RW low: write, high: read |
| 69 | + 0x01 P0 - - RS Register Select: 0: Instruction Register (IR) (AC when read), 1: data register (DR) |
| 70 | + ''' |
| 71 | + |
| 72 | + def __init__(self, addr=0x3f, busnum=-1, withBacklight=True, withOneTimeInit=False): |
| 73 | + ''' |
| 74 | + device writes! |
| 75 | + crosscheck also http://www.monkeyboard.org/tutorials/81-display/70-usb-serial-to-hd44780-lcd |
| 76 | + here a sequence is listed |
| 77 | + ''' |
| 78 | + self.addr = addr |
| 79 | + self.busnum = busnum |
| 80 | + |
| 81 | + self.bus = Adafruit_I2C(self.addr, self.busnum) |
| 82 | + |
| 83 | + self.displayshift = (self.LCD_CURSORMOVE | |
| 84 | + self.LCD_MOVERIGHT) |
| 85 | + self.displaymode = (self.LCD_ENTRYLEFT | |
| 86 | + self.LCD_ENTRYSHIFTDECREMENT) |
| 87 | + self.displaycontrol = (self.LCD_DISPLAYON | |
| 88 | + self.LCD_CURSOROFF | |
| 89 | + self.LCD_BLINKOFF) |
| 90 | + |
| 91 | + self.displayfunction = self.LCD_4BITMODE | self.LCD_1LINE | self.LCD_5x8DOTS |
| 92 | + self.displayfunction |= self.LCD_2LINE |
| 93 | + |
| 94 | + if withBacklight: |
| 95 | + self.blFlag=self.LCD_BACKLIGHT |
| 96 | + else: |
| 97 | + self.blFlag=self.LCD_NOBACKLIGHT |
| 98 | + |
| 99 | + # we can initialize the display only once after it had been powered on |
| 100 | + if(withOneTimeInit): |
| 101 | + self.bus.writeRaw8(0x3f) |
| 102 | + self.pulseEnable() |
| 103 | + sleep(0.0100) # TODO: Not clear if we have to wait that long |
| 104 | + self.write(self.displayfunction) # 0x28 |
| 105 | + |
| 106 | + self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol) # 0x08 + 0x4 = 0x0C |
| 107 | + self.write(self.LCD_ENTRYMODESET | self.displaymode) # 0x06 |
| 108 | + self.clear() # 0x01 |
| 109 | + self.home() |
| 110 | + |
| 111 | + def begin(self, cols, lines): |
| 112 | + if (lines > 1): |
| 113 | + self.numlines = lines |
| 114 | + self.displayfunction |= self.LCD_2LINE |
| 115 | + |
| 116 | + def home(self): |
| 117 | + self.write(self.LCD_RETURNHOME) # set cursor position to zero |
| 118 | + self.delayMicroseconds(3000) # this command takes a long time! |
| 119 | + |
| 120 | + def clear(self): |
| 121 | + self.write(self.LCD_CLEARDISPLAY) # command to clear display |
| 122 | + self.delayMicroseconds(3000) # 3000 microsecond sleep, clearing the display takes a long time |
| 123 | + |
| 124 | + def setCursor(self, col, row): |
| 125 | + self.row_offsets = [0x00, 0x40, 0x14, 0x54] |
| 126 | + if row > self.numlines: |
| 127 | + row = self.numlines |
| 128 | + row -= 1 # row_offsets is zero indexed |
| 129 | + self.write(self.LCD_SETDDRAMADDR | (col - 1 + self.row_offsets[row])) |
| 130 | + |
| 131 | + def noDisplay(self): |
| 132 | + """ Turn the display off (quickly) """ |
| 133 | + self.displaycontrol &= ~self.LCD_DISPLAYON |
| 134 | + self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol) |
| 135 | + |
| 136 | + def display(self): |
| 137 | + """ Turn the display on (quickly) """ |
| 138 | + self.displaycontrol |= self.LCD_DISPLAYON |
| 139 | + self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol) |
| 140 | + |
| 141 | + def noCursor(self): |
| 142 | + """ Turns the underline cursor off """ |
| 143 | + self.displaycontrol &= ~self.LCD_CURSORON |
| 144 | + self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol) |
| 145 | + |
| 146 | + def cursor(self): |
| 147 | + """ Turns the underline cursor on """ |
| 148 | + self.displaycontrol |= self.LCD_CURSORON |
| 149 | + self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol) |
| 150 | + |
| 151 | + def noBlink(self): |
| 152 | + """ Turn the blinking cursor off """ |
| 153 | + self.displaycontrol &= ~self.LCD_BLINKON |
| 154 | + self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol) |
| 155 | + |
| 156 | + def blink(self): |
| 157 | + """ Turn the blinking cursor on """ |
| 158 | + self.displaycontrol |= self.LCD_BLINKON |
| 159 | + self.write(self.LCD_DISPLAYCONTROL | self.displaycontrol) |
| 160 | + |
| 161 | + def DisplayLeft(self): |
| 162 | + """ These commands scroll the display without changing the RAM """ |
| 163 | + self.write(self.LCD_CURSORSHIFT | self.LCD_DISPLAYMOVE | self.LCD_MOVELEFT) |
| 164 | + |
| 165 | + def scrollDisplayRight(self): |
| 166 | + """ These commands scroll the display without changing the RAM """ |
| 167 | + self.write(self.LCD_CURSORSHIFT | self.LCD_DISPLAYMOVE | self.LCD_MOVERIGHT) |
| 168 | + |
| 169 | + def leftToRight(self): |
| 170 | + """ This is for text that flows Left to Right """ |
| 171 | + self.displaymode |= self.LCD_ENTRYLEFT |
| 172 | + self.write(self.LCD_ENTRYMODESET | self.displaymode) |
| 173 | + |
| 174 | + def rightToLeft(self): |
| 175 | + """ This is for text that flows Right to Left """ |
| 176 | + self.displaymode &= ~self.LCD_ENTRYLEFT |
| 177 | + self.write(self.LCD_ENTRYMODESET | self.displaymode) |
| 178 | + |
| 179 | + def autoscroll(self): |
| 180 | + """ This will 'right justify' text from the cursor """ |
| 181 | + self.displaymode |= self.LCD_ENTRYSHIFTINCREMENT |
| 182 | + self.write(self.LCD_ENTRYMODESET | self.displaymode) |
| 183 | + |
| 184 | + def noAutoscroll(self): |
| 185 | + """ This will 'left justify' text from the cursor """ |
| 186 | + self.displaymode &= ~self.LCD_ENTRYSHIFTINCREMENT |
| 187 | + self.write(self.LCD_ENTRYMODESET | self.displaymode) |
| 188 | + |
| 189 | + def delayMicroseconds(self, microseconds): |
| 190 | + seconds = microseconds / float(1000000) # divide microseconds by 1 million for seconds |
| 191 | + sleep(seconds) |
| 192 | + |
| 193 | + # clocks EN to latch command |
| 194 | + def pulseEnable(self): |
| 195 | + # uses underlying |
| 196 | + self.bus.writeRaw8((self.bus.bus.read_byte(self.addr) | self.EN | self.blFlag)) # | 0b0000 0100 # set "EN" high |
| 197 | + self.bus.writeRaw8(((self.bus.bus.read_byte(self.addr) | self.blFlag) & 0xFB)) # & 0b1111 1011 # set "EN" low |
| 198 | + |
| 199 | + # write data to lcd in 4 bit mode, 2 nibbles |
| 200 | + # high nibble is sent first |
| 201 | + def write(self, cmd): |
| 202 | + #write high nibble first |
| 203 | + self.bus.writeRaw8( (cmd & 0xF0) | self.blFlag ) |
| 204 | + hi= self.bus.bus.read_byte(self.addr) |
| 205 | + self.pulseEnable() |
| 206 | + |
| 207 | + # write low nibble second ... |
| 208 | + self.bus.writeRaw8( (cmd << 4) | self.blFlag ) |
| 209 | + lo= self.bus.bus.read_byte(self.addr) |
| 210 | + self.pulseEnable() |
| 211 | + self.bus.writeRaw8(self.blFlag) |
| 212 | + |
| 213 | + # write a character to lcd (or character rom) 0x09: backlight | RS=DR |
| 214 | + def write_char(self, charvalue): |
| 215 | + controlFlag = self.blFlag | self.RS |
| 216 | + |
| 217 | + # write high nibble |
| 218 | + self.bus.writeRaw8((controlFlag | (charvalue & 0xF0))) |
| 219 | + self.pulseEnable() |
| 220 | + |
| 221 | + # write low nibble |
| 222 | + self.bus.writeRaw8((controlFlag | (charvalue << 4))) |
| 223 | + self.pulseEnable() |
| 224 | + self.bus.writeRaw8(self.blFlag) |
| 225 | + |
| 226 | + # put char function |
| 227 | + def putc(self, char): |
| 228 | + self.write_char(ord(char)) |
| 229 | + |
| 230 | + def _setDDRAMAdress(self, line, col): |
| 231 | + # we write to the Data Display RAM (DDRAM) |
| 232 | + # TODO: Factor line offsets for other display organizations; this is for 20x4 only |
| 233 | + if line == 1: |
| 234 | + self.write(self.LCD_SETDDRAMADDR | (0x00 + col) ) |
| 235 | + if line == 2: |
| 236 | + self.write(self.LCD_SETDDRAMADDR | (0x40 + col) ) |
| 237 | + if line == 3: |
| 238 | + self.write(self.LCD_SETDDRAMADDR | (0x14 + col) ) |
| 239 | + if line == 4: |
| 240 | + self.write(self.LCD_SETDDRAMADDR | (0x54 + col) ) |
| 241 | + |
| 242 | + # put string function |
| 243 | + def message(self, string, line=1): |
| 244 | + """ Send string to LCD. Newline wraps to next line. |
| 245 | + Starts at line 1 unless passed starting line """ |
| 246 | + self._setDDRAMAdress(line, 0) |
| 247 | + for char in string: |
| 248 | + if char == '\n': |
| 249 | + line = 1 if line > 4 else line + 1 |
| 250 | + self._setDDRAMAdress(line, 0) |
| 251 | + else: |
| 252 | + self.putc(char) |
| 253 | + |
| 254 | + def putString(self, string): |
| 255 | + """ Sends a string to LCD starting at current cursor position |
| 256 | + Doesn't handle newline character |
| 257 | + """ |
| 258 | + for char in string: |
| 259 | + self.putc(char) |
| 260 | + |
| 261 | + # add custom characters (0 - 7) |
| 262 | + def lcd_load_custon_chars(self, fontdata): |
| 263 | + self.lcd_device.bus.write(0x40); |
| 264 | + for char in fontdata: |
| 265 | + for line in char: |
| 266 | + self.write_char(line) |
| 267 | + |
| 268 | +if __name__ == '__main__': |
| 269 | + from time import localtime, strftime |
| 270 | + |
| 271 | + initFlag=False |
| 272 | + debug=False |
| 273 | + backlight=True |
| 274 | + |
| 275 | + lcd = Adafruit_PCF8547LCD(0x3f,1,backlight, initFlag) |
| 276 | + lcd.begin(20,4) |
| 277 | + msg = "+" + "=" * 18 + "+\n" |
| 278 | + msg += "| 20x4 LCD |\n" |
| 279 | + msg += "| w/ PCF8547 |\n" |
| 280 | + msg += "+" + "=" * 18 + "+" |
| 281 | + |
| 282 | + lcd.message(msg) |
| 283 | + sleep(3) |
| 284 | + |
| 285 | + lcd.clear() |
| 286 | + lcd.cursor() |
| 287 | + lcd.message("Cursor Positioning") |
| 288 | + lcd.setCursor(2,4) |
| 289 | + lcd.putString(". <- (2,4)") |
| 290 | + lcd.setCursor(6,2) |
| 291 | + lcd.putString(". <- (6,2)") |
| 292 | + lcd.setCursor(11,3) |
| 293 | + lcd.putString(".") |
| 294 | + lcd.setCursor(1,3) |
| 295 | + lcd.putString("(11,3) -> ") |
| 296 | + |
| 297 | + sleep(5) |
| 298 | + |
| 299 | + lcd.clear() |
| 300 | + lcd.noCursor() |
| 301 | + lcd.message(" Simple Clock ",1) |
| 302 | + lcd.message("CTRL-C to quit",4) |
| 303 | + while True: |
| 304 | + lcd.message(strftime("%Y-%m-%d %H:%M:%S ", localtime()),3) |
| 305 | + sleep(1) |
0 commit comments