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.
This commit is contained in:
Nick Craig-Wood
2021-02-17 21:30:45 +00:00
committed by Daniel Esteban
parent c64d7920dc
commit ef34c13cc1
2 changed files with 71 additions and 10 deletions
+11 -1
View File
@@ -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 // Write writes len(data) bytes from data to display driver
func (g *GPIO) Write(data []byte) (n int, err error) { func (g *GPIO) Write(data []byte) (n int, err error) {
g.rw.Low() if !g.WriteOnly() {
g.rw.Low()
}
for _, d := range data { for _, d := range data {
g.write(d) g.write(d)
n++ n++
@@ -89,6 +96,9 @@ func (g *GPIO) Read(data []byte) (n int, err error) {
if len(data) == 0 { if len(data) == 0 {
return 0, errors.New("length greater than 0 is required") 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.rw.High()
g.reconfigureGPIOMode(machine.PinInput) g.reconfigureGPIOMode(machine.PinInput)
for i := 0; i < len(data); i++ { for i := 0; i < len(data); i++ {
+60 -9
View File
@@ -11,9 +11,23 @@ import (
"time" "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 { type Buser interface {
io.ReadWriter io.ReadWriter
SetCommandMode(set bool) SetCommandMode(set bool)
WriteOnly() bool
} }
type Device struct { type Device struct {
@@ -28,6 +42,9 @@ type Device struct {
cursor cursor cursor cursor
busyStatus []byte busyStatus []byte
clearHomeTime time.Duration // time clear/home instructions might take
instrExecTime time.Duration // time all other instructions might take
} }
type cursor struct { type cursor struct {
@@ -35,14 +52,18 @@ type cursor struct {
} }
type Config struct { type Config struct {
Width int16 Width int16
Height int16 Height int16
CursorBlink bool CursorBlink bool
CursorOnOff bool CursorOnOff bool
Font uint8 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 // 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) { func NewGPIO4Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
const fourBitMode = 4 const fourBitMode = 4
if len(dataPins) != fourBitMode { 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 // 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) { func NewGPIO8Bit(dataPins []machine.Pin, e, rs, rw machine.Pin) (Device, error) {
const eightBitMode = 8 const eightBitMode = 8
if len(dataPins) != eightBitMode { if len(dataPins) != eightBitMode {
@@ -68,6 +91,8 @@ func (d *Device) Configure(cfg Config) error {
if d.width == 0 || d.height == 0 { if d.width == 0 || d.height == 0 {
return errors.New("width and height must be set") return errors.New("width and height must be set")
} }
d.clearHomeTime = cfg.ClearHomeTime
d.instrExecTime = cfg.InstrExecTime
memoryMap := uint8(ONE_LINE) memoryMap := uint8(ONE_LINE)
if d.height > 1 { if d.height > 1 {
memoryMap = TWO_LINE memoryMap = TWO_LINE
@@ -186,7 +211,7 @@ func (d *Device) SendCommand(command byte) {
d.bus.SetCommandMode(true) d.bus.SetCommandMode(true)
d.bus.Write([]byte{command}) 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.SetCommandMode(false)
d.bus.Write([]byte{data}) 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 // busy returns true when hd447890 is busy
func (d *Device) Busy() bool { // 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.SetCommandMode(true)
d.bus.Read(d.busyStatus) d.bus.Read(d.busyStatus)
return (d.busyStatus[0] & BUSY) > 0 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. // Size returns the current size of the display.
func (d *Device) Size() (w, h int16) { func (d *Device) Size() (w, h int16) {
return int16(d.width), int16(d.height) return int16(d.width), int16(d.height)