mirror of
https://github.com/kidoman/embd
synced 2024-12-21 12:20:05 +01:00
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:
parent
dac729e4fd
commit
915b3b76a7
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 (
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
106
interface/display/characterdisplay/characterdisplay.go
Normal file
106
interface/display/characterdisplay/characterdisplay.go
Normal 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
|
||||
}
|
129
interface/display/characterdisplay/characterdisplay_test.go
Normal file
129
interface/display/characterdisplay/characterdisplay_test.go
Normal 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)
|
||||
}
|
45
samples/characterdisplay.go
Normal file
45
samples/characterdisplay.go
Normal 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()
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user