mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
282 lines
7.5 KiB
Go
282 lines
7.5 KiB
Go
// Package hd44780 provides a driver for the HD44780 LCD controller.
|
|
//
|
|
// Datasheet: https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
|
|
package hd44780 // import "tinygo.org/x/drivers/hd44780"
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"machine"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
// These are the default execution times for the Clear and
|
|
// Home commands and everything else.
|
|
//
|
|
// These are used if RW is passed as machine.NoPin and ignored
|
|
// otherwise.
|
|
//
|
|
// They are set conservatively here and can be tweaked in the
|
|
// Config structure.
|
|
DefaultClearHomeTime = 80 * time.Millisecond
|
|
DefaultInstrExecTime = 80 * time.Microsecond
|
|
)
|
|
|
|
type Buser interface {
|
|
io.ReadWriter
|
|
SetCommandMode(set bool)
|
|
WriteOnly() bool
|
|
}
|
|
|
|
type Device struct {
|
|
bus Buser
|
|
width uint8
|
|
height uint8
|
|
buffer []uint8
|
|
bufferLength uint8
|
|
|
|
rowOffset []uint8 // Row offsets in DDRAM
|
|
datalength uint8
|
|
|
|
cursor cursor
|
|
busyStatus []byte
|
|
|
|
clearHomeTime time.Duration // time clear/home instructions might take
|
|
instrExecTime time.Duration // time all other instructions might take
|
|
}
|
|
|
|
type cursor struct {
|
|
x, y uint8
|
|
}
|
|
|
|
type Config struct {
|
|
Width int16
|
|
Height int16
|
|
CursorBlink bool
|
|
CursorOnOff bool
|
|
Font uint8
|
|
ClearHomeTime time.Duration // time clear/home instructions might take - use 0 for the default
|
|
InstrExecTime time.Duration // time all other instructions might take - use 0 for the default
|
|
}
|
|
|
|
// NewGPIO4Bit returns 4bit data length HD44780 driver. Datapins are LCD DB pins starting from DB4 to DB7
|
|
//
|
|
// If your device has RW set permanently to ground then pass in rw as machine.NoPin
|
|
func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
|
|
const fourBitMode = 4
|
|
if len(dataPins) != fourBitMode {
|
|
return Device{}, errors.New("4 pins are required in data slice (D4-D7) when HD44780 is used in 4 bit mode")
|
|
}
|
|
return newGPIO(dataPins, e, rs, rw, DATA_LENGTH_4BIT), nil
|
|
}
|
|
|
|
// NewGPIO8Bit returns 8bit data length HD44780 driver. Datapins are LCD DB pins starting from DB0 to DB7
|
|
//
|
|
// If your device has RW set permanently to ground then pass in rw as machine.NoPin
|
|
func NewGPIO8Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
|
|
const eightBitMode = 8
|
|
if len(dataPins) != eightBitMode {
|
|
return Device{}, errors.New("8 pins are required in data slice (D0-D7) when HD44780 is used in 8 bit mode")
|
|
}
|
|
return newGPIO(dataPins, e, rs, rw, DATA_LENGTH_8BIT), nil
|
|
}
|
|
|
|
// 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 {
|
|
return errors.New("width and height must be set")
|
|
}
|
|
d.clearHomeTime = cfg.ClearHomeTime
|
|
d.instrExecTime = cfg.InstrExecTime
|
|
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([]byte{DATA_LENGTH_8BIT})
|
|
time.Sleep(5 * time.Millisecond)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
d.bus.Write([]byte{DATA_LENGTH_8BIT})
|
|
time.Sleep(150 * time.Microsecond)
|
|
|
|
}
|
|
|
|
if d.datalength == DATA_LENGTH_4BIT {
|
|
d.bus.Write([]byte{DATA_LENGTH_4BIT})
|
|
}
|
|
|
|
// 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.bufferLength = uint8(size)
|
|
for i := uint8(0); i < d.bufferLength; 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 {
|
|
|
|
// 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 ; d.cursor.x < d.width && totalDisplayedChars < d.bufferLength; d.cursor.x++ {
|
|
d.sendData(d.buffer[bufferPos])
|
|
bufferPos++
|
|
totalDisplayedChars++
|
|
}
|
|
if d.cursor.x >= d.width {
|
|
d.cursor.x = 0
|
|
}
|
|
if totalDisplayedChars >= d.bufferLength {
|
|
break
|
|
}
|
|
|
|
}
|
|
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 byte) {
|
|
d.bus.SetCommandMode(true)
|
|
d.bus.Write([]byte{command})
|
|
|
|
for d.isBusy(command == DISPLAY_CLEAR || command == CURSOR_HOME) {
|
|
}
|
|
}
|
|
|
|
// sendData sends byte data directly to display.
|
|
func (d *Device) sendData(data byte) {
|
|
d.bus.SetCommandMode(false)
|
|
d.bus.Write([]byte{data})
|
|
|
|
for d.isBusy(false) {
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
// isBusy returns true when hd447890 is isBusy
|
|
// or after the timeout specified
|
|
func (d *Device) isBusy(longDelay bool) bool {
|
|
if d.bus.WriteOnly() {
|
|
// Can't read busy flag if write only, so sleep a bit then return
|
|
if longDelay {
|
|
// Note that we sleep like this so the default
|
|
// time.Sleep is time.Sleep(constant) as
|
|
// time.Sleep(variable) doesn't seem to work on AVR yet
|
|
if d.clearHomeTime != 0 {
|
|
time.Sleep(d.clearHomeTime)
|
|
} else {
|
|
time.Sleep(DefaultClearHomeTime)
|
|
}
|
|
} else {
|
|
if d.instrExecTime != 0 {
|
|
time.Sleep(d.instrExecTime)
|
|
} else {
|
|
time.Sleep(DefaultInstrExecTime)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
d.bus.SetCommandMode(true)
|
|
d.bus.Read(d.busyStatus)
|
|
return (d.busyStatus[0] & BUSY) > 0
|
|
}
|
|
|
|
// Busy returns true when hd447890 is busy
|
|
func (d *Device) Busy() bool {
|
|
return d.isBusy(false)
|
|
}
|
|
|
|
// 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)
|
|
}
|