GPIO implements io readwriter interface, simplified display, smoke tests

This commit is contained in:
k-brk
2019-05-03 16:13:00 +02:00
committed by Ron Evans
parent 715829cdbd
commit 4560c61c61
4 changed files with 231 additions and 199 deletions
+2
View File
@@ -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=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=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=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
+148
View File
@@ -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
}
+48 -178
View File
@@ -3,176 +3,45 @@ package hd44780
import ( import (
"errors" "errors"
"image/color" "image/color"
"io"
"time" "time"
"machine"
) )
type Buser interface { type Buser interface {
Write(data byte) io.ReadWriter
Read() byte
SetCommandMode(set bool) SetCommandMode(set bool)
} }
type GPIO struct { // NewGPIO4Bit returns 4bit data length HD44780 driver. Datapins are LCD DB pins starting from DB4 to DB7
dataPins []machine.GPIO func NewGPIO4Bit(dataPins []uint8, e, rs, rw uint8) (Device, error) {
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) {
const fourBitMode = 4 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 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 // NewGPIO8Bit returns 8bit data length HD44780 driver. Datapins are LCD DB pins starting from DB0 to DB7
func NewGPIO8Bit(data []uint8, e, rs, rw uint8) (Device, error) { func NewGPIO8Bit(dataPins []uint8, e, rs, rw uint8) (Device, error) {
const eightBitMode = 8 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 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 return newGPIO(dataPins, 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
} }
type Device struct { type Device struct {
bus Buser bus Buser
width uint8 width uint8
height uint8 height uint8
buffer []uint8 buffer []uint8
bufferSize uint8 bufferLength uint8
rowOffset []uint8 // Row offsets in DDRAM rowOffset []uint8 // Row offsets in DDRAM
datalength uint8 datalength uint8
cursor cursor cursor cursor
busyStatus []byte
} }
type cursor struct { type cursor struct {
@@ -189,6 +58,7 @@ type Config struct {
// Configure initializes device // Configure initializes device
func (d *Device) Configure(cfg Config) error { func (d *Device) Configure(cfg Config) error {
d.busyStatus = make([]byte, 1)
d.width = uint8(cfg.Width) d.width = uint8(cfg.Width)
d.height = uint8(cfg.Height) d.height = uint8(cfg.Height)
if d.width == 0 || d.height == 0 { if d.width == 0 || d.height == 0 {
@@ -217,16 +87,17 @@ func (d *Device) Configure(cfg Config) error {
time.Sleep(15 * time.Millisecond) time.Sleep(15 * time.Millisecond)
d.bus.SetCommandMode(true) d.bus.SetCommandMode(true)
d.bus.Write(DATA_LENGTH_8BIT) d.bus.Write([]byte{DATA_LENGTH_8BIT})
time.Sleep(5 * time.Millisecond) time.Sleep(5 * time.Millisecond)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
d.bus.Write(DATA_LENGTH_8BIT) d.bus.Write([]byte{DATA_LENGTH_8BIT})
time.Sleep(150 * time.Microsecond) time.Sleep(150 * time.Microsecond)
} }
if d.datalength == DATA_LENGTH_4BIT { 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 // Busy flag is now accessible
@@ -244,8 +115,8 @@ func (d *Device) Write(data []byte) (n int, err error) {
if size > len(d.buffer) { if size > len(d.buffer) {
size = len(d.buffer) size = len(d.buffer)
} }
d.bufferSize = uint8(size) d.bufferLength = uint8(size)
for i := uint8(0); i < d.bufferSize; i++ { for i := uint8(0); i < d.bufferLength; i++ {
d.buffer[i] = data[i] d.buffer[i] = data[i]
} }
return size, nil 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 // Display sends the whole buffer to the screen at cursor position
func (d *Device) Display() error { func (d *Device) Display() error {
var totalDisplayed uint8
var bufferX uint8 // Buffer may contain less characters than its capacity.
var bufferY uint8 // We must be sure that we will not send unassigned characters
var curPosX uint8 // 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++ { for ; d.cursor.y < d.height; d.cursor.y++ {
d.SetCursor(d.cursor.x, d.cursor.y) d.SetCursor(d.cursor.x, d.cursor.y)
for curPosX = d.cursor.x; curPosX < d.width && totalDisplayed < d.bufferSize; curPosX++ { for ; d.cursor.x < d.width && totalDisplayedChars < d.bufferLength; d.cursor.x++ {
d.SendData(d.buffer[bufferY*(d.width)+bufferX]) d.sendData(d.buffer[bufferPos])
bufferX++ bufferPos++
totalDisplayed++ totalDisplayedChars++
} }
if bufferX > d.width { if d.cursor.x >= d.width {
bufferX = 0 d.cursor.x = 0
bufferY++
} }
if totalDisplayed >= d.bufferSize { if totalDisplayedChars >= d.bufferLength {
d.cursor.x = curPosX
break break
} }
if curPosX >= d.width {
curPosX = 0
}
d.cursor.x = curPosX
} }
return nil return nil
} }
@@ -308,18 +178,18 @@ func (d *Device) setRowOffsets() {
} }
// SendCommand sends commands to driver // SendCommand sends commands to driver
func (d *Device) SendCommand(command uint8) { func (d *Device) SendCommand(command byte) {
d.bus.SetCommandMode(true) d.bus.SetCommandMode(true)
d.bus.Write(command) d.bus.Write([]byte{command})
for d.Busy() { for d.Busy() {
} }
} }
// SendData sends byte data directly to display. // sendData sends byte data directly to display.
func (d *Device) SendData(data uint8) { func (d *Device) sendData(data byte) {
d.bus.SetCommandMode(false) d.bus.SetCommandMode(false)
d.bus.Write(data) d.bus.Write([]byte{data})
for d.Busy() { for d.Busy() {
} }
@@ -329,14 +199,14 @@ func (d *Device) SendData(data uint8) {
func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) { func (d *Device) CreateCharacter(cgramAddr uint8, data []byte) {
d.SendCommand(CGRAM_SET | cgramAddr) d.SendCommand(CGRAM_SET | cgramAddr)
for _, dd := range data { for _, dd := range data {
d.SendData(dd) d.sendData(dd)
} }
} }
// Busy returns true when hd447890 is busy // Busy returns true when hd447890 is busy
func (d *Device) Busy() bool { func (d *Device) Busy() bool {
status := d.bus.Read() d.bus.Read(d.busyStatus)
return (status & BUSY) > 0 return (d.busyStatus[0] & BUSY) > 0
} }
// SetPixel is not supported on devices which uses HD44780 driver // SetPixel is not supported on devices which uses HD44780 driver
+33 -21
View File
@@ -1,26 +1,38 @@
package hd44780 package hd44780
const ( const (
DISPLAY_CLEAR = 0x1 DISPLAY_CLEAR = 0x1
CURSOR_HOME = 0x2 CURSOR_HOME = 0x2
ENTRY_MODE = 0x4 ENTRY_MODE = 0x4
CURSOR_DECREASE = 0x4 CURSOR_DECREASE = ENTRY_MODE | 0x0
CURSOR_INCREASE = 0x6 CURSOR_INCREASE = ENTRY_MODE | 0x2
DISPLAY_SHIFT = 0x5 DISPLAY_SHIFT = ENTRY_MODE | 0x1
DISPLAY_NO_SHIFT = 0x4 DISPLAY_NO_SHIFT = ENTRY_MODE | 0x0
DISPLAY_ON = 0xC
DISPLAY_OFF = 0x8 DISPLAY_ON_OFF = 0x8
CURSOR_ON = 0xA DISPLAY_ON = DISPLAY_ON_OFF | 0x4
CURSOR_OFF = 0x8 DISPLAY_OFF = DISPLAY_ON_OFF | 0x0
CURSOR_BLINK_ON = 0x9 CURSOR_ON = DISPLAY_ON_OFF | 0x2
CURSOR_BLINK_OFF = 0x8 CURSOR_OFF = DISPLAY_ON_OFF | 0x0
DATA_LENGTH_8BIT = 0x30 CURSOR_BLINK_ON = DISPLAY_ON_OFF | 0x1
DATA_LENGTH_4BIT = 0x20 CURSOR_BLINK_OFF = DISPLAY_ON_OFF | 0x0
TWO_LINE = 0x28
ONE_LINE = 0x20 CURSOR_DISPLAY_SHIFT = 0x10
FONT_5X10 = 0x24 CURSOR_SHIFT_RIGHT = CURSOR_DISPLAY_SHIFT | 0x4
FONT_5X8 = 0x20 CURSOR_SHIFT_LEFT = CURSOR_DISPLAY_SHIFT | 0x0
BUSY = 0x80 DISPLAY_SHIFT_RIGHT = CURSOR_DISPLAY_SHIFT | 0xC
CGRAM_SET = 0x40 DISPLAY_SHIFT_LEFT = CURSOR_DISPLAY_SHIFT | 0x8
DDRAM_SET = 0x80
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
) )