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/serlcd/serlcd.go

255 lines
6.1 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 implementation for the SparkFun SerLCD intelligent
// LCD display. This display provides hardware interfaces for SPI, I2C, and
// UART. Implements conn.display.TextDisplay
package serlcd
import (
"errors"
"fmt"
"io"
"time"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/display"
)
// Representation of a SerLCD display.
type Dev struct {
conn conn.Conn
w io.Writer
cols int
rows int
// Display on/off, Curosr, Blink
displayDCB byte
}
const (
// For i2c there's a buffer limitation of 32 bytes.
// See this issue:
//
// https://github.com/sparkfun/OpenLCD/issues/29
//
// Writing more than that will lock the device up necessitating
// a device reset.
_MAX_I2C_WRITE = 32
DefaultI2CAddress uint16 = 0x72
)
var settingMode byte = 0x7c
var cmdMode byte = 0xfe
var clear = []byte{settingMode, 0x2d}
func wrap(err error) error {
return fmt.Errorf("serlcd: %w", err)
}
// Create a SerLCD display using a hardware interface that provides io.Writer.
// That can be the i2c.Bus, or a 3rd Party UART library for serial
// communications.
func NewSerLCD(writer io.Writer, rows, cols int) *Dev {
dev := &Dev{w: writer, rows: rows, cols: cols}
_ = dev.Display(true)
return dev
}
// Create a SerLCD display using a hardware interface that provides
// conn.Conn. For example, a conn.spi.Conn
func NewConn(conn conn.Conn, rows, cols int) *Dev {
dev := &Dev{conn: conn, rows: rows, cols: cols, w: nil}
return dev
}
// Enable/Disable auto scroll
func (dev *Dev) AutoScroll(enabled bool) (err error) {
return wrap(display.ErrNotImplemented)
}
// Return the number of columns the display supports
func (dev *Dev) Cols() int {
return dev.cols
}
// Clear the display and move the cursor home.
func (dev *Dev) Clear() (err error) {
_, err = dev.Write(clear)
time.Sleep(2 * time.Millisecond)
return
}
// Set the cursor mode. You can pass multiple arguments.
// Cursor(CursorOff, CursorUnderline)
func (dev *Dev) Cursor(mode ...display.CursorMode) (err error) {
dev.displayDCB &= 0x04
for _, cmd := range mode {
switch cmd {
case display.CursorBlink:
dev.displayDCB |= 0x01
case display.CursorUnderline:
dev.displayDCB |= 0x02
case display.CursorBlock:
dev.displayDCB |= 0x01
case display.CursorOff:
default:
err = wrap(display.ErrInvalidCommand)
return
}
}
dev.displayDCB = (dev.displayDCB | 0x08) & 0xf
_, err = dev.Write([]byte{cmdMode, dev.displayDCB})
return
}
// Halt shuts down the display. If the IO source implements io.Closer, it is
// called.
func (dev *Dev) Halt() (err error) {
err = dev.Clear()
if err != nil {
return
}
err = dev.Display(false)
if err != nil {
return
}
if dev.w != nil {
if cl, ok := dev.w.(io.Closer); ok {
err = cl.Close()
}
}
return
}
// Move the cursor home (MinRow(),MinCol())
func (dev *Dev) Home() (err error) {
err = dev.MoveTo(dev.MinRow(), dev.MinCol())
time.Sleep(2 * time.Millisecond)
return
}
// Return the min column position.
func (dev *Dev) MinCol() int {
return 0
}
// Return the min row position.
func (dev *Dev) MinRow() int {
return 0
}
// Move the cursor forward or backward.
func (dev *Dev) Move(dir display.CursorDirection) (err error) {
cmdByte := byte(0x10)
switch dir {
case display.Backward:
// Nothing
case display.Forward:
cmdByte |= 0x04
case display.Down:
fallthrough
case display.Up:
fallthrough
default:
err = wrap(display.ErrNotImplemented)
return
}
_, err = dev.Write([]byte{cmdMode, cmdByte})
return
}
// Move the cursor to an arbitrary position.
func (dev *Dev) MoveTo(row, col int) (err error) {
lineOffsets := []byte{0, 64, 20, 84}
if row < dev.MinRow() || row >= dev.Rows() ||
col < dev.MinCol() || col >= dev.Cols() {
return errors.New("serlcd: invalid MoveTo() offset")
}
cmdByte := byte(0x80) + lineOffsets[row] + byte(col)
_, err = dev.Write([]byte{cmdMode, byte(cmdByte)})
return
}
// Return the number of rows the display supports.
func (dev *Dev) Rows() int {
return dev.rows
}
// Turn the display on / off
func (dev *Dev) Display(on bool) (err error) {
if on {
dev.displayDCB |= 0x04
} else {
dev.displayDCB ^= 0x04
}
_, err = dev.Write([]byte{cmdMode, (dev.displayDCB | 0x08) & 0x0f})
return
}
// return info about the display.
func (dev *Dev) String() string {
ioType := "None"
if dev.conn != nil {
ioType = "periph.io.Conn.Conn"
} else if dev.w != nil {
ioType = fmt.Sprintf("%#v", dev.w)
}
return fmt.Sprintf("SparkFun SerLCD %dx%d Display - %s", dev.cols, dev.rows, ioType)
}
// Write a set of bytes to the display.
func (dev *Dev) Write(p []byte) (n int, err error) {
if dev.w != nil {
n, err = dev.w.Write(p)
return
}
for n < len(p) {
bytesToWrite := len(p) - n
if bytesToWrite > _MAX_I2C_WRITE {
bytesToWrite = _MAX_I2C_WRITE
}
w := p[n : n+bytesToWrite]
err = dev.conn.Tx(w, nil)
if err != nil {
break
}
n = n + bytesToWrite
time.Sleep(time.Duration(40*bytesToWrite) * time.Microsecond)
}
return
}
// Write a string output to the display.
func (dev *Dev) WriteString(text string) (n int, err error) {
n, err = dev.Write([]byte(text))
return
}
// Set the backlight intensity with 0 being off, and 255 being maximum.
func (dev *Dev) Backlight(intensity display.Intensity) error {
return dev.RGBBacklight(intensity, intensity, intensity)
}
// Set the character contrast on the device. Writes to EEPROM, so this should
// be used sparingly. The default device Contrast is 40.
func (dev *Dev) Contrast(contrast display.Contrast) error {
_, err := dev.Write([]byte{settingMode, 0x18, byte(contrast)})
return err
}
// Set the backlight color with 0 being off, and 255 being maximum intensity
// for each color.
func (dev *Dev) RGBBacklight(red, green, blue display.Intensity) error {
_, err := dev.Write([]byte{settingMode, 0x2b, byte(red & 0xff), byte(green & 0xff), byte(blue & 0xff)})
return err
}
var _ display.TextDisplay = &Dev{}
var _ display.DisplayContrast = &Dev{}
var _ display.DisplayBacklight = &Dev{}
var _ display.DisplayRGBBacklight = &Dev{}
var _ conn.Resource = &Dev{}