tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/hd44780/hd44780.go (about) 1 // Package hd44780 provides a driver for the HD44780 LCD controller. 2 // 3 // Datasheet: https://www.sparkfun.com/datasheets/LCD/HD44780.pdf 4 package hd44780 // import "tinygo.org/x/drivers/hd44780" 5 6 import ( 7 "errors" 8 "io" 9 "machine" 10 "time" 11 ) 12 13 const ( 14 // These are the default execution times for the Clear and 15 // Home commands and everything else. 16 // 17 // These are used if RW is passed as machine.NoPin and ignored 18 // otherwise. 19 // 20 // They are set conservatively here and can be tweaked in the 21 // Config structure. 22 DefaultClearHomeTime = 80 * time.Millisecond 23 DefaultInstrExecTime = 80 * time.Microsecond 24 ) 25 26 type Buser interface { 27 io.ReadWriter 28 SetCommandMode(set bool) 29 WriteOnly() bool 30 } 31 32 type Device struct { 33 bus Buser 34 width uint8 35 height uint8 36 buffer []uint8 37 bufferLength uint8 38 39 rowOffset []uint8 // Row offsets in DDRAM 40 datalength uint8 41 42 cursor cursor 43 busyStatus []byte 44 45 clearHomeTime time.Duration // time clear/home instructions might take 46 instrExecTime time.Duration // time all other instructions might take 47 } 48 49 type cursor struct { 50 x, y uint8 51 } 52 53 type Config struct { 54 Width int16 55 Height int16 56 CursorBlink bool 57 CursorOnOff bool 58 Font uint8 59 ClearHomeTime time.Duration // time clear/home instructions might take - use 0 for the default 60 InstrExecTime time.Duration // time all other instructions might take - use 0 for the default 61 } 62 63 // NewGPIO4Bit returns 4bit data length HD44780 driver. Datapins are LCD DB pins starting from DB4 to DB7 64 // 65 // If your device has RW set permanently to ground then pass in rw as machine.NoPin 66 func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) { 67 const fourBitMode = 4 68 if len(dataPins) != fourBitMode { 69 return Device{}, errors.New("4 pins are required in data slice (D4-D7) when HD44780 is used in 4 bit mode") 70 } 71 return newGPIO(dataPins, e, rs, rw, DATA_LENGTH_4BIT), nil 72 } 73 74 // NewGPIO8Bit returns 8bit data length HD44780 driver. Datapins are LCD DB pins starting from DB0 to DB7 75 // 76 // If your device has RW set permanently to ground then pass in rw as machine.NoPin 77 func NewGPIO8Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) { 78 const eightBitMode = 8 79 if len(dataPins) != eightBitMode { 80 return Device{}, errors.New("8 pins are required in data slice (D0-D7) when HD44780 is used in 8 bit mode") 81 } 82 return newGPIO(dataPins, e, rs, rw, DATA_LENGTH_8BIT), nil 83 } 84 85 // Configure initializes device 86 func (d *Device) Configure(cfg Config) error { 87 d.busyStatus = make([]byte, 1) 88 d.width = uint8(cfg.Width) 89 d.height = uint8(cfg.Height) 90 if d.width == 0 || d.height == 0 { 91 return errors.New("width and height must be set") 92 } 93 d.clearHomeTime = cfg.ClearHomeTime 94 d.instrExecTime = cfg.InstrExecTime 95 memoryMap := uint8(ONE_LINE) 96 if d.height > 1 { 97 memoryMap = TWO_LINE 98 } 99 d.setRowOffsets() 100 d.ClearBuffer() 101 102 cursor := CURSOR_OFF 103 if cfg.CursorOnOff { 104 cursor = CURSOR_ON 105 } 106 cursorBlink := CURSOR_BLINK_OFF 107 if cfg.CursorBlink { 108 cursorBlink = CURSOR_BLINK_ON 109 } 110 if !(cfg.Font == FONT_5X8 || cfg.Font == FONT_5X10) { 111 cfg.Font = FONT_5X8 112 } 113 114 //Wait 15ms after Vcc rises to 4.5V 115 time.Sleep(15 * time.Millisecond) 116 117 d.bus.SetCommandMode(true) 118 d.bus.Write([]byte{DATA_LENGTH_8BIT}) 119 time.Sleep(5 * time.Millisecond) 120 121 for i := 0; i < 2; i++ { 122 d.bus.Write([]byte{DATA_LENGTH_8BIT}) 123 time.Sleep(150 * time.Microsecond) 124 125 } 126 127 if d.datalength == DATA_LENGTH_4BIT { 128 d.bus.Write([]byte{DATA_LENGTH_4BIT}) 129 } 130 131 // Busy flag is now accessible 132 d.SendCommand(memoryMap | cfg.Font | d.datalength) 133 d.SendCommand(DISPLAY_OFF) 134 d.SendCommand(DISPLAY_CLEAR) 135 d.SendCommand(ENTRY_MODE | CURSOR_INCREASE | DISPLAY_NO_SHIFT) 136 d.SendCommand(DISPLAY_ON | uint8(cursor) | uint8(cursorBlink)) 137 return nil 138 } 139 140 // Write writes data to internal buffer 141 func (d *Device) Write(data []byte) (n int, err error) { 142 size := len(data) 143 if size > len(d.buffer) { 144 size = len(d.buffer) 145 } 146 d.bufferLength = uint8(size) 147 for i := uint8(0); i < d.bufferLength; i++ { 148 d.buffer[i] = data[i] 149 } 150 return size, nil 151 } 152 153 // Display sends the whole buffer to the screen at cursor position 154 func (d *Device) Display() error { 155 156 // Buffer may contain less characters than its capacity. 157 // We must be sure that we will not send unassigned characters 158 // That would result in sending zero values of buffer slice and 159 // potentialy displaying some character. 160 var totalDisplayedChars uint8 161 162 var bufferPos uint8 163 164 for ; d.cursor.y < d.height; d.cursor.y++ { 165 d.SetCursor(d.cursor.x, d.cursor.y) 166 167 for ; d.cursor.x < d.width && totalDisplayedChars < d.bufferLength; d.cursor.x++ { 168 d.sendData(d.buffer[bufferPos]) 169 bufferPos++ 170 totalDisplayedChars++ 171 } 172 if d.cursor.x >= d.width { 173 d.cursor.x = 0 174 } 175 if totalDisplayedChars >= d.bufferLength { 176 break 177 } 178 179 } 180 return nil 181 } 182 183 // SetCursor moves cursor to position x,y, where (0,0) is top left corner and (width-1, height-1) bottom right 184 func (d *Device) SetCursor(x, y uint8) { 185 d.cursor.x = x 186 d.cursor.y = y 187 d.SendCommand(DDRAM_SET | (x + (d.rowOffset[y] * y))) 188 } 189 190 // SetRowOffsets sets initial memory addresses coresponding to the display rows 191 // Each row on display has different starting address in DDRAM. Rows are not mapped in order. 192 // These addresses tend to differ between the types of the displays (16x2, 16x4, 20x4 etc ..), 193 // https://web.archive.org/web/20111122175541/http://web.alfredstate.edu/weimandn/lcd/lcd_addressing/lcd_addressing_index.html 194 func (d *Device) setRowOffsets() { 195 switch d.height { 196 case 1: 197 d.rowOffset = []uint8{} 198 case 2: 199 d.rowOffset = []uint8{0x0, 0x40, 0x0, 0x40} 200 case 4: 201 d.rowOffset = []uint8{0x0, 0x40, d.width, 0x40 + d.width} 202 default: 203 d.rowOffset = []uint8{0x0, 0x40, d.width, 0x40 + d.width} 204 205 } 206 } 207 208 // SendCommand sends commands to driver 209 func (d *Device) SendCommand(command byte) { 210 d.bus.SetCommandMode(true) 211 d.bus.Write([]byte{command}) 212 213 for d.busy(command == DISPLAY_CLEAR || command == CURSOR_HOME) { 214 } 215 } 216 217 // sendData sends byte data directly to display. 218 func (d *Device) sendData(data byte) { 219 d.bus.SetCommandMode(false) 220 d.bus.Write([]byte{data}) 221 222 for d.busy(false) { 223 } 224 } 225 226 // CreateCharacter crates characters using data and stores it under cgram Addr in CGRAM 227 func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) { 228 d.SendCommand(CGRAM_SET | cgramAddr) 229 for _, dd := range data { 230 d.sendData(dd) 231 } 232 } 233 234 // busy returns true when hd447890 is busy 235 // or after the timeout specified 236 func (d *Device) busy(longDelay bool) bool { 237 if d.bus.WriteOnly() { 238 // Can't read busy flag if write only, so sleep a bit then return 239 if longDelay { 240 // Note that we sleep like this so the default 241 // time.Sleep is time.Sleep(constant) as 242 // time.Sleep(variable) doesn't seem to work on AVR yet 243 if d.clearHomeTime != 0 { 244 time.Sleep(d.clearHomeTime) 245 } else { 246 time.Sleep(DefaultClearHomeTime) 247 } 248 } else { 249 if d.instrExecTime != 0 { 250 time.Sleep(d.instrExecTime) 251 } else { 252 time.Sleep(DefaultInstrExecTime) 253 } 254 } 255 return false 256 } 257 d.bus.SetCommandMode(true) 258 d.bus.Read(d.busyStatus) 259 return (d.busyStatus[0] & BUSY) > 0 260 } 261 262 // Busy returns true when hd447890 is busy 263 func (d *Device) Busy() bool { 264 return d.busy(false) 265 } 266 267 // Size returns the current size of the display. 268 func (d *Device) Size() (w, h int16) { 269 return int16(d.width), int16(d.height) 270 } 271 272 // ClearDisplay clears displayed content and buffer 273 func (d *Device) ClearDisplay() { 274 d.SendCommand(DISPLAY_CLEAR) 275 d.ClearBuffer() 276 } 277 278 // ClearBuffer clears internal buffer 279 func (d *Device) ClearBuffer() { 280 d.buffer = make([]uint8, d.width*d.height) 281 }