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 deadprogram
parent 41d6a4c3fa
commit a53a39922b
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
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++ {
+60 -9
View File
@@ -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)