Skip to content

Commit b09dc06

Browse files
x86edgsexton
andauthored
waveshare2in13v3/waveshare2in13v4: adding v3 and v4 waveshare 2.13" ePaper displays (#65)
* adding v3 and v4 waveshare * updating file * Update waveshare213v4.go Remove unreferenced, unexported values. * Update waveshare213v3.go Remove unreferenced, unexported constants. --------- Co-authored-by: gsexton <georges@mhsoftware.com>
1 parent cad2275 commit b09dc06

18 files changed

+3786
-0
lines changed

waveshare2in13v3/controller.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright 2021 The Periph Authors. All rights reserved.
2+
// Use of this source code is governed under the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
5+
package waveshare2in13v3
6+
7+
type controller interface {
8+
sendCommand(byte)
9+
sendData([]byte)
10+
waitUntilIdle()
11+
}
12+
13+
func initDisplay(ctrl controller, opts *Opts) {
14+
ctrl.waitUntilIdle()
15+
ctrl.sendCommand(swReset)
16+
ctrl.waitUntilIdle()
17+
18+
ctrl.sendCommand(driverOutputControl)
19+
ctrl.sendData([]byte{0xf9, 0x00, 0x00})
20+
21+
ctrl.sendCommand(dataEntryModeSetting)
22+
ctrl.sendData([]byte{0x03})
23+
24+
setWindow(ctrl, 0, 0, opts.Width-1, opts.Height-1)
25+
setCursor(ctrl, 0, 0)
26+
27+
ctrl.sendCommand(borderWaveformControl)
28+
ctrl.sendData([]byte{0x05})
29+
30+
ctrl.sendCommand(displayUpdateControl1)
31+
ctrl.sendData([]byte{0x00, 0x80})
32+
33+
ctrl.sendCommand(tempSensorSelect)
34+
ctrl.sendData([]byte{0x80})
35+
36+
ctrl.waitUntilIdle()
37+
38+
setLut(ctrl, opts.FullUpdate)
39+
}
40+
41+
func configDisplayMode(ctrl controller, mode PartialUpdate, lut LUT) {
42+
var vcom byte
43+
var borderWaveformControlValue byte
44+
45+
switch mode {
46+
case Full:
47+
vcom = 0x55
48+
borderWaveformControlValue = 0x03
49+
case Partial:
50+
vcom = 0x24
51+
borderWaveformControlValue = 0x01
52+
}
53+
54+
ctrl.sendCommand(writeVcomRegister)
55+
ctrl.sendData([]byte{vcom})
56+
57+
ctrl.sendCommand(borderWaveformControl)
58+
ctrl.sendData([]byte{borderWaveformControlValue})
59+
60+
ctrl.sendCommand(writeLutRegister)
61+
ctrl.sendData(lut[:70])
62+
63+
ctrl.sendCommand(writeDisplayOptionRegister)
64+
ctrl.sendData([]byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00})
65+
66+
// Start up the parts likely used by a draw operation soon.
67+
ctrl.sendCommand(displayUpdateControl2)
68+
ctrl.sendData([]byte{displayUpdateEnableClock | displayUpdateEnableAnalog})
69+
70+
ctrl.sendCommand(masterActivation)
71+
ctrl.waitUntilIdle()
72+
}
73+
74+
func updateDisplay(ctrl controller, mode PartialUpdate) {
75+
var displayUpdateFlags byte
76+
77+
if mode == Partial {
78+
// Make use of red buffer
79+
displayUpdateFlags = 0b1000_0000
80+
}
81+
82+
ctrl.sendCommand(displayUpdateControl1)
83+
ctrl.sendData([]byte{displayUpdateFlags})
84+
85+
ctrl.sendCommand(displayUpdateControl2)
86+
ctrl.sendData([]byte{
87+
displayUpdateDisableClock |
88+
displayUpdateDisableAnalog |
89+
displayUpdateDisplay |
90+
displayUpdateEnableClock |
91+
displayUpdateEnableAnalog,
92+
})
93+
94+
ctrl.sendCommand(masterActivation)
95+
ctrl.waitUntilIdle()
96+
}
97+
98+
// new
99+
100+
// turnOnDisplay turns on the display if mode = true it does a partial display
101+
func turnOnDisplay(ctrl controller, mode PartialUpdate) {
102+
var upMode byte = 0xC7
103+
if mode {
104+
upMode = 0x0f
105+
}
106+
ctrl.sendCommand(displayUpdateControl2)
107+
ctrl.sendData([]byte{upMode})
108+
ctrl.sendCommand(masterActivation)
109+
ctrl.waitUntilIdle()
110+
}
111+
112+
func lookUpTable(ctrl controller, lut LUT) {
113+
ctrl.sendCommand(writeLutRegister)
114+
ctrl.sendData(lut[:153])
115+
ctrl.waitUntilIdle()
116+
}
117+
118+
func setLut(ctrl controller, lut LUT) {
119+
lookUpTable(ctrl, lut)
120+
ctrl.sendCommand(endOptionEOPT)
121+
ctrl.sendData([]byte{lut[153]})
122+
ctrl.sendCommand(gateDrivingVoltageControl)
123+
ctrl.sendData([]byte{lut[154]})
124+
ctrl.sendCommand(sourceDrivingVoltageControl)
125+
ctrl.sendData(lut[155:157])
126+
ctrl.sendCommand(writeVcomRegister)
127+
ctrl.sendData([]byte{lut[158]})
128+
}
129+
130+
func setWindow(ctrl controller, x_start int, y_start int, x_end int, y_end int) {
131+
ctrl.sendCommand(setRAMXAddressStartEndPosition)
132+
ctrl.sendData([]byte{byte((x_start >> 3) & 0xFF), byte((x_end >> 3) & 0xFF)})
133+
134+
ctrl.sendCommand(setRAMYAddressStartEndPosition)
135+
ctrl.sendData([]byte{byte(y_start & 0xFF), byte((y_start >> 8) & 0xFF), byte(y_end & 0xFF), byte((y_end >> 8) & 0xFF)})
136+
}
137+
138+
func setCursor(ctrl controller, x int, y int) {
139+
ctrl.sendCommand(setRAMXAddressCounter)
140+
// x point must be the multiple of 8 or the last 3 bits will be ignored
141+
ctrl.sendData([]byte{byte(x & 0xFF)})
142+
143+
ctrl.sendCommand(setRAMYAddressCounter)
144+
ctrl.sendData([]byte{byte(y & 0xFF), byte((y >> 8) & 0xFF)})
145+
}
146+
147+
func clear(ctrl controller, color byte, opts *Opts) {
148+
var linewidth int
149+
if opts.Width%8 == 0 {
150+
linewidth = int(opts.Width / 8)
151+
} else {
152+
linewidth = int(opts.Width/8) + 1
153+
}
154+
155+
var buff []byte
156+
ctrl.sendCommand(writeRAMBW)
157+
for j := 0; j < opts.Height; j++ {
158+
for i := 0; i < linewidth; i++ {
159+
buff = append(buff, color)
160+
}
161+
}
162+
ctrl.sendData(buff)
163+
164+
turnOnDisplay(ctrl, false)
165+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// Copyright 2021 The Periph Authors. All rights reserved.
2+
// Use of this source code is governed under the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
5+
package waveshare2in13v3
6+
7+
import (
8+
"bytes"
9+
"testing"
10+
11+
"github.com/google/go-cmp/cmp"
12+
"github.com/google/go-cmp/cmp/cmpopts"
13+
)
14+
15+
type record struct {
16+
cmd byte
17+
data []byte
18+
}
19+
20+
type fakeController []record
21+
22+
func (r *fakeController) sendCommand(cmd byte) {
23+
*r = append(*r, record{
24+
cmd: cmd,
25+
})
26+
}
27+
28+
func (r *fakeController) sendData(data []byte) {
29+
cur := &(*r)[len(*r)-1]
30+
cur.data = append(cur.data, data...)
31+
}
32+
33+
func (*fakeController) waitUntilIdle() {
34+
}
35+
36+
func TestInitDisplay(t *testing.T) {
37+
for _, tc := range []struct {
38+
name string
39+
opts Opts
40+
want []record
41+
}{
42+
{
43+
name: "epd2in13v3",
44+
opts: EPD2in13v3,
45+
want: []record{
46+
{cmd: swReset},
47+
{
48+
cmd: driverOutputControl,
49+
data: []byte{250 - 1, 0, 0},
50+
},
51+
{cmd: dataEntryModeSetting, data: []uint8{0x03}},
52+
{cmd: setRAMXAddressStartEndPosition, data: []uint8{0x00, 0x0f}},
53+
{cmd: setRAMYAddressStartEndPosition, data: []uint8{0x00, 0x00, 0xf9, 0x00}},
54+
{cmd: setRAMXAddressCounter, data: []uint8{0x00}},
55+
{cmd: setRAMYAddressCounter, data: []uint8{0x00, 0x00}},
56+
{cmd: borderWaveformControl, data: []uint8{0x05}},
57+
{cmd: displayUpdateControl1, data: []uint8{0x00, 0x80}},
58+
{cmd: tempSensorSelect, data: []uint8{0x80}},
59+
{cmd: writeLutRegister, data: EPD2in13v3.FullUpdate[:153]},
60+
{cmd: endOptionEOPT, data: []uint8{EPD2in13v3.FullUpdate[153]}},
61+
{cmd: gateDrivingVoltageControl, data: []uint8{EPD2in13v3.FullUpdate[154]}},
62+
{cmd: sourceDrivingVoltageControl, data: EPD2in13v3.FullUpdate[155:157]},
63+
{cmd: writeVcomRegister, data: []uint8{EPD2in13v3.FullUpdate[158]}},
64+
},
65+
},
66+
} {
67+
t.Run(tc.name, func(t *testing.T) {
68+
var got fakeController
69+
70+
initDisplay(&got, &tc.opts)
71+
72+
if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" {
73+
t.Errorf("initDisplay() difference (-got +want):\n%s", diff)
74+
}
75+
})
76+
}
77+
}
78+
79+
func TestConfigDisplayMode(t *testing.T) {
80+
for _, tc := range []struct {
81+
name string
82+
mode PartialUpdate
83+
lut LUT
84+
want []record
85+
}{
86+
{
87+
name: "full",
88+
mode: Full,
89+
lut: bytes.Repeat([]byte{'F'}, 100),
90+
want: []record{
91+
{cmd: writeVcomRegister, data: []byte{0x55}},
92+
{cmd: borderWaveformControl, data: []byte{0x03}},
93+
{cmd: writeLutRegister, data: bytes.Repeat([]byte{'F'}, 70)},
94+
{cmd: 0x37, data: []byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00}},
95+
{cmd: displayUpdateControl2, data: []byte{0xc0}},
96+
{cmd: masterActivation},
97+
},
98+
},
99+
{
100+
name: "partial",
101+
mode: Partial,
102+
lut: bytes.Repeat([]byte{'P'}, 70),
103+
want: []record{
104+
{cmd: writeVcomRegister, data: []byte{0x24}},
105+
{cmd: borderWaveformControl, data: []byte{0x01}},
106+
{cmd: writeLutRegister, data: bytes.Repeat([]byte{'P'}, 70)},
107+
{cmd: 0x37, data: []byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00}},
108+
{cmd: displayUpdateControl2, data: []byte{0xc0}},
109+
{cmd: masterActivation},
110+
},
111+
},
112+
} {
113+
t.Run(tc.name, func(t *testing.T) {
114+
var got fakeController
115+
116+
configDisplayMode(&got, tc.mode, tc.lut)
117+
118+
if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" {
119+
t.Errorf("configDisplayMode() difference (-got +want):\n%s", diff)
120+
}
121+
})
122+
}
123+
}
124+
125+
func TestUpdateDisplay(t *testing.T) {
126+
for _, tc := range []struct {
127+
name string
128+
mode PartialUpdate
129+
want []record
130+
}{
131+
{
132+
name: "full",
133+
mode: Full,
134+
want: []record{
135+
{cmd: displayUpdateControl1, data: []byte{0}},
136+
{cmd: displayUpdateControl2, data: []byte{0xc7}},
137+
{cmd: masterActivation},
138+
},
139+
},
140+
{
141+
name: "partial",
142+
mode: Partial,
143+
want: []record{
144+
{cmd: displayUpdateControl1, data: []byte{0x80}},
145+
{cmd: displayUpdateControl2, data: []byte{0xc7}},
146+
{cmd: masterActivation},
147+
},
148+
},
149+
} {
150+
t.Run(tc.name, func(t *testing.T) {
151+
var got fakeController
152+
153+
updateDisplay(&got, tc.mode)
154+
155+
if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" {
156+
t.Errorf("updateDisplay() difference (-got +want):\n%s", diff)
157+
}
158+
})
159+
}
160+
}
161+
162+
func TestClear(t *testing.T) {
163+
var buff []byte
164+
const linewidth = int(122/8) + 1
165+
for j := 0; j < 250; j++ {
166+
for i := 0; i < linewidth; i++ {
167+
buff = append(buff, 0x00)
168+
}
169+
}
170+
for _, tc := range []struct {
171+
name string
172+
opts Opts
173+
color byte
174+
want []record
175+
}{
176+
{
177+
name: "clear",
178+
opts: EPD2in13v3,
179+
want: []record{
180+
{cmd: writeRAMBW, data: buff},
181+
{cmd: displayUpdateControl2, data: []byte{0xC7}},
182+
{cmd: masterActivation},
183+
},
184+
},
185+
} {
186+
t.Run(tc.name, func(t *testing.T) {
187+
var got fakeController
188+
189+
clear(&got, tc.color, &tc.opts)
190+
191+
if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" {
192+
t.Errorf("updateDisplay() difference (-got +want):\n%s", diff)
193+
}
194+
})
195+
}
196+
}

waveshare2in13v3/doc.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2021 The Periph Authors. All rights reserved.
2+
// Use of this source code is governed under the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
5+
// Package waveshare2in13v3 controls Waveshare 2.13 v3 e-paper displays.
6+
//
7+
// Datasheet:
8+
// https://files.waveshare.com/upload/5/59/2.13inch_e-Paper_V3_Specificition.pdf
9+
//
10+
// Product page:
11+
// 2.13 inch version 3: https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT_Manual#Resources
12+
// This display is an Active Matrix Electrophoretic Display (AM EPD), with
13+
// interface and a reference system design. The display is capable to display
14+
// imagesat 1-bit white, black full display capabilities. The 2.13inch active area
15+
// contains 250×122 pixels. The module is a TFT-array driving electrophoresis
16+
// display, withintegrated circuits including gate driver, source driver, MCU
17+
// interface, timingcontroller, oscillator, DC-DC, SRAM, LUT, VCOM. Module can be
18+
// used in portableelectronic devices, such as Electronic Shelf Label (ESL) System.
19+
package waveshare2in13v3

0 commit comments

Comments
 (0)