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.
408 lines
9.2 KiB
Go
408 lines
9.2 KiB
Go
// 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/conn/v3"
|
|
"periph.io/x/conn/v3/gpio"
|
|
"periph.io/x/conn/v3/physic"
|
|
"periph.io/x/conn/v3/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 ratio"
|
|
}
|
|
|
|
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 controls the common output status which changes the vertical display direction.
|
|
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 := [...]byte{
|
|
byte(o.Bias),
|
|
byte(o.SegmentDirection),
|
|
byte(o.CommonDirection),
|
|
byte(o.Display),
|
|
setStartLine | o.StartLine,
|
|
powerControl,
|
|
byte(o.RegulationRatio.getValue()),
|
|
displayOn,
|
|
setContrast,
|
|
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
|
|
}
|