mirror of
https://github.com/kidoman/embd
synced 2025-07-03 11:57:38 +02:00
controller: add a lib for the HD44780 character display controller
the hd44780 package supports HD44780 character display controllers connected by either a 4-bit GPIO bus or an I2C bus it also includes a high-level wrapper for easily printing messages
This commit is contained in:
parent
03b5f0ceb6
commit
dac729e4fd
5 changed files with 1167 additions and 0 deletions
255
controller/hd44780/hd44780_test.go
Normal file
255
controller/hd44780/hd44780_test.go
Normal file
|
@ -0,0 +1,255 @@
|
|||
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")
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue