1
0
mirror of https://github.com/kidoman/embd synced 2024-06-01 08:38:05 +02:00
embd/controller/hd44780/hd44780_test.go
Matthew Dale 915b3b76a7 interface: add an abstraction layer for character displays
the characterdisplay package is an abstraction layer for controlling
character displays

also includes refactors to the hd44780 package to fit the
characterdisplay Controller interface
2015-02-25 00:12:09 -08:00

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")
}
}