diff --git a/Makefile b/Makefile index fc7b22d26..f5edbe76e 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,8 @@ smoke-test: @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hd44780/text/main.go @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/hd44780i2c/main.go + @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hub75/main.go @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/basic diff --git a/examples/hd44780i2c/main.go b/examples/hd44780i2c/main.go new file mode 100644 index 000000000..1675275d0 --- /dev/null +++ b/examples/hd44780i2c/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "machine" + "strconv" + "time" + + "tinygo.org/x/drivers/hd44780i2c" +) + +func main() { + + // Note: most HD44780 LCD modules requires 5V power, however some variations + // use 3.3V (and may be damaged by 5V). + + machine.I2C0.Configure(machine.I2CConfig{ + Frequency: machine.TWI_FREQ_400KHZ, + }) + + lcd := hd44780i2c.New(machine.I2C0, 0x27) // some modules have address 0x3F + + lcd.Configure(hd44780i2c.Config{ + Width: 16, // required + Height: 2, // required + CursorOn: true, + CursorBlink: true, + }) + + lcd.Print([]byte(" TinyGo\n LCD Test ")) + + // CGRAM address 0x0-0x7 can be used to store 8 custom characters + lcd.CreateCharacter(0x0, []byte{0x00, 0x11, 0x0E, 0x1F, 0x15, 0x1F, 0x1F, 0x1F}) + lcd.Print([]byte{0x0}) + + // You can use https://maxpromer.github.io/LCD-Character-Creator/ + // to crete your own characters. + + time.Sleep(time.Millisecond * 7000) + + for i := 0; i < 5; i++ { + lcd.BacklightOn(false) + time.Sleep(time.Millisecond * 250) + lcd.BacklightOn(true) + time.Sleep(time.Millisecond * 250) + } + + lcd.CursorOn(false) + lcd.CursorBlink(false) + + i := 0 + for { + + lcd.ClearDisplay() + lcd.SetCursor(2, 1) + lcd.Print([]byte(strconv.FormatInt(int64(i), 10))) + i++ + time.Sleep(time.Millisecond * 100) + + } +} diff --git a/hd44780i2c/hd44780i2c.go b/hd44780i2c/hd44780i2c.go new file mode 100644 index 000000000..97be411cb --- /dev/null +++ b/hd44780i2c/hd44780i2c.go @@ -0,0 +1,242 @@ +// 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" + "machine" + "time" +) + +// Device wraps an I2C connection to a HD44780 I2C LCD with related data. +type Device struct { + bus machine.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 machine.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. +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). +// +// if y (row) is set larger than actual rows, it would be set to 0. +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 { + d.cursor.x++ + if d.cursor.x >= d.width { + d.newLine() + } + d.sendData(uint8(rune(chr))) + } + } +} + +// 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) +} diff --git a/hd44780i2c/registers.go b/hd44780i2c/registers.go new file mode 100644 index 000000000..dae58ead8 --- /dev/null +++ b/hd44780i2c/registers.go @@ -0,0 +1,44 @@ +package hd44780i2c + +const ( + + // commands + DISPLAY_CLEAR = 0x01 + CURSOR_HOME = 0x02 + ENTRY_MODE = 0x04 + DISPLAY_ON_OFF = 0x08 + CURSOR_DISPLAY_SHIFT = 0x10 + FUNCTION_MODE = 0x20 + CGRAM_SET = 0x40 + DDRAM_SET = 0x80 + + // flags for display entry mode + // CURSOR_DECREASE = 0x00 + CURSOR_INCREASE = 0x02 + // DISPLAY_SHIFT = 0x01 + DISPLAY_NO_SHIFT = 0x00 + + // flags for display on/off control + DISPLAY_ON = 0x04 + DISPLAY_OFF = 0x00 + CURSOR_ON = 0x02 + CURSOR_OFF = 0x00 + CURSOR_BLINK_ON = 0x01 + CURSOR_BLINK_OFF = 0x00 + + // flags for function set + // DATA_LENGTH_8BIT = 0x10 + DATA_LENGTH_4BIT = 0x00 + TWO_LINE = 0x08 + ONE_LINE = 0x00 + FONT_5X10 = 0x04 + FONT_5X8 = 0x00 + + // flags for backlight control + BACKLIGHT_ON = 0x08 + BACKLIGHT_OFF = 0x00 + + En = 0x04 // Enable bit + // Rw = 0x02 // Read/Write bit + Rs = 0x01 // Register select bit +)