mirror of https://github.com/periph/devices
hd44780: Restructure to implement conn/display/TextDisplay. Refine to use GPIO.Group (#91)
* Add support for Adafruit I2C/SPI Backpack * Add support for PCF857x Backpacks * Add support for conn/gpio/Group * Implement conn/display/TextDisplay * Add examples * Cleanups * Change backlight implementationpull/109/head
parent
bc9678aa18
commit
9a7f7ac5da
@ -0,0 +1,60 @@
|
|||||||
|
# Hitachi HD44780 Package
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Hitachi HD44780 is an LCD driver chip. It's used in a variety of text LCD
|
||||||
|
displays. The datasheet is available here:
|
||||||
|
|
||||||
|
https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
|
||||||
|
|
||||||
|
Generally, there are three kinds of displays that use this chip.
|
||||||
|
|
||||||
|
1. A raw display. This is a board with a row of pin connectors at the top. It's
|
||||||
|
made for interfacing directly to GPIO pins. This is the most complicated method
|
||||||
|
of using the LCD because you have to wire a minimum of 6 GPIO pins (4 data, 1
|
||||||
|
reset, 1 enable) and 2 power pins to make it work.
|
||||||
|
|
||||||
|
There are also complex initialization routines that have to be performed. This
|
||||||
|
is further complicated by having specific initialization calls depending upon
|
||||||
|
whether the device is connected to 4 data lines, or 8 data lines.
|
||||||
|
|
||||||
|
2. A backpack display. With this type of LCD display, an I2C, serial, or SPI
|
||||||
|
interface is provided. This type of display is easier because there are fewer
|
||||||
|
pins, but the complex intialization remains. Examples of backpack interfaces
|
||||||
|
include The Adafruit I2C/SPI backpack (MCP23008/74HC595D), the generic
|
||||||
|
LCDXXXX/PCF8547T backpack, etc.
|
||||||
|
|
||||||
|
3. The third and final variety is the "Intelligent" display. These displays have
|
||||||
|
a micro-controller that is connected to the LCD chip. Typically these intelligent
|
||||||
|
displays support multiple I/O methods and the micro-controller handles the
|
||||||
|
LCD initialization/communication.
|
||||||
|
|
||||||
|
Examples of this kind of display would be the SparkFun SerLCD display, the
|
||||||
|
MatrixOrbital LK2047T, the AdaFruit USB+Serial Backpack etc.
|
||||||
|
|
||||||
|
## Interfacing Notes
|
||||||
|
|
||||||
|
The driver package is designed to use the gpio.Group interface. This allows
|
||||||
|
the LCD driver to be agnostic about the physical connection between the display
|
||||||
|
and the host device. Any host/expander that supports the
|
||||||
|
periph.io/x/conn/v3/gpio.Group interface can be used to easily drive the LCD
|
||||||
|
display.
|
||||||
|
|
||||||
|
## Hardware Notes
|
||||||
|
|
||||||
|
DO NOT attempt to source VCC for the unit backlight, or sink VCC to ground.
|
||||||
|
The backlight draws ~250ma of current which exceeds the current capability
|
||||||
|
of GPIO pins. It will permanently damage your device. If you would like to
|
||||||
|
control the backlight, connect a GPIO pin through a 1K Ohm resistor to a
|
||||||
|
transistor (2N2222 or equivalent).
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If nothing displays at all, check the contrast. Adjust the contrast control
|
||||||
|
until the 5x7 dot grid on the display is visible.
|
||||||
|
|
||||||
|
If the first row contains blocks of dots, and the other rows are blank, then
|
||||||
|
the initialization of the device failed. Check IO pins are connected properly.
|
||||||
|
|
||||||
|
If the text is garbled, verify the gpio.Group is configured and the IO Pins
|
||||||
|
are connected to the right pins of the LCD display.
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
// 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 hd44780
|
||||||
|
|
||||||
|
import (
|
||||||
|
"periph.io/x/conn/v3/gpio"
|
||||||
|
"periph.io/x/conn/v3/i2c"
|
||||||
|
"periph.io/x/conn/v3/spi"
|
||||||
|
"periph.io/x/devices/v3/mcp23xxx"
|
||||||
|
"periph.io/x/devices/v3/nxp74hc595"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Name is the LCD pin name, and the integer value is the GPIO
|
||||||
|
// number (not physical) of the MCP23008 I2C GPIO Expander.
|
||||||
|
d4 = 3
|
||||||
|
d5 = 4
|
||||||
|
d6 = 5
|
||||||
|
d7 = 6
|
||||||
|
rsPin = 1
|
||||||
|
enablePin = 2
|
||||||
|
backlightPin = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
// This function returns a display configured to use the Adafruit I2C/SPI LCD Backpack.
|
||||||
|
//
|
||||||
|
// # Product Information
|
||||||
|
//
|
||||||
|
// https://www.adafruit.com/product/292
|
||||||
|
//
|
||||||
|
// The I2C side of this backpack uses an MCP23008 I/O expander. This function
|
||||||
|
// creates an MCP23008 device with the required pin configuration. To use this,
|
||||||
|
// get an I2C bus, and call this function with the bus, i2c address, number of
|
||||||
|
// rows, and columns.
|
||||||
|
func NewAdafruitI2CBackpack(bus i2c.Bus, address uint16, rows, cols int) (*HD44780, error) {
|
||||||
|
mcp, err := mcp23xxx.NewI2C(bus, mcp23xxx.MCP23008, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gr := *mcp.Group(0, []int{d4, d5, d6, d7, rsPin, enablePin, backlightPin})
|
||||||
|
reset, _ := gr.ByOffset(4).(gpio.PinOut)
|
||||||
|
enable, _ := gr.ByOffset(5).(gpio.PinOut)
|
||||||
|
bl := gr.ByOffset(6).(gpio.PinOut)
|
||||||
|
return NewHD44780(gr, reset, enable, NewBacklight(bl), rows, cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function returns a display configured to use the SPI side of the Adafruit
|
||||||
|
// I2c/SPI backpack. The SPI side uses a 74HC595 Serial->Parallel shift register.
|
||||||
|
func NewAdafruitSPIBackpack(conn spi.Conn, rows, cols int) (*HD44780, error) {
|
||||||
|
chip, err := nxp74hc595.New(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// The SPI side has the same pins but in reverse order from the I2C side.
|
||||||
|
gr, _ := chip.Group(d7, d6, d5, d4)
|
||||||
|
rs := chip.Pins[rsPin]
|
||||||
|
e := chip.Pins[enablePin]
|
||||||
|
bl := chip.Pins[backlightPin]
|
||||||
|
return NewHD44780(gr, rs, e, NewBacklight(bl), rows, cols)
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
// 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 hd44780
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/display"
|
||||||
|
"periph.io/x/conn/v3/gpio"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A monochrome backlight Implements display.Backlight It uses a single
|
||||||
|
// GPIO Pin to turn the backlight on or off.
|
||||||
|
type GPIOMonoBacklight struct {
|
||||||
|
blPin gpio.PinOut
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a GPIO pin that turns the backlight on/off, construct a monobacklight
|
||||||
|
// to use with HD44780.
|
||||||
|
func NewBacklight(blPin gpio.PinOut) *GPIOMonoBacklight {
|
||||||
|
return &GPIOMonoBacklight{blPin: blPin}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn the display backlight on or off.
|
||||||
|
func (bl *GPIOMonoBacklight) Backlight(intensity display.Intensity) (err error) {
|
||||||
|
if intensity == 0 {
|
||||||
|
err = bl.blPin.Out(gpio.Low)
|
||||||
|
} else {
|
||||||
|
err = bl.blPin.Out(gpio.High)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *GPIOMonoBacklight) String() string {
|
||||||
|
return fmt.Sprintf("%#v", bl)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ display.DisplayBacklight = &GPIOMonoBacklight{}
|
||||||
@ -0,0 +1,225 @@
|
|||||||
|
// Copyright 2018 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 hd44780
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3"
|
||||||
|
"periph.io/x/conn/v3/gpio"
|
||||||
|
)
|
||||||
|
|
||||||
|
// lineTwo offset for the second line in the LCD buffer.
|
||||||
|
const lineTwo = 0x40
|
||||||
|
|
||||||
|
// Deprecated: Use HD44780
|
||||||
|
type Dev struct {
|
||||||
|
// data pins
|
||||||
|
dataPins []gpio.PinOut
|
||||||
|
|
||||||
|
// register select pin
|
||||||
|
rsPin gpio.PinOut
|
||||||
|
|
||||||
|
// enable pin
|
||||||
|
enablePin gpio.PinOut
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NewHD44780()
|
||||||
|
// New creates and initializes the LCD device
|
||||||
|
//
|
||||||
|
// data - references to data pins
|
||||||
|
// rs - rs pin
|
||||||
|
// e - strobe pin
|
||||||
|
func New(data []gpio.PinOut, rs, e gpio.PinOut) (*Dev, error) {
|
||||||
|
if len(data) != 4 {
|
||||||
|
return nil, fmt.Errorf("expected 4 data pins, passed %d", len(data))
|
||||||
|
}
|
||||||
|
dev := &Dev{
|
||||||
|
dataPins: data,
|
||||||
|
enablePin: e,
|
||||||
|
rsPin: rs,
|
||||||
|
}
|
||||||
|
if err := dev.Reset(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the HC-44780 chipset, clears the screen buffer and moves cursor to the
|
||||||
|
// home of screen (line 0, column 0).
|
||||||
|
func (r *Dev) Reset() error {
|
||||||
|
if err := r.clearBits(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
delayMs(15)
|
||||||
|
|
||||||
|
if err := r.rsPin.Out(gpio.Low); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.enablePin.Out(gpio.Low); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.bulkSendData(resetSequence, r.write4Bits); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.bulkSendData(initSequence, r.writeInstruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Dev) String() string {
|
||||||
|
return "HD44870, 4 bit mode"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Halt clears the LCD screen
|
||||||
|
func (r *Dev) Halt() error {
|
||||||
|
if err := r.writeInstruction(0x01); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delayMs(2)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCursor positions the cursor
|
||||||
|
//
|
||||||
|
// line - screen line, 0-based
|
||||||
|
// column - column, 0-based
|
||||||
|
func (r *Dev) SetCursor(line uint8, column uint8) error {
|
||||||
|
return r.writeInstruction(0x80 | (line*lineTwo + column))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the data string
|
||||||
|
//
|
||||||
|
// data string to display
|
||||||
|
func (r *Dev) Print(data string) error {
|
||||||
|
for _, v := range []byte(data) {
|
||||||
|
if err := r.WriteChar(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteChar writes a single byte (character) at the cursor position.
|
||||||
|
//
|
||||||
|
// data - character code
|
||||||
|
func (r *Dev) WriteChar(data uint8) error {
|
||||||
|
if err := r.sendData(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.write4Bits(data >> 4); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.write4Bits(data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delayUs(10)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// service methods
|
||||||
|
|
||||||
|
var resetSequence = [][]uint{
|
||||||
|
{0x03, 50}, // init 1-st cycle
|
||||||
|
{0x03, 10}, // init 2-nd cycle
|
||||||
|
{0x03, 10}, // init 3-rd cycle
|
||||||
|
{0x02, 10}, // init finish
|
||||||
|
}
|
||||||
|
|
||||||
|
var initSequence = [][]uint{
|
||||||
|
{0x14, 0}, // 4-bit mode, 2 lines, 5x7 chars high
|
||||||
|
{0x10, 0}, // disable display
|
||||||
|
{0x01, 2000}, // clear screen
|
||||||
|
{0x06, 0}, // cursor shift right, no display move
|
||||||
|
{0x0c, 0}, // enable display no cursor
|
||||||
|
{0x01, 2000}, // clear screen
|
||||||
|
{0x02, 2000}, // cursor home
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Dev) bulkSendData(seq [][]uint, f func(_data uint8) error) error {
|
||||||
|
for _, v := range seq {
|
||||||
|
if err := f(uint8(v[0])); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if v[1] > 0 {
|
||||||
|
delayUs(v[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Dev) clearBits() error {
|
||||||
|
for _, v := range r.dataPins {
|
||||||
|
if err := v.Out(gpio.Low); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Dev) write4Bits(data uint8) error {
|
||||||
|
for i, v := range r.dataPins {
|
||||||
|
if data&(1<<uint(i)) > 0 {
|
||||||
|
if err := v.Out(gpio.High); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := v.Out(gpio.Low); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.strobe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Dev) sendInstruction() error {
|
||||||
|
if err := r.rsPin.Out(gpio.Low); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.enablePin.Out(gpio.Low)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Dev) sendData() error {
|
||||||
|
if err := r.rsPin.Out(gpio.High); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.enablePin.Out(gpio.Low)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Dev) writeInstruction(data uint8) error {
|
||||||
|
if err := r.sendInstruction(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// write high 4 bits
|
||||||
|
if err := r.write4Bits(data >> 4); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// write low bits
|
||||||
|
if err := r.write4Bits(data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delayUs(50)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Dev) strobe() error {
|
||||||
|
if err := r.enablePin.Out(gpio.High); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delayUs(2)
|
||||||
|
return r.enablePin.Out(gpio.Low)
|
||||||
|
}
|
||||||
|
|
||||||
|
func delayUs(ms uint) {
|
||||||
|
time.Sleep(time.Duration(ms) * time.Microsecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func delayMs(ms int) {
|
||||||
|
time.Sleep(time.Duration(ms) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ conn.Resource = &Dev{}
|
||||||
@ -0,0 +1,160 @@
|
|||||||
|
// 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 hd44780_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/display"
|
||||||
|
"periph.io/x/conn/v3/display/displaytest"
|
||||||
|
"periph.io/x/conn/v3/gpio"
|
||||||
|
"periph.io/x/conn/v3/i2c/i2creg"
|
||||||
|
"periph.io/x/conn/v3/physic"
|
||||||
|
"periph.io/x/conn/v3/spi"
|
||||||
|
"periph.io/x/conn/v3/spi/spireg"
|
||||||
|
"periph.io/x/devices/v3/hd44780"
|
||||||
|
"periph.io/x/devices/v3/pcf857x"
|
||||||
|
"periph.io/x/host/v3"
|
||||||
|
"periph.io/x/host/v3/gpioioctl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This example shows using a gpio.Group with an HD44780 display. For this
|
||||||
|
// example its using the periph.io/x/host/gpioioctl package to obtain
|
||||||
|
// the gpio.Group and pins. You can use an I/O device that implements
|
||||||
|
// gpio.Group and gpio.PinOut to drive a display.
|
||||||
|
func Example() {
|
||||||
|
var err error
|
||||||
|
if _, err = host.Init(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
chip := gpioioctl.Chips[0]
|
||||||
|
var ls gpio.Group
|
||||||
|
// Using a group to obtain the pins. The first 4 pins in the group are the
|
||||||
|
// data pins, and the remaining ones are reset, enable, and backlight.
|
||||||
|
// For 8 bit mode, specify additional data pins.
|
||||||
|
ls, err = chip.LineSet(gpioioctl.LineOutput, gpio.NoEdge, gpio.PullNoChange,
|
||||||
|
"GPIO27", "GPIO22", "GPIO23", "GPIO24", "GPIO17", "GPIO18", "GPIO25")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
pins := ls.Pins()
|
||||||
|
reset := pins[4].(gpio.PinOut)
|
||||||
|
enable := pins[5].(gpio.PinOut)
|
||||||
|
bl := hd44780.NewBacklight(pins[6].(gpio.PinOut))
|
||||||
|
lcd, err := hd44780.NewHD44780(ls, reset, enable, bl, 2, 16)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
n, err := lcd.WriteString("Hello")
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
fmt.Printf("n=%d, err=%s\n", n, err)
|
||||||
|
fmt.Println("lcd=", lcd.String())
|
||||||
|
|
||||||
|
_ = lcd.Home()
|
||||||
|
_ = lcd.MoveTo(1, 1)
|
||||||
|
_, _ = lcd.WriteString("Line 1")
|
||||||
|
_ = lcd.MoveTo(2, 2)
|
||||||
|
_, _ = lcd.WriteString("Line 2")
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
_ = lcd.Clear()
|
||||||
|
|
||||||
|
fmt.Println("calling TestTextDisplay")
|
||||||
|
|
||||||
|
errs := displaytest.TestTextDisplay(lcd, true)
|
||||||
|
fmt.Println("back from TestTextDisplay")
|
||||||
|
for _, e := range errs {
|
||||||
|
if !errors.Is(e, display.ErrNotImplemented) {
|
||||||
|
log.Println(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new HD44780 that uses the Adafruit I2C/SPI Backpack.
|
||||||
|
func ExampleNewAdafruitI2CBackpack() {
|
||||||
|
// 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 := hd44780.NewAdafruitI2CBackpack(bus, 0x20, 4, 20)
|
||||||
|
fmt.Println(dev.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
_ = dev.Clear()
|
||||||
|
_, _ = dev.WriteString("Hello")
|
||||||
|
fmt.Println("wrote hello")
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
fmt.Println("calling test text display")
|
||||||
|
_ = displaytest.TestTextDisplay(dev, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewAdafruitSPIBackpack() {
|
||||||
|
if _, err := host.Init(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pc, err := spireg.Open("")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer pc.Close()
|
||||||
|
conn, err := pc.Connect(physic.MegaHertz, spi.Mode1, 8)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
display, err := hd44780.NewAdafruitSPIBackpack(conn, 2, 26)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = display.Clear()
|
||||||
|
_, _ = display.WriteString("Hello")
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
_ = displaytest.TestTextDisplay(display, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewPCF857xBackpack() {
|
||||||
|
// 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 := hd44780.NewPCF857xBackpack(bus, pcf857x.DefaultAddress, 4, 20)
|
||||||
|
fmt.Println(dev.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
for range 5 {
|
||||||
|
fmt.Println("toggling backlight")
|
||||||
|
_ = dev.Backlight(0)
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
_ = dev.Backlight(255)
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
}
|
||||||
|
_ = dev.Clear()
|
||||||
|
_, _ = dev.WriteString("Hello")
|
||||||
|
fmt.Println("wrote hello")
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
fmt.Println("calling test text display")
|
||||||
|
_ = displaytest.TestTextDisplay(dev, true)
|
||||||
|
}
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
// 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 hd44780
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
periphDisplay "periph.io/x/conn/v3/display"
|
||||||
|
"periph.io/x/conn/v3/display/displaytest"
|
||||||
|
"periph.io/x/conn/v3/i2c/i2ctest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getLCD(t *testing.T, recordingName string) (*HD44780, error) {
|
||||||
|
bus := &i2ctest.Playback{Ops: recordingData[recordingName], DontPanic: true}
|
||||||
|
dev, err := NewAdafruitI2CBackpack(bus, 0x20, 2, 16)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dev, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
testRows = 2
|
||||||
|
testCols = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
var liveDevice = false
|
||||||
|
|
||||||
|
func TestBasic(t *testing.T) {
|
||||||
|
display, err := getLCD(t, "TestBasic")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s := display.String()
|
||||||
|
t.Log(s)
|
||||||
|
if len(s) == 0 {
|
||||||
|
t.Error("display.String()")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = display.WriteString("1234567890")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = display.MoveTo(2, 2)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, err = display.WriteString("2345678901")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
rows := display.Rows()
|
||||||
|
if rows != testRows {
|
||||||
|
t.Errorf("display.Rows() expected %d, received %d", testRows, rows)
|
||||||
|
}
|
||||||
|
cols := display.Cols()
|
||||||
|
if cols != testCols {
|
||||||
|
t.Errorf("display.Cols() expected %d, received %d", testCols, cols)
|
||||||
|
}
|
||||||
|
if liveDevice {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
err = display.Halt()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterface(t *testing.T) {
|
||||||
|
display, err := getLCD(t, "TestInterface")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() { _ = display.Halt() }()
|
||||||
|
errs := displaytest.TestTextDisplay(display, liveDevice)
|
||||||
|
for _, err := range errs {
|
||||||
|
if !errors.Is(err, periphDisplay.ErrNotImplemented) {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if liveDevice {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBacklights(t *testing.T) {
|
||||||
|
display, err := getLCD(t, "TestBacklights")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_ = display.Halt()
|
||||||
|
})
|
||||||
|
|
||||||
|
err = display.Backlight(0)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = display.Backlight(0xff)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
for ix := range 3 {
|
||||||
|
colors := make([]periphDisplay.Intensity, 3)
|
||||||
|
colors[ix] = 0xff
|
||||||
|
err = display.RGBBacklight(colors[0], colors[1], colors[2])
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,50 @@
|
|||||||
|
// 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 hd44780
|
||||||
|
|
||||||
|
import (
|
||||||
|
"periph.io/x/conn/v3/gpio"
|
||||||
|
"periph.io/x/conn/v3/i2c"
|
||||||
|
"periph.io/x/devices/v3/pcf857x"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Name is the LCD pin name, and the integer value is the GPIO
|
||||||
|
// number (not physical) of the PCF8574 I2C GPIO Expander.
|
||||||
|
pcf_d4 = 4
|
||||||
|
pcf_d5 = 5
|
||||||
|
pcf_d6 = 6
|
||||||
|
pcf_d7 = 7
|
||||||
|
pcf_rsPin = 0
|
||||||
|
pcf_enablePin = 2
|
||||||
|
pcf_backlightPin = 3
|
||||||
|
pcf_rwPin = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// This function returns a display configured to use the pcf8574 i2c backpacks.
|
||||||
|
//
|
||||||
|
// # Product Information
|
||||||
|
//
|
||||||
|
// https://www.handsontec.com/dataspecs/I2C_2004_LCD.pdf
|
||||||
|
//
|
||||||
|
// This function creates a PCF8574 backpack device with the required pin
|
||||||
|
// configuration. To use this, get an I2C bus, and call this function with the
|
||||||
|
// bus, i2c address, number of rows, and columns.
|
||||||
|
func NewPCF857xBackpack(bus i2c.Bus, address uint16, rows, cols int) (*HD44780, error) {
|
||||||
|
pcf, err := pcf857x.New(bus, address, pcf857x.PCF8574)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// R/W is connected on this backpack. Set it to low.
|
||||||
|
_ = pcf.Pins[pcf_rwPin].Out(gpio.Low)
|
||||||
|
|
||||||
|
// Create our gpio.Group
|
||||||
|
gr, _ := pcf.Group(pcf_d4, pcf_d5, pcf_d6, pcf_d7, pcf_rsPin, pcf_enablePin, pcf_backlightPin)
|
||||||
|
grPins := gr.Pins()
|
||||||
|
reset := grPins[4].(gpio.PinOut)
|
||||||
|
enable := grPins[5].(gpio.PinOut)
|
||||||
|
bl := grPins[6].(gpio.PinOut)
|
||||||
|
return NewHD44780(gr, reset, enable, NewBacklight(bl), rows, cols)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue