From a53a39922ba4342558f4dc50cfe03431e0a4cc83 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 17 Feb 2021 21:30:45 +0000 Subject: [PATCH] hd44780: add a mode to work with boards where the RW pin is grounded On some HD44780 boards (eg the Keyestudio LCD1602 expansion shield), the RW pin isn't brought out and is permanently grounded. This means that the board can't be read from, and in particular the busy status can't be read. This patch adapts the package to work with boards like these. To signal this to the package, set the RW pin to machine.NoPin. The package will then disallow all reading and use adjustable timing based writing. The timing can be adjusted the configuration. --- hd44780/gpio.go | 12 +++++++- hd44780/hd44780.go | 69 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/hd44780/gpio.go b/hd44780/gpio.go index e3f3a67..0549ab7 100644 --- a/hd44780/gpio.go +++ b/hd44780/gpio.go @@ -57,9 +57,16 @@ func (g *GPIO) SetCommandMode(set bool) { } } +// WriteOnly is true if you passed rw in as machine.NoPin +func (g *GPIO) WriteOnly() bool { + return g.rw == machine.NoPin +} + // Write writes len(data) bytes from data to display driver func (g *GPIO) Write(data []byte) (n int, err error) { - g.rw.Low() + if !g.WriteOnly() { + g.rw.Low() + } for _, d := range data { g.write(d) n++ @@ -89,6 +96,9 @@ func (g *GPIO) Read(data []byte) (n int, err error) { if len(data) == 0 { return 0, errors.New("length greater than 0 is required") } + if g.WriteOnly() { + return 0, errors.New("Read not supported if RW not wired") + } g.rw.High() g.reconfigureGPIOMode(machine.PinInput) for i := 0; i < len(data); i++ { diff --git a/hd44780/hd44780.go b/hd44780/hd44780.go index 0440a9d..c06b4bc 100644 --- a/hd44780/hd44780.go +++ b/hd44780/hd44780.go @@ -11,9 +11,23 @@ import ( "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 { @@ -28,6 +42,9 @@ type Device struct { 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 { @@ -35,14 +52,18 @@ type cursor struct { } type Config struct { - Width int16 - Height int16 - CursorBlink bool - CursorOnOff bool - Font uint8 + 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 { @@ -52,6 +73,8 @@ func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) } // 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 { @@ -68,6 +91,8 @@ func (d *Device) Configure(cfg Config) error { 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 @@ -186,7 +211,7 @@ func (d *Device) SendCommand(command byte) { d.bus.SetCommandMode(true) d.bus.Write([]byte{command}) - for d.Busy() { + for d.busy(command == DISPLAY_CLEAR || command == CURSOR_HOME) { } } @@ -195,7 +220,7 @@ func (d *Device) sendData(data byte) { d.bus.SetCommandMode(false) d.bus.Write([]byte{data}) - for d.Busy() { + for d.busy(false) { } } @@ -207,13 +232,39 @@ func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) { } } -// Busy returns true when hd447890 is busy -func (d *Device) Busy() bool { +// busy returns true when hd447890 is busy +// or after the timeout specified +func (d *Device) busy(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.busy(false) +} + // Size returns the current size of the display. func (d *Device) Size() (w, h int16) { return int16(d.width), int16(d.height)