// Package hd44780i2c implements a driver for the Hitachi HD44780 LCD display module // with an I2C adapter. // // Datasheet: https://www.sparkfun.com/datasheets/LCD/HD44780.pdf package hd44780i2c import ( "errors" "time" "tinygo.org/x/drivers" ) // Device wraps an I2C connection to a HD44780 I2C LCD with related data. type Device struct { bus drivers.I2C addr uint8 width uint8 height uint8 cursor cursor backlight uint8 displayfunction uint8 displaycontrol uint8 displaymode uint8 } type cursor struct { x, y uint8 } // Config for HD44780 I2C LCD. type Config struct { Width uint8 Height uint8 Font uint8 CursorOn bool CursorBlink bool } // New creates a new HD44780 I2C LCD connection. The I2C bus must already be // configured. // // This function only creates the Device object, it does not touch the device. func New(bus drivers.I2C, addr uint8) Device { if addr == 0 { addr = 0x27 } return Device{ bus: bus, addr: addr, } } // Configure sets up the display. Display itself and backlight is default on. func (d *Device) Configure(cfg Config) error { if cfg.Width == 0 || cfg.Height == 0 { return errors.New("width and height must be set") } d.width = uint8(cfg.Width) d.height = uint8(cfg.Height) delayms(50) d.backlight = BACKLIGHT_ON d.expanderWrite(0) delayms(1000) d.write4bits(0x03 << 4) delayus(4500) d.write4bits(0x03 << 4) delayus(4500) d.write4bits(0x03 << 4) delayus(150) d.write4bits(0x02 << 4) d.displayfunction = DATA_LENGTH_4BIT | ONE_LINE | FONT_5X8 if d.height > 1 { d.displayfunction |= TWO_LINE } if cfg.Font != 0 && d.height == 1 { d.displayfunction |= FONT_5X10 } d.sendCommand(FUNCTION_MODE | d.displayfunction) d.displaycontrol = DISPLAY_ON | CURSOR_OFF | CURSOR_BLINK_OFF if cfg.CursorOn { d.displaycontrol |= CURSOR_ON } if cfg.CursorBlink { d.displaycontrol |= CURSOR_BLINK_ON } d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol) d.ClearDisplay() d.displaymode = CURSOR_INCREASE | DISPLAY_NO_SHIFT d.sendCommand(ENTRY_MODE | d.displaymode) d.Home() return nil } // ClearDisplay clears all texts on the display and sets the cursor back to position (0, 0). func (d *Device) ClearDisplay() { d.sendCommand(DISPLAY_CLEAR) d.cursor.x = 0 d.cursor.y = 0 delayus(2000) } // Home sets the cursor back to position (0, 0). func (d *Device) Home() { d.sendCommand(CURSOR_HOME) d.cursor.x = 0 d.cursor.y = 0 delayus(2000) } // SetCursor sets the cursor to a specific position (x, y). // // For example, on 16x2 LCDs the range of x (column) is 0~15 and y (row) is 0~1. // if y is larger than actual rows, it would be set to 0 (restart from first row). func (d *Device) SetCursor(x, y uint8) { rowOffset := []uint8{0x0, 0x40, 0x14, 0x54} if y > (d.height - 1) { y = 0 } d.cursor.x = x d.cursor.y = y d.sendCommand(DDRAM_SET | (x + (rowOffset[y]))) } // Print prints text on the display (started from current cursor position). // // It would automatically break to new line when the text is too long. // You can also use \n as line breakers. func (d *Device) Print(data []byte) { for _, chr := range data { if chr == '\n' { d.newLine() } else { if d.cursor.x >= d.width { d.newLine() } d.sendData(uint8(rune(chr))) d.cursor.x++ } } } // CreateCharacter crates custom characters (using data parameter) // and stores it under CGRAM address (using cgramAddr, 0x0-0x7). func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) { cgramAddr &= 0x7 d.sendCommand(CGRAM_SET | cgramAddr<<3) for _, dd := range data { d.sendData(dd) } d.SetCursor(d.cursor.x, d.cursor.y) } // DisplayOn turns on/off the display. func (d *Device) DisplayOn(option bool) { if option { d.displaycontrol |= DISPLAY_ON } else { d.displaycontrol &= ^uint8(DISPLAY_ON) } d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol) } // CursorOn display/hides the cursor. func (d *Device) CursorOn(option bool) { if option { d.displaycontrol |= CURSOR_ON } else { d.displaycontrol &= ^uint8(CURSOR_ON) } d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol) } // CursorBlink turns on/off the blinking cursor mode. func (d *Device) CursorBlink(option bool) { if option { d.displaycontrol |= CURSOR_BLINK_ON } else { d.displaycontrol &= ^uint8(CURSOR_BLINK_ON) } d.sendCommand(DISPLAY_ON_OFF | d.displaycontrol) } // BacklightOn turns on/off the display backlight. func (d *Device) BacklightOn(option bool) { if option { d.backlight = BACKLIGHT_ON } else { d.backlight = BACKLIGHT_OFF } d.expanderWrite(0) } func (d *Device) newLine() { d.cursor.x = 0 d.cursor.y++ d.SetCursor(d.cursor.x, d.cursor.y) } func delayms(t uint16) { time.Sleep(time.Millisecond * time.Duration(t)) } func delayus(t uint16) { time.Sleep(time.Microsecond * time.Duration(t)) } func (d *Device) expanderWrite(value uint8) { d.bus.Tx(uint16(d.addr), []uint8{value | d.backlight}, nil) } func (d *Device) pulseEnable(value uint8) { d.expanderWrite(value | En) delayus(1) d.expanderWrite(value & ^uint8(En)) delayus(50) } func (d *Device) write4bits(value uint8) { d.expanderWrite(value) d.pulseEnable(value) } func (d *Device) write(value uint8, mode uint8) { d.write4bits(uint8(value&0xf0) | mode) d.write4bits(uint8((value<<4)&0xf0) | mode) } func (d *Device) sendCommand(value uint8) { d.write(value, 0) } func (d *Device) sendData(value uint8) { d.write(value, Rs) }