embd/controller/hd44780/hd44780_test.go

256 lines
6.9 KiB
Go

package hd44780
import (
"fmt"
"reflect"
"testing"
"time"
"github.com/kidoman/embd"
)
const testAddr byte = 0x20
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 (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 {
be := &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 <-be.en.values == embd.Low &&
<-be.en.values == embd.High &&
<-be.en.values == embd.Low {
rs = <-be.rs.values
b |= byte(<-be.d4.values) << 4
b |= byte(<-be.d5.values) << 5
b |= byte(<-be.d6.values) << 6
b |= byte(<-be.d7.values) << 7
}
// wait for EN low,high,low then read low nibble
if <-be.en.values == embd.Low &&
<-be.en.values == embd.High &&
<-be.en.values == embd.Low {
b |= byte(<-be.d4.values)
b |= byte(<-be.d5.values) << 1
b |= byte(<-be.d6.values) << 2
b |= byte(<-be.d7.values) << 3
be.writes = append(be.writes, instruction{rs, b})
}
}
}()
return be
}
func (be *mockGPIOConnection) pins() []*mockDigitalPin {
return []*mockDigitalPin{be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.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) {
be := newMockGPIOConnection()
NewGPIO(be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight, Negative)
for idx, pin := range be.pins() {
if pin.direction != embd.Out {
t.Errorf("Pin %d not set to direction Out", idx)
}
}
}
func TestInitialize4Bit_lcdInit(t *testing.T) {
be := newMockGPIOConnection()
NewGPIO(be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight, Negative)
instructions := []instruction{
instruction{embd.Low, lcdInit},
instruction{embd.Low, lcdInit4bit},
instruction{embd.Low, byte(lcdSetEntryMode)},
instruction{embd.Low, byte(lcdSetDisplayMode)},
instruction{embd.Low, byte(lcdSetFunctionMode)},
}
if !reflect.DeepEqual(instructions, be.writes) {
t.Errorf(
"\nExpected\t%s\nActual\t\t%+v",
printInstructionsAsBinary(instructions),
printInstructionsAsBinary(be.writes))
}
}
func TestGPIOConnectionClose(t *testing.T) {
be := newMockGPIOConnection()
bus, _ := NewGPIO(be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight, Negative)
bus.Close()
for idx, pin := range be.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")
}
}