mirror of
https://github.com/kidoman/embd
synced 2024-12-22 04:40:04 +01: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
212
controller/hd44780/character_display.go
Normal file
212
controller/hd44780/character_display.go
Normal file
@ -0,0 +1,212 @@
|
||||
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()
|
||||
}
|
85
controller/hd44780/character_display_test.go
Normal file
85
controller/hd44780/character_display_test.go
Normal file
@ -0,0 +1,85 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
572
controller/hd44780/hd44780.go
Normal file
572
controller/hd44780/hd44780.go
Normal file
@ -0,0 +1,572 @@
|
||||
/*
|
||||
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:
|
||||
Adafruit https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_CharLCD/Adafruit_CharLCD.py
|
||||
hwio https://github.com/mrmorphic/hwio/blob/master/devices/hd44780/hd44780_i2c.go
|
||||
LiquidCrystal https://github.com/arduino/Arduino/blob/master/libraries/LiquidCrystal/LiquidCrystal.cpp
|
||||
*/
|
||||
package hd44780
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/kidoman/embd"
|
||||
)
|
||||
|
||||
type Polarity bool
|
||||
type entryMode byte
|
||||
type displayMode byte
|
||||
type functionMode byte
|
||||
|
||||
const (
|
||||
Negative Polarity = false
|
||||
Positive Polarity = true
|
||||
|
||||
writeDelay = 37 * time.Microsecond
|
||||
pulseDelay = 1 * time.Microsecond
|
||||
clearDelay = 1520 * time.Microsecond
|
||||
|
||||
// Initialize display
|
||||
lcdInit byte = 0x33 // 00110011
|
||||
lcdInit4bit byte = 0x32 // 00110010
|
||||
|
||||
// Commands
|
||||
lcdClearDisplay byte = 0x01 // 00000001
|
||||
lcdReturnHome byte = 0x02 // 00000010
|
||||
lcdCursorShift byte = 0x10 // 00010000
|
||||
lcdSetCGRamAddr byte = 0x40 // 01000000
|
||||
lcdSetDDRamAddr byte = 0x80 // 10000000
|
||||
|
||||
// Cursor and display move flags
|
||||
lcdCursorMove byte = 0x00 // 00000000
|
||||
lcdDisplayMove byte = 0x08 // 00001000
|
||||
lcdMoveLeft byte = 0x00 // 00000000
|
||||
lcdMoveRight byte = 0x04 // 00000100
|
||||
|
||||
// Entry mode flags
|
||||
lcdSetEntryMode entryMode = 0x04 // 00000100
|
||||
lcdEntryDecrement entryMode = 0x00 // 00000000
|
||||
lcdEntryIncrement entryMode = 0x02 // 00000010
|
||||
lcdEntryShiftOff entryMode = 0x00 // 00000000
|
||||
lcdEntryShiftOn entryMode = 0x01 // 00000001
|
||||
|
||||
// Display mode flags
|
||||
lcdSetDisplayMode displayMode = 0x08 // 00001000
|
||||
lcdDisplayOff displayMode = 0x00 // 00000000
|
||||
lcdDisplayOn displayMode = 0x04 // 00000100
|
||||
lcdCursorOff displayMode = 0x00 // 00000000
|
||||
lcdCursorOn displayMode = 0x02 // 00000010
|
||||
lcdBlinkOff displayMode = 0x00 // 00000000
|
||||
lcdBlinkOn displayMode = 0x01 // 00000001
|
||||
|
||||
// Function mode flags
|
||||
lcdSetFunctionMode functionMode = 0x20 // 00100000
|
||||
lcd4BitMode functionMode = 0x00 // 00000000
|
||||
lcd8BitMode functionMode = 0x10 // 00010000
|
||||
lcd1Line functionMode = 0x00 // 00000000
|
||||
lcd2Line functionMode = 0x08 // 00001000
|
||||
lcd5x8Dots functionMode = 0x00 // 00000000
|
||||
lcd5x10Dots functionMode = 0x04 // 00000100
|
||||
)
|
||||
|
||||
// HD44780 represents an HD44780-compatible character LCD controller.
|
||||
type HD44780 struct {
|
||||
Connection
|
||||
eMode entryMode
|
||||
dMode displayMode
|
||||
fMode functionMode
|
||||
}
|
||||
|
||||
// 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,
|
||||
modes ...ModeSetter,
|
||||
) (*HD44780, error) {
|
||||
pins := []embd.DigitalPin{rs, en, d4, d5, d6, d7, backlight}
|
||||
for _, pin := range pins {
|
||||
if pin == nil {
|
||||
continue
|
||||
}
|
||||
err := pin.SetDirection(embd.Out)
|
||||
if err != nil {
|
||||
glog.Errorf("hd44780: error setting pin %+v to out direction: %s", pin, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return New(
|
||||
NewGPIOConnection(rs, en, d4, d5, d6, d7, backlight, blPolarity),
|
||||
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...)
|
||||
}
|
||||
|
||||
// New creates a new HD44780 connected by a Connection bus.
|
||||
func New(bus Connection, modes ...ModeSetter) (*HD44780, error) {
|
||||
controller := &HD44780{
|
||||
Connection: bus,
|
||||
eMode: 0x00,
|
||||
dMode: 0x00,
|
||||
fMode: 0x00,
|
||||
}
|
||||
err := controller.lcdInit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = controller.SetMode(modes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return controller, nil
|
||||
}
|
||||
|
||||
func (controller *HD44780) lcdInit() error {
|
||||
glog.V(2).Info("hd44780: initializing display")
|
||||
err := controller.WriteInstruction(lcdInit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(2).Info("hd44780: initializing display in 4-bit mode")
|
||||
return controller.WriteInstruction(lcdInit4bit)
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// EntryDecrement is a ModeSetter that sets the HD44780 to entry decrement mode.
|
||||
func EntryDecrement(hd *HD44780) { hd.eMode &= ^lcdEntryIncrement }
|
||||
|
||||
// EntryIncrement is a ModeSetter that sets the HD44780 to entry increment mode.
|
||||
func EntryIncrement(hd *HD44780) { hd.eMode |= lcdEntryIncrement }
|
||||
|
||||
// EntryShiftOff is a ModeSetter that sets the HD44780 to entry shift off mode.
|
||||
func EntryShiftOff(hd *HD44780) { hd.eMode &= ^lcdEntryShiftOn }
|
||||
|
||||
// EntryShiftOn is a ModeSetter that sets the HD44780 to entry shift on mode.
|
||||
func EntryShiftOn(hd *HD44780) { hd.eMode |= lcdEntryShiftOn }
|
||||
|
||||
// DisplayOff is a ModeSetter that sets the HD44780 to display off mode.
|
||||
func DisplayOff(hd *HD44780) { hd.dMode &= ^lcdDisplayOn }
|
||||
|
||||
// DisplayOn is a ModeSetter that sets the HD44780 to display on mode.
|
||||
func DisplayOn(hd *HD44780) { hd.dMode |= lcdDisplayOn }
|
||||
|
||||
// CursorOff is a ModeSetter that sets the HD44780 to cursor off mode.
|
||||
func CursorOff(hd *HD44780) { hd.dMode &= ^lcdCursorOn }
|
||||
|
||||
// CursorOn is a ModeSetter that sets the HD44780 to cursor on mode.
|
||||
func CursorOn(hd *HD44780) { hd.dMode |= lcdCursorOn }
|
||||
|
||||
// BlinkOff is a ModeSetter that sets the HD44780 to cursor blink off mode.
|
||||
func BlinkOff(hd *HD44780) { hd.dMode &= ^lcdBlinkOn }
|
||||
|
||||
// BlinkOn is a ModeSetter that sets the HD44780 to cursor blink on mode.
|
||||
func BlinkOn(hd *HD44780) { hd.dMode |= lcdBlinkOn }
|
||||
|
||||
// FourBitMode is a ModeSetter that sets the HD44780 to 4-bit bus mode.
|
||||
func FourBitMode(hd *HD44780) { hd.fMode &= ^lcd8BitMode }
|
||||
|
||||
// EightBitMode is a ModeSetter that sets the HD44780 to 8-bit bus mode.
|
||||
func EightBitMode(hd *HD44780) { hd.fMode |= lcd8BitMode }
|
||||
|
||||
// OneLine is a ModeSetter that sets the HD44780 to 1-line display mode.
|
||||
func OneLine(hd *HD44780) { hd.fMode &= ^lcd2Line }
|
||||
|
||||
// TwoLine is a ModeSetter that sets the HD44780 to 2-line display mode.
|
||||
func TwoLine(hd *HD44780) { hd.fMode |= lcd2Line }
|
||||
|
||||
// Dots5x8 is a ModeSetter that sets the HD44780 to 5x8-pixel character mode.
|
||||
func Dots5x8(hd *HD44780) { hd.fMode &= ^lcd5x10Dots }
|
||||
|
||||
// Dots5x10 is a ModeSetter that sets the HD44780 to 5x10-pixel character mode.
|
||||
func Dots5x10(hd *HD44780) { hd.fMode |= lcd5x10Dots }
|
||||
|
||||
// EntryIncrementEnabled returns true if entry increment mode is enabled.
|
||||
func (hd *HD44780) EntryIncrementEnabled() bool { return hd.eMode&lcdEntryIncrement > 0 }
|
||||
|
||||
// EntryShiftEnabled returns true if entry shift mode is enabled.
|
||||
func (hd *HD44780) EntryShiftEnabled() bool { return hd.eMode&lcdEntryShiftOn > 0 }
|
||||
|
||||
// DisplayEnabled returns true if the display is on.
|
||||
func (hd *HD44780) DisplayEnabled() bool { return hd.dMode&lcdDisplayOn > 0 }
|
||||
|
||||
// CursorEnabled returns true if the cursor is on.
|
||||
func (hd *HD44780) CursorEnabled() bool { return hd.dMode&lcdCursorOn > 0 }
|
||||
|
||||
// BlinkEnabled returns true if the cursor blink mode is enabled.
|
||||
func (hd *HD44780) BlinkEnabled() bool { return hd.dMode&lcdBlinkOn > 0 }
|
||||
|
||||
// EightBitModeEnabled returns true if 8-bit bus mode is enabled and false if 4-bit
|
||||
// bus mode is enabled.
|
||||
func (hd *HD44780) EightBitModeEnabled() bool { return hd.fMode&lcd8BitMode > 0 }
|
||||
|
||||
// TwoLineEnabled returns true if 2-line display mode is enabled and false if 1-line
|
||||
// display mode is enabled.
|
||||
func (hd *HD44780) TwoLineEnabled() bool { return hd.fMode&lcd2Line > 0 }
|
||||
|
||||
// Dots5x10Enabled returns true if 5x10-pixel characters are enabled.
|
||||
func (hd *HD44780) Dots5x10Enabled() bool { return hd.fMode&lcd5x8Dots > 0 }
|
||||
|
||||
// SetModes modifies the entry mode, display mode, and function mode with the
|
||||
// given mode setter functions.
|
||||
func (hd *HD44780) SetMode(modes ...ModeSetter) error {
|
||||
for _, m := range modes {
|
||||
m(hd)
|
||||
}
|
||||
functions := []func() error{
|
||||
func() error { return hd.setEntryMode() },
|
||||
func() error { return hd.setDisplayMode() },
|
||||
func() error { return hd.setFunctionMode() },
|
||||
}
|
||||
for _, f := range functions {
|
||||
err := f()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hd *HD44780) setEntryMode() error {
|
||||
return hd.WriteInstruction(byte(lcdSetEntryMode | hd.eMode))
|
||||
}
|
||||
|
||||
func (hd *HD44780) setDisplayMode() error {
|
||||
return hd.WriteInstruction(byte(lcdSetDisplayMode | hd.dMode))
|
||||
}
|
||||
|
||||
func (hd *HD44780) setFunctionMode() error {
|
||||
return hd.WriteInstruction(byte(lcdSetFunctionMode | hd.fMode))
|
||||
}
|
||||
|
||||
// DisplayOff sets the display mode to off.
|
||||
func (hd *HD44780) DisplayOff() error {
|
||||
DisplayOff(hd)
|
||||
return hd.setDisplayMode()
|
||||
}
|
||||
|
||||
// DisplayOn sets the display mode to on.
|
||||
func (hd *HD44780) DisplayOn() error {
|
||||
DisplayOn(hd)
|
||||
return hd.setDisplayMode()
|
||||
}
|
||||
|
||||
// CursorOff turns the cursor off.
|
||||
func (hd *HD44780) CursorOff() error {
|
||||
CursorOff(hd)
|
||||
return hd.setDisplayMode()
|
||||
}
|
||||
|
||||
// CursorOn turns the cursor on.
|
||||
func (hd *HD44780) CursorOn() error {
|
||||
CursorOn(hd)
|
||||
return hd.setDisplayMode()
|
||||
}
|
||||
|
||||
// BlinkOff sets cursor blink mode off.
|
||||
func (hd *HD44780) BlinkOff() error {
|
||||
BlinkOff(hd)
|
||||
return hd.setDisplayMode()
|
||||
}
|
||||
|
||||
// BlinkOn sets cursor blink mode on.
|
||||
func (hd *HD44780) BlinkOn() error {
|
||||
BlinkOn(hd)
|
||||
return hd.setDisplayMode()
|
||||
}
|
||||
|
||||
// ShiftLeft shifts the cursor and all characters to the left.
|
||||
func (hd *HD44780) ShiftLeft() error {
|
||||
return hd.WriteInstruction(lcdCursorShift | lcdDisplayMove | lcdMoveLeft)
|
||||
}
|
||||
|
||||
// ShiftRight shifts the cursor and all characters to the right.
|
||||
func (hd *HD44780) ShiftRight() error {
|
||||
return hd.WriteInstruction(lcdCursorShift | lcdDisplayMove | lcdMoveRight)
|
||||
}
|
||||
|
||||
// Home moves the cursor and all characters to the home position.
|
||||
func (hd *HD44780) Home() error {
|
||||
err := hd.WriteInstruction(lcdReturnHome)
|
||||
time.Sleep(clearDelay)
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear clears the display and mode settings sets the cursor to the home position.
|
||||
func (hd *HD44780) Clear() error {
|
||||
err := hd.WriteInstruction(lcdClearDisplay)
|
||||
time.Sleep(clearDelay)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetCursor sets the input cursor to the given bye.
|
||||
func (hd *HD44780) SetCursor(value byte) error {
|
||||
return hd.WriteInstruction(lcdSetDDRamAddr | value)
|
||||
}
|
||||
|
||||
// WriteInstruction writes a byte to the bus with register select in data mode.
|
||||
func (hd *HD44780) WriteChar(value byte) error {
|
||||
return hd.Write(true, value)
|
||||
}
|
||||
|
||||
// WriteInstruction writes a byte to the bus with register select in command mode.
|
||||
func (hd *HD44780) WriteInstruction(value byte) error {
|
||||
return hd.Write(false, value)
|
||||
}
|
||||
|
||||
// Close closes the underlying Connection.
|
||||
func (hd *HD44780) Close() error {
|
||||
return hd.Connection.Close()
|
||||
}
|
||||
|
||||
// Connection abstracts the different methods of communicating with an HD44780.
|
||||
type Connection interface {
|
||||
// Write writes a byte to the HD44780 controller with the register select
|
||||
// flag either on or off.
|
||||
Write(rs bool, data byte) error
|
||||
|
||||
// BacklightOff turns the optional backlight off.
|
||||
BacklightOff() error
|
||||
|
||||
// BacklightOn turns the optional backlight on.
|
||||
BacklightOn() error
|
||||
|
||||
// Close closes all open resources.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// GPIOConnection implements Connection using a 4-bit GPIO bus.
|
||||
type GPIOConnection struct {
|
||||
RS, EN embd.DigitalPin
|
||||
D4, D5, D6, D7 embd.DigitalPin
|
||||
Backlight embd.DigitalPin
|
||||
BLPolarity Polarity
|
||||
}
|
||||
|
||||
// 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,
|
||||
) *GPIOConnection {
|
||||
return &GPIOConnection{
|
||||
RS: rs,
|
||||
EN: en,
|
||||
D4: d4,
|
||||
D5: d5,
|
||||
D6: d6,
|
||||
D7: d7,
|
||||
Backlight: backlight,
|
||||
BLPolarity: blPolarity,
|
||||
}
|
||||
}
|
||||
|
||||
// BacklightOff turns the optional backlight off.
|
||||
func (conn *GPIOConnection) BacklightOff() error {
|
||||
if conn.Backlight != nil {
|
||||
return conn.Backlight.Write(conn.backlightSignal(false))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BacklightOn turns the optional backlight on.
|
||||
func (conn *GPIOConnection) BacklightOn() error {
|
||||
if conn.Backlight != nil {
|
||||
return conn.Backlight.Write(conn.backlightSignal(true))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *GPIOConnection) backlightSignal(state bool) int {
|
||||
if state == bool(conn.BLPolarity) {
|
||||
return embd.High
|
||||
} else {
|
||||
return embd.Low
|
||||
}
|
||||
}
|
||||
|
||||
// Write writes a register select flag and byte to the 4-bit GPIO connection.
|
||||
func (conn *GPIOConnection) Write(rs bool, data byte) error {
|
||||
glog.V(3).Infof("hd44780: writing to GPIO RS: %t, data: %#x", rs, data)
|
||||
rsInt := embd.Low
|
||||
if rs {
|
||||
rsInt = embd.High
|
||||
}
|
||||
functions := []func() error{
|
||||
func() error { return conn.RS.Write(rsInt) },
|
||||
func() error { return conn.D4.Write(int((data >> 4) & 0x01)) },
|
||||
func() error { return conn.D5.Write(int((data >> 5) & 0x01)) },
|
||||
func() error { return conn.D6.Write(int((data >> 6) & 0x01)) },
|
||||
func() error { return conn.D7.Write(int((data >> 7) & 0x01)) },
|
||||
func() error { return conn.pulseEnable() },
|
||||
func() error { return conn.D4.Write(int(data & 0x01)) },
|
||||
func() error { return conn.D5.Write(int((data >> 1) & 0x01)) },
|
||||
func() error { return conn.D6.Write(int((data >> 2) & 0x01)) },
|
||||
func() error { return conn.D7.Write(int((data >> 3) & 0x01)) },
|
||||
func() error { return conn.pulseEnable() },
|
||||
}
|
||||
for _, f := range functions {
|
||||
err := f()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
time.Sleep(writeDelay)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *GPIOConnection) pulseEnable() error {
|
||||
values := []int{embd.Low, embd.High, embd.Low}
|
||||
for _, v := range values {
|
||||
time.Sleep(pulseDelay)
|
||||
err := conn.EN.Write(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes all open DigitalPins.
|
||||
func (conn *GPIOConnection) Close() error {
|
||||
glog.V(2).Info("hd44780: closing all GPIO pins")
|
||||
pins := []embd.DigitalPin{
|
||||
conn.RS,
|
||||
conn.EN,
|
||||
conn.D4,
|
||||
conn.D5,
|
||||
conn.D6,
|
||||
conn.D7,
|
||||
conn.Backlight,
|
||||
}
|
||||
|
||||
for _, pin := range pins {
|
||||
err := pin.Close()
|
||||
if err != nil {
|
||||
glog.Errorf("hd44780: error closing pin %+v: %s", pin, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// I2CConnection implements Connection using an I²C bus.
|
||||
type I2CConnection struct {
|
||||
I2C embd.I2CBus
|
||||
Addr byte
|
||||
PinMap I2CPinMap
|
||||
Backlight bool
|
||||
}
|
||||
|
||||
// I2CPinMap represents a mapping between the pins on an I²C port expander and
|
||||
// the pins on the HD44780 controller.
|
||||
type I2CPinMap struct {
|
||||
RS, RW, EN byte
|
||||
D4, D5, D6, D7 byte
|
||||
Backlight byte
|
||||
BLPolarity Polarity
|
||||
}
|
||||
|
||||
var (
|
||||
// Standard pin mapping for an MJKDZ-based I²C backpack.
|
||||
MJKDZPinMap I2CPinMap = I2CPinMap{
|
||||
RS: 6, RW: 5, EN: 4,
|
||||
D4: 0, D5: 1, D6: 2, D7: 3,
|
||||
Backlight: 7,
|
||||
BLPolarity: Negative,
|
||||
}
|
||||
// Standard pin mapping for a PCF8574-based I²C backpack.
|
||||
PCF8574PinMap I2CPinMap = I2CPinMap{
|
||||
RS: 0, RW: 1, EN: 2,
|
||||
D4: 4, D5: 5, D6: 6, D7: 7,
|
||||
Backlight: 3,
|
||||
BLPolarity: Positive,
|
||||
}
|
||||
)
|
||||
|
||||
// NewI2CConnection returns a new Connection based on an I²C bus.
|
||||
func NewI2CConnection(i2c embd.I2CBus, addr byte, pinMap I2CPinMap) *I2CConnection {
|
||||
return &I2CConnection{
|
||||
I2C: i2c,
|
||||
Addr: addr,
|
||||
PinMap: pinMap,
|
||||
}
|
||||
}
|
||||
|
||||
// BacklightOff turns the optional backlight off.
|
||||
func (conn *I2CConnection) BacklightOff() error {
|
||||
conn.Backlight = false
|
||||
return conn.Write(false, 0x00)
|
||||
}
|
||||
|
||||
// BacklightOn turns the optional backlight on.
|
||||
func (conn *I2CConnection) BacklightOn() error {
|
||||
conn.Backlight = true
|
||||
return conn.Write(false, 0x00)
|
||||
}
|
||||
|
||||
// Write writes a register select flag and byte to the I²C connection.
|
||||
func (conn *I2CConnection) Write(rs bool, data byte) error {
|
||||
var instructionHigh byte = 0x00
|
||||
instructionHigh |= ((data >> 4) & 0x01) << conn.PinMap.D4
|
||||
instructionHigh |= ((data >> 5) & 0x01) << conn.PinMap.D5
|
||||
instructionHigh |= ((data >> 6) & 0x01) << conn.PinMap.D6
|
||||
instructionHigh |= ((data >> 7) & 0x01) << conn.PinMap.D7
|
||||
|
||||
var instructionLow byte = 0x00
|
||||
instructionLow |= (data & 0x01) << conn.PinMap.D4
|
||||
instructionLow |= ((data >> 1) & 0x01) << conn.PinMap.D5
|
||||
instructionLow |= ((data >> 2) & 0x01) << conn.PinMap.D6
|
||||
instructionLow |= ((data >> 3) & 0x01) << conn.PinMap.D7
|
||||
|
||||
instructions := []byte{instructionHigh, instructionLow}
|
||||
for _, ins := range instructions {
|
||||
if rs {
|
||||
ins |= 0x01 << conn.PinMap.RS
|
||||
}
|
||||
if conn.Backlight == bool(conn.PinMap.BLPolarity) {
|
||||
ins |= 0x01 << conn.PinMap.Backlight
|
||||
}
|
||||
glog.V(3).Infof("hd44780: writing to I2C: %#x", ins)
|
||||
err := conn.pulseEnable(ins)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
time.Sleep(writeDelay)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *I2CConnection) pulseEnable(data byte) error {
|
||||
bytes := []byte{data, data | (0x01 << conn.PinMap.EN), data}
|
||||
for _, b := range bytes {
|
||||
time.Sleep(pulseDelay)
|
||||
err := conn.I2C.WriteByte(conn.Addr, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the I²C connection.
|
||||
func (conn *I2CConnection) Close() error {
|
||||
glog.V(2).Info("hd44780: closing I2C bus")
|
||||
return conn.I2C.Close()
|
||||
}
|
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")
|
||||
}
|
||||
}
|
43
samples/hd44780.go
Normal file
43
samples/hd44780.go
Normal file
@ -0,0 +1,43 @@
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"time"
|
||||
|
||||
"github.com/kidoman/embd"
|
||||
"github.com/kidoman/embd/controller/hd44780"
|
||||
|
||||
_ "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)
|
||||
|
||||
display, err := hd44780.NewI2CCharacterDisplay(
|
||||
bus,
|
||||
0x20,
|
||||
hd44780.PCF8574PinMap,
|
||||
20,
|
||||
4,
|
||||
hd44780.TwoLine,
|
||||
hd44780.BlinkOn,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer display.Close()
|
||||
|
||||
display.Clear()
|
||||
display.Message("Hello, world!\n@embd")
|
||||
time.Sleep(10 * time.Second)
|
||||
display.BacklightOff()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user