embd/controller/hd44780/hd44780_test.go

320 lines
8.9 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) ReadBytes(addr byte, num int) ([]byte, error) { return []byte{0x00}, nil }
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")
}
}