|
|
// Copyright 2023 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 inky
|
|
|
|
|
|
import (
|
|
|
"encoding/binary"
|
|
|
"fmt"
|
|
|
"image"
|
|
|
"image/color"
|
|
|
"image/draw"
|
|
|
"log"
|
|
|
"math"
|
|
|
"time"
|
|
|
|
|
|
"periph.io/x/conn/v3"
|
|
|
"periph.io/x/conn/v3/display"
|
|
|
"periph.io/x/conn/v3/gpio"
|
|
|
"periph.io/x/conn/v3/gpio/gpioreg"
|
|
|
"periph.io/x/conn/v3/physic"
|
|
|
"periph.io/x/conn/v3/spi"
|
|
|
)
|
|
|
|
|
|
var _ display.Drawer = &DevImpression{}
|
|
|
var _ conn.Resource = &DevImpression{}
|
|
|
var _ draw.Image = &DevImpression{}
|
|
|
|
|
|
var (
|
|
|
// For more: https://github.com/pimoroni/inky/issues/115#issuecomment-887453065
|
|
|
dsc = []color.NRGBA{
|
|
|
{0, 0, 0, 0}, // Black
|
|
|
{255, 255, 255, 255}, // White
|
|
|
{0, 255, 0, 255}, // Green
|
|
|
{0, 0, 255, 255}, // Blue
|
|
|
{255, 0, 0, 255}, // Red
|
|
|
{255, 255, 0, 255}, // Yellow
|
|
|
{255, 140, 0, 255}, // Orange
|
|
|
{255, 255, 255, 255},
|
|
|
}
|
|
|
|
|
|
sc = []color.NRGBA{
|
|
|
{57, 48, 57, 0}, // Black
|
|
|
{255, 255, 255, 255}, // White
|
|
|
{58, 91, 70, 255}, // Green
|
|
|
{61, 59, 94, 255}, // Blue
|
|
|
{156, 72, 75, 255}, // Red
|
|
|
{208, 190, 71, 255}, // Yellow
|
|
|
{177, 106, 73, 255}, // Orange
|
|
|
{255, 255, 255, 255},
|
|
|
}
|
|
|
|
|
|
sc7 = []color.NRGBA{
|
|
|
{0, 0, 0, 0}, // Black
|
|
|
{217, 242, 255, 255}, // White
|
|
|
{3, 124, 76, 255}, // Green
|
|
|
{27, 46, 198, 255}, // Blue
|
|
|
{245, 80, 34, 255}, // Red
|
|
|
{255, 255, 68, 255}, // Yellow
|
|
|
{239, 121, 44, 255}, // Orange
|
|
|
{255, 255, 255, 255},
|
|
|
}
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
ucSpeed = 3000 * physic.KiloHertz
|
|
|
acSpeed = 5000 * physic.KiloHertz
|
|
|
|
|
|
uc8159PSR = 0x00
|
|
|
uc8159PWR = 0x01
|
|
|
uc8159POF = 0x02
|
|
|
uc8159PFS = 0x03
|
|
|
uc8159PON = 0x04
|
|
|
uc8159BTST = 0x06
|
|
|
uc8159DSLP = 0x07
|
|
|
uc8159DTM1 = 0x10
|
|
|
uc8159DSP = 0x11
|
|
|
uc8159DRF = 0x12
|
|
|
uc8159IPC = 0x13
|
|
|
uc8159PLL = 0x30
|
|
|
uc8159TSC = 0x40
|
|
|
uc8159TSE = 0x41
|
|
|
uc8159TSW = 0x42
|
|
|
uc8159TSR = 0x43
|
|
|
uc8159CDI = 0x50
|
|
|
uc8159LPD = 0x51
|
|
|
uc8159TCON = 0x60
|
|
|
uc8159TRES = 0x61
|
|
|
uc8159DAM = 0x65
|
|
|
uc8159REV = 0x70
|
|
|
uc8159FLG = 0x71
|
|
|
uc8159AMV = 0x80
|
|
|
uc8159VV = 0x81
|
|
|
uc8159VDCS = 0x82
|
|
|
uc8159PWS = 0xE3
|
|
|
uc8159TSSET = 0xE5
|
|
|
|
|
|
ac073TC1PSR = 0x00
|
|
|
ac073TC1PWR = 0x01
|
|
|
ac073TC1POF = 0x02
|
|
|
ac073TC1POFS = 0x03
|
|
|
ac073TC1PON = 0x04
|
|
|
ac073TC1BTST1 = 0x05
|
|
|
ac073TC1BTST2 = 0x06
|
|
|
ac073TC1DSLP = 0x07
|
|
|
ac073TC1BTST3 = 0x08
|
|
|
ac073TC1DTM = 0x10
|
|
|
ac073TC1DSP = 0x11
|
|
|
ac073TC1DRF = 0x12
|
|
|
ac073TC1IPC = 0x13
|
|
|
ac073TC1PLL = 0x30
|
|
|
ac073TC1TSC = 0x40
|
|
|
ac073TC1TSE = 0x41
|
|
|
ac073TC1TSW = 0x42
|
|
|
ac073TC1TSR = 0x43
|
|
|
ac073TC1CDI = 0x50
|
|
|
ac073TC1LPD = 0x51
|
|
|
ac073TC1TCON = 0x60
|
|
|
ac073TC1TRES = 0x61
|
|
|
ac073TC1DAM = 0x65
|
|
|
ac073TC1REV = 0x70
|
|
|
ac073TC1FLG = 0x71
|
|
|
ac073TC1AMV = 0x80
|
|
|
ac073TC1VV = 0x81
|
|
|
ac073TC1VDCS = 0x82
|
|
|
ac073TC1TVDCS = 0x84
|
|
|
ac073TC1AGID = 0x86
|
|
|
ac073TC1CMDH = 0xAA
|
|
|
ac073TC1CCSET = 0xE0
|
|
|
ac073TC1PWS = 0xE3
|
|
|
ac073TC1TSSET = 0xE6
|
|
|
)
|
|
|
|
|
|
// DevImpression is a handle to an Inky Impression.
|
|
|
type DevImpression struct {
|
|
|
Dev
|
|
|
|
|
|
// Color Palette used to convert images to the 7 color.
|
|
|
Palette color.Palette
|
|
|
// Representation of the pixels.
|
|
|
Pix []uint8
|
|
|
|
|
|
// Saturation level used by the color palette.
|
|
|
saturation uint
|
|
|
// Resolution magic number used for resetting the panel.
|
|
|
res int
|
|
|
}
|
|
|
|
|
|
// NewImpression opens a handle to an Inky Impression.
|
|
|
func NewImpression(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinIn, o *Opts) (*DevImpression, error) {
|
|
|
if o.ModelColor != Multi {
|
|
|
return nil, fmt.Errorf("unsupported color: %v", o.ModelColor)
|
|
|
}
|
|
|
|
|
|
cSpeed := ucSpeed
|
|
|
if o.Model == IMPRESSION73 {
|
|
|
cSpeed = acSpeed
|
|
|
}
|
|
|
// The SPI driver has a max buffer size of 4K bytes, but our image size
|
|
|
// is 192K Bytes. To make the Impression 7.3 treat this as single trans-
|
|
|
// action, we have to take over control of the CS pin and manipulate it
|
|
|
// as required.
|
|
|
c, err := p.Connect(cSpeed, spi.Mode0|spi.NoCS, cs0Pin)
|
|
|
if err != nil {
|
|
|
return nil, fmt.Errorf("failed to connect to inky over spi: %v", err)
|
|
|
}
|
|
|
|
|
|
// Get the maxTxSize from the conn if it implements the conn.Limits interface,
|
|
|
// otherwise use 4096 bytes.
|
|
|
maxTxSize := 0
|
|
|
if limits, ok := c.(conn.Limits); ok {
|
|
|
maxTxSize = limits.MaxTxSize()
|
|
|
}
|
|
|
if maxTxSize == 0 {
|
|
|
if o.Model == IMPRESSION73 {
|
|
|
maxTxSize = math.MaxInt // No chunking for AC073TC1 by default.
|
|
|
} else {
|
|
|
maxTxSize = 4096 // Use a conservative default.
|
|
|
}
|
|
|
}
|
|
|
|
|
|
d := &DevImpression{
|
|
|
Dev: Dev{
|
|
|
c: c,
|
|
|
maxTxSize: maxTxSize,
|
|
|
dc: dc,
|
|
|
r: reset,
|
|
|
busy: busy,
|
|
|
color: o.ModelColor,
|
|
|
border: o.BorderColor,
|
|
|
model: o.Model,
|
|
|
variant: o.DisplayVariant,
|
|
|
pcbVariant: o.PCBVariant,
|
|
|
cs: gpioreg.ByName("GPIO8"),
|
|
|
},
|
|
|
saturation: 50, // Looks good enough for most of the images.
|
|
|
}
|
|
|
|
|
|
switch o.Model {
|
|
|
case IMPRESSION4:
|
|
|
d.width = 640
|
|
|
d.height = 400
|
|
|
d.res = 0b10
|
|
|
case IMPRESSION57:
|
|
|
d.width = 600
|
|
|
d.height = 448
|
|
|
d.res = 0b11
|
|
|
case IMPRESSION73:
|
|
|
d.width = 800
|
|
|
d.height = 480
|
|
|
d.res = 0b11
|
|
|
}
|
|
|
// Prefer the passed in values via Opts.
|
|
|
if o.Width != 0 && o.Height != 0 {
|
|
|
d.width = o.Width
|
|
|
d.height = o.Height
|
|
|
}
|
|
|
d.bounds = image.Rect(0, 0, d.width, d.height)
|
|
|
|
|
|
d.Pix = make([]uint8, d.height*d.width)
|
|
|
|
|
|
return d, nil
|
|
|
}
|
|
|
|
|
|
// blend recalculates the palette based on the saturation level.
|
|
|
func (d *DevImpression) blend() []color.Color {
|
|
|
sat := float64(d.saturation / 100)
|
|
|
|
|
|
pr := []color.Color{}
|
|
|
for i := 0; i < 7; i++ {
|
|
|
var rs, gs, bs uint8
|
|
|
if d.Dev.model == IMPRESSION73 {
|
|
|
rs, gs, bs =
|
|
|
uint8(float64(sc7[i].R)*sat),
|
|
|
uint8(float64(sc7[i].G)*sat),
|
|
|
uint8(float64(sc7[i].B)*sat)
|
|
|
} else {
|
|
|
rs, gs, bs =
|
|
|
uint8(float64(sc[i].R)*sat),
|
|
|
uint8(float64(sc[i].G)*sat),
|
|
|
uint8(float64(sc[i].B)*sat)
|
|
|
}
|
|
|
|
|
|
rd, gd, bd :=
|
|
|
uint8(float64(dsc[i].R)*(1.0-sat)),
|
|
|
uint8(float64(dsc[i].G)*(1.0-sat)),
|
|
|
uint8(float64(dsc[i].B)*(1.0-sat))
|
|
|
|
|
|
pr = append(pr, color.RGBA{rs + rd, gs + gd, bs + bd, dsc[i].A})
|
|
|
}
|
|
|
// Add Transparent color and return the result.
|
|
|
return append(pr, color.RGBA{255, 255, 255, 0})
|
|
|
}
|
|
|
|
|
|
// Saturation returns the current saturation level.
|
|
|
func (d *DevImpression) Saturation() uint {
|
|
|
return d.saturation
|
|
|
}
|
|
|
|
|
|
// SetSaturaton changes the saturation level. This will take effect on the next call to [*DevImpression.Draw]().
|
|
|
func (d *DevImpression) SetSaturation(level uint) error {
|
|
|
if level > 100 {
|
|
|
return fmt.Errorf("saturation level needs to be between 0 and 100")
|
|
|
}
|
|
|
d.saturation = level
|
|
|
// so that caller can recalculate next time they need it.
|
|
|
d.Palette = nil
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
// SetBorder changes the border color. This will take effect on the next call to [*DevImpression.Draw]().
|
|
|
func (d *DevImpression) SetBorder(c ImpressionColor) {
|
|
|
d.border = Color(c)
|
|
|
}
|
|
|
|
|
|
// Render renders the content of the [*DevImpression.Pix] to the screen.
|
|
|
func (d *DevImpression) Render() error {
|
|
|
if d.flipVertically {
|
|
|
for w := 0; w < len(d.Pix)/2-1; w = w + d.width {
|
|
|
for offset := 0; offset < d.width; offset++ {
|
|
|
d.Pix[w+offset], d.Pix[len(d.Pix)-d.width-w+offset] = d.Pix[len(d.Pix)-d.width-w+offset], d.Pix[w+offset]
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if d.flipHorizontally {
|
|
|
for offset := 0; offset < len(d.Pix)-1; offset = offset + d.width {
|
|
|
for i, j := 0, d.width-1; i < j; i, j = i+1, j-1 {
|
|
|
d.Pix[i+offset], d.Pix[j+offset] = d.Pix[j+offset], d.Pix[i+offset]
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
merged := make([]uint8, len(d.Pix)/2)
|
|
|
for i, offset := 0, 0; i < len(d.Pix)-1; i, offset = i+2, offset+1 {
|
|
|
merged[offset] = (d.Pix[i]<<4)&0xF0 | d.Pix[i+1]&0x0F
|
|
|
}
|
|
|
|
|
|
return d.update(merged)
|
|
|
}
|
|
|
|
|
|
func (d *DevImpression) cycleResetGPIO() error {
|
|
|
if err := d.r.Out(gpio.Low); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
return d.r.Out(gpio.High)
|
|
|
}
|
|
|
|
|
|
func (d *DevImpression) resetUC() error {
|
|
|
if err := d.cycleResetGPIO(); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
d.wait(1 * time.Second)
|
|
|
|
|
|
// Resolution Setting
|
|
|
// 10bit horizontal followed by a 10bit vertical resolution
|
|
|
tres := make([]byte, 4)
|
|
|
binary.LittleEndian.PutUint16(tres[0:], uint16(d.width))
|
|
|
binary.LittleEndian.PutUint16(tres[2:], uint16(d.height))
|
|
|
|
|
|
if err := d.sendCommand(uc8159TRES, tres); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
// Panel Setting
|
|
|
// 0b11000000 = Resolution select, 0b00 = 640x480, our panel is 0b11 = 600x448
|
|
|
// 0b00100000 = LUT selection, 0 = ext flash, 1 = registers, we use ext flash
|
|
|
// 0b00010000 = Ignore
|
|
|
// 0b00001000 = Gate scan direction, 0 = down, 1 = up (default)
|
|
|
// 0b00000100 = Source shift direction, 0 = left, 1 = right (default)
|
|
|
// 0b00000010 = DC-DC converter, 0 = off, 1 = on
|
|
|
// 0b00000001 = Soft reset, 0 = Reset, 1 = Normal (Default)
|
|
|
// 0b11 = 600x448
|
|
|
// 0b10 = 640x400
|
|
|
if err := d.sendCommand(
|
|
|
uc8159PSR,
|
|
|
[]byte{
|
|
|
byte(d.res<<6) | 0b101111, // See above for more magic numbers
|
|
|
0x08, // display_colours == UC81597C
|
|
|
}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
// Power Settings
|
|
|
if err := d.sendCommand(
|
|
|
uc8159PWR,
|
|
|
[]byte{
|
|
|
(0x06 << 3) | // ??? - not documented in UC8159 datasheet
|
|
|
(0x01 << 2) | // SOURCE_INTERNAL_DC_DC
|
|
|
(0x01 << 1) | // GATE_INTERNAL_DC_DC
|
|
|
(0x01), // LV_SOURCE_INTERNAL_DC_DC
|
|
|
0x00, // VGx_20V
|
|
|
0x23, // UC81597C
|
|
|
0x23, // UC81597C
|
|
|
}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
// Set the PLL clock frequency to 50Hz
|
|
|
// 0b11000000 = Ignore
|
|
|
// 0b00111000 = M
|
|
|
// 0b00000111 = N
|
|
|
// PLL = 2MHz * (M / N)
|
|
|
// PLL = 2MHz * (7 / 4)
|
|
|
// PLL = 2,800,000 ???
|
|
|
if err := d.sendCommand(uc8159PLL, []byte{0x3C}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
// 0b00111100
|
|
|
// Send the TSE register to the display
|
|
|
if err := d.sendCommand(uc8159TSE, []byte{0x00}); err != nil { // Color
|
|
|
return err
|
|
|
}
|
|
|
// VCOM and Data Interval setting
|
|
|
// 0b11100000 = Vborder control (0b001 = LUTB voltage)
|
|
|
// 0b00010000 = Data polarity
|
|
|
// 0b00001111 = Vcom and data interval (0b0111 = 10, default)
|
|
|
|
|
|
cdi := make([]byte, 2)
|
|
|
binary.LittleEndian.PutUint16(cdi[0:], uint16(d.border<<5)|0x17) // 0b00110111
|
|
|
if err := d.sendCommand(uc8159CDI, cdi); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
// Gate/Source non-overlap period
|
|
|
// 0b11110000 = Source to Gate (0b0010 = 12nS, default)
|
|
|
// 0b00001111 = Gate to Source
|
|
|
if err := d.sendCommand(uc8159TCON, []byte{0x22}); err != nil { // 0b00100010
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
// Disable external flash
|
|
|
if err := d.sendCommand(uc8159DAM, []byte{0x00}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
// UC81597C
|
|
|
if err := d.sendCommand(uc8159PWS, []byte{0xAA}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
// Power off sequence
|
|
|
// 0b00110000 = power off sequence of VDH and VDL, 0b00 = 1 frame (default)
|
|
|
// All other bits ignored?
|
|
|
if err := d.sendCommand(uc8159PFS, []byte{0x00}); err != nil { // PFS_1_FRAME
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
func (d *DevImpression) resetAC() error {
|
|
|
// Reference code: https://github.com/pimoroni/inky/blob/a7d380231cc5fac754243b8d66d2c5674c1bd1ac/inky/inky_ac073tc1a.py#L229
|
|
|
|
|
|
if err := d.cycleResetGPIO(); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
if err := d.cycleResetGPIO(); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
d.wait(1 * time.Second)
|
|
|
|
|
|
// Sending init commands to display
|
|
|
if err := d.sendCommand(ac073TC1CMDH, []byte{0x49, 0x55, 0x20, 0x08, 0x09, 0x18}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1PWR, []byte{0x3F, 0x00, 0x32, 0x2A, 0x0E, 0x2A}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1PSR, []byte{0x5F, 0x69}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1POFS, []byte{0x00, 0x54, 0x00, 0x44}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1BTST1, []byte{0x40, 0x1F, 0x1F, 0x2C}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1BTST2, []byte{0x6F, 0x1F, 0x16, 0x25}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1BTST3, []byte{0x6F, 0x1F, 0x1F, 0x22}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1IPC, []byte{0x00, 0x04}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1PLL, []byte{0x02}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1TSE, []byte{0x00}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1CDI, []byte{0x3F}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1TCON, []byte{0x02, 0x00}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1TRES, []byte{0x03, 0x20, 0x01, 0xE0}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1VDCS, []byte{0x1E}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1TVDCS, []byte{0x00}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1AGID, []byte{0x00}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1PWS, []byte{0x2F}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1CCSET, []byte{0x00}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
if err := d.sendCommand(ac073TC1TSSET, []byte{0x00}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
func (d *DevImpression) update(pix []uint8) error {
|
|
|
if d.model == IMPRESSION73 {
|
|
|
return d.updateAC(pix)
|
|
|
}
|
|
|
return d.updateUC(pix)
|
|
|
}
|
|
|
|
|
|
func (d *DevImpression) updateUC(pix []uint8) error {
|
|
|
if err := d.resetUC(); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
if err := d.sendCommand(uc8159DTM1, pix); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
if err := d.sendCommand(uc8159PON, nil); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
d.wait(200 * time.Millisecond)
|
|
|
|
|
|
if err := d.sendCommand(uc8159DRF, nil); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
d.wait(32 * time.Second)
|
|
|
|
|
|
if err := d.sendCommand(uc8159POF, nil); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
d.wait(200 * time.Millisecond)
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
func (d *DevImpression) updateAC(pix []uint8) error {
|
|
|
if err := d.resetAC(); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
// TODO there has to be a better way to force the white colour to be used instead of clear...
|
|
|
buf := make([]byte, len(pix))
|
|
|
for i := range pix {
|
|
|
buf[i] = pix[i]
|
|
|
if buf[i]&0xF == 7 {
|
|
|
buf[i] = (buf[i] & 0xF0) + 1
|
|
|
}
|
|
|
if buf[i]&0xF0 == 0x70 {
|
|
|
buf[i] = (buf[i] & 0xF) + 0x10
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if err := d.sendCommand(ac073TC1DTM, buf); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
if err := d.sendCommand(ac073TC1PON, nil); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
d.wait(400 * time.Millisecond)
|
|
|
|
|
|
if err := d.sendCommand(ac073TC1DRF, []byte{0x00}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
d.wait(45 * time.Second) // 41 seconds in testing
|
|
|
|
|
|
if err := d.sendCommand(ac073TC1POF, []byte{0x00}); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
d.wait(400 * time.Millisecond)
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
// Wait for busy/wait pin.
|
|
|
func (d *DevImpression) wait(dur time.Duration) {
|
|
|
// Set it as input, with a pull down and enable rising edge triggering.
|
|
|
if err := d.busy.In(gpio.PullDown, gpio.RisingEdge); err != nil {
|
|
|
log.Printf("Err: %s", err)
|
|
|
return
|
|
|
}
|
|
|
if d.busy.Read() == gpio.High {
|
|
|
time.Sleep(dur)
|
|
|
return
|
|
|
}
|
|
|
// Wait for rising edges (Low -> High) or the timeout.
|
|
|
tEnd := time.Now().Add(dur)
|
|
|
edgeDur := dur
|
|
|
for tEnd.After(time.Now()) {
|
|
|
// Debounce the edge
|
|
|
edge := d.busy.WaitForEdge(edgeDur)
|
|
|
if edge {
|
|
|
// The python driver is using 10ms debounce period
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
l := d.busy.Read()
|
|
|
if l {
|
|
|
// It's still high. Return
|
|
|
return
|
|
|
}
|
|
|
// It was a bounce. Recalculate the duration to wait for the edge.
|
|
|
edgeDur = tEnd.Sub(time.Now())
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ColorModel returns the device native color model.
|
|
|
func (d *DevImpression) ColorModel() color.Model {
|
|
|
if d.Palette == nil {
|
|
|
d.Palette = d.blend()
|
|
|
}
|
|
|
return d.Palette
|
|
|
}
|
|
|
|
|
|
// At returns the color of the pixel at (x, y).
|
|
|
func (d *DevImpression) At(x, y int) color.Color {
|
|
|
if d.Palette == nil {
|
|
|
d.Palette = d.blend()
|
|
|
}
|
|
|
return d.Palette[d.Pix[y*d.width+x]]
|
|
|
}
|
|
|
|
|
|
// Set sets the pixel at (x, y) to the given color. This will take effect on the next [*DevImpression.Draw]().
|
|
|
func (d *DevImpression) Set(x, y int, c color.Color) {
|
|
|
if d.Palette == nil {
|
|
|
d.Palette = d.blend()
|
|
|
}
|
|
|
d.Pix[y*d.width+x] = uint8(d.Palette.Index(c))
|
|
|
}
|
|
|
|
|
|
// Draw updates the display with the image.
|
|
|
func (d *DevImpression) Draw(r image.Rectangle, src image.Image, sp image.Point) error {
|
|
|
if r != d.Bounds() {
|
|
|
return fmt.Errorf("partial updates are not supported r=%#v bounds=%#v", r, d.Bounds())
|
|
|
}
|
|
|
|
|
|
if src.Bounds() != d.Bounds() {
|
|
|
return fmt.Errorf("image must be the same size as bounds: %v", d.Bounds())
|
|
|
}
|
|
|
|
|
|
// Dither the image using Floyd–Steinberg dithering algorithm otherwise it won't look as good on the screen.
|
|
|
draw.FloydSteinberg.Draw(d, r, src, image.Point{})
|
|
|
return d.Render()
|
|
|
}
|
|
|
|
|
|
// DrawAll redraws the whole display.
|
|
|
func (d *DevImpression) DrawAll(src image.Image) error {
|
|
|
return d.Draw(d.Bounds(), src, image.Point{})
|
|
|
}
|