-
Notifications
You must be signed in to change notification settings - Fork 37
/
waveshare213v2.go
349 lines (290 loc) · 9.19 KB
/
waveshare213v2.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
// Copyright 2021 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 waveshare2in13v2
import (
"fmt"
"image"
"image/color"
"image/draw"
"time"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/display"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/physic"
"periph.io/x/conn/v3/spi"
"periph.io/x/devices/v3/ssd1306/image1bit"
"periph.io/x/host/v3/rpi"
)
// Commands
const (
driverOutputControl byte = 0x01
gateDrivingVoltageControl byte = 0x03
sourceDrivingVoltageControl byte = 0x04
deepSleepMode byte = 0x10
dataEntryModeSetting byte = 0x11
swReset byte = 0x12
masterActivation byte = 0x20
displayUpdateControl1 byte = 0x21
displayUpdateControl2 byte = 0x22
writeRAMBW byte = 0x24
writeRAMRed byte = 0x26
writeVcomRegister byte = 0x2C
writeLutRegister byte = 0x32
setDummyLinePeriod byte = 0x3A
setGateTime byte = 0x3B
borderWaveformControl byte = 0x3C
writeDisplayOptionRegister byte = 0x37
setRAMXAddressStartEndPosition byte = 0x44
setRAMYAddressStartEndPosition byte = 0x45
setRAMXAddressCounter byte = 0x4E
setRAMYAddressCounter byte = 0x4F
setAnalogBlockControl byte = 0x74
setDigitalBlockControl byte = 0x7E
)
// Register values
const (
gateDrivingVoltage19V = 0x15
sourceDrivingVoltageVSH1_15V = 0x41
sourceDrivingVoltageVSH2_5V = 0xA8
sourceDrivingVoltageVSL_neg15V = 0x32
)
// Flags for the displayUpdateControl2 command
const (
displayUpdateDisableClock byte = 1 << iota
displayUpdateDisableAnalog
displayUpdateDisplay
displayUpdateMode2
displayUpdateLoadLUTFromOTP
displayUpdateLoadTemperature
displayUpdateEnableClock
displayUpdateEnableAnalog
)
// Dev defines the handler which is used to access the display.
type Dev struct {
c conn.Conn
dc gpio.PinOut
cs gpio.PinOut
rst gpio.PinOut
busy gpio.PinIn
bounds image.Rectangle
buffer *image1bit.VerticalLSB
mode PartialUpdate
opts *Opts
}
// Corner describes a corner on the physical device and is used to define the
// origin for drawing operations.
type Corner uint8
const (
TopLeft Corner = iota
TopRight
BottomRight
BottomLeft
)
// LUT contains the waveform that is used to program the display.
type LUT []byte
// Opts definies the structure of the display configuration.
type Opts struct {
Width int
Height int
Origin Corner
FullUpdate LUT
PartialUpdate LUT
}
// PartialUpdate defines if the display should do a full update or just a partial update.
type PartialUpdate bool
const (
// Full should update the complete display.
Full PartialUpdate = false
// Partial should update only partial parts of the display.
Partial PartialUpdate = true
)
// EPD2in13v2 cointains display configuration for the Waveshare 2in13v2.
var EPD2in13v2 = Opts{
Width: 122,
Height: 250,
FullUpdate: LUT{
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, //LUT0: BB: VS 0 ~7
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, //LUT1: BW: VS 0 ~7
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, //LUT2: WB: VS 0 ~7
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, //LUT3: WW: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT4: VCOM: VS 0 ~7
0x03, 0x03, 0x00, 0x00, 0x02, // TP0 A~D RP0
0x09, 0x09, 0x00, 0x00, 0x02, // TP1 A~D RP1
0x03, 0x03, 0x00, 0x00, 0x02, // TP2 A~D RP2
0x00, 0x00, 0x00, 0x00, 0x00, // TP3 A~D RP3
0x00, 0x00, 0x00, 0x00, 0x00, // TP4 A~D RP4
0x00, 0x00, 0x00, 0x00, 0x00, // TP5 A~D RP5
0x00, 0x00, 0x00, 0x00, 0x00, // TP6 A~D RP6
},
PartialUpdate: LUT{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT0: BB: VS 0 ~7
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT1: BW: VS 0 ~7
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT2: WB: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT3: WW: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT4: VCOM: VS 0 ~7
0x0A, 0x00, 0x00, 0x00, 0x00, // TP0 A~D RP0
0x00, 0x00, 0x00, 0x00, 0x00, // TP1 A~D RP1
0x00, 0x00, 0x00, 0x00, 0x00, // TP2 A~D RP2
0x00, 0x00, 0x00, 0x00, 0x00, // TP3 A~D RP3
0x00, 0x00, 0x00, 0x00, 0x00, // TP4 A~D RP4
0x00, 0x00, 0x00, 0x00, 0x00, // TP5 A~D RP5
0x00, 0x00, 0x00, 0x00, 0x00, // TP6 A~D RP6
},
}
// flipPt returns a new image.Point with the X and Y coordinates exchanged.
func flipPt(pt image.Point) image.Point {
return image.Point{X: pt.Y, Y: pt.X}
}
// New creates new handler which is used to access the display.
func New(p spi.Port, dc, cs, rst gpio.PinOut, busy gpio.PinIn, opts *Opts) (*Dev, error) {
c, err := p.Connect(5*physic.MegaHertz, spi.Mode0, 8)
if err != nil {
return nil, err
}
if err := busy.In(gpio.Float, gpio.FallingEdge); err != nil {
return nil, err
}
displaySize := image.Pt(opts.Width, opts.Height)
// The physical X axis is sized to have one-byte alignment on the (0,0)
// on-display position after rotation.
bufferSize := image.Pt((opts.Width+7)/8*8, opts.Height)
switch opts.Origin {
case TopLeft, BottomRight:
case TopRight, BottomLeft:
displaySize = flipPt(displaySize)
bufferSize = flipPt(bufferSize)
default:
return nil, fmt.Errorf("unknown corner %v", opts.Origin)
}
d := &Dev{
c: c,
dc: dc,
cs: cs,
rst: rst,
busy: busy,
bounds: image.Rectangle{Max: displaySize},
buffer: image1bit.NewVerticalLSB(image.Rectangle{
Max: bufferSize,
}),
mode: Full,
opts: opts,
}
// Default color
draw.Src.Draw(d.buffer, d.buffer.Bounds(), &image.Uniform{image1bit.On}, image.Point{})
return d, nil
}
// NewHat creates new handler which is used to access the display. Default Waveshare Hat configuration is used.
func NewHat(p spi.Port, opts *Opts) (*Dev, error) {
dc := rpi.P1_22
cs := rpi.P1_24
rst := rpi.P1_11
busy := rpi.P1_18
return New(p, dc, cs, rst, busy, opts)
}
func (d *Dev) configMode(ctrl controller) {
var lut LUT
if d.mode == Full {
lut = d.opts.FullUpdate
} else {
lut = d.opts.PartialUpdate
}
configDisplayMode(ctrl, d.mode, lut)
}
// Init configures the display for usage through the other functions.
func (d *Dev) Init() error {
// Hardware Reset
if err := d.reset(); err != nil {
return err
}
eh := errorHandler{d: *d}
initDisplay(&eh, d.opts)
if eh.err == nil {
d.configMode(&eh)
}
return eh.err
}
// SetUpdateMode changes the way updates to the displayed image are applied. In
// Full mode (the default) a full refresh is done with all pixels cleared and
// re-applied. In Partial mode only the changed pixels are updated (aligned to
// multiples of 8 on the horizontal axis), potentially leaving behind small
// optical artifacts due to the way e-paper displays work.
//
// The vendor datasheet recommends a full update at least once every 24 hours.
// When using partial updates the Clear function can be used for the purpose,
// followed by re-drawing.
func (d *Dev) SetUpdateMode(mode PartialUpdate) error {
d.mode = mode
eh := errorHandler{d: *d}
d.configMode(&eh)
return eh.err
}
// Clear clears the display.
func (d *Dev) Clear(color color.Color) error {
return d.Draw(d.buffer.Bounds(), &image.Uniform{
C: image1bit.BitModel.Convert(color).(image1bit.Bit),
}, image.Point{})
}
// ColorModel returns a 1Bit color model.
func (d *Dev) ColorModel() color.Model {
return image1bit.BitModel
}
// Bounds returns the bounds for the configurated display.
func (d *Dev) Bounds() image.Rectangle {
return d.bounds
}
// Draw draws the given image to the display. Only the destination area is
// uploaded. Depending on the update mode the whole display or the destination
// area is refreshed.
func (d *Dev) Draw(dstRect image.Rectangle, src image.Image, srcPts image.Point) error {
opts := drawOpts{
devSize: d.bounds.Max,
origin: d.opts.Origin,
buffer: d.buffer,
dstRect: dstRect,
src: src,
srcPts: srcPts,
}
eh := errorHandler{d: *d}
drawImage(&eh, &opts)
if eh.err == nil {
updateDisplay(&eh, d.mode)
}
return eh.err
}
// DrawPartial draws the given image to the display.
//
// Deprecated: Use Draw instead. DrawPartial merely forwards all calls.
func (d *Dev) DrawPartial(dstRect image.Rectangle, src image.Image, srcPts image.Point) error {
return d.Draw(dstRect, src, srcPts)
}
// Halt clears the display.
func (d *Dev) Halt() error {
return d.Clear(image1bit.On)
}
// String returns a string containing configuration information.
func (d *Dev) String() string {
return fmt.Sprintf("epd.Dev{%s, %s, Width: %d, Height: %d}", d.c, d.dc, d.bounds.Dx(), d.bounds.Dy())
}
// Sleep makes the controller enter deep sleep mode. It can be woken up by
// calling Init again.
func (d *Dev) Sleep() error {
eh := errorHandler{d: *d}
// Turn off DC/DC converter, clock, output load and MCU. RAM content is
// retained.
eh.sendCommand(deepSleepMode)
eh.sendData([]byte{0x01})
return eh.err
}
// Reset the hardware
func (d *Dev) reset() error {
eh := errorHandler{d: *d}
eh.rstOut(gpio.High)
time.Sleep(200 * time.Millisecond)
eh.rstOut(gpio.Low)
time.Sleep(200 * time.Millisecond)
eh.rstOut(gpio.High)
time.Sleep(200 * time.Millisecond)
return eh.err
}
var _ display.Drawer = &Dev{}