Skip to content

Commit 6b31587

Browse files
authored
inky: Fix Impression Driver (#106)
*Changed the SPI CS pin to work under automatic or manual control. This allows it to work when CS is controlled by SPI, or when it is controlled manually. We don't want it to fail in the configuration the Pimoroni python driver requires. *Changed the wait() routine to use a 10ms debounce on WaitForEdge() as the python driver does. *If the BUSY line is high on entry to wait to go directly to a timed wait as the python driver does. *Fixed errors in the palette definitions. Removed extra memory allocation.
1 parent f009056 commit 6b31587

File tree

5 files changed

+115
-40
lines changed

5 files changed

+115
-40
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ require (
1212
github.com/mattn/go-colorable v0.1.13
1313
golang.org/x/image v0.23.0
1414
periph.io/x/conn/v3 v3.7.2
15-
periph.io/x/host/v3 v3.8.4
15+
periph.io/x/host/v3 v3.8.5
1616
)
1717

1818
require (

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
1717
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1818
periph.io/x/conn/v3 v3.7.2 h1:qt9dE6XGP5ljbFnCKRJ9OOCoiOyBGlw7JZgoi72zZ1s=
1919
periph.io/x/conn/v3 v3.7.2/go.mod h1:Ao0b4sFRo4QOx6c1tROJU1fLJN1hUIYggjOrkIVnpGg=
20-
periph.io/x/host/v3 v3.8.4 h1:QNleTythDd0k6Chu0n+ISrJFlf3LFig9oNbtOIkxoCc=
21-
periph.io/x/host/v3 v3.8.4/go.mod h1:hPq8dISZIc+UNfWoRj+bPH3XEBQqJPdFdx218W92mdc=
20+
periph.io/x/host/v3 v3.8.5 h1:g4g5xE1XZtDiGl1UAJaUur1aT7uNiFLMkyMEiZ7IHII=
21+
periph.io/x/host/v3 v3.8.5/go.mod h1:hPq8dISZIc+UNfWoRj+bPH3XEBQqJPdFdx218W92mdc=

inky/impression.go

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"periph.io/x/conn/v3"
1818
"periph.io/x/conn/v3/display"
1919
"periph.io/x/conn/v3/gpio"
20+
"periph.io/x/conn/v3/gpio/gpioreg"
2021
"periph.io/x/conn/v3/physic"
2122
"periph.io/x/conn/v3/spi"
2223
)
@@ -28,36 +29,36 @@ var _ draw.Image = &DevImpression{}
2829
var (
2930
// For more: https://github.com/pimoroni/inky/issues/115#issuecomment-887453065
3031
dsc = []color.NRGBA{
31-
{0, 0, 0, 0}, // Black
32+
{0, 0, 0, 255}, // Black
3233
{255, 255, 255, 255}, // White
3334
{0, 255, 0, 255}, // Green
3435
{0, 0, 255, 255}, // Blue
3536
{255, 0, 0, 255}, // Red
3637
{255, 255, 0, 255}, // Yellow
3738
{255, 140, 0, 255}, // Orange
38-
{255, 255, 255, 255},
39+
{255, 255, 255, 0}, // Clear
3940
}
4041

4142
sc = []color.NRGBA{
42-
{57, 48, 57, 0}, // Black
43+
{57, 48, 57, 255}, // Black
4344
{255, 255, 255, 255}, // White
4445
{58, 91, 70, 255}, // Green
4546
{61, 59, 94, 255}, // Blue
4647
{156, 72, 75, 255}, // Red
4748
{208, 190, 71, 255}, // Yellow
4849
{177, 106, 73, 255}, // Orange
49-
{255, 255, 255, 255},
50+
{255, 255, 255, 0}, // Clear
5051
}
5152

5253
sc7 = []color.NRGBA{
53-
{0, 0, 0, 0}, // Black
54+
{0, 0, 0, 255}, // Black
5455
{217, 242, 255, 255}, // White
5556
{3, 124, 76, 255}, // Green
5657
{27, 46, 198, 255}, // Blue
5758
{245, 80, 34, 255}, // Red
5859
{255, 255, 68, 255}, // Yellow
5960
{239, 121, 44, 255}, // Orange
60-
{255, 255, 255, 255},
61+
{255, 255, 255, 0}, // Clear
6162
}
6263
)
6364

@@ -155,7 +156,7 @@ func NewImpression(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinI
155156
if o.Model == IMPRESSION73 {
156157
cSpeed = acSpeed
157158
}
158-
c, err := p.Connect(cSpeed, spi.Mode0, cs0Pin)
159+
c, err := p.Connect(cSpeed, spi.Mode0, 8)
159160
if err != nil {
160161
return nil, fmt.Errorf("failed to connect to inky over spi: %v", err)
161162
}
@@ -173,6 +174,11 @@ func NewImpression(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinI
173174
maxTxSize = 4096 // Use a conservative default.
174175
}
175176
}
177+
// If possible, grab the CS pin.
178+
cs := gpioreg.ByName(cs0Pin)
179+
if cs != nil && cs.Out(csDisabled) != nil {
180+
cs = nil
181+
}
176182

177183
d := &DevImpression{
178184
Dev: Dev{
@@ -186,6 +192,7 @@ func NewImpression(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinI
186192
model: o.Model,
187193
variant: o.DisplayVariant,
188194
pcbVariant: o.PCBVariant,
195+
cs: cs,
189196
},
190197
saturation: 50, // Looks good enough for most of the images.
191198
}
@@ -217,10 +224,10 @@ func NewImpression(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinI
217224
}
218225

219226
// blend recalculates the palette based on the saturation level.
220-
func (d *DevImpression) blend() []color.Color {
221-
sat := float64(d.saturation / 100)
227+
func (d *DevImpression) blend() color.Palette {
228+
sat := float64(d.saturation) / 100.0
222229

223-
pr := []color.Color{}
230+
pr := make([]color.Color, 0)
224231
for i := 0; i < 7; i++ {
225232
var rs, gs, bs uint8
226233
if d.Dev.model == IMPRESSION73 {
@@ -288,7 +295,7 @@ func (d *DevImpression) Render() error {
288295

289296
merged := make([]uint8, len(d.Pix)/2)
290297
for i, offset := 0, 0; i < len(d.Pix)-1; i, offset = i+2, offset+1 {
291-
merged[offset] = (d.Pix[i]<<4)&0xF0 | d.Pix[i+1]&0x0F
298+
merged[offset] = ((d.Pix[i] << 4) & 0xF0) | (d.Pix[i+1] & 0x0F)
292299
}
293300

294301
return d.update(merged)
@@ -519,17 +526,16 @@ func (d *DevImpression) updateAC(pix []uint8) error {
519526
}
520527

521528
// TODO there has to be a better way to force the white colour to be used instead of clear...
522-
buf := make([]byte, len(pix))
523529
for i := range pix {
524530
if pix[i]&0xF == 7 {
525-
buf[i] = (pix[i] & 0xF0) + 1
531+
pix[i] = (pix[i] & 0xF0) + 1
526532
}
527533
if pix[i]&0xF0 == 0x70 {
528-
buf[i] = (pix[i] & 0xF) + 0x10
534+
pix[i] = (pix[i] & 0xF) + 0x10
529535
}
530536
}
531537

532-
if err := d.sendCommand(ac073TC1DTM, buf); err != nil {
538+
if err := d.sendCommand(ac073TC1DTM, pix); err != nil {
533539
return err
534540
}
535541

@@ -558,8 +564,28 @@ func (d *DevImpression) wait(dur time.Duration) {
558564
log.Printf("Err: %s", err)
559565
return
560566
}
567+
if d.busy.Read() == gpio.High {
568+
time.Sleep(dur)
569+
return
570+
}
561571
// Wait for rising edges (Low -> High) or the timeout.
562-
d.busy.WaitForEdge(dur)
572+
tEnd := time.Now().Add(dur)
573+
edgeDur := dur
574+
for tEnd.After(time.Now()) {
575+
// Debounce the edge
576+
edge := d.busy.WaitForEdge(edgeDur)
577+
if edge {
578+
// The python driver is using 10ms debounce period
579+
time.Sleep(10 * time.Millisecond)
580+
l := d.busy.Read()
581+
if l {
582+
// It's still high. Return
583+
return
584+
}
585+
// It was a bounce. Recalculate the duration to wait for the edge.
586+
edgeDur = time.Until(tEnd)
587+
}
588+
}
563589
}
564590

565591
// ColorModel returns the device native color model.
@@ -589,7 +615,7 @@ func (d *DevImpression) Set(x, y int, c color.Color) {
589615
// Draw updates the display with the image.
590616
func (d *DevImpression) Draw(r image.Rectangle, src image.Image, sp image.Point) error {
591617
if r != d.Bounds() {
592-
return fmt.Errorf("partial updates are not supported")
618+
return fmt.Errorf("partial updates are not supported r=%#v bounds=%#v", r, d.Bounds())
593619
}
594620

595621
if src.Bounds() != d.Bounds() {
@@ -598,6 +624,7 @@ func (d *DevImpression) Draw(r image.Rectangle, src image.Image, sp image.Point)
598624

599625
// Dither the image using Floyd–Steinberg dithering algorithm otherwise it won't look as good on the screen.
600626
draw.FloydSteinberg.Draw(d, r, src, image.Point{})
627+
601628
return d.Render()
602629
}
603630

inky/inky.go

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,27 @@ import (
1414
"periph.io/x/conn/v3"
1515
"periph.io/x/conn/v3/display"
1616
"periph.io/x/conn/v3/gpio"
17+
"periph.io/x/conn/v3/gpio/gpioreg"
1718
"periph.io/x/conn/v3/physic"
1819
"periph.io/x/conn/v3/spi"
1920
)
2021

2122
var _ display.Drawer = &Dev{}
2223
var _ conn.Resource = &Dev{}
2324

24-
const (
25-
cs0Pin = 8
26-
)
27-
2825
var borderColor = map[Color]byte{
2926
Black: 0x00,
3027
Red: 0x73,
3128
Yellow: 0x33,
3229
White: 0x31,
3330
}
3431

32+
const (
33+
cs0Pin = "GPIO8"
34+
csEnabled = gpio.Low
35+
csDisabled = gpio.High
36+
)
37+
3538
// Dev is a handle to an Inky.
3639
type Dev struct {
3740
c conn.Conn
@@ -65,6 +68,8 @@ type Dev struct {
6568
variant uint
6669
// PCB Variant of the panel. Represents a version string as a number (12 -> 1.2).
6770
pcbVariant uint
71+
// cs is the chip-select pin for SPI. Refer to setCSPin() for information.
72+
cs gpio.PinOut
6873
}
6974

7075
// New opens a handle to an Inky pHAT or wHAT.
@@ -73,7 +78,7 @@ func New(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinIn, o *Opts
7378
return nil, fmt.Errorf("unsupported color: %v", o.ModelColor)
7479
}
7580

76-
c, err := p.Connect(488*physic.KiloHertz, spi.Mode0, cs0Pin)
81+
c, err := p.Connect(488*physic.KiloHertz, spi.Mode0, 8)
7782
if err != nil {
7883
return nil, fmt.Errorf("failed to connect to inky over spi: %v", err)
7984
}
@@ -87,7 +92,11 @@ func New(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinIn, o *Opts
8792
if maxTxSize == 0 {
8893
maxTxSize = 4096 // Use a conservative default.
8994
}
90-
95+
// If possible, grab the CS pin.
96+
cs := gpioreg.ByName(cs0Pin)
97+
if cs != nil && cs.Out(csDisabled) != nil {
98+
cs = nil
99+
}
91100
d := &Dev{
92101
c: c,
93102
maxTxSize: maxTxSize,
@@ -99,6 +108,7 @@ func New(p spi.Port, dc gpio.PinOut, reset gpio.PinOut, busy gpio.PinIn, o *Opts
99108
model: o.Model,
100109
variant: o.DisplayVariant,
101110
pcbVariant: o.PCBVariant,
111+
cs: cs,
102112
}
103113

104114
switch o.Model {
@@ -330,43 +340,79 @@ func (d *Dev) reset() (err error) {
330340
}
331341
}()
332342
if err := d.sendCommand(0x12, nil); err != nil { // Soft Reset
333-
return fmt.Errorf("failed to reset inky: %v", err)
343+
return fmt.Errorf("inky: failed to reset inky: %v", err)
334344
}
335345
d.busy.WaitForEdge(-1)
336346
return
337347
}
338348

339-
func (d *Dev) sendCommand(command byte, data []byte) error {
340-
if err := d.dc.Out(gpio.Low); err != nil {
341-
return err
349+
// setCSPin sets the ChipSelect pin to the desired mode. The Pimoroni driver
350+
// uses manual control over the CS pin. To do this, they require the
351+
// Raspberry Pi /boot/firmware/config.txt to have dtloverlay=spi0-0cs set.
352+
//
353+
// So, if we run with automatic CS handling, we won't be compatible with the
354+
// pimoroni samples. If we run with manual control required, we then require
355+
// the dtoverlay setting. We really don't want to be incompatible with the
356+
// Pimoroni driver because that will confuse people. If the CS Pin is
357+
// not in use, use manual control, and if it is used by the SPI driver, let
358+
// it handle it.
359+
func (d *Dev) setCSPin(mode gpio.Level) error {
360+
if d.cs != nil {
361+
return d.cs.Out(mode)
362+
}
363+
return nil
364+
}
365+
366+
func (d *Dev) sendCommand(command byte, data []byte) (err error) {
367+
err = d.setCSPin(csEnabled)
368+
if err != nil {
369+
return
370+
}
371+
372+
if err = d.dc.Out(gpio.Low); err != nil {
373+
return
342374
}
343-
if err := d.c.Tx([]byte{command}, nil); err != nil {
344-
return fmt.Errorf("failed to send command %x to inky: %v", command, err)
375+
if err = d.c.Tx([]byte{command}, nil); err != nil {
376+
err = fmt.Errorf("inky: failed to send command %x to inky: %v", command, err)
377+
return
345378
}
379+
err = d.setCSPin(csDisabled)
380+
if err != nil {
381+
return
382+
}
383+
346384
if data != nil {
347-
if err := d.sendData(data); err != nil {
348-
return fmt.Errorf("failed to send data for command %x to inky: %v", command, err)
385+
if err = d.sendData(data); err != nil {
386+
err = fmt.Errorf("inky: failed to send data for command %x to inky: %v", command, err)
387+
return
349388
}
350389
}
351-
return nil
390+
return
352391
}
353392

354-
func (d *Dev) sendData(data []byte) error {
355-
if err := d.dc.Out(gpio.High); err != nil {
393+
func (d *Dev) sendData(data []byte) (err error) {
394+
err = d.setCSPin(csEnabled)
395+
if err != nil {
396+
return
397+
}
398+
if err = d.dc.Out(gpio.High); err != nil {
356399
return err
357400
}
401+
358402
for len(data) != 0 {
359403
var chunk []byte
360404
if len(data) > d.maxTxSize {
361405
chunk, data = data[:d.maxTxSize], data[d.maxTxSize:]
362406
} else {
363407
chunk, data = data, nil
364408
}
365-
if err := d.c.Tx(chunk, nil); err != nil {
366-
return fmt.Errorf("failed to send data to inky: %v", err)
409+
if err = d.c.Tx(chunk, nil); err != nil {
410+
err = fmt.Errorf("inky: failed to send data to inky: %v", err)
411+
return
367412
}
368413
}
369-
return nil
414+
err = d.setCSPin(csDisabled)
415+
return
370416
}
371417

372418
func pack(bits []bool) ([]byte, error) {

inky/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ func (c *Color) Set(s string) error {
6969
*c = Yellow
7070
case "white":
7171
*c = White
72+
case "multi":
73+
*c = Multi
7274
default:
7375
return fmt.Errorf("unknown color %q: expected either black, red, yellow or white", s)
7476
}

0 commit comments

Comments
 (0)