You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/matrixorbital/lk2047t.go

378 lines
9.7 KiB
Go

// Copyright 2024 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
// This package provides an interface to MatrixOrbital Character LCD displays.
// The LK2047T display is compatible with the Adafruit USB-LCD Backpack.
package matrixorbital
import (
"errors"
"fmt"
"io"
"sync"
// "time"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/display"
"periph.io/x/conn/v3/gpio"
)
// Constants for programmable LEDs on some models.
type LEDColor int
const (
// Constants for colors supported by LEDs.
Off LEDColor = iota
Red
Green
Yellow
)
// The LK2047T is a basic MatrixOrbital LCD display. It's a 20x4 LCD with a
// keypad and LEDs.
//
// Implements periph.io/x/conn/v3/display.TextDisplay, Backlight, and
// DisplayContrast.
type LK2047T struct {
// Pins represents the set of gpio.PinOut pins exposed by the device. For
// units with LEDS, the pins are used to manipulate them. For the Adafruit
// USB/LCD backpack, 4 pins are exposed.
Pins []gpio.PinOut
d conn.Conn
writer io.Writer
mu sync.Mutex
rows int
cols int
chKeyboard chan byte
shutdown chan struct{}
}
type GPOEnabledDisplay interface {
// SetGPO turns a GPO pin on or off
GPO(pin int, l gpio.Level) error
}
// Command byte values used by the display. This only implements a subset of
// commands.
var cmdByte byte = 0xfe
var autoScrollOff = []byte{cmdByte, 0x52}
var autoScrollOn = []byte{cmdByte, 0x51}
var blockCursorOff = []byte{cmdByte, 0x54}
var blockCursorOn = []byte{cmdByte, 0x53}
var clearScreen = []byte{cmdByte, 0x58}
var cursorBack = []byte{cmdByte, 0x4c}
var cursorBlinkOff = []byte{cmdByte, 0x54}
var cursorBlinkOn = []byte{cmdByte, 0x53}
var cursorForward = []byte{cmdByte, 0x4d}
var displayOff = []byte{cmdByte, 0x46}
var displayOn = []byte{cmdByte, 0x42}
var goHome = []byte{cmdByte, 0x48}
var keypadBacklightOff = []byte{cmdByte, 0x98}
var setBrightness = []byte{cmdByte, 0x99}
var setContrast = []byte{cmdByte, 0x50}
var setCursorPosition = []byte{cmdByte, 0x47}
var setGPOOn = []byte{cmdByte, 0x57}
var setGPOOff = []byte{cmdByte, 0x56}
var underlineCursorOff = []byte{cmdByte, 0x4b}
var underlineCursorOn = []byte{cmdByte, 0x4a}
func wrapErr(err error) error {
if err == nil {
return nil
}
return fmt.Errorf("lk2047t: %w", err)
}
// Create a new LCD device using a periph.io/conn/Conn
func NewConnLK2047T(conn conn.Conn, rows, cols int) *LK2047T {
dev := &LK2047T{d: conn, rows: rows, cols: cols, Pins: make([]gpio.PinOut, 6)}
a := GPOEnabledDisplay(dev)
makePins(&a, dev.Pins)
return dev
}
// Create a new LCD device using an io.Writer. If your display is connected
// using a hardware interface that periph.io doesn't support (e.g. UART),
// you can still use this package as long as the hardware interface provides
// the io.Writer interface. rows is the number of lines the display supports,
// and cols is the character width of the device.
func NewWriterLK2047T(writer io.Writer, rows, cols int) *LK2047T {
dev := &LK2047T{writer: writer, rows: rows, cols: cols, Pins: make([]gpio.PinOut, 6)}
a := GPOEnabledDisplay(dev)
makePins(&a, dev.Pins)
return dev
}
// Enable or disable AutoScroll.
func (dev *LK2047T) AutoScroll(enabled bool) (err error) {
if enabled {
_, err = dev.Write(autoScrollOn)
} else {
_, err = dev.Write(autoScrollOff)
}
return
}
// Clears the screen, and moves the cursor to the home position.
func (dev *LK2047T) Clear() (err error) {
_, err = dev.Write(clearScreen)
if err == nil {
err = dev.Home()
}
return
}
// Return the number of columns supported by the device.
func (dev *LK2047T) Cols() int {
return dev.cols
}
// Set the cursor mode. E.G. underline, block, etc.
func (dev *LK2047T) Cursor(modes ...display.CursorMode) (err error) {
for _, mode := range modes {
switch mode {
case display.CursorOff:
_, err = dev.Write(blockCursorOff)
if err == nil {
_, err = dev.Write(underlineCursorOff)
}
if err == nil {
_, err = dev.Write(cursorBlinkOff)
}
case display.CursorUnderline:
_, err = dev.Write(underlineCursorOn)
case display.CursorBlock:
_, err = dev.Write(blockCursorOn)
case display.CursorBlink:
_, err = dev.Write(cursorBlinkOn)
default:
err = fmt.Errorf("lk2047t: invalid cursor mode %d", mode)
}
if err != nil {
break
}
}
return
}
// Halt shuts down the display, and closes the output device if it implements
// io.Closer. If a keypad read operation is running, closing the device will
// terminate it.
func (dev *LK2047T) Halt() (err error) {
err = dev.Display(false)
_ = dev.KeypadBacklight(false)
if err != nil {
return err
}
dev.mu.Lock()
defer dev.mu.Unlock()
if dev.shutdown != nil {
dev.shutdown <- struct{}{}
}
var cl io.Closer
var ok bool
if dev.d != nil {
cl, ok = dev.d.(io.Closer)
} else {
cl, ok = dev.writer.(io.Closer)
}
if ok {
err = cl.Close()
} else {
err = errors.New("output connection doesn't support io.Closer()")
}
err = wrapErr(err)
return
}
// Home resets the cursor to the default position.
func (dev *LK2047T) Home() (err error) {
_, err = dev.Write(goHome)
return
}
// MinCol returns the numbering scheme of the device's minimum column number.
// Generally, it will be 0 or 1
func (dev *LK2047T) MinCol() int {
return 1
}
// MinRow returns the numbering scheme of the device's minimum row (line)
// number. Generally, it will be 0 or 1.
func (dev *LK2047T) MinRow() int {
return 1
}
// Move the cursor forward or backwards.
func (dev *LK2047T) Move(direction display.CursorDirection) (err error) {
switch direction {
case display.Forward:
_, err = dev.Write(cursorForward)
case display.Backward:
_, err = dev.Write(cursorBack)
case display.Up:
case display.Down:
default:
err = errors.New("lk2047t: invalid move direction")
}
return
}
// Move the cursor to an arbitrary row/column on the device.
func (dev *LK2047T) MoveTo(row, col int) (err error) {
if row < 1 || row > dev.rows || col < 1 || col > dev.cols {
return fmt.Errorf("lk2047t: MoveTo(%d, %d) value out of range", row, col)
}
_, err = dev.Write([]byte{setCursorPosition[0], setCursorPosition[1], byte(col), byte(row)})
return err
}
// ReadKeypad reads from the displays built-in keypad. The io device used by the
// display must implement io.Reader. If it does not, then an error is returned.
func (dev *LK2047T) ReadKeypad() (<-chan byte, error) {
dev.mu.Lock()
defer dev.mu.Unlock()
if dev.chKeyboard != nil {
return dev.chKeyboard, nil
}
var rdr io.Reader
var ok bool
if dev.writer == nil {
rdr, ok = dev.d.(io.Reader)
} else {
rdr, ok = dev.writer.(io.Reader)
}
if !ok {
return nil, errors.New("lk2047t: output device does not implement io.Reader")
}
dev.chKeyboard = make(chan byte, 8)
dev.shutdown = make(chan struct{})
go func() {
defer func() {
dev.mu.Lock()
close(dev.chKeyboard)
dev.chKeyboard = nil
dev.mu.Unlock()
}()
buf := make([]byte, 4)
var err error
var n int
for err == nil {
select {
case <-dev.shutdown:
return
default:
n, err = rdr.Read(buf)
if n > 0 {
for ix := range n {
dev.chKeyboard <- buf[ix]
}
}
}
}
}()
return dev.chKeyboard, nil
}
// Return the number of rows supported by the device.
func (dev *LK2047T) Rows() int {
return dev.rows
}
// Set the intensity of the backlight. Refer to the docs in the lcd package
// for warnings on this function. Provides periph.io/x/conn/v3/display.Backlight
func (dev *LK2047T) Backlight(intensity display.Intensity) error {
_, err := dev.Write([]byte{setBrightness[0], setBrightness[1], byte(intensity)})
return err
}
// Set the constrast of the display. Refer to the docs in the lcd package
// for warnings on this function. Provides periph.io/x/conn/v3/display.DisplayContrast
func (dev *LK2047T) Contrast(contrast display.Contrast) error {
_, err := dev.Write([]byte{setContrast[0], setContrast[1], byte(contrast)})
return err
}
// Set the display on or off.
func (dev *LK2047T) Display(on bool) (err error) {
if on {
_, err = dev.Write([]byte{displayOn[0], displayOn[1], 0})
} else {
_, err = dev.Write(displayOff)
}
return
}
func (dev *LK2047T) KeypadBacklight(on bool) error {
if on {
return dev.Display(on)
}
_, err := dev.Write(keypadBacklightOff)
return err
}
// Set the specified output pin state.
func (dev *LK2047T) GPO(pin int, on gpio.Level) (err error) {
if on {
_, err = dev.Write([]byte{setGPOOn[0], setGPOOn[1], byte(pin)})
} else {
_, err = dev.Write([]byte{setGPOOff[0], setGPOOff[1], byte(pin)})
}
return
}
// Set an led to a supported color. number is 0 based.
func (dev *LK2047T) LED(number int, color LEDColor) error {
if color < Off || color > Yellow {
return fmt.Errorf("lk2047t: invalid color: %d", color)
}
err := dev.Pins[number*2].Out(gpio.Level(color&Red == Red))
if err != nil {
return err
}
err = dev.Pins[number*2+1].Out(gpio.Level(color&Green == Green))
return err
}
func (dev *LK2047T) String() string {
var ioType any
if dev.d != nil {
ioType = dev.d
} else {
ioType = dev.writer
}
return fmt.Sprintf("MatrixOrbital LK204-7T LCD Display: Rows: %d Cols: %d Connection: %T", dev.rows, dev.cols, ioType)
}
// Write commands or data to the display
func (dev *LK2047T) Write(p []byte) (n int, err error) {
dev.mu.Lock()
defer dev.mu.Unlock()
if dev.writer == nil {
err = dev.d.Tx(p, nil)
n = len(p)
} else {
n, err = dev.writer.Write(p)
}
err = wrapErr(err)
return
}
// WriteString sends a text string to the display.
func (dev *LK2047T) WriteString(text string) (int, error) {
n, err := dev.Write([]byte(text))
return n, err
}
var _ display.TextDisplay = &LK2047T{}
var _ GPOEnabledDisplay = &LK2047T{}
var _ display.DisplayContrast = &LK2047T{}
var _ display.DisplayBacklight = &LK2047T{}
var _ conn.Resource = &LK2047T{}