mirror of
https://github.com/kidoman/embd
synced 2024-11-01 07:48:43 +01:00
915b3b76a7
the characterdisplay package is an abstraction layer for controlling character displays also includes refactors to the hd44780 package to fit the characterdisplay Controller interface
319 lines
8.8 KiB
Go
319 lines
8.8 KiB
Go
package hd44780
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/kidoman/embd"
|
|
)
|
|
|
|
const (
|
|
testAddr byte = 0x20
|
|
cols = 20
|
|
rows = 4
|
|
)
|
|
|
|
var testRowAddr RowAddress = RowAddress20Col
|
|
|
|
type mockDigitalPin struct {
|
|
direction embd.Direction
|
|
values chan int
|
|
closed bool
|
|
}
|
|
|
|
func newMockDigitalPin() *mockDigitalPin {
|
|
return &mockDigitalPin{
|
|
values: make(chan int, 256),
|
|
closed: false,
|
|
}
|
|
}
|
|
|
|
func (pin *mockDigitalPin) Watch(edge embd.Edge, handler func(embd.DigitalPin)) error { return nil }
|
|
func (pin *mockDigitalPin) StopWatching() error { return nil }
|
|
func (pin *mockDigitalPin) N() int { return 0 }
|
|
func (pin *mockDigitalPin) Read() (int, error) { return 0, nil }
|
|
func (pin *mockDigitalPin) TimePulse(state int) (time.Duration, error) { return time.Duration(0), nil }
|
|
func (pin *mockDigitalPin) ActiveLow(b bool) error { return nil }
|
|
func (pin *mockDigitalPin) PullUp() error { return nil }
|
|
func (pin *mockDigitalPin) PullDown() error { return nil }
|
|
|
|
func (pin *mockDigitalPin) Write(val int) error {
|
|
pin.values <- val
|
|
return nil
|
|
}
|
|
|
|
func (pin *mockDigitalPin) SetDirection(dir embd.Direction) error {
|
|
pin.direction = dir
|
|
return nil
|
|
}
|
|
|
|
func (pin *mockDigitalPin) Close() error {
|
|
pin.closed = true
|
|
return nil
|
|
}
|
|
|
|
type mockGPIOConnection struct {
|
|
rs, en *mockDigitalPin
|
|
d4, d5, d6, d7 *mockDigitalPin
|
|
backlight *mockDigitalPin
|
|
writes []instruction
|
|
}
|
|
|
|
type instruction struct {
|
|
rs int
|
|
data byte
|
|
}
|
|
|
|
func (conn *mockGPIOConnection) Write(rs bool, data byte) error { return nil }
|
|
func (conn *mockGPIOConnection) BacklightOff() error { return nil }
|
|
func (conn *mockGPIOConnection) BacklightOn() error { return nil }
|
|
func (conn *mockGPIOConnection) Close() error { return nil }
|
|
|
|
func (ins *instruction) printAsBinary() string {
|
|
return fmt.Sprintf("RS:%d|Byte:%s", ins.rs, printByteAsBinary(ins.data))
|
|
}
|
|
|
|
func printInstructionsAsBinary(ins []instruction) string {
|
|
var binary []string
|
|
for _, i := range ins {
|
|
binary = append(binary, i.printAsBinary())
|
|
}
|
|
return fmt.Sprintf("%+v", binary)
|
|
}
|
|
|
|
func newMockGPIOConnection() *mockGPIOConnection {
|
|
conn := &mockGPIOConnection{
|
|
rs: newMockDigitalPin(),
|
|
en: newMockDigitalPin(),
|
|
d4: newMockDigitalPin(),
|
|
d5: newMockDigitalPin(),
|
|
d6: newMockDigitalPin(),
|
|
d7: newMockDigitalPin(),
|
|
backlight: newMockDigitalPin(),
|
|
}
|
|
go func() {
|
|
for {
|
|
var b byte = 0x00
|
|
var rs int = 0
|
|
// wait for EN low,high,low then read high nibble
|
|
if <-conn.en.values == embd.Low &&
|
|
<-conn.en.values == embd.High &&
|
|
<-conn.en.values == embd.Low {
|
|
rs = <-conn.rs.values
|
|
b |= byte(<-conn.d4.values) << 4
|
|
b |= byte(<-conn.d5.values) << 5
|
|
b |= byte(<-conn.d6.values) << 6
|
|
b |= byte(<-conn.d7.values) << 7
|
|
}
|
|
// wait for EN low,high,low then read low nibble
|
|
if <-conn.en.values == embd.Low &&
|
|
<-conn.en.values == embd.High &&
|
|
<-conn.en.values == embd.Low {
|
|
b |= byte(<-conn.d4.values)
|
|
b |= byte(<-conn.d5.values) << 1
|
|
b |= byte(<-conn.d6.values) << 2
|
|
b |= byte(<-conn.d7.values) << 3
|
|
conn.writes = append(conn.writes, instruction{rs, b})
|
|
}
|
|
}
|
|
}()
|
|
return conn
|
|
}
|
|
|
|
func (conn *mockGPIOConnection) pins() []*mockDigitalPin {
|
|
return []*mockDigitalPin{conn.rs, conn.en, conn.d4, conn.d5, conn.d6, conn.d7, conn.backlight}
|
|
}
|
|
|
|
type mockI2CBus struct {
|
|
writes []byte
|
|
closed bool
|
|
}
|
|
|
|
func (bus *mockI2CBus) ReadByte(addr byte) (byte, error) { return 0x00, nil }
|
|
func (bus *mockI2CBus) WriteBytes(addr byte, value []byte) error { return nil }
|
|
func (bus *mockI2CBus) ReadFromReg(addr, reg byte, value []byte) error { return nil }
|
|
func (bus *mockI2CBus) ReadByteFromReg(addr, reg byte) (byte, error) { return 0x00, nil }
|
|
func (bus *mockI2CBus) ReadWordFromReg(addr, reg byte) (uint16, error) { return 0, nil }
|
|
func (bus *mockI2CBus) WriteToReg(addr, reg byte, value []byte) error { return nil }
|
|
func (bus *mockI2CBus) WriteByteToReg(addr, reg, value byte) error { return nil }
|
|
func (bus *mockI2CBus) WriteWordToReg(addr, reg byte, value uint16) error { return nil }
|
|
|
|
func (bus *mockI2CBus) WriteByte(addr, value byte) error {
|
|
bus.writes = append(bus.writes, value)
|
|
return nil
|
|
}
|
|
|
|
func (bus *mockI2CBus) Close() error {
|
|
bus.closed = true
|
|
return nil
|
|
}
|
|
|
|
func newMockI2CBus() *mockI2CBus {
|
|
return &mockI2CBus{closed: false}
|
|
}
|
|
|
|
func printByteAsBinary(b byte) string {
|
|
return fmt.Sprintf("%08b(%#x)", b, b)
|
|
}
|
|
|
|
func printBytesAsBinary(bytes []byte) string {
|
|
var binary []string
|
|
for _, w := range bytes {
|
|
binary = append(binary, printByteAsBinary(w))
|
|
}
|
|
return fmt.Sprintf("%+v", binary)
|
|
}
|
|
|
|
func TestInitialize4Bit_directionOut(t *testing.T) {
|
|
mock := newMockGPIOConnection()
|
|
NewGPIO(mock.rs, mock.en, mock.d4, mock.d5, mock.d6, mock.d7, mock.backlight, Negative, testRowAddr)
|
|
for idx, pin := range mock.pins() {
|
|
if pin.direction != embd.Out {
|
|
t.Errorf("Pin %d not set to direction Out", idx)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInitialize4Bit_lcdInit(t *testing.T) {
|
|
mock := newMockGPIOConnection()
|
|
gpio, _ := NewGPIO(mock.rs, mock.en, mock.d4, mock.d5, mock.d6, mock.d7, mock.backlight, Negative, testRowAddr)
|
|
instructions := []instruction{
|
|
instruction{embd.Low, lcdInit},
|
|
instruction{embd.Low, lcdInit4bit},
|
|
instruction{embd.Low, byte(gpio.eMode | lcdSetEntryMode)},
|
|
instruction{embd.Low, byte(gpio.dMode | lcdSetDisplayMode)},
|
|
instruction{embd.Low, byte(gpio.fMode | lcdSetFunctionMode)},
|
|
}
|
|
|
|
if !reflect.DeepEqual(instructions, mock.writes) {
|
|
t.Errorf(
|
|
"\nExpected\t%s\nActual\t\t%+v",
|
|
printInstructionsAsBinary(instructions),
|
|
printInstructionsAsBinary(mock.writes))
|
|
}
|
|
}
|
|
|
|
func TestGPIOConnectionClose(t *testing.T) {
|
|
mock := newMockGPIOConnection()
|
|
bus, _ := NewGPIO(mock.rs, mock.en, mock.d4, mock.d5, mock.d6, mock.d7, mock.backlight, Negative, testRowAddr)
|
|
bus.Close()
|
|
for idx, pin := range mock.pins() {
|
|
if !pin.closed {
|
|
t.Errorf("Pin %d was not closed", idx)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestI2CConnectionPinMap(t *testing.T) {
|
|
cases := []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"instruction": lcdDisplayMove | lcdMoveRight,
|
|
"pinMap": MJKDZPinMap,
|
|
"expected": []byte{
|
|
0x0, // 00000000 high nibble
|
|
0x10, // 00010000
|
|
0x0, // 00000000
|
|
0xc, // 00001100 low nibble
|
|
0x1c, // 00011100
|
|
0xc, // 00001100
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"instruction": lcdDisplayMove | lcdMoveRight,
|
|
"pinMap": PCF8574PinMap,
|
|
"expected": []byte{
|
|
0x8, // 00001000 high nibble
|
|
0xc, // 00001100
|
|
0x8, // 00001000
|
|
0xc8, // 11001000 low nibble
|
|
0xcc, // 11001100
|
|
0xc8, // 11001000
|
|
},
|
|
},
|
|
}
|
|
|
|
for idx, c := range cases {
|
|
instruction := c["instruction"].(byte)
|
|
pinMap := c["pinMap"].(I2CPinMap)
|
|
expected := c["expected"].([]byte)
|
|
|
|
i2c := newMockI2CBus()
|
|
conn := NewI2CConnection(i2c, testAddr, pinMap)
|
|
rawInstruction := instruction
|
|
// instructions (RS = false) with backlight on
|
|
conn.Backlight = true
|
|
conn.Write(false, rawInstruction)
|
|
|
|
if !reflect.DeepEqual(expected, i2c.writes) {
|
|
t.Errorf(
|
|
"Case %d:\nExpected\t%s\nActual\t\t%s",
|
|
idx+1,
|
|
printBytesAsBinary(expected),
|
|
printBytesAsBinary(i2c.writes))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestI2CConnectionClose(t *testing.T) {
|
|
i2c := newMockI2CBus()
|
|
conn := NewI2CConnection(i2c, testAddr, MJKDZPinMap)
|
|
conn.Close()
|
|
if !i2c.closed {
|
|
t.Error("I2C bus was not closed")
|
|
}
|
|
}
|
|
|
|
func TestNewGPIO_initPins(t *testing.T) {
|
|
var pins []*mockDigitalPin
|
|
for i := 0; i < 7; i++ {
|
|
pins = append(pins, newMockDigitalPin())
|
|
}
|
|
NewGPIO(
|
|
pins[0],
|
|
pins[1],
|
|
pins[2],
|
|
pins[3],
|
|
pins[4],
|
|
pins[5],
|
|
pins[6],
|
|
Negative,
|
|
testRowAddr,
|
|
)
|
|
for idx, pin := range pins {
|
|
if pin.direction != embd.Out {
|
|
t.Errorf("Pin %d not set to direction Out(%d), set to %d", idx, embd.Out, pin.direction)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDefaultModes(t *testing.T) {
|
|
display, _ := New(newMockGPIOConnection(), testRowAddr)
|
|
|
|
if display.EightBitModeEnabled() {
|
|
t.Error("Expected display to be initialized in 4-bit mode")
|
|
}
|
|
if display.TwoLineEnabled() {
|
|
t.Error("Expected display to be initialized in one-line mode")
|
|
}
|
|
if display.Dots5x10Enabled() {
|
|
t.Error("Expected display to be initialized in 5x8-dots mode")
|
|
}
|
|
if !display.EntryIncrementEnabled() {
|
|
t.Error("Expected display to be initialized in entry increment mode")
|
|
}
|
|
if display.EntryShiftEnabled() {
|
|
t.Error("Expected display to be initialized in entry shift off mode")
|
|
}
|
|
if !display.DisplayEnabled() {
|
|
t.Error("Expected display to be initialized in display on mode")
|
|
}
|
|
if display.CursorEnabled() {
|
|
t.Error("Expected display to be initialized in cursor off mode")
|
|
}
|
|
if display.BlinkEnabled() {
|
|
t.Error("Expected display to be initialized in blink off mode")
|
|
}
|
|
}
|