st7567: add device driver for 128x64 dot matrix LCD (#451)

This adds experimental support for the st7567 LCD device which is used by the GFX HAT from Pimoroni.

The example just places some pixels on the device and iterates over the possible contrast values, going into power save mode and waking up.

The command line tool can take a PNG image and paints the black pixels onto the LCD.

Example and command line tool have been tested with a GFX Hat.
pull/1/head
Lars Hoogestraat 6 years ago committed by GitHub
parent 8a5f0035b2
commit cb7455c2ca

@ -0,0 +1,10 @@
// Copyright 2020 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 st7567 implements an interface to the single-chip dot matrix LCD
//
// Datasheet
//
// https://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7567.pdf
package st7567

@ -0,0 +1,120 @@
// Copyright 2020 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 st7567_test implements an example for the GFX HAT from Pimoroni
//
// Datasheet
//
// https://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7567.pdf
package st7567_test
import (
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"periph.io/x/periph/conn/gpio/gpioreg"
"periph.io/x/periph/conn/spi/spireg"
"periph.io/x/periph/experimental/devices/st7567"
"periph.io/x/periph/host"
)
func Example() {
if _, err := host.Init(); err != nil {
log.Fatal(err)
}
conn, err := spireg.Open("SPI0.0")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
dc := gpioreg.ByName("6")
reset := gpioreg.ByName("5")
cs := gpioreg.ByName("8")
o := &st7567.Opts{
Bias: st7567.Bias17,
CommonDirection: st7567.CommonDirReverse,
SegmentDirection: st7567.SegmentDirNormal,
Display: st7567.DisplayNormal,
RegulationRatio: st7567.RegulationRatio{st7567.RegResistorRR0, st7567.RegResistorRR1},
StartLine: 0,
Contrast: 40,
}
dev, err := st7567.New(conn, dc, reset, cs, o)
if err != nil {
log.Fatal(err)
}
defer func() {
fmt.Println("halting device")
dev.Halt()
}()
//Control-C trap
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
fmt.Println("halting device")
dev.Halt()
os.Exit(1)
}()
for x := 0; x < st7567.Width; x++ {
for y := 0; y < st7567.Height; y++ {
dev.SetPixel(x, y, true)
}
if err = dev.Update(); err != nil {
log.Fatal(err)
}
time.Sleep(40 * time.Millisecond)
}
for i := 0; i < 64; i++ {
fmt.Printf("current contrast value: %d\n", i)
if err = dev.SetContrast(byte(i)); err != nil {
log.Fatal(err)
}
if err = dev.Update(); err != nil {
log.Fatal(err)
}
time.Sleep(100 * time.Millisecond)
}
if err = dev.Update(); err != nil {
log.Fatal(err)
}
fmt.Println("entering power save mode")
time.Sleep(2 * time.Second)
if err = dev.PowerSave(); err != nil {
log.Fatal(err)
}
time.Sleep(2 * time.Second)
fmt.Println("leaving power save mode")
if err = dev.WakeUp(); err != nil {
log.Fatal(err)
}
time.Sleep(2 * time.Second)
}

@ -0,0 +1,406 @@
// Copyright 2020 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 st7567 implements an interface to the single-chip dot matrix LCD
//
// Datasheet
//
// https://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7567.pdf
package st7567
import (
"errors"
"fmt"
"strings"
"time"
"periph.io/x/periph/conn"
"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/physic"
"periph.io/x/periph/conn/spi"
)
const (
// Width the max pixel width
Width = 128
// Height the max pixel height
Height = 64
pageSize = 128
// displayOff (0xae): Display OFF (sleep mode)
displayOff = 0xae
// displayOn (0xaf): Display ON in normal mode
displayOn = 0xaf
// setStartLine (0x40-7f): Set display start line
setStartLine = 0x40
// setPageStart (0xb0-b7): Set page start address
setPageStart = 0xb0
// setColl (0x00-0x0f): Set lower column address
setColl = 0x00
// setCollH (0x10-0x1f): Set higher column address
setCollH = 0x10
// displayRAM (0xa4): Resume to RAM content display
displayRAM = 0xa4
// displayEntire (0xa5): Entire display ON
displayEntire = 0xa5
// enterRMWMode (0xe0): Enter the Read Modify Write mode
enterRMWMode = 0xe0
// exitRMWMode (0xee): Leave the Read Modify Write mode
exitRMWMode = 0xee
// powerControl (0x2c): Control built-in power circuit
powerControl = 0x2f
// setContrast (0x81): Set contrast control
setContrast = 0x81
)
// Dev is a handle to a ST7567.
type Dev struct {
c conn.Conn
//dc low when sending a command, high when sending data.
dc gpio.PinOut
//rst reset pin, active low.
rst gpio.PinOut
//cs chip select pin
cs gpio.PinIn
//pixels the array containing the pixel map
pixels [1024]byte
}
// Bias selects the LCD bias ratio of the voltage required for driving the LCD
type Bias byte
const (
// Bias17 (0xa3): Select BIAS setting 1/7
Bias17 Bias = 0xa3
// Bias19 (0xa2): Select BIAS setting 1/9
Bias19 Bias = 0xa2
)
func (b *Bias) Set(s string) error {
switch s {
case "17":
*b = Bias17
case "19":
*b = Bias19
default:
return fmt.Errorf("unknown Bias %q: expected either 17 or 19", s)
}
return nil
}
func (b *Bias) String() string {
switch *b {
case Bias17:
return "Bias 1/7"
case Bias19:
return "Bias 1/9"
default:
return "Unknown"
}
}
// SegmentDirection is the direction of the segments
type SegmentDirection byte
const (
// SegmentDirNormal (0xa0): Column address 0 is mapped to SEG0
SegmentDirNormal SegmentDirection = 0xa0
// SegmentDirReverse (0xa1): Column address 128 is mapped to SEG0
SegmentDirReverse SegmentDirection = 0xa1
)
func (sd *SegmentDirection) Set(s string) error {
switch s {
case "normal":
*sd = SegmentDirNormal
case "reverse":
*sd = SegmentDirReverse
default:
return fmt.Errorf("unknown SegmentDirection %q: expected either 'normal' or 'reverse'", s)
}
return nil
}
func (sd *SegmentDirection) String() string {
switch *sd {
case SegmentDirNormal:
return "Normal segment direction"
case SegmentDirReverse:
return "Reverse segment direction"
default:
return "Unknown"
}
}
// CommonDirection controls the common output status which changes the vertical display direction.
type CommonDirection byte
const (
// CommonDirNormal (0xc0): Column address 0 is mapped to SEG0
CommonDirNormal CommonDirection = 0xc0
// CommonDirReverse (0xc8): Column address 128 is mapped to SEG0
CommonDirReverse CommonDirection = 0xc8
)
func (cd *CommonDirection) Set(s string) error {
switch s {
case "normal":
*cd = CommonDirNormal
case "reverse":
*cd = CommonDirReverse
default:
return fmt.Errorf("unknown CommonDirection %q: expected either 'normal' or 'reverse'", s)
}
return nil
}
func (cd *CommonDirection) String() string {
switch *cd {
case CommonDirNormal:
return "Normal common direction"
case CommonDirReverse:
return "Reverse common direction"
default:
return "Unknown"
}
}
// Display contains if the display is in normal or inverse mode (black will be white and vice versa)
type Display byte
const (
// DisplayNormal (0xa6): Normal display
DisplayNormal Display = 0xa6
// DisplayInverse (0xa7): Inverse display
DisplayInverse Display = 0xa7
)
func (d *Display) Set(s string) error {
switch s {
case "normal":
*d = DisplayNormal
case "inverse":
*d = DisplayInverse
default:
return fmt.Errorf("unknown Display %q: expected either 'normal' or 'inverse'", s)
}
return nil
}
func (d *Display) String() string {
switch *d {
case DisplayNormal:
return "Normal display"
case DisplayInverse:
return "Inverse display"
default:
return "Unknown"
}
}
// RegulationResistor is the single regulation resistor value
type RegulationResistor byte
const (
// RegResistorRR0 (0x21): Regulation Resistor ratio
RegResistorRR0 RegulationResistor = 0x21
// RegResistorRR1 (0x22): Regulation Resistor ratio
RegResistorRR1 RegulationResistor = 0x22
// RegResistorRR2 (0x24): Regulation Resistor ratio
RegResistorRR2 RegulationResistor = 0x24
)
func (rr *RegulationResistor) Set(s string) error {
switch s {
case "RR0":
*rr = RegResistorRR0
case "RR1":
*rr = RegResistorRR1
case "RR2":
*rr = RegResistorRR2
default:
return fmt.Errorf("unknown RegulataionRatio %q: expected either 'RR0' or 'RR1' or 'RR2'", s)
}
return nil
}
func (rr *RegulationResistor) String() string {
switch *rr {
case RegResistorRR0:
return "Regulation resistor RR0"
case RegResistorRR1:
return "Regulation resistor RR1"
case RegResistorRR2:
return "Regulation resistor RR2"
default:
return "Unknown"
}
}
//RegulationRatio selects the regulation resistor ratio
type RegulationRatio []RegulationResistor
func (rrs *RegulationRatio) String() string {
return "regulation resistor ratios"
}
func (rrs *RegulationRatio) Set(value string) error {
values := strings.Split(value, ",")
for _, v := range values {
rr := new(RegulationResistor)
if err := rr.Set(v); err != nil {
return err
}
*rrs = append(*rrs, *rr)
}
return nil
}
func (rrs RegulationRatio) getValue() RegulationResistor {
var out RegulationResistor
for _, v := range rrs {
out |= v
}
return out
}
// Opts contains the configuration for the S7567 device.
type Opts struct {
// Bias selects the LCD bias ratio of the voltage required for driving the LCD.
Bias Bias
// SegmentDirection is the direction of the segments.
SegmentDirection SegmentDirection
// CommonDirection is the direction of the segments.
CommonDirection CommonDirection
// Display changes the selected and non-selected voltage of SEG.
Display Display
// RegulationRatio controls the regulation ratio of the built-in regulator.
RegulationRatio RegulationRatio
// StartLine sets the line address of the Display Data RAM to determine the initial display line.
StartLine byte
// Contrast the value to adjust the display contrast.
Contrast byte
}
// New opens a handle to a ST7567 LCD.
func New(p spi.Port, dc gpio.PinOut, rst gpio.PinOut, cs gpio.PinIn, o *Opts) (*Dev, error) {
c, err := p.Connect(1000*physic.KiloHertz, spi.Mode0, 8)
if err != nil {
return nil, errors.New("could not connect to device")
}
d := &Dev{
c: c,
dc: dc,
rst: rst,
cs: cs,
}
cmd := make([]byte, 9)
cmd = append(cmd, byte(o.Bias))
cmd = append(cmd, byte(o.SegmentDirection))
cmd = append(cmd, byte(o.CommonDirection))
cmd = append(cmd, byte(o.Display))
cmd = append(cmd, setStartLine|o.StartLine)
cmd = append(cmd, powerControl)
cmd = append(cmd, byte(o.RegulationRatio.getValue()))
cmd = append(cmd, displayOn)
cmd = append(cmd, setContrast)
cmd = append(cmd, o.Contrast)
if err := d.sendCommand(cmd); err != nil {
return nil, err
}
return d, nil
}
// Halt resets the registers and switches the driver off.
func (d *Dev) Halt() error {
return d.reset()
}
// SetContrast sets the contrast
func (d *Dev) SetContrast(value byte) error {
return d.sendCommand([]byte{setContrast, value})
}
// SetPixel sets a pixel in the pixels array
func (d *Dev) SetPixel(x, y int, active bool) {
offset := (y / 8 * Width) + x
pageAddress := y % 8
d.pixels[offset] &= ^(1 << byte(pageAddress))
d.pixels[offset] |= bTob(active) & 1 << byte(pageAddress)
}
// Update updates the display
func (d *Dev) Update() error {
if err := d.sendCommand([]byte{enterRMWMode}); err != nil {
return err
}
for i := 0; i < 8; i++ {
offset := i * pageSize
if err := d.sendCommand([]byte{setPageStart | byte(i), setColl, setCollH}); err != nil {
return err
}
if err := d.sendData(d.pixels[offset : offset+pageSize]); err != nil {
return err
}
}
if err := d.sendCommand([]byte{exitRMWMode}); err != nil {
return err
}
return nil
}
// PowerSave turning the display into sleep
func (d *Dev) PowerSave() error {
return d.sendCommand([]byte{displayOff, displayEntire})
}
// WakeUp wakes the display up from power saving mode
func (d *Dev) WakeUp() error {
return d.sendCommand([]byte{displayRAM, displayOn})
}
func (d *Dev) sendCommand(c []byte) error {
if err := d.dc.Out(gpio.Low); err != nil {
return err
}
return d.c.Tx(c, nil)
}
func (d *Dev) sendData(c []byte) error {
if err := d.dc.Out(gpio.High); err != nil {
return err
}
return d.c.Tx(c, nil)
}
func (d *Dev) reset() error {
if err := d.rst.Out(gpio.Low); err != nil {
return err
}
time.Sleep(50 * time.Millisecond)
if err := d.rst.Out(gpio.High); err != nil {
return err
}
time.Sleep(50 * time.Millisecond)
return nil
}
func bTob(b bool) byte {
if b {
return 1
}
return 0
}
Loading…
Cancel
Save