mirror of
https://github.com/kidoman/embd
synced 2024-12-22 21:00:05 +01:00
320 lines
8.9 KiB
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")
|
|
}
|
|
}
|