mirror of https://github.com/periph/devices
Devices: Initial Add of Waveshare 1602 Support (#100)
* Initial Add of Waveshare 1602 Supportpull/102/head
parent
ea55a05aa1
commit
38cb3394bc
@ -0,0 +1,374 @@
|
|||||||
|
// Copyright 2025 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.
|
||||||
|
|
||||||
|
// The aip31068 is an HD44780 compatible I²C driver chip. It provides an I²C
|
||||||
|
// interface to an LCD. This is not a _backpack_ chip in the sense that it
|
||||||
|
// provides GPIO pins via an I²C interface. The I²C write commands go directly
|
||||||
|
// to the LCD display driver.
|
||||||
|
//
|
||||||
|
// Implements periph.io/x/conn/display/TextDisplay
|
||||||
|
//
|
||||||
|
// # Datasheet
|
||||||
|
//
|
||||||
|
// https://support.newhavendisplay.com/hc/en-us/article_attachments/4414498095511
|
||||||
|
package aip31068
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3"
|
||||||
|
"periph.io/x/conn/v3/display"
|
||||||
|
"periph.io/x/conn/v3/i2c"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
busyFlag byte = 0x80
|
||||||
|
cmdByte byte = 0xfe
|
||||||
|
dataByte byte = 0x40
|
||||||
|
moreControls byte = 0x80
|
||||||
|
packageName = "aip31068"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotImplemented = fmt.Errorf("%s: %w", packageName, display.ErrNotImplemented)
|
||||||
|
|
||||||
|
rowConstants = [][]byte{{0, 0, 64}, {0, 0, 64, 20, 84}}
|
||||||
|
clearScreen = []byte{cmdByte, 0x01}
|
||||||
|
goHome = []byte{cmdByte, 0x02}
|
||||||
|
setCursorPosition = []byte{cmdByte, 0x80}
|
||||||
|
displayMode = []byte{cmdByte, 0x20}
|
||||||
|
defaultEntryMode = []byte{cmdByte, 0x06}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Dev struct {
|
||||||
|
rows int
|
||||||
|
cols int
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
d *i2c.Dev
|
||||||
|
blink bool
|
||||||
|
on bool
|
||||||
|
cursor bool
|
||||||
|
blMono display.DisplayBacklight
|
||||||
|
blRGB display.DisplayRGBBacklight
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrap(err error) error {
|
||||||
|
if err == nil || strings.HasPrefix(err.Error(), packageName) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s: %w", packageName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates an aip31068 based LCD.
|
||||||
|
//
|
||||||
|
// backlight is a controller that manipulates the display backlight. If the
|
||||||
|
// display backlight is hard-wired on, then this can be nil. Otherwise, it
|
||||||
|
// should implement either display.DisplayBacklight or
|
||||||
|
// display.DisplayRGBBacklight.
|
||||||
|
func New(bus i2c.Bus,
|
||||||
|
address uint16,
|
||||||
|
backlight any,
|
||||||
|
rows,
|
||||||
|
cols int) (*Dev, error) {
|
||||||
|
|
||||||
|
dev := &Dev{
|
||||||
|
d: &i2c.Dev{Bus: bus, Addr: address},
|
||||||
|
rows: rows,
|
||||||
|
cols: cols,
|
||||||
|
}
|
||||||
|
switch bl := backlight.(type) {
|
||||||
|
case display.DisplayBacklight:
|
||||||
|
dev.blMono = bl
|
||||||
|
case display.DisplayRGBBacklight:
|
||||||
|
dev.blRGB = bl
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dev.init()
|
||||||
|
if err != nil {
|
||||||
|
dev = nil
|
||||||
|
}
|
||||||
|
return dev, wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the display initialization routine,
|
||||||
|
func (dev *Dev) init() error {
|
||||||
|
// Set the lines display value
|
||||||
|
var modeToSet = []byte{cmdByte, displayMode[1]}
|
||||||
|
if dev.rows > 1 {
|
||||||
|
modeToSet[1] = modeToSet[1] | 0x08
|
||||||
|
}
|
||||||
|
_, err := dev.Write(modeToSet)
|
||||||
|
if err == nil {
|
||||||
|
err = dev.Display(true)
|
||||||
|
time.Sleep(40 * time.Microsecond)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = dev.Clear()
|
||||||
|
time.Sleep(2000 * time.Microsecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = dev.Home()
|
||||||
|
time.Sleep(40 * time.Microsecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// Set the entry mode
|
||||||
|
_, err = dev.Write(defaultEntryMode)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
_ = dev.Backlight(0xff)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
err = wrap(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the row offset value
|
||||||
|
func getRowConstant(row, maxcols int) byte {
|
||||||
|
var offset int
|
||||||
|
if maxcols != 16 {
|
||||||
|
offset = 1
|
||||||
|
}
|
||||||
|
return rowConstants[offset][row]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable/Disable auto scroll
|
||||||
|
func (dev *Dev) AutoScroll(enabled bool) error {
|
||||||
|
return 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() error {
|
||||||
|
_, err := dev.Write(clearScreen)
|
||||||
|
if err != nil {
|
||||||
|
err = wrap(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the cursor mode. You can pass multiple arguments.
|
||||||
|
// Cursor(CursorOff, CursorUnderline)
|
||||||
|
func (dev *Dev) Cursor(modes ...display.CursorMode) (err error) {
|
||||||
|
var val = byte(0x08)
|
||||||
|
if dev.on {
|
||||||
|
val |= 0x04
|
||||||
|
}
|
||||||
|
for _, mode := range modes {
|
||||||
|
switch mode {
|
||||||
|
case display.CursorOff:
|
||||||
|
// dev.Write(underlineCursorOff)
|
||||||
|
dev.blink = false
|
||||||
|
dev.cursor = false
|
||||||
|
case display.CursorBlink:
|
||||||
|
dev.blink = true
|
||||||
|
dev.cursor = true
|
||||||
|
val |= 0x01
|
||||||
|
case display.CursorUnderline:
|
||||||
|
dev.cursor = true
|
||||||
|
dev.blink = true
|
||||||
|
// dev.Write(underlineCursorOn)
|
||||||
|
val |= 0x02
|
||||||
|
case display.CursorBlock:
|
||||||
|
dev.cursor = true
|
||||||
|
dev.blink = true
|
||||||
|
val |= 0x01
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Waveshare1602 - unexpected cursor: %d", mode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = dev.Write([]byte{cmdByte, val & 0x0f})
|
||||||
|
return wrap(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn the display on / off
|
||||||
|
func (dev *Dev) Display(on bool) error {
|
||||||
|
dev.on = on
|
||||||
|
val := byte(0x08)
|
||||||
|
if on {
|
||||||
|
val |= 0x04
|
||||||
|
}
|
||||||
|
if dev.blink {
|
||||||
|
val |= 0x01
|
||||||
|
}
|
||||||
|
if dev.cursor {
|
||||||
|
val |= 0x02
|
||||||
|
}
|
||||||
|
_, err := dev.Write([]byte{cmdByte, val})
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Halt clears the display, turns the backlight off, and turns the display off.
|
||||||
|
// Halt() is called for the data pins gpio.Group.
|
||||||
|
func (dev *Dev) Halt() error {
|
||||||
|
_ = dev.Clear()
|
||||||
|
_ = dev.Display(false)
|
||||||
|
_ = dev.Backlight(0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the cursor home (MinRow(),MinCol())
|
||||||
|
func (dev *Dev) Home() error {
|
||||||
|
_, err := dev.Write(goHome)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the min column position.
|
||||||
|
func (dev *Dev) MinCol() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the min row position.
|
||||||
|
func (dev *Dev) MinRow() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the cursor forward or backward.
|
||||||
|
func (dev *Dev) Move(dir display.CursorDirection) (err error) {
|
||||||
|
var val byte = 0x10
|
||||||
|
switch dir {
|
||||||
|
case display.Backward:
|
||||||
|
|
||||||
|
case display.Forward:
|
||||||
|
val |= 0x04
|
||||||
|
case display.Down, display.Up:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
err = ErrNotImplemented
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = dev.Write([]byte{cmdByte, val})
|
||||||
|
err = wrap(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the cursor to arbitrary position.
|
||||||
|
func (dev *Dev) MoveTo(row, col int) (err error) {
|
||||||
|
if row < dev.MinRow() || row > dev.rows || col < dev.MinCol() || col > dev.cols {
|
||||||
|
err = fmt.Errorf("%s.MoveTo(%d,%d) value out of range", packageName, row, col)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var cmd = []byte{cmdByte, setCursorPosition[1]}
|
||||||
|
cmd[1] |= getRowConstant(row, dev.cols) + byte(col-1)
|
||||||
|
_, err = dev.Write(cmd)
|
||||||
|
err = wrap(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the number of rows the display supports.
|
||||||
|
func (dev *Dev) Rows() int {
|
||||||
|
return dev.rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev *Dev) String() string {
|
||||||
|
return fmt.Sprintf("%s Rows: %d Cols: %d", packageName, dev.rows, dev.cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the busy flag to make sure it's clear to write. It's a little wonky
|
||||||
|
// initially but then smooths out, so it makes a best effort and ignores errors.
|
||||||
|
func (dev *Dev) waitForFree() {
|
||||||
|
tLimit := time.Now().Add(3 * time.Millisecond)
|
||||||
|
w := make([]byte, 2)
|
||||||
|
r := make([]byte, 1)
|
||||||
|
for time.Now().Before(tLimit) {
|
||||||
|
err := dev.d.Tx(w, r)
|
||||||
|
if err == nil && (r[0]&busyFlag) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a set of bytes to the display. This routine handles control
|
||||||
|
// and data characters transparently.
|
||||||
|
func (dev *Dev) Write(p []byte) (n int, err error) {
|
||||||
|
dev.mu.Lock()
|
||||||
|
defer dev.mu.Unlock()
|
||||||
|
dev.waitForFree()
|
||||||
|
|
||||||
|
lastControl := -1
|
||||||
|
for i := range len(p) {
|
||||||
|
if p[i] == cmdByte {
|
||||||
|
lastControl = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w := make([]byte, 0, len(p))
|
||||||
|
|
||||||
|
for pos := 0; pos < len(p); {
|
||||||
|
|
||||||
|
// So, when we're writing, we need to send a control byte first
|
||||||
|
// that says type data, or cmd. We then send the bytes. If the
|
||||||
|
// type changes, then we need to send a new control byte.
|
||||||
|
//
|
||||||
|
// If there are more control bytes, then the control byte has bit 7
|
||||||
|
// set, and we send a control byte for each character sent.
|
||||||
|
var controlByte byte = 0x00
|
||||||
|
if p[pos] == cmdByte {
|
||||||
|
pos += 1
|
||||||
|
} else {
|
||||||
|
controlByte |= dataByte
|
||||||
|
}
|
||||||
|
if pos < lastControl {
|
||||||
|
controlByte |= moreControls
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos - 1) <= lastControl {
|
||||||
|
w = append(w, controlByte)
|
||||||
|
}
|
||||||
|
|
||||||
|
w = append(w, p[pos])
|
||||||
|
pos += 1
|
||||||
|
}
|
||||||
|
err = dev.d.Tx(w, nil)
|
||||||
|
if err == nil {
|
||||||
|
n = len(p)
|
||||||
|
}
|
||||||
|
err = wrap(err)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a string output to the display.
|
||||||
|
func (dev *Dev) WriteString(text string) (n int, err error) {
|
||||||
|
return dev.Write([]byte(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the backlight intensity.
|
||||||
|
func (dev *Dev) Backlight(intensity display.Intensity) error {
|
||||||
|
if dev.blMono != nil {
|
||||||
|
return dev.blMono.Backlight(intensity)
|
||||||
|
} else if dev.blRGB != nil {
|
||||||
|
return dev.blRGB.RGBBacklight(intensity, intensity, intensity)
|
||||||
|
}
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// For units that have an RGB Backlight, set the backlight color/intensity.
|
||||||
|
// The range of the values is 0-255.
|
||||||
|
func (dev *Dev) RGBBacklight(red, green, blue display.Intensity) error {
|
||||||
|
if dev.blRGB != nil {
|
||||||
|
return dev.blRGB.RGBBacklight(red, green, blue)
|
||||||
|
} else if dev.blMono != nil {
|
||||||
|
return dev.blMono.Backlight(red | green | blue)
|
||||||
|
}
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ conn.Resource = &Dev{}
|
||||||
|
var _ display.TextDisplay = &Dev{}
|
||||||
|
var _ display.DisplayBacklight = &Dev{}
|
||||||
|
var _ display.DisplayRGBBacklight = &Dev{}
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2025 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.
|
||||||
|
|
||||||
|
package aip31068_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/display"
|
||||||
|
"periph.io/x/conn/v3/display/displaytest"
|
||||||
|
"periph.io/x/conn/v3/i2c/i2ctest"
|
||||||
|
"periph.io/x/devices/v3/aip31068"
|
||||||
|
"periph.io/x/devices/v3/waveshare1602"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pause time.Duration = 0
|
||||||
|
var liveDevice bool
|
||||||
|
|
||||||
|
func getDev(recordingName string) (*aip31068.Dev, error) {
|
||||||
|
bus := &i2ctest.Playback{Ops: recordingData[recordingName], DontPanic: true}
|
||||||
|
dev, err := waveshare1602.New(bus, waveshare1602.LCD1602RGBBacklight, 2, 16)
|
||||||
|
return dev, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasic(t *testing.T) {
|
||||||
|
dev, err := getDev("TestBasic")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s := dev.String()
|
||||||
|
if len(s) == 0 {
|
||||||
|
t.Error("error on String()")
|
||||||
|
}
|
||||||
|
t.Log(s)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_ = dev.Halt()
|
||||||
|
})
|
||||||
|
|
||||||
|
err = dev.Clear()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = dev.Backlight(0xff)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
n, err := dev.WriteString("aip31068")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if n != 8 {
|
||||||
|
t.Error("expected 8 bytes written")
|
||||||
|
}
|
||||||
|
time.Sleep(5 * pause)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComplete(t *testing.T) {
|
||||||
|
dev, err := getDev("TestComplete")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_ = dev.Halt()
|
||||||
|
})
|
||||||
|
testErrs := displaytest.TestTextDisplay(dev, liveDevice)
|
||||||
|
for _, err := range testErrs {
|
||||||
|
if !errors.Is(err, display.ErrNotImplemented) {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBacklights(t *testing.T) {
|
||||||
|
dev, err := getDev("TestBacklights")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_ = dev.Halt()
|
||||||
|
})
|
||||||
|
|
||||||
|
for ix := range 3 {
|
||||||
|
leds := make([]display.Intensity, 3)
|
||||||
|
leds[ix] = 0xff
|
||||||
|
err = dev.RGBBacklight(leds[0], leds[1], leds[2])
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
time.Sleep(pause)
|
||||||
|
}
|
||||||
|
err = dev.Backlight(0)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
time.Sleep(pause)
|
||||||
|
err = dev.Backlight(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
time.Sleep(pause)
|
||||||
|
}
|
||||||
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright 2025 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.
|
||||||
|
|
||||||
|
package aip31068_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"periph.io/x/conn/v3/i2c/i2ctest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auto-Generated by i2ctest.BusTest
|
||||||
|
|
||||||
|
var recordingData = map[string][]i2ctest.IO{
|
||||||
|
"TestBacklights": {
|
||||||
|
{Addr: 0x60, W: []uint8{0x0, 0x81}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x1, 0x5}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x28}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x2}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x6}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x15}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x4, 0xff}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x20}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x4}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x1}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x0}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x2, 0x1}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x3, 0x1}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x4, 0x1}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x2a}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x8}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x0}}},
|
||||||
|
"TestBasic": {
|
||||||
|
{Addr: 0x60, W: []uint8{0x0, 0x81}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x1, 0x5}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x28}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x2}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x6}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x15}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x2, 0xff}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x3, 0xff}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x4, 0xff}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x2a}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x61, 0x69, 0x70, 0x33, 0x31, 0x30, 0x36, 0x38}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x8}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x0}}},
|
||||||
|
"TestComplete": {
|
||||||
|
{Addr: 0x60, W: []uint8{0x0, 0x81}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x1, 0x5}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x28}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x2}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x6}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x15}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x61, 0x69, 0x70, 0x33, 0x31, 0x30, 0x36, 0x38, 0x20, 0x52, 0x6f, 0x77, 0x73, 0x3a, 0x20, 0x32, 0x20, 0x43, 0x6f, 0x6c, 0x73, 0x3a, 0x20, 0x31, 0x36}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x41, 0x75, 0x74, 0x6f, 0x20, 0x53, 0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x20, 0x54, 0x65, 0x73, 0x74}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x80}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x41}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x42}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x43}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x44}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x45}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x20}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x47}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x48}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x49}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x4a}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x20}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x4c}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x4d}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x4e}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x4f}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x20}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc0}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x41}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x42}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x43}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x44}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x45}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x20}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x47}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x48}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x49}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x4a}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x20}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x4c}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x4d}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x4e}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x4f}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x20}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x61, 0x75, 0x74, 0x6f, 0x20, 0x73, 0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x20, 0x68, 0x61, 0x70, 0x70, 0x65, 0x6e}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x41, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x20, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x80}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x28, 0x31, 0x2c, 0x31, 0x29}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x28, 0x32, 0x2c, 0x32, 0x29}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x80}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x4f, 0x66, 0x66}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x80}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x55, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x65}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xe}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x80}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xd}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x80}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x42, 0x6c, 0x69, 0x6e, 0x6b}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xd}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x3e}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x14}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x14}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x30}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x10}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x31}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x10}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x32}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x10}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x33}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x10}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x34}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x10}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x35}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x10}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x36}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x10}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x37}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x10}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x38}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x10}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x39}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x10}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x53, 0x65, 0x74, 0x20, 0x64, 0x65, 0x76, 0x20, 0x6f, 0x66, 0x66}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x8}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0xc}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x40, 0x53, 0x65, 0x74, 0x20, 0x64, 0x65, 0x76, 0x20, 0x6f, 0x6e}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x1}},
|
||||||
|
{Addr: 0x3e, W: []uint8{0x0, 0x8}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x0}}},
|
||||||
|
}
|
||||||
@ -0,0 +1,212 @@
|
|||||||
|
// Copyright 2025 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.
|
||||||
|
|
||||||
|
// The PCA9633 is a four-channel LED PWM controller. Additionally, it provides
|
||||||
|
// features for dimming and blink.
|
||||||
|
//
|
||||||
|
// # Datasheet
|
||||||
|
//
|
||||||
|
// https://www.nxp.com/docs/en/data-sheet/PCA9633.pdf
|
||||||
|
package pca9633
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/display"
|
||||||
|
"periph.io/x/conn/v3/i2c"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LEDStructure byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LEDs are connected in OpenDrain format
|
||||||
|
STRUCT_OPENDRAIN LEDStructure = iota
|
||||||
|
// LEDs are connected in TotemPole format.
|
||||||
|
STRUCT_TOTEMPOLE
|
||||||
|
)
|
||||||
|
|
||||||
|
type LEDMode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
MODE_FULL_OFF LEDMode = iota
|
||||||
|
MODE_FULL_ON
|
||||||
|
// The brightness of the LED is controlled by the PWM setting.
|
||||||
|
MODE_PWM
|
||||||
|
// The brightness of the LED is controlled by the PWM setting AND the group
|
||||||
|
// PWM/blinking options.
|
||||||
|
MODE_PWM_PLUS_GROUP
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Register offsets from the datasheet
|
||||||
|
_DEV_MODE1 byte = iota
|
||||||
|
_DEV_MODE2
|
||||||
|
_PWM0
|
||||||
|
_PWM1
|
||||||
|
_PWM2
|
||||||
|
_PWM3
|
||||||
|
_GRPPWM
|
||||||
|
_GRPFREQ
|
||||||
|
_LED_MODE
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_DEV_MODE_BLINK byte = 0x20
|
||||||
|
_DEV_MODE_TOTEM byte = 0x08
|
||||||
|
_DEV_MODE_INVERT byte = 0x10
|
||||||
|
_DEV_MODE2_DEFAULT byte = 0x05
|
||||||
|
_DEV_MODE1_DEFAULT byte = 0x81
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dev represents a PCA9633 LED PWM Controller.
|
||||||
|
type Dev struct {
|
||||||
|
d *i2c.Dev
|
||||||
|
modes []LEDMode
|
||||||
|
// bit settings for device mode register 2
|
||||||
|
devMode2 byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns an initialized PCA9633 device ready for use.
|
||||||
|
func New(bus i2c.Bus, address uint16, ledStructure LEDStructure) (*Dev, error) {
|
||||||
|
dev := &Dev{d: &i2c.Dev{Bus: bus, Addr: address},
|
||||||
|
modes: make([]LEDMode, 4),
|
||||||
|
devMode2: _DEV_MODE2_DEFAULT}
|
||||||
|
|
||||||
|
if ledStructure == STRUCT_TOTEMPOLE {
|
||||||
|
dev.devMode2 |= _DEV_MODE_TOTEM
|
||||||
|
}
|
||||||
|
return dev, dev.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev *Dev) init() error {
|
||||||
|
// We have to write 0 to bit 5 to turn on the PWM oscillator...
|
||||||
|
err := dev.d.Tx([]byte{_DEV_MODE1, _DEV_MODE1_DEFAULT}, nil)
|
||||||
|
if err == nil {
|
||||||
|
err = dev.d.Tx([]byte{_DEV_MODE2, dev.devMode2}, nil)
|
||||||
|
if err == nil {
|
||||||
|
err = dev.SetModes(MODE_FULL_OFF, MODE_FULL_OFF, MODE_FULL_OFF, MODE_FULL_OFF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrap(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("pca9633: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Halt stops all LED display by setting them all to MODE_FULL_OFF. Implements
|
||||||
|
// conn.Resource
|
||||||
|
func (dev *Dev) Halt() error {
|
||||||
|
return dev.SetModes(MODE_FULL_OFF, MODE_FULL_OFF, MODE_FULL_OFF, MODE_FULL_OFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the output intensity for LEDs. If intensity is 0, the LED is set to full
|
||||||
|
// off. If intensity==255, the LED is set to full on, otherwise the LED is PWMd
|
||||||
|
// to the desired intensity.
|
||||||
|
func (dev *Dev) Out(intensities ...display.Intensity) error {
|
||||||
|
newModes := make([]LEDMode, len(dev.modes))
|
||||||
|
copy(newModes, dev.modes)
|
||||||
|
for ix := range len(intensities) {
|
||||||
|
if intensities[ix] == 0 {
|
||||||
|
newModes[ix] = MODE_FULL_OFF
|
||||||
|
} else if intensities[ix] >= 0xff && dev.modes[ix] == MODE_FULL_OFF {
|
||||||
|
newModes[ix] = MODE_FULL_ON
|
||||||
|
} else {
|
||||||
|
if dev.modes[ix] != MODE_PWM && dev.modes[ix] != MODE_PWM_PLUS_GROUP {
|
||||||
|
newModes[ix] = MODE_PWM
|
||||||
|
}
|
||||||
|
err := dev.d.Tx([]byte{_PWM0 + byte(ix), byte(intensities[ix])}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return wrap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dev.SetModes(newModes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGroupPWMBlink sets the group level PWM value, and optionally, a blink
|
||||||
|
// duration. Blink duration can range from 41,666 uS to 10.625 S. If 0, blink
|
||||||
|
// is disabled.
|
||||||
|
//
|
||||||
|
// Refer to the datasheet on this functionality. If the mode is not blink,
|
||||||
|
// then it's group PWM, but group PWM is only applied if the individual led
|
||||||
|
// mode is MODE_PWM_PLUS_GROUP
|
||||||
|
func (dev *Dev) SetGroupPWMBlink(intensity display.Intensity, blinkDuration time.Duration) error {
|
||||||
|
periodIncrement := 41_666 * time.Microsecond
|
||||||
|
newDevMode := dev.devMode2
|
||||||
|
if blinkDuration >= periodIncrement {
|
||||||
|
// calculate the duration value.
|
||||||
|
var blinkSetting int
|
||||||
|
cnt := int(blinkDuration / periodIncrement)
|
||||||
|
if cnt < 0 {
|
||||||
|
blinkSetting = 0
|
||||||
|
} else if cnt > 0xff {
|
||||||
|
blinkSetting = 0xff
|
||||||
|
} else {
|
||||||
|
blinkSetting = cnt
|
||||||
|
}
|
||||||
|
if blinkSetting == 0 {
|
||||||
|
newDevMode ^= _DEV_MODE_BLINK
|
||||||
|
} else {
|
||||||
|
err := dev.d.Tx([]byte{_GRPFREQ, byte(blinkSetting)}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return wrap(err)
|
||||||
|
}
|
||||||
|
if dev.devMode2&_DEV_MODE_BLINK != _DEV_MODE_BLINK {
|
||||||
|
newDevMode |= _DEV_MODE_BLINK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if dev.devMode2&_DEV_MODE_BLINK == _DEV_MODE_BLINK {
|
||||||
|
newDevMode ^= _DEV_MODE_BLINK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if newDevMode != dev.devMode2 {
|
||||||
|
err := dev.d.Tx([]byte{_DEV_MODE2, newDevMode}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return wrap(err)
|
||||||
|
}
|
||||||
|
dev.devMode2 = newDevMode
|
||||||
|
}
|
||||||
|
err := dev.d.Tx([]byte{_GRPPWM, byte(intensity)}, nil)
|
||||||
|
return wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetInvert allows you to easily invert the meaning of the PWM values. This
|
||||||
|
// is useful if you're driving LEDs with a transistor or other device that
|
||||||
|
// inverts the output.
|
||||||
|
func (dev *Dev) SetInvert(invert bool) error {
|
||||||
|
if invert {
|
||||||
|
dev.devMode2 |= _DEV_MODE_INVERT
|
||||||
|
} else {
|
||||||
|
dev.devMode2 ^= _DEV_MODE_INVERT
|
||||||
|
}
|
||||||
|
err := dev.d.Tx([]byte{_DEV_MODE2, dev.devMode2}, nil)
|
||||||
|
return wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetModes sets the output mode of LEDs. The value for modes should be
|
||||||
|
// one of the LEDMode constants.
|
||||||
|
func (dev *Dev) SetModes(modes ...LEDMode) error {
|
||||||
|
var mode byte
|
||||||
|
var changed bool
|
||||||
|
for i := range len(modes) {
|
||||||
|
changed = changed || (modes[i] != dev.modes[i])
|
||||||
|
mode |= (byte(modes[i]) << (i * 2))
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copy(dev.modes, modes)
|
||||||
|
err := dev.d.Tx([]byte{_LED_MODE, mode}, nil)
|
||||||
|
return wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev *Dev) String() string {
|
||||||
|
return fmt.Sprintf("PCA9633::%#v", dev.d)
|
||||||
|
}
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright 2025 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.
|
||||||
|
|
||||||
|
package pca9633
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/display"
|
||||||
|
"periph.io/x/conn/v3/i2c/i2ctest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var recordingData = map[string][]i2ctest.IO{
|
||||||
|
"TestBasic": {
|
||||||
|
{Addr: 0x60, W: []uint8{0x0, 0x81}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x1, 0x5}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x1}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x4}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x10}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x40}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x1, 0x15}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x1, 0x5}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x6, 0x80}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x3f}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x2, 0xff}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x3, 0xff}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x4, 0xff}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x7, 0x30}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x1, 0x25}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x6, 0x80}},
|
||||||
|
{Addr: 0x60, W: []uint8{0x8, 0x0}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasic(t *testing.T) {
|
||||||
|
bus := &i2ctest.Playback{Ops: recordingData["TestBasic"]}
|
||||||
|
dev, err := New(bus, 0x60, STRUCT_OPENDRAIN)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range 4 {
|
||||||
|
values := make([]display.Intensity, 4)
|
||||||
|
values[i] = 0xff
|
||||||
|
err = dev.Out(values...)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dev.SetInvert(true)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = dev.SetInvert(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dev.SetGroupPWMBlink(0x80, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = dev.SetModes(MODE_PWM_PLUS_GROUP, MODE_PWM_PLUS_GROUP, MODE_PWM_PLUS_GROUP, MODE_FULL_OFF)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dev.Out(0xff, 0xff, 0xff)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dev.SetGroupPWMBlink(0x80, 2*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := dev.String()
|
||||||
|
if len(s) == 0 {
|
||||||
|
t.Error("empty string")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dev.Halt()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2025 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.
|
||||||
|
|
||||||
|
// The Waveshare 1602 LCD is a 2 line by 16 column LCD display. It's available
|
||||||
|
// in multiple variants:
|
||||||
|
//
|
||||||
|
// - LCD1602 5V Blue Backlight
|
||||||
|
// - LCD1602 3.3V Yellow Backlight
|
||||||
|
// - LCD1602 3.3V Blue Backlight
|
||||||
|
//
|
||||||
|
// These are bare LCD displays with no backpack. They have an hd44780 compatible
|
||||||
|
// driver chip. Use the driver located in the [hd44780] package.
|
||||||
|
//
|
||||||
|
// - LCD1602 I²C Module, White color w/ Blue Background, 16x2 characters, 3.3V/5V
|
||||||
|
// - LCD1602 I²C Module, Options for 3 Colors 3.3v/5v Backlight Adjustable
|
||||||
|
//
|
||||||
|
// These displays use the [aip31068] I²C LCD Driver chip. The command set is
|
||||||
|
// compatible with the HD44780. The tri-color version has purchase options to
|
||||||
|
// select a backlight color and uses an SN3193 to dim the backlight.
|
||||||
|
//
|
||||||
|
// - LCD1602 RGB Module, 16x2 Characters LCD, RGB Backlight, 3.3V/5V, I²C Bus
|
||||||
|
//
|
||||||
|
// This display uses the AiP31068 I²C LCD Driver w/ a PCA9633 RGB LED PWM
|
||||||
|
// controller.
|
||||||
|
package waveshare1602
|
||||||
|
|
||||||
|
import (
|
||||||
|
"periph.io/x/conn/v3/display"
|
||||||
|
"periph.io/x/conn/v3/i2c"
|
||||||
|
"periph.io/x/devices/v3/aip31068"
|
||||||
|
"periph.io/x/devices/v3/pca9633"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Variant string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SKU 19537 - RGB Backlight
|
||||||
|
LCD1602RGBBacklight Variant = "LCD1602RGBBacklight"
|
||||||
|
// SKU 23991 - I²C w/ Monochrome Backlight
|
||||||
|
LCD1602MonoBacklight Variant = "LCD1602MonoBacklight"
|
||||||
|
// Not Implemented. SKU 30494, 30495, and 30496. Uses an SN3193 for
|
||||||
|
// controlling the backlight.
|
||||||
|
LCD1602DimmableMonoBacklight Variant = "LCD1602DimmableMonoBacklight"
|
||||||
|
|
||||||
|
_LCD_ADDRESS uint16 = 0x3e
|
||||||
|
_RGB_ADDRESS uint16 = 0x60
|
||||||
|
)
|
||||||
|
|
||||||
|
type RGBBLController struct {
|
||||||
|
controller *pca9633.Dev
|
||||||
|
variant Variant
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new LCD display.
|
||||||
|
func New(bus i2c.Bus, variant Variant, rows, cols int) (*aip31068.Dev, error) {
|
||||||
|
var bl any
|
||||||
|
|
||||||
|
if variant == LCD1602RGBBacklight {
|
||||||
|
blcontroller, err := pca9633.New(bus, _RGB_ADDRESS, pca9633.STRUCT_OPENDRAIN)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bl = &RGBBLController{variant: variant, controller: blcontroller}
|
||||||
|
} else if variant == LCD1602DimmableMonoBacklight {
|
||||||
|
return nil, display.ErrNotImplemented
|
||||||
|
}
|
||||||
|
return aip31068.New(bus, _LCD_ADDRESS, bl, rows, cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *RGBBLController) String() string {
|
||||||
|
return string(bl.variant)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For units that have an RGB Backlight, set the backlight color/intensity.
|
||||||
|
// This unit does not persist settings in EEPROM, so you can call it as often
|
||||||
|
// as desired. The range of the values is 0-255.
|
||||||
|
func (bl *RGBBLController) RGBBacklight(red, green, blue display.Intensity) error {
|
||||||
|
// The device is really connected to the LEDs in this channel order...
|
||||||
|
return bl.controller.Out(blue, green, red)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ display.DisplayRGBBacklight = &RGBBLController{}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2025 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.
|
||||||
|
|
||||||
|
package waveshare1602_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/display"
|
||||||
|
"periph.io/x/conn/v3/display/displaytest"
|
||||||
|
"periph.io/x/conn/v3/i2c/i2creg"
|
||||||
|
"periph.io/x/devices/v3/waveshare1602"
|
||||||
|
"periph.io/x/host/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
// Make sure periph is initialized.
|
||||||
|
if _, err := host.Init(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open default I²C bus.
|
||||||
|
bus, err := i2creg.Open("")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to open I²C: %v", err)
|
||||||
|
}
|
||||||
|
defer bus.Close()
|
||||||
|
dev, err := waveshare1602.New(bus, waveshare1602.LCD1602RGBBacklight, 2, 16)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
_ = dev.Backlight(display.Intensity(0xff))
|
||||||
|
_ = dev.Clear()
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
_, _ = dev.WriteString("Hello")
|
||||||
|
_ = dev.MoveTo(2, 2)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
_, _ = dev.WriteString("1234567890")
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
displaytest.TestTextDisplay(dev, true)
|
||||||
|
_ = dev.Halt()
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue