inky: Add wHAT support (#430)

* Fix white border color
* Add convenience functions DrawAll and SetModelColor
* Tidy up the initialisation code and apply bugfixes from the upstream
Python library
pull/1/head
David Sansome 6 years ago committed by M-A
parent 71fdeecc9a
commit 189a8fc87a

@ -2,11 +2,14 @@
// Use of this source code is governed under the Apache License, Version 2.0 // Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
// Package inky drives an Inky pHAT E ink display. // Package inky drives an Inky pHAT or wHAT E ink display.
// //
// Datasheet // Datasheet
// //
// Inky lacks a true datasheet, so the code here is derived from the reference // Inky lacks a true datasheet, so the code here is derived from the reference
// implementation by Pimoroni: // implementation by Pimoroni:
// https://github.com/pimoroni/inky // https://github.com/pimoroni/inky
//
// The display seems to use a SSD1675 controller:
// https://www.china-epaper.com/uploads/soft/DEPG0420R01V3.0.pdf
package inky package inky

@ -18,12 +18,6 @@ import (
"periph.io/x/periph/conn/spi" "periph.io/x/periph/conn/spi"
) )
const (
// Constants for an Inky pHAT
cols = 104
rows = 212
)
// Color is used to define which model of inky is being used, and also for // Color is used to define which model of inky is being used, and also for
// setting the border color. // setting the border color.
type Color int type Color int
@ -36,11 +30,36 @@ const (
White White
) )
var borderColor = map[Color]byte{ func (c *Color) String() string {
Black: 0x00, switch *c {
Red: 0x33, case Black:
Yellow: 0x33, return "black"
White: 0xff, case Red:
return "red"
case Yellow:
return "yellow"
case White:
return "white"
default:
return "Unknown"
}
}
// Set sets the Color to a value represented by the string s. Set implements the flag.Value interface.
func (c *Color) Set(s string) error {
switch s {
case "black":
*c = (Color)(Black)
case "red":
*c = (Color)(Red)
case "yellow":
*c = (Color)(Yellow)
case "white":
*c = (Color)(White)
default:
return fmt.Errorf("Unknown color %q: expected either black, red, yellow or white", s)
}
return nil
} }
// Model lists the supported e-ink display models. // Model lists the supported e-ink display models.
@ -49,9 +68,33 @@ type Model int
// Supported Model. // Supported Model.
const ( const (
PHAT Model = iota PHAT Model = iota
// TODO: Add wHAT here when supported. WHAT
) )
func (m *Model) String() string {
switch *m {
case PHAT:
return "PHAT"
case WHAT:
return "WHAT"
default:
return "Unknown"
}
}
// Set sets the Model to a value represented by the string s. Set implements the flag.Value interface.
func (m *Model) Set(s string) error {
switch s {
case "PHAT":
*m = (Model)(PHAT)
case "WHAT":
*m = (Model)(WHAT)
default:
return fmt.Errorf("Unknown model %q: expected either PHAT or WHAT", s)
}
return nil
}
// Opts is the options to specify which device is being controlled and its // Opts is the options to specify which device is being controlled and its
// default settings. // default settings.
type Opts struct { type Opts struct {
@ -63,7 +106,16 @@ type Opts struct {
BorderColor Color BorderColor Color
} }
// New opens a handle to an Inky pHAT. const spiChunkSize = 4096
var borderColor = map[Color]byte{
Black: 0x00,
Red: 0x73,
Yellow: 0x33,
White: 0x31,
}
// New opens a handle to an Inky pHAT or wHAT.
func New(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinIn, o *Opts) (*Dev, error) { func New(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinIn, o *Opts) (*Dev, error) {
if o.ModelColor != Black && o.ModelColor != Red && o.ModelColor != Yellow { if o.ModelColor != Black && o.ModelColor != Red && o.ModelColor != Yellow {
return nil, fmt.Errorf("unsupported color: %v", o.ModelColor) return nil, fmt.Errorf("unsupported color: %v", o.ModelColor)
@ -83,6 +135,14 @@ func New(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinIn, o *Opts
border: o.BorderColor, border: o.BorderColor,
} }
switch o.Model {
case PHAT:
d.bounds = image.Rect(0, 0, 104, 212)
d.flipVertically = true
case WHAT:
d.bounds = image.Rect(0, 0, 400, 300)
}
return d, nil return d, nil
} }
@ -95,6 +155,10 @@ type Dev struct {
r gpio.PinOut r gpio.PinOut
// High when device is busy. // High when device is busy.
busy gpio.PinIn busy gpio.PinIn
// Size of this model's display.
bounds image.Rectangle
// Whether this model needs the image flipped vertically.
flipVertically bool
// Color of device screen (red, yellow or black). // Color of device screen (red, yellow or black).
color Color color Color
@ -107,6 +171,16 @@ func (d *Dev) SetBorder(c Color) {
d.border = c d.border = c
} }
// SetModelColor changes the model color. This will not take effect until the next Draw().
// Useful if you want to switch between two-color and three-color drawing.
func (d *Dev) SetModelColor(c Color) error {
if c != Black && c != Red && c != Yellow {
return fmt.Errorf("unsupported color: %v", c)
}
d.color = c
return nil
}
// String implements conn.Resource. // String implements conn.Resource.
func (d *Dev) String() string { func (d *Dev) String() string {
return "Inky pHAT" return "Inky pHAT"
@ -149,7 +223,7 @@ func (d *Dev) ColorModel() color.Model {
// Bounds implements display.Drawer // Bounds implements display.Drawer
func (d *Dev) Bounds() image.Rectangle { func (d *Dev) Bounds() image.Rectangle {
return image.Rect(0, 0, rows, cols) return d.bounds
} }
// Draw implements display.Drawer // Draw implements display.Drawer
@ -164,14 +238,17 @@ func (d *Dev) Draw(dstRect image.Rectangle, src image.Image, srcPtrs image.Point
b := src.Bounds() b := src.Bounds()
// Black/white pixels. // Black/white pixels.
white := make([]bool, rows*cols) white := make([]bool, b.Size().Y*b.Size().X)
// Red/Transparent pixels. // Red/Transparent pixels.
red := make([]bool, rows*cols) red := make([]bool, b.Size().Y*b.Size().X)
for x := b.Min.X; x < b.Max.X; x++ { for x := b.Min.X; x < b.Max.X; x++ {
for y := b.Min.Y; y < b.Max.Y; y++ { for y := b.Min.Y; y < b.Max.Y; y++ {
i := x*cols + y i := y*b.Size().X + x
srcX := x srcX := x
srcY := b.Max.Y - y - 1 srcY := y
if d.flipVertically {
srcY = b.Max.Y - y - 1
}
r, g, b, _ := d.ColorModel().Convert(src.At(srcX, srcY)).RGBA() r, g, b, _ := d.ColorModel().Convert(src.At(srcX, srcY)).RGBA()
if r >= 0x8000 && g >= 0x8000 && b >= 0x8000 { if r >= 0x8000 && g >= 0x8000 && b >= 0x8000 {
white[i] = true white[i] = true
@ -188,84 +265,53 @@ func (d *Dev) Draw(dstRect image.Rectangle, src image.Image, srcPtrs image.Point
return d.update(borderColor[d.border], bufA, bufB) return d.update(borderColor[d.border], bufA, bufB)
} }
// DrawAll redraws the whole display.
func (d *Dev) DrawAll(src image.Image) error {
return d.Draw(d.Bounds(), src, image.ZP)
}
func (d *Dev) update(border byte, black []byte, red []byte) (err error) { func (d *Dev) update(border byte, black []byte, red []byte) (err error) {
if err := d.reset(); err != nil { if err := d.reset(); err != nil {
return err return err
} }
if err := d.sendCommand(0x74, []byte{0x54}); err != nil { // Set Analog Block Control.
return err
}
if err := d.sendCommand(0x7e, []byte{0x3b}); err != nil { // Set Digital Block Control.
return err
}
r := make([]byte, 3) r := make([]byte, 3)
binary.LittleEndian.PutUint16(r, rows) binary.LittleEndian.PutUint16(r, uint16(d.Bounds().Size().Y))
if err := d.sendCommand(0x01, r); err != nil { // Gate setting h := make([]byte, 4)
return err binary.LittleEndian.PutUint16(h[2:], uint16(d.Bounds().Size().Y))
}
init := []struct { type cmdData struct {
cmd byte cmd byte
data []byte data []byte
}{ }
{0x03, []byte{0x10, 0x01}}, // Gate Driving Voltage. cmds := []cmdData{
{0x3a, []byte{0x07}}, // Dummy line period {0x01, r}, // Gate setting
{0x3b, []byte{0x04}}, // Gate line width {0x74, []byte{0x54}}, // Set Analog Block Control.
{0x11, []byte{0x03}}, // Data entry mode setting 0x03 = X/Y increment {0x7e, []byte{0x3b}}, // Set Digital Block Control.
{0x04, nil}, // Power on {0x03, []byte{0x17}}, // Gate Driving Voltage.
{0x2c, []byte{0x3c}}, // VCOM Register, 0x3c = -1.5v? {0x04, []byte{0x41, 0xac, 0x32}}, // Gate Driving Voltage.
{0x3a, []byte{0x07}}, // Dummy line period
{0x3b, []byte{0x04}}, // Gate line width
{0x11, []byte{0x03}}, // Data entry mode setting 0x03 = X/Y increment
{0x2c, []byte{0x3c}}, // VCOM Register, 0x3c = -1.5v?
{0x3c, []byte{0x00}}, {0x3c, []byte{0x00}},
{0x3c, []byte{byte(border)}}, // Border colour {0x3c, []byte{byte(border)}}, // Border colour
} {0x32, modelLUT[d.color]}, // Set LUTs.
{0x44, []byte{0x00, byte(d.Bounds().Size().X/8) - 1}}, // Set RAM Y Start/End
for _, c := range init { {0x45, h}, // Set RAM X Start/End
if err := d.sendCommand(c.cmd, c.data); err != nil { {0x4e, []byte{0x00}}, // Set RAM X Pointer Start
return err {0x4f, []byte{0x00, 0x00}}, // Set RAM Y Pointer Start
}
}
switch d.color {
case Black:
if err := d.sendCommand(0x32, blackLUT[:]); err != nil {
return err
}
case Red:
if err := d.sendCommand(0x32, redLUT[:]); err != nil {
return err
}
case Yellow:
if err := d.sendCommand(0x04, []byte{0x07}); err != nil { // Set voltage of VSH and VSL.
return err
}
if err := d.sendCommand(0x32, yellowLUT[:]); err != nil {
return err
}
}
h := make([]byte, 4)
binary.LittleEndian.PutUint16(h[2:], rows)
write := []struct {
cmd byte
data []byte
}{
{0x44, []byte{0x00, cols/8 - 1}}, // Set RAM X Start/End
{0x45, h}, // Set RAM Y Start/End
{0x43, []byte{0x00}},
{0x4e, []byte{0x00}},
{0x4f, []byte{0x00, 0x00}},
{0x24, black}, {0x24, black},
{0x4e, []byte{0x00}}, // Set RAM X Pointer Start
{0x43, []byte{0x00}}, {0x4f, []byte{0x00, 0x00}}, // Set RAM Y Pointer Start
{0x4f, []byte{0x00, 0x00}},
{0x26, red}, {0x26, red},
{0x22, []byte{0xc7}},
} }
if d.color == Yellow {
cmds = append(cmds, cmdData{0x04, []byte{0x07, 0xac, 0x32}}) // Set voltage of VSH and VSL
}
cmds = append(cmds, cmdData{0x22, []byte{0xc7}}) // Update the image.
for _, c := range write { for _, c := range cmds {
if err := d.sendCommand(c.cmd, c.data); err != nil { if err := d.sendCommand(c.cmd, c.data); err != nil {
return err return err
} }
@ -332,14 +378,19 @@ func (d *Dev) sendCommand(command byte, data []byte) error {
} }
func (d *Dev) sendData(data []byte) error { func (d *Dev) sendData(data []byte) error {
if len(data) > 4096 {
return fmt.Errorf("sending more data than chunk size: %d > 4096", len(data))
}
if err := d.dc.Out(gpio.High); err != nil { if err := d.dc.Out(gpio.High); err != nil {
return err return err
} }
if err := d.c.Tx(data, nil); err != nil { for len(data) != 0 {
return fmt.Errorf("failed to send data to inky: %v", err) var chunk []byte
if len(data) > spiChunkSize {
chunk, data = data[:spiChunkSize], data[spiChunkSize:]
} else {
chunk, data = data, nil
}
if err := d.c.Tx(chunk, nil); err != nil {
return fmt.Errorf("failed to send data to inky: %v", err)
}
} }
return nil return nil
} }

@ -12,7 +12,6 @@ var (
0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
} }
redLUT = [...]byte{ redLUT = [...]byte{
0x48, 0xa0, 0x10, 0x10, 0x13, 0x0, 0x0, 0x48, 0xa0, 0x80, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x48, 0xa0, 0x10, 0x10, 0x13, 0x0, 0x0, 0x48, 0xa0, 0x80, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x48, 0xa5, 0x0, 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x48, 0xa5, 0x0, 0xbb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
@ -20,7 +19,6 @@ var (
0x10, 0x10, 0x2, 0x2, 0x2, 0x40, 0x20, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x10, 0x10, 0x2, 0x2, 0x2, 0x40, 0x20, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
} }
yellowLUT = [...]byte{ yellowLUT = [...]byte{
0xfa, 0x94, 0x8c, 0xc0, 0xd0, 0x0, 0x0, 0xfa, 0x94, 0x2c, 0x80, 0xe0, 0x0, 0x0, 0xfa, 0x0, 0xfa, 0x94, 0x8c, 0xc0, 0xd0, 0x0, 0x0, 0xfa, 0x94, 0x2c, 0x80, 0xe0, 0x0, 0x0, 0xfa, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xfa, 0x94, 0xf8, 0x80, 0x50, 0x0, 0xcc, 0xbf, 0x58, 0xfc, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfa, 0x94, 0xf8, 0x80, 0x50, 0x0, 0xcc, 0xbf, 0x58, 0xfc, 0x80,
@ -28,4 +26,10 @@ var (
0x8, 0x20, 0x8, 0x4, 0x0, 0x0, 0x10, 0x10, 0x8, 0x8, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x8, 0x20, 0x8, 0x4, 0x0, 0x0, 0x10, 0x10, 0x8, 0x8, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
} }
modelLUT = map[Color][]byte{
Black: blackLUT[:],
Red: redLUT[:],
Yellow: yellowLUT[:],
}
) )

Loading…
Cancel
Save