mirror of https://github.com/periph/devices
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.
255 lines
6.1 KiB
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{}
|