diff --git a/experimental/devices/st7567/doc.go b/experimental/devices/st7567/doc.go new file mode 100644 index 0000000..3fee807 --- /dev/null +++ b/experimental/devices/st7567/doc.go @@ -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 diff --git a/experimental/devices/st7567/example_test.go b/experimental/devices/st7567/example_test.go new file mode 100644 index 0000000..846bbcf --- /dev/null +++ b/experimental/devices/st7567/example_test.go @@ -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) +} diff --git a/experimental/devices/st7567/st7567.go b/experimental/devices/st7567/st7567.go new file mode 100644 index 0000000..51bf4a0 --- /dev/null +++ b/experimental/devices/st7567/st7567.go @@ -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 +}