Skip to content

Commit 4a0bcba

Browse files
epd2in66b: Waveshare 2.66inch E-Paper Display Module (B) for Raspberry Pi Pico (#673)
epd2in66b: add working driver Co-authored-by: Thomas Richner <[email protected]>
1 parent 7d98364 commit 4a0bcba

File tree

6 files changed

+499
-1
lines changed

6 files changed

+499
-1
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst
1919
# Recursively find all *_test.go files from cwd & reduce to unique dir names
2020
HAS_TESTS = $(sort $(dir $(call rwildcard,,*_test.go)))
2121
# Exclude anything we explicitly don't want to test for whatever reason
22-
EXCLUDE_TESTS = image
22+
EXCLUDE_TESTS = image waveshare-epd/epd2in66b
2323
TESTS = $(filter-out $(addsuffix /%,$(EXCLUDE_TESTS)),$(HAS_TESTS))
2424

2525
unit-test:
+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package main
2+
3+
import (
4+
"image/color"
5+
"machine"
6+
"time"
7+
8+
"tinygo.org/x/drivers"
9+
"tinygo.org/x/drivers/waveshare-epd/epd2in66b"
10+
"tinygo.org/x/tinyfont"
11+
"tinygo.org/x/tinyfont/freemono"
12+
)
13+
14+
var (
15+
black = color.RGBA{0, 0, 0, 0xff}
16+
white = color.RGBA{0xff, 0xff, 0xff, 0xff}
17+
red = color.RGBA{0xff, 0, 0, 0xff}
18+
)
19+
20+
func main() {
21+
machine.Serial.Configure(machine.UARTConfig{})
22+
time.Sleep(2 * time.Second)
23+
24+
machine.SPI1.Configure(machine.SPIConfig{
25+
Frequency: epd2in66b.Baudrate,
26+
})
27+
28+
println("started")
29+
30+
// in case you have a Pico module, you can directly use
31+
// dev, err := epd2in66b.NewPicoModule()
32+
33+
display := epd2in66b.New(machine.SPI1)
34+
35+
cfg := epd2in66b.Config{
36+
DataPin: machine.GP8,
37+
ChipSelectPin: machine.GP9,
38+
ResetPin: machine.GP12,
39+
BusyPin: machine.GP13,
40+
}
41+
err := display.Configure(cfg)
42+
if err != nil {
43+
panic(err)
44+
}
45+
46+
err = display.Reset()
47+
if err != nil {
48+
panic(err)
49+
}
50+
51+
println("draw checkerboard")
52+
drawCheckerBoard(&display)
53+
54+
println("draw 'hello'")
55+
tinyfont.WriteLineRotated(&display, &freemono.Bold24pt7b, 40, 10, "Hello!", white, tinyfont.ROTATION_90)
56+
tinyfont.WriteLineRotated(&display, &freemono.Bold12pt7b, 10, 10, "tinygo rocks", white, tinyfont.ROTATION_90)
57+
err = display.Display()
58+
if err != nil {
59+
panic(err)
60+
}
61+
}
62+
63+
func drawCheckerBoard(display drivers.Displayer) {
64+
s := 8
65+
width, height := display.Size()
66+
for x := 0; x <= int(width)-s; x += s {
67+
for y := 0; y <= int(height)-s; y += s {
68+
c := red
69+
if (x/s)%2 == (y/s)%2 {
70+
c = black
71+
}
72+
73+
showRect(display, x, y, s, s, c)
74+
}
75+
}
76+
}
77+
78+
func showRect(display drivers.Displayer, x int, y int, w int, h int, c color.RGBA) {
79+
for i := x; i < x+w; i++ {
80+
for j := y; j < y+h; j++ {
81+
display.SetPixel(int16(i), int16(j), c)
82+
}
83+
}
84+
}

smoketest.sh

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl6
8080
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13/main.go
8181
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
8282
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd4in2/main.go
83+
tinygo build -size short -o ./build/test.hex -target=pico ./examples/waveshare-epd/epd2in66b/main.go
8384
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
8485
tinygo build -size short -o ./build/test.bin -target=m5stamp-c3 ./examples/ws2812
8586
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go

waveshare-epd/epd2in66b/dev.go

+287
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
// Package epd2in66b implements a driver for the Waveshare 2.66inch E-Paper E-Ink Display Module (B)
2+
// for Raspberry Pi Pico, 296×152, Red / Black / White
3+
// Datasheet: https://files.waveshare.com/upload/e/ec/2.66inch-e-paper-b-specification.pdf
4+
package epd2in66b
5+
6+
import (
7+
"image/color"
8+
"machine"
9+
"time"
10+
11+
"tinygo.org/x/drivers"
12+
)
13+
14+
const (
15+
displayWidth = 152
16+
displayHeight = 296
17+
)
18+
19+
const Baudrate = 4_000_000 // 4 MHz
20+
21+
type Config struct {
22+
ResetPin machine.Pin
23+
DataPin machine.Pin
24+
ChipSelectPin machine.Pin
25+
BusyPin machine.Pin
26+
}
27+
28+
type Device struct {
29+
bus drivers.SPI
30+
cs machine.Pin
31+
dc machine.Pin
32+
rst machine.Pin
33+
busy machine.Pin
34+
35+
blackBuffer []byte
36+
redBuffer []byte
37+
}
38+
39+
// New allocates a new device.
40+
// The bus is expected to be configured and ready for use.
41+
func New(bus drivers.SPI) Device {
42+
pixelCount := displayWidth * displayHeight
43+
44+
bufLen := pixelCount / 8
45+
46+
return Device{
47+
bus: bus,
48+
blackBuffer: make([]byte, bufLen),
49+
redBuffer: make([]byte, bufLen),
50+
}
51+
}
52+
53+
// Configure configures the device and its pins.
54+
func (d *Device) Configure(c Config) error {
55+
d.cs = c.ChipSelectPin
56+
d.dc = c.DataPin
57+
d.rst = c.ResetPin
58+
d.busy = c.BusyPin
59+
60+
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
61+
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
62+
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
63+
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
64+
65+
return nil
66+
}
67+
68+
func (d *Device) Size() (x, y int16) {
69+
return displayWidth, displayHeight
70+
}
71+
72+
// SetPixel modifies the internal buffer in a single pixel.
73+
// The display has 3 colors: red, black and white
74+
//
75+
// - white = RGBA(255,255,255, 1-255)
76+
// - red = RGBA(1-255,0,0,1-255)
77+
// - Anything else as black
78+
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
79+
if x < 0 || x >= displayWidth || y < 0 || y >= displayHeight {
80+
return
81+
}
82+
83+
bytePos, bitPos := pos(x, y, displayWidth)
84+
85+
if c.R == 0xff && c.G == 0xff && c.B == 0xff && c.A > 0 { // white
86+
set(d.blackBuffer, bytePos, bitPos)
87+
unset(d.redBuffer, bytePos, bitPos)
88+
} else if c.R != 0 && c.G == 0 && c.B == 0 && c.A > 0 { // red-ish
89+
set(d.blackBuffer, bytePos, bitPos)
90+
set(d.redBuffer, bytePos, bitPos)
91+
} else { // black or other
92+
unset(d.blackBuffer, bytePos, bitPos)
93+
unset(d.redBuffer, bytePos, bitPos)
94+
}
95+
}
96+
97+
func set(buf []byte, bytePos, bitPos int) {
98+
buf[bytePos] |= 0x1 << bitPos
99+
}
100+
101+
func unset(buf []byte, bytePos, bitPos int) {
102+
buf[bytePos] &^= 0x1 << bitPos
103+
}
104+
105+
func pos(x, y, stride int16) (bytePos int, bitPos int) {
106+
p := int(x) + int(y)*int(stride)
107+
bytePos = p / 8
108+
109+
// reverse bit position as it is reversed on the device's buffer
110+
bitPos = 7 - p%8
111+
112+
return bytePos, bitPos
113+
}
114+
115+
func (d *Device) Display() error {
116+
// Write RAM (Black White) / RAM 0x24
117+
// 1 == white, 0 == black
118+
if err := d.sendCommandByte(0x24); err != nil {
119+
return err
120+
}
121+
122+
if err := d.sendData(d.blackBuffer); err != nil {
123+
return err
124+
}
125+
126+
// Write RAM (RED) / RAM 0x26)
127+
// 0 == blank, 1 == red
128+
if err := d.sendCommandByte(0x26); err != nil {
129+
return err
130+
}
131+
132+
if err := d.sendData(d.redBuffer); err != nil {
133+
return err
134+
}
135+
136+
return d.turnOnDisplay()
137+
}
138+
139+
func (d *Device) ClearBuffer() {
140+
fill(d.redBuffer, 0x00)
141+
fill(d.blackBuffer, 0xff)
142+
}
143+
144+
func (d *Device) turnOnDisplay() error {
145+
// also documented as 'Master Activation'
146+
if err := d.sendCommandByte(0x20); err != nil {
147+
return err
148+
}
149+
d.WaitUntilIdle()
150+
return nil
151+
}
152+
153+
func (d *Device) Reset() error {
154+
d.hwReset()
155+
d.WaitUntilIdle()
156+
157+
// soft reset & set defaults
158+
if err := d.sendCommandByte(0x12); err != nil {
159+
return err
160+
}
161+
d.WaitUntilIdle()
162+
163+
// data entry mode setting
164+
if err := d.sendCommandSequence([]byte{0x11, 0x03}); err != nil {
165+
return err
166+
}
167+
168+
if err := d.setWindow(0, displayWidth-1, 0, displayHeight-1); err != nil {
169+
return err
170+
}
171+
172+
// display update control 1 - resolution setting
173+
if err := d.sendCommandSequence([]byte{0x21, 0x00, 0x80}); err != nil {
174+
return err
175+
}
176+
177+
if err := d.setCursor(0, 0); err != nil {
178+
return err
179+
}
180+
d.WaitUntilIdle()
181+
182+
return nil
183+
}
184+
185+
func (d *Device) setCursor(x, y uint16) error {
186+
// Set RAM X address counter
187+
if err := d.sendCommandSequence([]byte{0x4e, byte(x & 0x1f)}); err != nil {
188+
return err
189+
}
190+
191+
// Set RAM Y address counter
192+
yLo := byte(y)
193+
yHi := byte(y>>8) & 0x1
194+
if err := d.sendCommandSequence([]byte{0x4f, yLo, yHi}); err != nil {
195+
return err
196+
}
197+
198+
return nil
199+
}
200+
201+
func (d *Device) hwReset() {
202+
d.rst.High()
203+
time.Sleep(50 * time.Millisecond)
204+
d.rst.Low()
205+
time.Sleep(2 * time.Millisecond)
206+
d.rst.High()
207+
time.Sleep(50 * time.Millisecond)
208+
}
209+
210+
func (d *Device) setWindow(xstart, xend, ystart, yend int16) error {
211+
// set RAM X-address start / end position
212+
d1 := byte((xstart >> 3) & 0x1f)
213+
d2 := byte((xend >> 3) & 0x1f)
214+
if err := d.sendCommandSequence([]byte{0x44, d1, d2}); err != nil {
215+
return err
216+
}
217+
218+
// set RAM Y-address start / end position
219+
ystartLo := byte(ystart)
220+
ystartHi := byte(ystart>>8) & 0x1
221+
222+
yendLo := byte(yend)
223+
yendHi := byte(yend>>8) & 0x1
224+
225+
return d.sendCommandSequence([]byte{0x45, ystartLo, ystartHi, yendLo, yendHi})
226+
}
227+
228+
func (d *Device) WaitUntilIdle() {
229+
// give it some time to get busy
230+
time.Sleep(50 * time.Millisecond)
231+
232+
for d.busy.Get() { // high = busy
233+
time.Sleep(10 * time.Millisecond)
234+
}
235+
236+
// give it some extra time
237+
time.Sleep(50 * time.Millisecond)
238+
}
239+
240+
// sendCommandSequence sends the first byte in the buffer as a 'command' and all following bytes as data
241+
func (d *Device) sendCommandSequence(seq []byte) error {
242+
err := d.sendCommandByte(seq[0])
243+
if err != nil {
244+
return err
245+
}
246+
247+
for i := 1; i < len(seq); i++ {
248+
err = d.sendDataByte(seq[i])
249+
if err != nil {
250+
return err
251+
}
252+
}
253+
254+
return nil
255+
}
256+
257+
func (d *Device) sendCommandByte(b byte) error {
258+
d.dc.Low()
259+
d.cs.Low()
260+
_, err := d.bus.Transfer(b)
261+
d.cs.High()
262+
return err
263+
}
264+
265+
func (d *Device) sendDataByte(b byte) error {
266+
d.dc.High()
267+
d.cs.Low()
268+
_, err := d.bus.Transfer(b)
269+
d.cs.High()
270+
return err
271+
}
272+
273+
func (d *Device) sendData(b []byte) error {
274+
d.dc.High()
275+
d.cs.Low()
276+
err := d.bus.Tx(b, nil)
277+
d.cs.High()
278+
return err
279+
}
280+
281+
// fill quickly fills a slice with a given value
282+
func fill(s []byte, b byte) {
283+
s[0] = b
284+
for j := 1; j < len(s); j *= 2 {
285+
copy(s[j:], s[:j])
286+
}
287+
}

0 commit comments

Comments
 (0)