diff --git a/experimental/devices/inky/doc.go b/experimental/devices/inky/doc.go index fd4ce18..4b5d6d9 100644 --- a/experimental/devices/inky/doc.go +++ b/experimental/devices/inky/doc.go @@ -2,11 +2,14 @@ // Use of this source code is governed under the Apache License, Version 2.0 // 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 // // Inky lacks a true datasheet, so the code here is derived from the reference // implementation by Pimoroni: // 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 diff --git a/experimental/devices/inky/inky.go b/experimental/devices/inky/inky.go index ab78512..24eec3c 100644 --- a/experimental/devices/inky/inky.go +++ b/experimental/devices/inky/inky.go @@ -18,12 +18,6 @@ import ( "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 // setting the border color. type Color int @@ -36,11 +30,36 @@ const ( White ) -var borderColor = map[Color]byte{ - Black: 0x00, - Red: 0x33, - Yellow: 0x33, - White: 0xff, +func (c *Color) String() string { + switch *c { + case Black: + return "black" + 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. @@ -49,9 +68,33 @@ type Model int // Supported Model. const ( 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 // default settings. type Opts struct { @@ -63,7 +106,16 @@ type Opts struct { 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) { if o.ModelColor != Black && o.ModelColor != Red && o.ModelColor != Yellow { 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, } + 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 } @@ -95,6 +155,10 @@ type Dev struct { r gpio.PinOut // High when device is busy. 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 Color @@ -107,6 +171,16 @@ func (d *Dev) SetBorder(c Color) { 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. func (d *Dev) String() string { return "Inky pHAT" @@ -149,7 +223,7 @@ func (d *Dev) ColorModel() color.Model { // Bounds implements display.Drawer func (d *Dev) Bounds() image.Rectangle { - return image.Rect(0, 0, rows, cols) + return d.bounds } // Draw implements display.Drawer @@ -164,14 +238,17 @@ func (d *Dev) Draw(dstRect image.Rectangle, src image.Image, srcPtrs image.Point b := src.Bounds() // Black/white pixels. - white := make([]bool, rows*cols) + white := make([]bool, b.Size().Y*b.Size().X) // 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 y := b.Min.Y; y < b.Max.Y; y++ { - i := x*cols + y + i := y*b.Size().X + 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() if r >= 0x8000 && g >= 0x8000 && b >= 0x8000 { 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) } +// 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) { if err := d.reset(); err != nil { 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) - binary.LittleEndian.PutUint16(r, rows) - if err := d.sendCommand(0x01, r); err != nil { // Gate setting - return err - } + binary.LittleEndian.PutUint16(r, uint16(d.Bounds().Size().Y)) + h := make([]byte, 4) + binary.LittleEndian.PutUint16(h[2:], uint16(d.Bounds().Size().Y)) - init := []struct { + type cmdData struct { cmd byte data []byte - }{ - {0x03, []byte{0x10, 0x01}}, // 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 - {0x04, nil}, // Power on - {0x2c, []byte{0x3c}}, // VCOM Register, 0x3c = -1.5v? + } + cmds := []cmdData{ + {0x01, r}, // Gate setting + {0x74, []byte{0x54}}, // Set Analog Block Control. + {0x7e, []byte{0x3b}}, // Set Digital Block Control. + {0x03, []byte{0x17}}, // Gate Driving Voltage. + {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{byte(border)}}, // Border colour - } - - for _, c := range init { - if err := d.sendCommand(c.cmd, c.data); err != nil { - return err - } - } - - 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}}, + {0x32, modelLUT[d.color]}, // Set LUTs. + {0x44, []byte{0x00, byte(d.Bounds().Size().X/8) - 1}}, // Set RAM Y Start/End + {0x45, h}, // Set RAM X Start/End + {0x4e, []byte{0x00}}, // Set RAM X Pointer Start + {0x4f, []byte{0x00, 0x00}}, // Set RAM Y Pointer Start {0x24, black}, - - {0x43, []byte{0x00}}, - {0x4f, []byte{0x00, 0x00}}, + {0x4e, []byte{0x00}}, // Set RAM X Pointer Start + {0x4f, []byte{0x00, 0x00}}, // Set RAM Y Pointer Start {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 { return err } @@ -332,14 +378,19 @@ func (d *Dev) sendCommand(command byte, 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 { return err } - if err := d.c.Tx(data, nil); err != nil { - return fmt.Errorf("failed to send data to inky: %v", err) + for len(data) != 0 { + 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 } diff --git a/experimental/devices/inky/lut.go b/experimental/devices/inky/lut.go index d418992..932aa7b 100644 --- a/experimental/devices/inky/lut.go +++ b/experimental/devices/inky/lut.go @@ -12,7 +12,6 @@ var ( 0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, } - redLUT = [...]byte{ 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, @@ -20,7 +19,6 @@ var ( 0x10, 0x10, 0x2, 0x2, 0x2, 0x40, 0x20, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, } - yellowLUT = [...]byte{ 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, @@ -28,4 +26,10 @@ var ( 0x8, 0x20, 0x8, 0x4, 0x0, 0x0, 0x10, 0x10, 0x8, 0x8, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, } + + modelLUT = map[Color][]byte{ + Black: blackLUT[:], + Red: redLUT[:], + Yellow: yellowLUT[:], + } )