diff --git a/.circleci/config.yml b/.circleci/config.yml index d6a72e1..5157683 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,3 +33,5 @@ jobs: - run: tinygo build -size short -o test.elf -target=circuitplay-express ./examples/thermistor/main.go - run: tinygo build -size short -o test.elf -target=itsybitsy-m0 ./examples/vl53l1x/main.go - run: tinygo build -size short -o test.elf -target=circuitplay-express ./examples/ws2812/main.go + - run: tinygo build -size short -o test.elf -target=microbit ./examples/hd44780/text/main.go + - run: tinygo build -size short -o test.elf -target=microbit ./examples/hd44780/customchar/main.go diff --git a/hd44780/gpio.go b/hd44780/gpio.go new file mode 100644 index 0000000..fafa646 --- /dev/null +++ b/hd44780/gpio.go @@ -0,0 +1,148 @@ +package hd44780 + +import ( + "errors" + + "machine" +) + +type GPIO struct { + dataPins []machine.GPIO + e machine.GPIO + rw machine.GPIO + rs machine.GPIO + + write func(data byte) + read func() byte +} + +func newGPIO(dataPins []uint8, e, rs, rw uint8, mode byte) Device { + + pins := make([]machine.GPIO, len(dataPins)) + for i := 0; i < len(dataPins); i++ { + m := machine.GPIO{Pin: dataPins[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) (n int, err error) { + g.rw.Low() + for _, d := range data { + g.write(d) + n++ + } + return n, nil +} + +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(data []byte) (n int, err error) { + if len(data) == 0 { + return 0, errors.New("Length greater than 0 is required") + } + g.rs.Low() + g.rw.High() + g.reconfigureGPIOMode(machine.GPIO_INPUT) + for i := 0; i < len(data); i++ { + data[i] = g.read() + n++ + } + g.reconfigureGPIOMode(machine.GPIO_OUTPUT) + return n, nil +} + +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 byte) { + mask := byte(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() byte { + bits := byte(0) + for i := uint8(0); i < uint8(len(g.dataPins)); i++ { + if g.dataPins[i].Get() { + bits |= (1 << i) + } + } + return bits +} diff --git a/hd44780/hd44780.go b/hd44780/hd44780.go index 2f90e42..f2de062 100644 --- a/hd44780/hd44780.go +++ b/hd44780/hd44780.go @@ -3,176 +3,45 @@ package hd44780 import ( "errors" "image/color" + "io" "time" - - "machine" ) type Buser interface { - Write(data byte) - Read() byte + io.ReadWriter 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) { +// NewGPIO4Bit returns 4bit data length HD44780 driver. Datapins are LCD DB pins starting from DB4 to DB7 +func NewGPIO4Bit(dataPins []uint8, e, rs, rw uint8) (Device, error) { const fourBitMode = 4 - if len(data) != fourBitMode { + if len(dataPins) != 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 + return newGPIO(dataPins, e, rs, rw, DATA_LENGTH_4BIT), nil } -// NewGPIO8Bit returns 8bit data length HD44780 driver -func NewGPIO8Bit(data []uint8, e, rs, rw uint8) (Device, error) { +// NewGPIO8Bit returns 8bit data length HD44780 driver. Datapins are LCD DB pins starting from DB0 to DB7 +func NewGPIO8Bit(dataPins []uint8, e, rs, rw uint8) (Device, error) { const eightBitMode = 8 - if len(data) != eightBitMode { + if len(dataPins) != 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 + return newGPIO(dataPins, e, rs, rw, DATA_LENGTH_8BIT), nil } type Device struct { - bus Buser - width uint8 - height uint8 - buffer []uint8 - bufferSize uint8 + bus Buser + width uint8 + height uint8 + buffer []uint8 + bufferLength uint8 rowOffset []uint8 // Row offsets in DDRAM datalength uint8 - cursor cursor + cursor cursor + busyStatus []byte } type cursor struct { @@ -189,6 +58,7 @@ type Config struct { // Configure initializes device func (d *Device) Configure(cfg Config) error { + d.busyStatus = make([]byte, 1) d.width = uint8(cfg.Width) d.height = uint8(cfg.Height) if d.width == 0 || d.height == 0 { @@ -217,16 +87,17 @@ func (d *Device) Configure(cfg Config) error { time.Sleep(15 * time.Millisecond) d.bus.SetCommandMode(true) - d.bus.Write(DATA_LENGTH_8BIT) + d.bus.Write([]byte{DATA_LENGTH_8BIT}) time.Sleep(5 * time.Millisecond) for i := 0; i < 2; i++ { - d.bus.Write(DATA_LENGTH_8BIT) + d.bus.Write([]byte{DATA_LENGTH_8BIT}) time.Sleep(150 * time.Microsecond) + } if d.datalength == DATA_LENGTH_4BIT { - d.bus.Write(DATA_LENGTH_4BIT >> 4) + d.bus.Write([]byte{DATA_LENGTH_4BIT >> 4}) } // Busy flag is now accessible @@ -244,8 +115,8 @@ func (d *Device) Write(data []byte) (n int, err error) { if size > len(d.buffer) { size = len(d.buffer) } - d.bufferSize = uint8(size) - for i := uint8(0); i < d.bufferSize; i++ { + d.bufferLength = uint8(size) + for i := uint8(0); i < d.bufferLength; i++ { d.buffer[i] = data[i] } return size, nil @@ -253,31 +124,30 @@ func (d *Device) Write(data []byte) (n int, err error) { // 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 + + // Buffer may contain less characters than its capacity. + // We must be sure that we will not send unassigned characters + // That would result in sending zero values of buffer slice and + // potentialy displaying some character. + var totalDisplayedChars uint8 + + var bufferPos 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++ + for ; d.cursor.x < d.width && totalDisplayedChars < d.bufferLength; d.cursor.x++ { + d.sendData(d.buffer[bufferPos]) + bufferPos++ + totalDisplayedChars++ } - if bufferX > d.width { - bufferX = 0 - bufferY++ + if d.cursor.x >= d.width { + d.cursor.x = 0 } - if totalDisplayed >= d.bufferSize { - d.cursor.x = curPosX + if totalDisplayedChars >= d.bufferLength { break } - if curPosX >= d.width { - curPosX = 0 - } - d.cursor.x = curPosX + } return nil } @@ -308,18 +178,18 @@ func (d *Device) setRowOffsets() { } // SendCommand sends commands to driver -func (d *Device) SendCommand(command uint8) { +func (d *Device) SendCommand(command byte) { d.bus.SetCommandMode(true) - d.bus.Write(command) + d.bus.Write([]byte{command}) for d.Busy() { } } -// SendData sends byte data directly to display. -func (d *Device) SendData(data uint8) { +// sendData sends byte data directly to display. +func (d *Device) sendData(data byte) { d.bus.SetCommandMode(false) - d.bus.Write(data) + d.bus.Write([]byte{data}) for d.Busy() { } @@ -329,14 +199,14 @@ func (d *Device) SendData(data uint8) { func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) { d.SendCommand(CGRAM_SET | cgramAddr) for _, dd := range data { - d.SendData(dd) + d.sendData(dd) } } // Busy returns true when hd447890 is busy func (d *Device) Busy() bool { - status := d.bus.Read() - return (status & BUSY) > 0 + d.bus.Read(d.busyStatus) + return (d.busyStatus[0] & BUSY) > 0 } // SetPixel is not supported on devices which uses HD44780 driver diff --git a/hd44780/registers.go b/hd44780/registers.go index d1cd685..edcbaaf 100644 --- a/hd44780/registers.go +++ b/hd44780/registers.go @@ -1,26 +1,38 @@ package hd44780 const ( - DISPLAY_CLEAR = 0x1 - CURSOR_HOME = 0x2 + 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 + CURSOR_DECREASE = ENTRY_MODE | 0x0 + CURSOR_INCREASE = ENTRY_MODE | 0x2 + DISPLAY_SHIFT = ENTRY_MODE | 0x1 + DISPLAY_NO_SHIFT = ENTRY_MODE | 0x0 + + DISPLAY_ON_OFF = 0x8 + DISPLAY_ON = DISPLAY_ON_OFF | 0x4 + DISPLAY_OFF = DISPLAY_ON_OFF | 0x0 + CURSOR_ON = DISPLAY_ON_OFF | 0x2 + CURSOR_OFF = DISPLAY_ON_OFF | 0x0 + CURSOR_BLINK_ON = DISPLAY_ON_OFF | 0x1 + CURSOR_BLINK_OFF = DISPLAY_ON_OFF | 0x0 + + CURSOR_DISPLAY_SHIFT = 0x10 + CURSOR_SHIFT_RIGHT = CURSOR_DISPLAY_SHIFT | 0x4 + CURSOR_SHIFT_LEFT = CURSOR_DISPLAY_SHIFT | 0x0 + DISPLAY_SHIFT_RIGHT = CURSOR_DISPLAY_SHIFT | 0xC + DISPLAY_SHIFT_LEFT = CURSOR_DISPLAY_SHIFT | 0x8 + + FUNCTION_MODE = 0x20 + DATA_LENGTH_8BIT = FUNCTION_MODE | 0x10 + DATA_LENGTH_4BIT = FUNCTION_MODE | 0x0 + TWO_LINE = FUNCTION_MODE | 0x8 + ONE_LINE = FUNCTION_MODE | 0x0 + FONT_5X10 = FUNCTION_MODE | 0x4 + FONT_5X8 = FUNCTION_MODE | 0x0 + + BUSY = 0x80 + CGRAM_SET = 0x40 + DDRAM_SET = 0x80 )