From 715829cdbd8c48ebd22cde4249be0a791e6aa777 Mon Sep 17 00:00:00 2001 From: k-brk <25877802+k-brk@users.noreply.github.com> Date: Wed, 1 May 2019 00:12:31 +0200 Subject: [PATCH] Support for HD44780 in 4 and 8 bit mode --- examples/hd44780/customchar/main.go | 20 ++ examples/hd44780/text/main.go | 20 ++ hd44780/hd44780.go | 361 ++++++++++++++++++++++++++++ hd44780/registers.go | 26 ++ 4 files changed, 427 insertions(+) create mode 100644 examples/hd44780/customchar/main.go create mode 100644 examples/hd44780/text/main.go create mode 100644 hd44780/hd44780.go create mode 100644 hd44780/registers.go diff --git a/examples/hd44780/customchar/main.go b/examples/hd44780/customchar/main.go new file mode 100644 index 0000000..0366232 --- /dev/null +++ b/examples/hd44780/customchar/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "machine" + + "github.com/tinygo-org/drivers/hd44780" +) + +func main() { + + lcd, _ := hd44780.NewGPIO4Bit([]uint8{machine.P0, machine.P1, machine.P2, machine.P3}, machine.P4, machine.P5, machine.P6) + + lcd.Configure(hd44780.Config{Width: 16, Height: 2, CursorOnOff: true, CursorBlink: true}) + lcd.CreateCharacter(0x0, []byte{0x04, 0x0E, 0x0E, 0x0E, 0x0E, 0x1F, 0x04, 0x0}) + lcd.Write([]byte{0x0}) + lcd.Display() + for { + + } +} diff --git a/examples/hd44780/text/main.go b/examples/hd44780/text/main.go new file mode 100644 index 0000000..497b83a --- /dev/null +++ b/examples/hd44780/text/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "machine" + + "github.com/tinygo-org/drivers/hd44780" +) + +func main() { + + lcd, _ := hd44780.NewGPIO4Bit([]uint8{machine.P0, machine.P1, machine.P2, machine.P3}, machine.P4, machine.P5, machine.P6) + + lcd.Configure(hd44780.Config{Width: 16, Height: 2, CursorOnOff: true, CursorBlink: true}) + lcd.Write([]byte("This is a long line")) + lcd.Display() + + for { + + } +} diff --git a/hd44780/hd44780.go b/hd44780/hd44780.go new file mode 100644 index 0000000..2f90e42 --- /dev/null +++ b/hd44780/hd44780.go @@ -0,0 +1,361 @@ +package hd44780 + +import ( + "errors" + "image/color" + "time" + + "machine" +) + +type Buser interface { + Write(data byte) + Read() byte + SetCommandMode(set bool) +} + +type GPIO struct { + dataPins []machine.GPIO + e machine.GPIO + rw machine.GPIO + rs machine.GPIO + + write func(data byte) + read func() uint8 +} + +// NewGPIO4Bit returns 4bit data length HD44780 driver +func NewGPIO4Bit(data []uint8, e, rs, rw uint8) (Device, error) { + const fourBitMode = 4 + if len(data) != fourBitMode { + return Device{}, errors.New("4 pins are required in data slice (D7-D4) when HD44780 is used in 4 bit mode") + } + return newGPIO(data, e, rs, rw, DATA_LENGTH_4BIT), nil +} + +// NewGPIO8Bit returns 8bit data length HD44780 driver +func NewGPIO8Bit(data []uint8, e, rs, rw uint8) (Device, error) { + const eightBitMode = 8 + if len(data) != eightBitMode { + return Device{}, errors.New("8 pins are required in data slice (D7-D0) when HD44780 is used in 8 bit mode") + } + return newGPIO(data, e, rs, rw, DATA_LENGTH_8BIT), nil +} + +func newGPIO(data []uint8, e, rs, rw uint8, mode byte) Device { + pins := make([]machine.GPIO, len(data)) + for i := 0; i < len(data); i++ { + m := machine.GPIO{Pin: data[i]} + m.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT}) + pins[i] = m + } + enable := machine.GPIO{e} + enable.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT}) + registerSelect := machine.GPIO{rs} + registerSelect.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT}) + readWrite := machine.GPIO{rw} + readWrite.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT}) + readWrite.Low() + + gpio := GPIO{ + dataPins: pins, + e: enable, + rs: registerSelect, + rw: readWrite, + } + + if mode == DATA_LENGTH_4BIT { + gpio.write = gpio.write4BitMode + gpio.read = gpio.read4BitMode + } else { + gpio.write = gpio.write8BitMode + gpio.read = gpio.read8BitMode + } + + return Device{ + bus: &gpio, + datalength: mode, + } +} + +// SetCommandMode sets command/instruction mode +func (g *GPIO) SetCommandMode(set bool) { + if set { + g.rs.Low() + } else { + g.rs.High() + } +} + +func (g *GPIO) Write(data byte) { + g.rw.Low() + g.write(data) +} + +func (g *GPIO) write8BitMode(data byte) { + g.e.High() + g.setPins(data) + g.e.Low() +} + +func (g *GPIO) write4BitMode(data byte) { + g.e.High() + g.setPins(data >> 4) + g.e.Low() + + g.e.High() + g.setPins(data) + g.e.Low() +} + +func (g *GPIO) Read() byte { + g.rs.Low() + g.rw.High() + g.reconfigureGPIOMode(machine.GPIO_INPUT) + data := g.read() + g.reconfigureGPIOMode(machine.GPIO_OUTPUT) + return data +} + +func (g *GPIO) read4BitMode() byte { + g.e.High() + data := (g.pins() << 4 & 0xF0) + g.e.Low() + g.e.High() + data |= (g.pins() & 0x0F) + g.e.Low() + return data +} +func (g *GPIO) read8BitMode() byte { + g.e.High() + data := g.pins() + g.e.Low() + return data +} +func (g *GPIO) reconfigureGPIOMode(mode machine.GPIOMode) { + for i := 0; i < len(g.dataPins); i++ { + g.dataPins[i].Configure(machine.GPIOConfig{Mode: mode}) + } +} + +// setPins sets high or low state on all data pins depending on data +func (g *GPIO) setPins(data uint8) { + mask := uint8(1) + for i := 0; i < len(g.dataPins); i++ { + if (data & mask) != 0 { + g.dataPins[i].High() + } else { + g.dataPins[i].Low() + } + mask = mask << 1 + } +} + +// pins returns current state of data pins. MSB is D7 +func (g *GPIO) pins() uint8 { + bits := uint8(0) + for i := uint8(0); i < uint8(len(g.dataPins)); i++ { + if g.dataPins[i].Get() { + bits |= (1 << i) + } + } + return bits +} + +type Device struct { + bus Buser + width uint8 + height uint8 + buffer []uint8 + bufferSize uint8 + + rowOffset []uint8 // Row offsets in DDRAM + datalength uint8 + + cursor cursor +} + +type cursor struct { + x, y uint8 +} + +type Config struct { + Width int16 + Height int16 + CursorBlink bool + CursorOnOff bool + Font uint8 +} + +// Configure initializes device +func (d *Device) Configure(cfg Config) error { + d.width = uint8(cfg.Width) + d.height = uint8(cfg.Height) + if d.width == 0 || d.height == 0 { + return errors.New("Width and height must be set") + } + memoryMap := uint8(ONE_LINE) + if d.height > 1 { + memoryMap = TWO_LINE + } + d.setRowOffsets() + d.ClearBuffer() + + cursor := CURSOR_OFF + if cfg.CursorOnOff { + cursor = CURSOR_ON + } + cursorBlink := CURSOR_BLINK_OFF + if cfg.CursorBlink { + cursorBlink = CURSOR_BLINK_ON + } + if !(cfg.Font == FONT_5X8 || cfg.Font == FONT_5X10) { + cfg.Font = FONT_5X8 + } + + //Wait 15ms after Vcc rises to 4.5V + time.Sleep(15 * time.Millisecond) + + d.bus.SetCommandMode(true) + d.bus.Write(DATA_LENGTH_8BIT) + time.Sleep(5 * time.Millisecond) + + for i := 0; i < 2; i++ { + d.bus.Write(DATA_LENGTH_8BIT) + time.Sleep(150 * time.Microsecond) + } + + if d.datalength == DATA_LENGTH_4BIT { + d.bus.Write(DATA_LENGTH_4BIT >> 4) + } + + // Busy flag is now accessible + d.SendCommand(memoryMap | cfg.Font | d.datalength) + d.SendCommand(DISPLAY_OFF) + d.SendCommand(DISPLAY_CLEAR) + d.SendCommand(ENTRY_MODE | CURSOR_INCREASE | DISPLAY_NO_SHIFT) + d.SendCommand(DISPLAY_ON | uint8(cursor) | uint8(cursorBlink)) + return nil +} + +// Write writes data to internal buffer +func (d *Device) Write(data []byte) (n int, err error) { + size := len(data) + if size > len(d.buffer) { + size = len(d.buffer) + } + d.bufferSize = uint8(size) + for i := uint8(0); i < d.bufferSize; i++ { + d.buffer[i] = data[i] + } + return size, nil +} + +// Display sends the whole buffer to the screen at cursor position +func (d *Device) Display() error { + var totalDisplayed uint8 + var bufferX uint8 + var bufferY uint8 + var curPosX uint8 + + for ; d.cursor.y < d.height; d.cursor.y++ { + d.SetCursor(d.cursor.x, d.cursor.y) + + for curPosX = d.cursor.x; curPosX < d.width && totalDisplayed < d.bufferSize; curPosX++ { + d.SendData(d.buffer[bufferY*(d.width)+bufferX]) + bufferX++ + totalDisplayed++ + } + if bufferX > d.width { + bufferX = 0 + bufferY++ + } + if totalDisplayed >= d.bufferSize { + d.cursor.x = curPosX + break + } + if curPosX >= d.width { + curPosX = 0 + } + d.cursor.x = curPosX + } + return nil +} + +// SetCursor moves cursor to position x,y, where (0,0) is top left corner and (width-1, height-1) bottom right +func (d *Device) SetCursor(x, y uint8) { + d.cursor.x = x + d.cursor.y = y + d.SendCommand(DDRAM_SET | (x + (d.rowOffset[y] * y))) +} + +// SetRowOffsets sets initial memory addresses coresponding to the display rows +// Each row on display has different starting address in DDRAM. Rows are not mapped in order. +// These addresses tend to differ between the types of the displays (16x2, 16x4, 20x4 etc ..), +// https://web.archive.org/web/20111122175541/http://web.alfredstate.edu/weimandn/lcd/lcd_addressing/lcd_addressing_index.html +func (d *Device) setRowOffsets() { + switch d.height { + case 1: + d.rowOffset = []uint8{} + case 2: + d.rowOffset = []uint8{0x0, 0x40, 0x0, 0x40} + case 4: + d.rowOffset = []uint8{0x0, 0x40, d.width, 0x40 + d.width} + default: + d.rowOffset = []uint8{0x0, 0x40, d.width, 0x40 + d.width} + + } +} + +// SendCommand sends commands to driver +func (d *Device) SendCommand(command uint8) { + d.bus.SetCommandMode(true) + d.bus.Write(command) + + for d.Busy() { + } +} + +// SendData sends byte data directly to display. +func (d *Device) SendData(data uint8) { + d.bus.SetCommandMode(false) + d.bus.Write(data) + + for d.Busy() { + } +} + +// CreateCharacter crates characters using data and stores it under cgram Addr in CGRAM +func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) { + d.SendCommand(CGRAM_SET | cgramAddr) + for _, dd := range data { + d.SendData(dd) + } +} + +// Busy returns true when hd447890 is busy +func (d *Device) Busy() bool { + status := d.bus.Read() + return (status & BUSY) > 0 +} + +// SetPixel is not supported on devices which uses HD44780 driver +func (d *Device) SetPixel(x, y int16, c color.RGBA) { + panic("HD44780 does not support setting individual pixels") +} + +// Size returns the current size of the display. +func (d *Device) Size() (w, h int16) { + return int16(d.width), int16(d.height) +} + +// ClearDisplay clears displayed content and buffer +func (d *Device) ClearDisplay() { + d.SendCommand(DISPLAY_CLEAR) + d.ClearBuffer() +} + +// ClearBuffer clears internal buffer +func (d *Device) ClearBuffer() { + d.buffer = make([]uint8, d.width*d.height) +} diff --git a/hd44780/registers.go b/hd44780/registers.go new file mode 100644 index 0000000..d1cd685 --- /dev/null +++ b/hd44780/registers.go @@ -0,0 +1,26 @@ +package hd44780 + +const ( + DISPLAY_CLEAR = 0x1 + CURSOR_HOME = 0x2 + ENTRY_MODE = 0x4 + CURSOR_DECREASE = 0x4 + CURSOR_INCREASE = 0x6 + DISPLAY_SHIFT = 0x5 + DISPLAY_NO_SHIFT = 0x4 + DISPLAY_ON = 0xC + DISPLAY_OFF = 0x8 + CURSOR_ON = 0xA + CURSOR_OFF = 0x8 + CURSOR_BLINK_ON = 0x9 + CURSOR_BLINK_OFF = 0x8 + DATA_LENGTH_8BIT = 0x30 + DATA_LENGTH_4BIT = 0x20 + TWO_LINE = 0x28 + ONE_LINE = 0x20 + FONT_5X10 = 0x24 + FONT_5X8 = 0x20 + BUSY = 0x80 + CGRAM_SET = 0x40 + DDRAM_SET = 0x80 +)