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
This commit is contained in:
Matthew Dale 2015-02-21 22:50:56 -08:00
parent dac729e4fd
commit 915b3b76a7
8 changed files with 489 additions and 364 deletions

View File

@ -1,212 +0,0 @@
package hd44780
import (
"github.com/golang/glog"
"github.com/kidoman/embd"
)
// DefaultModes are the default initialization modes for a CharacterDisplay.
var DefaultModes []ModeSetter = []ModeSetter{
FourBitMode,
OneLine,
Dots5x8,
EntryIncrement,
EntryShiftOff,
DisplayOn,
CursorOff,
BlinkOff,
}
// CharacterDisplay represents an abstract character display and provides a
// convenience layer on top of the basic HD44780 library.
type CharacterDisplay struct {
*HD44780
Cols int
Rows int
p *position
}
type position struct {
col int
row int
}
// NewGPIOCharacterDisplay creates a new CharacterDisplay connected by a 4-bit GPIO bus.
func NewGPIOCharacterDisplay(
rs, en, d4, d5, d6, d7, backlight interface{},
blPolarity Polarity,
cols, rows int,
modes ...ModeSetter,
) (*CharacterDisplay, error) {
pinKeys := []interface{}{rs, en, d4, d5, d6, d7, backlight}
pins := [7]embd.DigitalPin{}
for idx, key := range pinKeys {
if key == nil {
continue
}
var digitalPin embd.DigitalPin
if pin, ok := key.(embd.DigitalPin); ok {
digitalPin = pin
} else {
var err error
digitalPin, err = embd.NewDigitalPin(key)
if err != nil {
glog.V(1).Infof("hd44780: error creating digital pin %+v: %s", key, err)
return nil, err
}
}
pins[idx] = digitalPin
}
hd, err := NewGPIO(
pins[0],
pins[1],
pins[2],
pins[3],
pins[4],
pins[5],
pins[6],
blPolarity,
append(DefaultModes, modes...)...,
)
if err != nil {
return nil, err
}
return NewCharacterDisplay(hd, cols, rows)
}
// NewI2CCharacterDisplay creates a new CharacterDisplay connected by an I²C bus.
func NewI2CCharacterDisplay(
i2c embd.I2CBus,
addr byte,
pinMap I2CPinMap,
cols, rows int,
modes ...ModeSetter,
) (*CharacterDisplay, error) {
hd, err := NewI2C(i2c, addr, pinMap, append(DefaultModes, modes...)...)
if err != nil {
return nil, err
}
return NewCharacterDisplay(hd, cols, rows)
}
// NewCharacterDisplay creates a new character display abstraction for an
// HD44780-compatible controller.
func NewCharacterDisplay(hd *HD44780, cols, rows int) (*CharacterDisplay, error) {
display := &CharacterDisplay{
HD44780: hd,
Cols: cols,
Rows: rows,
p: &position{0, 0},
}
err := display.BacklightOn()
if err != nil {
return nil, err
}
return display, nil
}
// Home moves the cursor and all characters to the home position.
func (disp *CharacterDisplay) Home() error {
disp.currentPosition(0, 0)
return disp.HD44780.Home()
}
// Clear clears the display, preserving the mode settings and setting the correct home.
func (disp *CharacterDisplay) Clear() error {
disp.currentPosition(0, 0)
err := disp.HD44780.Clear()
if err != nil {
return err
}
err = disp.SetMode()
if err != nil {
return err
}
if !disp.isLeftToRight() {
return disp.SetCursor(disp.Cols-1, 0)
}
return nil
}
// Message prints the given string on the display.
func (disp *CharacterDisplay) Message(message string) error {
bytes := []byte(message)
for _, b := range bytes {
if b == byte('\n') {
err := disp.Newline()
if err != nil {
return err
}
continue
}
err := disp.WriteChar(b)
if err != nil {
return err
}
if disp.isLeftToRight() {
disp.p.col++
} else {
disp.p.col--
}
if disp.p.col >= disp.Cols || disp.p.col < 0 {
err := disp.Newline()
if err != nil {
return err
}
}
}
return nil
}
// Newline moves the input cursor to the beginning of the next line.
func (disp *CharacterDisplay) Newline() error {
var col int
if disp.isLeftToRight() {
col = 0
} else {
col = disp.Cols - 1
}
return disp.SetCursor(col, disp.p.row+1)
}
func (disp *CharacterDisplay) isLeftToRight() bool {
// EntryIncrement and EntryShiftOn is right-to-left
// EntryDecrement and EntryShiftOn is left-to-right
// EntryIncrement and EntryShiftOff is left-to-right
// EntryDecrement and EntryShiftOff is right-to-left
return disp.EntryIncrementEnabled() != disp.EntryShiftEnabled()
}
// SetCursor sets the input cursor to the given position.
func (disp *CharacterDisplay) SetCursor(col, row int) error {
if row >= disp.Rows {
row = disp.Rows - 1
}
disp.currentPosition(col, row)
return disp.HD44780.SetCursor(byte(col) + disp.lcdRowOffset(row))
}
func (disp *CharacterDisplay) lcdRowOffset(row int) byte {
// Offset for up to 4 rows
if row > 3 {
row = 3
}
switch disp.Cols {
case 16:
// 16-char line mappings
return []byte{0x00, 0x40, 0x10, 0x50}[row]
default:
// default to the 20-char line mappings
return []byte{0x00, 0x40, 0x14, 0x54}[row]
}
}
func (disp *CharacterDisplay) currentPosition(col, row int) {
disp.p.col = col
disp.p.row = row
}
// Close closes the underlying HD44780 controller.
func (disp *CharacterDisplay) Close() error {
return disp.HD44780.Close()
}

View File

@ -1,85 +0,0 @@
package hd44780
import (
"testing"
"github.com/kidoman/embd"
)
const (
rows = 20
cols = 4
)
func TestNewGPIOCharacterDisplay_initPins(t *testing.T) {
var pins []*mockDigitalPin
for i := 0; i < 7; i++ {
pins = append(pins, newMockDigitalPin())
}
NewGPIOCharacterDisplay(
pins[0],
pins[1],
pins[2],
pins[3],
pins[4],
pins[5],
pins[6],
Negative,
cols,
rows,
)
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) {
displayGPIO, _ := NewGPIOCharacterDisplay(
newMockDigitalPin(),
newMockDigitalPin(),
newMockDigitalPin(),
newMockDigitalPin(),
newMockDigitalPin(),
newMockDigitalPin(),
newMockDigitalPin(),
Negative,
cols,
rows,
)
displayI2C, _ := NewI2CCharacterDisplay(
newMockI2CBus(),
testAddr,
MJKDZPinMap,
cols,
rows,
)
for idx, display := range []*CharacterDisplay{displayGPIO, displayI2C} {
if display.EightBitModeEnabled() {
t.Errorf("Display %d: Expected display to be initialized in 4-bit mode", idx)
}
if display.TwoLineEnabled() {
t.Errorf("Display %d: Expected display to be initialized in one-line mode", idx)
}
if display.Dots5x10Enabled() {
t.Errorf("Display %d: Expected display to be initialized in 5x8-dots mode", idx)
}
if !display.EntryIncrementEnabled() {
t.Errorf("Display %d: Expected display to be initialized in entry increment mode", idx)
}
if display.EntryShiftEnabled() {
t.Errorf("Display %d: Expected display to be initialized in entry shift off mode", idx)
}
if !display.DisplayEnabled() {
t.Errorf("Display %d: Expected display to be initialized in display on mode", idx)
}
if display.CursorEnabled() {
t.Errorf("Display %d: Expected display to be initialized in cursor off mode", idx)
}
if display.BlinkEnabled() {
t.Errorf("Display %d: Expected display to be initialized in blink off mode", idx)
}
}
}

View File

@ -3,12 +3,6 @@ Package hd44780 allows controlling an HD44780-compatible character LCD
controller. Currently the library is write-only and does not support
reading from the display controller.
Character Display
The CharacterDisplay type provides a convenience layer on top of the basic
HD44780 library. It includes functions for easier message printing and abstracts
some of the quirky behaviors of 4-row displays.
Resources
This library is based three other HD44780 libraries:
@ -25,14 +19,24 @@ import (
"github.com/kidoman/embd"
)
type Polarity bool
type entryMode byte
type displayMode byte
type functionMode byte
// RowAddress defines the DDRAM address of the first column of each row, up to 4 rows.
type RowAddress [4]byte
var (
RowAddress16Col RowAddress = [4]byte{0x00, 0x40, 0x10, 0x50} // row addresses for 16-column displays
RowAddress20Col RowAddress = [4]byte{0x00, 0x40, 0x14, 0x54} // row addresses for 20-column displays
)
// BacklightPolarity defines the polarity of the backlight switch, either positive or negative
type BacklightPolarity bool
const (
Negative Polarity = false
Positive Polarity = true
Negative BacklightPolarity = false
Positive BacklightPolarity = true
writeDelay = 37 * time.Microsecond
pulseDelay = 1 * time.Microsecond
@ -84,18 +88,38 @@ const (
// HD44780 represents an HD44780-compatible character LCD controller.
type HD44780 struct {
Connection
eMode entryMode
dMode displayMode
fMode functionMode
eMode entryMode
dMode displayMode
fMode functionMode
rowAddr RowAddress
}
// NewGPIO creates a new HD44780 connected by a 4-bit GPIO bus.
func NewGPIO(
rs, en, d4, d5, d6, d7, backlight embd.DigitalPin,
blPolarity Polarity,
rs, en, d4, d5, d6, d7, backlight interface{},
blPolarity BacklightPolarity,
rowAddr RowAddress,
modes ...ModeSetter,
) (*HD44780, error) {
pins := []embd.DigitalPin{rs, en, d4, d5, d6, d7, backlight}
pinKeys := []interface{}{rs, en, d4, d5, d6, d7, backlight}
pins := [7]embd.DigitalPin{}
for idx, key := range pinKeys {
if key == nil {
continue
}
var digitalPin embd.DigitalPin
if pin, ok := key.(embd.DigitalPin); ok {
digitalPin = pin
} else {
var err error
digitalPin, err = embd.NewDigitalPin(key)
if err != nil {
glog.V(1).Infof("hd44780: error creating digital pin %+v: %s", key, err)
return nil, err
}
}
pins[idx] = digitalPin
}
for _, pin := range pins {
if pin == nil {
continue
@ -107,29 +131,45 @@ func NewGPIO(
}
}
return New(
NewGPIOConnection(rs, en, d4, d5, d6, d7, backlight, blPolarity),
NewGPIOConnection(
pins[0],
pins[1],
pins[2],
pins[3],
pins[4],
pins[5],
pins[6],
blPolarity),
rowAddr,
modes...,
)
}
// NewI2C creates a new HD44780 connected by an I²C bus.
func NewI2C(i2c embd.I2CBus, addr byte, pinMap I2CPinMap, modes ...ModeSetter) (*HD44780, error) {
return New(NewI2CConnection(i2c, addr, pinMap), modes...)
func NewI2C(
i2c embd.I2CBus,
addr byte,
pinMap I2CPinMap,
rowAddr RowAddress,
modes ...ModeSetter,
) (*HD44780, error) {
return New(NewI2CConnection(i2c, addr, pinMap), rowAddr, modes...)
}
// New creates a new HD44780 connected by a Connection bus.
func New(bus Connection, modes ...ModeSetter) (*HD44780, error) {
func New(bus Connection, rowAddr RowAddress, modes ...ModeSetter) (*HD44780, error) {
controller := &HD44780{
Connection: bus,
eMode: 0x00,
dMode: 0x00,
fMode: 0x00,
rowAddr: rowAddr,
}
err := controller.lcdInit()
if err != nil {
return nil, err
}
err = controller.SetMode(modes...)
err = controller.SetMode(append(DefaultModes, modes...)...)
if err != nil {
return nil, err
}
@ -146,6 +186,18 @@ func (controller *HD44780) lcdInit() error {
return controller.WriteInstruction(lcdInit4bit)
}
// DefaultModes are the default initialization modes for an HD44780.
var DefaultModes []ModeSetter = []ModeSetter{
FourBitMode,
OneLine,
Dots5x8,
EntryIncrement,
EntryShiftOff,
DisplayOn,
CursorOff,
BlinkOff,
}
// ModeSetter defines a function used for setting modes on an HD44780.
// ModeSetters must be used with the SetMode function or in a constructor.
type ModeSetter func(*HD44780)
@ -312,12 +364,29 @@ func (hd *HD44780) Home() error {
// Clear clears the display and mode settings sets the cursor to the home position.
func (hd *HD44780) Clear() error {
err := hd.WriteInstruction(lcdClearDisplay)
if err != nil {
return err
}
time.Sleep(clearDelay)
return err
// have to set mode here because clear also clears some mode settings
return hd.SetMode()
}
// SetCursor sets the input cursor to the given bye.
func (hd *HD44780) SetCursor(value byte) error {
// SetCursor sets the input cursor to the given position.
func (hd *HD44780) SetCursor(col, row int) error {
return hd.SetDDRamAddr(byte(col) + hd.lcdRowOffset(row))
}
func (hd *HD44780) lcdRowOffset(row int) byte {
// Offset for up to 4 rows
if row > 3 {
row = 3
}
return hd.rowAddr[row]
}
// SetDDRamAddr sets the input cursor to the given address.
func (hd *HD44780) SetDDRamAddr(value byte) error {
return hd.WriteInstruction(lcdSetDDRamAddr | value)
}
@ -357,13 +426,13 @@ type GPIOConnection struct {
RS, EN embd.DigitalPin
D4, D5, D6, D7 embd.DigitalPin
Backlight embd.DigitalPin
BLPolarity Polarity
BLPolarity BacklightPolarity
}
// NewGPIOConnection returns a new Connection based on a 4-bit GPIO bus.
func NewGPIOConnection(
rs, en, d4, d5, d6, d7, backlight embd.DigitalPin,
blPolarity Polarity,
blPolarity BacklightPolarity,
) *GPIOConnection {
return &GPIOConnection{
RS: rs,
@ -480,7 +549,7 @@ type I2CPinMap struct {
RS, RW, EN byte
D4, D5, D6, D7 byte
Backlight byte
BLPolarity Polarity
BLPolarity BacklightPolarity
}
var (

View File

@ -9,7 +9,13 @@ import (
"github.com/kidoman/embd"
)
const testAddr byte = 0x20
const (
testAddr byte = 0x20
cols = 20
rows = 4
)
var testRowAddr RowAddress = RowAddress20Col
type mockDigitalPin struct {
direction embd.Direction
@ -60,6 +66,11 @@ type instruction struct {
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))
}
@ -73,7 +84,7 @@ func printInstructionsAsBinary(ins []instruction) string {
}
func newMockGPIOConnection() *mockGPIOConnection {
be := &mockGPIOConnection{
conn := &mockGPIOConnection{
rs: newMockDigitalPin(),
en: newMockDigitalPin(),
d4: newMockDigitalPin(),
@ -87,32 +98,32 @@ func newMockGPIOConnection() *mockGPIOConnection {
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
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 <-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})
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 be
return conn
}
func (be *mockGPIOConnection) pins() []*mockDigitalPin {
return []*mockDigitalPin{be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight}
func (conn *mockGPIOConnection) pins() []*mockDigitalPin {
return []*mockDigitalPin{conn.rs, conn.en, conn.d4, conn.d5, conn.d6, conn.d7, conn.backlight}
}
type mockI2CBus struct {
@ -156,9 +167,9 @@ func printBytesAsBinary(bytes []byte) string {
}
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() {
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)
}
@ -166,29 +177,29 @@ func TestInitialize4Bit_directionOut(t *testing.T) {
}
func TestInitialize4Bit_lcdInit(t *testing.T) {
be := newMockGPIOConnection()
NewGPIO(be.rs, be.en, be.d4, be.d5, be.d6, be.d7, be.backlight, Negative)
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(lcdSetEntryMode)},
instruction{embd.Low, byte(lcdSetDisplayMode)},
instruction{embd.Low, byte(lcdSetFunctionMode)},
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, be.writes) {
if !reflect.DeepEqual(instructions, mock.writes) {
t.Errorf(
"\nExpected\t%s\nActual\t\t%+v",
printInstructionsAsBinary(instructions),
printInstructionsAsBinary(be.writes))
printInstructionsAsBinary(mock.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)
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 be.pins() {
for idx, pin := range mock.pins() {
if !pin.closed {
t.Errorf("Pin %d was not closed", idx)
}
@ -253,3 +264,55 @@ func TestI2CConnectionClose(t *testing.T) {
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")
}
}

View File

@ -0,0 +1,106 @@
/*
Package characterdisplay provides an ease-of-use layer on top of a character
display controller.
*/
package characterdisplay
// Controller is an interface that describes the basic functionality of a character
// display controller.
type Controller interface {
DisplayOff() error // turns the display off
DisplayOn() error // turns the display on
CursorOff() error // sets the cursor visibility to off
CursorOn() error // sets the cursor visibility to on
BlinkOff() error // sets the cursor blink off
BlinkOn() error // sets the cursor blink on
ShiftLeft() error // moves the cursor and text one column to the left
ShiftRight() error // moves the cursor and text one column to the right
BacklightOff() error // turns the display backlight off
BacklightOn() error // turns the display backlight on
Home() error // moves the cursor to the home position
Clear() error // clears the display and moves the cursor to the home position
WriteChar(byte) error // writes a character to the display
SetCursor(col, row int) error // sets the cursor position
Close() error // closes the controller resources
}
// Display represents an abstract character display and provides a
// ease-of-use layer on top of a character display controller.
type Display struct {
Controller
cols, rows int
p *position
}
type position struct {
col int
row int
}
// New creates a new Display
func New(controller Controller, cols, rows int) *Display {
return &Display{
Controller: controller,
cols: cols,
rows: rows,
p: &position{0, 0},
}
}
// Home moves the cursor and all characters to the home position.
func (disp *Display) Home() error {
disp.setCurrentPosition(0, 0)
return disp.Controller.Home()
}
// Clear clears the display, preserving the mode settings and setting the correct home.
func (disp *Display) Clear() error {
disp.setCurrentPosition(0, 0)
return disp.Controller.Clear()
}
// Message prints the given string on the display, including interpreting newline
// characters and wrapping at the end of lines.
func (disp *Display) Message(message string) error {
bytes := []byte(message)
for _, b := range bytes {
if b == byte('\n') {
err := disp.Newline()
if err != nil {
return err
}
continue
}
err := disp.WriteChar(b)
if err != nil {
return err
}
disp.p.col++
if disp.p.col >= disp.cols || disp.p.col < 0 {
err := disp.Newline()
if err != nil {
return err
}
}
}
return nil
}
// Newline moves the input cursor to the beginning of the next line.
func (disp *Display) Newline() error {
return disp.SetCursor(0, disp.p.row+1)
}
// SetCursor sets the input cursor to the given position.
func (disp *Display) SetCursor(col, row int) error {
if row >= disp.rows {
row = disp.rows - 1
}
disp.setCurrentPosition(col, row)
return disp.Controller.SetCursor(col, row)
}
func (disp *Display) setCurrentPosition(col, row int) {
disp.p.col = col
disp.p.row = row
}

View File

@ -0,0 +1,129 @@
package characterdisplay
import (
"reflect"
"testing"
"time"
)
const (
rows = 4
cols = 20
)
type mockController struct {
calls chan call
}
type call struct {
name string
arguments []interface{}
}
func noArgCall(name string) call {
return call{name, []interface{}{}}
}
func (mock *mockController) DisplayOff() error { mock.calls <- noArgCall("DisplayOff"); return nil }
func (mock *mockController) DisplayOn() error { mock.calls <- noArgCall("DisplayOn"); return nil }
func (mock *mockController) CursorOff() error { mock.calls <- noArgCall("CursorOff"); return nil }
func (mock *mockController) CursorOn() error { mock.calls <- noArgCall("CursorOn"); return nil }
func (mock *mockController) BlinkOff() error { mock.calls <- noArgCall("BlinkOff"); return nil }
func (mock *mockController) BlinkOn() error { mock.calls <- noArgCall("BlinkOn"); return nil }
func (mock *mockController) ShiftLeft() error { mock.calls <- noArgCall("ShiftLeft"); return nil }
func (mock *mockController) ShiftRight() error { mock.calls <- noArgCall("ShiftRight"); return nil }
func (mock *mockController) BacklightOff() error { mock.calls <- noArgCall("BacklightOff"); return nil }
func (mock *mockController) BacklightOn() error { mock.calls <- noArgCall("BacklightOn"); return nil }
func (mock *mockController) Home() error { mock.calls <- noArgCall("Home"); return nil }
func (mock *mockController) Clear() error { mock.calls <- noArgCall("Clear"); return nil }
func (mock *mockController) Close() error { mock.calls <- noArgCall("Close"); return nil }
func (mock *mockController) WriteChar(b byte) error {
mock.calls <- call{"WriteChar", []interface{}{b}}
return nil
}
func (mock *mockController) SetCursor(col, row int) error {
mock.calls <- call{"SetCursor", []interface{}{col, row}}
return nil
}
func (mock *mockController) testExpectedCalls(expectedCalls []call, t *testing.T) {
for _, expectedCall := range expectedCalls {
select {
case actualCall := <-mock.calls:
if !reflect.DeepEqual(expectedCall, actualCall) {
t.Errorf("Expected call %+v, actual call %+v", expectedCall, actualCall)
}
case <-time.After(time.Millisecond * 1):
t.Errorf("Timeout reading next call. Expected call %+v", expectedCall)
}
}
ExtraCallsCheck:
for {
select {
case extraCall := <-mock.calls:
t.Errorf("Unexpected call %+v", extraCall)
case <-time.After(time.Millisecond * 1):
break ExtraCallsCheck
}
}
}
func newMockController() *mockController {
return &mockController{make(chan call, 256)}
}
func TestNewline(t *testing.T) {
mock := newMockController()
disp := New(mock, cols, rows)
disp.Newline()
expectedCalls := []call{
call{"SetCursor", []interface{}{0, 1}},
}
mock.testExpectedCalls(expectedCalls, t)
}
func TestMessage(t *testing.T) {
mock := newMockController()
disp := New(mock, cols, rows)
disp.Message("ab")
expectedCalls := []call{
call{"WriteChar", []interface{}{byte('a')}},
call{"WriteChar", []interface{}{byte('b')}},
}
mock.testExpectedCalls(expectedCalls, t)
}
func TestMessage_newLine(t *testing.T) {
mock := newMockController()
disp := New(mock, cols, rows)
disp.Message("a\nb")
expectedCalls := []call{
call{"WriteChar", []interface{}{byte('a')}},
call{"SetCursor", []interface{}{0, 1}},
call{"WriteChar", []interface{}{byte('b')}},
}
mock.testExpectedCalls(expectedCalls, t)
}
func TestMessage_wrap(t *testing.T) {
mock := newMockController()
disp := New(mock, cols, rows)
disp.SetCursor(cols-1, 0)
disp.Message("ab")
expectedCalls := []call{
call{"SetCursor", []interface{}{cols - 1, 0}},
call{"WriteChar", []interface{}{byte('a')}},
call{"SetCursor", []interface{}{0, 1}},
call{"WriteChar", []interface{}{byte('b')}},
}
mock.testExpectedCalls(expectedCalls, t)
}

View File

@ -0,0 +1,45 @@
// +build ignore
package main
import (
"flag"
"time"
"github.com/kidoman/embd"
"github.com/kidoman/embd/controller/hd44780"
"github.com/kidoman/embd/interface/display/characterdisplay"
_ "github.com/kidoman/embd/host/all"
)
func main() {
flag.Parse()
if err := embd.InitI2C(); err != nil {
panic(err)
}
defer embd.CloseI2C()
bus := embd.NewI2CBus(1)
controller, err := hd44780.NewI2C(
bus,
0x20,
hd44780.PCF8574PinMap,
hd44780.RowAddress20Col,
hd44780.TwoLine,
hd44780.BlinkOn,
)
if err != nil {
panic(err)
}
display := characterdisplay.New(controller, 20, 4)
defer display.Close()
display.Clear()
display.Message("Hello, world!\n@embd | characterdisplay")
time.Sleep(10 * time.Second)
display.BacklightOff()
}

View File

@ -22,22 +22,32 @@ func main() {
bus := embd.NewI2CBus(1)
display, err := hd44780.NewI2CCharacterDisplay(
hd, err := hd44780.NewI2C(
bus,
0x20,
hd44780.PCF8574PinMap,
20,
4,
hd44780.RowAddress20Col,
hd44780.TwoLine,
hd44780.BlinkOn,
)
if err != nil {
panic(err)
}
defer display.Close()
defer hd.Close()
display.Clear()
display.Message("Hello, world!\n@embd")
hd.Clear()
message := "Hello, world!"
bytes := []byte(message)
for _, b := range bytes {
hd.WriteChar(b)
}
hd.SetCursor(0, 1)
message = "@embd | hd44780"
bytes = []byte(message)
for _, b := range bytes {
hd.WriteChar(b)
}
time.Sleep(10 * time.Second)
display.BacklightOff()
hd.BacklightOff()
}