Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

waveshare213v2: Implement partial updates #41

Merged
merged 7 commits into from
Dec 24, 2021
105 changes: 105 additions & 0 deletions waveshare2in13v2/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// 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

type controller interface {
sendCommand(byte)
sendData([]byte)
waitUntilIdle()
}

func initDisplay(ctrl controller, opts *Opts) {
ctrl.waitUntilIdle()
ctrl.sendCommand(swReset)
ctrl.waitUntilIdle()

ctrl.sendCommand(setAnalogBlockControl)
ctrl.sendData([]byte{0x54})

ctrl.sendCommand(setDigitalBlockControl)
ctrl.sendData([]byte{0x3B})

ctrl.sendCommand(driverOutputControl)
ctrl.sendData([]byte{
byte((opts.Height - 1) % 0xFF),
byte((opts.Height - 1) / 0xFF),
0x00,
})

ctrl.sendCommand(gateDrivingVoltageControl)
ctrl.sendData([]byte{gateDrivingVoltage19V})

ctrl.sendCommand(sourceDrivingVoltageControl)
ctrl.sendData([]byte{sourceDrivingVoltageVSH1_15V, sourceDrivingVoltageVSH2_5V, sourceDrivingVoltageVSL_neg15V})

ctrl.sendCommand(setDummyLinePeriod)
ctrl.sendData([]byte{0x30})

ctrl.sendCommand(setGateTime)
ctrl.sendData([]byte{0x0A})
}

func configDisplayMode(ctrl controller, mode PartialUpdate, lut LUT) {
var vcom byte
var borderWaveformControlValue byte

switch mode {
case Full:
vcom = 0x55
borderWaveformControlValue = 0x03
case Partial:
vcom = 0x24
borderWaveformControlValue = 0x01
}

ctrl.sendCommand(writeVcomRegister)
ctrl.sendData([]byte{vcom})

ctrl.sendCommand(borderWaveformControl)
ctrl.sendData([]byte{borderWaveformControlValue})

ctrl.sendCommand(writeLutRegister)
ctrl.sendData(lut[:70])

if mode == Partial {
// Undocumented command used in vendor example code.
ctrl.sendCommand(0x37)
ctrl.sendData([]byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00})

ctrl.sendCommand(displayUpdateControl2)
ctrl.sendData([]byte{
displayUpdateEnableClock |
displayUpdateEnableAnalog,
})

ctrl.sendCommand(masterActivation)
}

ctrl.waitUntilIdle()
}

func updateDisplay(ctrl controller, mode PartialUpdate) {
var displayUpdateFlags byte

if mode == Partial {
// Make use of red buffer
displayUpdateFlags = 0b1000_0000
}

ctrl.sendCommand(displayUpdateControl1)
ctrl.sendData([]byte{displayUpdateFlags})

ctrl.sendCommand(displayUpdateControl2)
ctrl.sendData([]byte{
displayUpdateDisableClock |
displayUpdateDisableAnalog |
displayUpdateDisplay |
displayUpdateEnableClock |
displayUpdateEnableAnalog,
})

ctrl.sendCommand(masterActivation)
ctrl.waitUntilIdle()
}
157 changes: 157 additions & 0 deletions waveshare2in13v2/controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// 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 (
"bytes"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)

type record struct {
cmd byte
data []byte
}

type fakeController []record

func (r *fakeController) sendCommand(cmd byte) {
*r = append(*r, record{
cmd: cmd,
})
}

func (r *fakeController) sendData(data []byte) {
cur := &(*r)[len(*r)-1]
cur.data = append(cur.data, data...)
}

func (*fakeController) waitUntilIdle() {
}

func TestInitDisplay(t *testing.T) {
for _, tc := range []struct {
name string
opts Opts
want []record
}{
{
name: "epd2in13v2",
opts: EPD2in13v2,
want: []record{
{cmd: swReset},
{cmd: setAnalogBlockControl, data: []byte{0x54}},
{cmd: setDigitalBlockControl, data: []byte{0x3b}},
{
cmd: driverOutputControl,
data: []byte{250 - 1, 0, 0},
},
{cmd: gateDrivingVoltageControl, data: []byte{gateDrivingVoltage19V}},
{
cmd: sourceDrivingVoltageControl,
data: []byte{
sourceDrivingVoltageVSH1_15V,
sourceDrivingVoltageVSH2_5V,
sourceDrivingVoltageVSL_neg15V,
},
},
{cmd: setDummyLinePeriod, data: []byte{0x30}},
{cmd: setGateTime, data: []byte{0x0a}},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var got fakeController

initDisplay(&got, &tc.opts)

if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" {
t.Errorf("initDisplay() difference (-got +want):\n%s", diff)
}
})
}
}

func TestConfigDisplayMode(t *testing.T) {
for _, tc := range []struct {
name string
mode PartialUpdate
lut LUT
want []record
}{
{
name: "full",
mode: Full,
lut: bytes.Repeat([]byte{'F'}, 100),
want: []record{
{cmd: writeVcomRegister, data: []byte{0x55}},
{cmd: borderWaveformControl, data: []byte{0x03}},
{cmd: writeLutRegister, data: bytes.Repeat([]byte{'F'}, 70)},
},
},
{
name: "partial",
mode: Partial,
lut: bytes.Repeat([]byte{'P'}, 70),
want: []record{
{cmd: writeVcomRegister, data: []byte{0x24}},
{cmd: borderWaveformControl, data: []byte{0x01}},
{cmd: writeLutRegister, data: bytes.Repeat([]byte{'P'}, 70)},
{cmd: 0x37, data: []byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00}},
{cmd: displayUpdateControl2, data: []byte{0xc0}},
{cmd: masterActivation},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var got fakeController

configDisplayMode(&got, tc.mode, tc.lut)

if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" {
t.Errorf("configDisplayMode() difference (-got +want):\n%s", diff)
}
})
}
}

func TestUpdateDisplay(t *testing.T) {
for _, tc := range []struct {
name string
mode PartialUpdate
want []record
}{
{
name: "full",
mode: Full,
want: []record{
{cmd: displayUpdateControl1, data: []byte{0}},
{cmd: displayUpdateControl2, data: []byte{0xc7}},
{cmd: masterActivation},
},
},
{
name: "partial",
mode: Partial,
want: []record{
{cmd: displayUpdateControl1, data: []byte{0x80}},
{cmd: displayUpdateControl2, data: []byte{0xc7}},
{cmd: masterActivation},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var got fakeController

updateDisplay(&got, tc.mode)

if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" {
t.Errorf("updateDisplay() difference (-got +want):\n%s", diff)
}
})
}
}
29 changes: 6 additions & 23 deletions waveshare2in13v2/drawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ import (
"periph.io/x/devices/v3/ssd1306/image1bit"
)

type controller interface {
sendCommand(byte)
sendData([]byte)
}

// setMemoryArea configures the target drawing area (horizontal is in bytes,
// vertical in pixels).
func setMemoryArea(ctrl controller, area image.Rectangle) {
Expand Down Expand Up @@ -59,6 +54,7 @@ func setMemoryArea(ctrl controller, area image.Rectangle) {
type drawOpts struct {
cmd byte
devSize image.Point
buffer *image1bit.VerticalLSB
dstRect image.Rectangle
src image.Image
srcPts image.Point
Expand All @@ -68,15 +64,9 @@ type drawSpec struct {
// Destination on display in pixels, normalized to fit into actual size.
DstRect image.Rectangle

// Size of memory area to write; horizontally in bytes, vertically in
// pixels.
// Area to send to device; horizontally in bytes (thus aligned to
// 8 pixels), vertically in pixels.
MemRect image.Rectangle

// Size of image buffer, horizontally aligned to multiples of 8 pixels.
BufferSize image.Point

// Destination rectangle within image buffer.
BufferRect image.Rectangle
}

func (o *drawOpts) spec() drawSpec {
Expand All @@ -88,12 +78,6 @@ func (o *drawOpts) spec() drawSpec {
s.DstRect.Min.X/8, s.DstRect.Min.Y,
(s.DstRect.Max.X+7)/8, s.DstRect.Max.Y,
)
s.BufferSize = image.Pt(s.MemRect.Dx()*8, s.MemRect.Dy())
s.BufferRect = image.Rectangle{
Min: image.Point{X: s.DstRect.Min.X - (s.MemRect.Min.X * 8)},
Max: image.Point{Y: s.DstRect.Dy()},
}
s.BufferRect.Max.X = s.BufferRect.Min.X + s.DstRect.Dx()

return s
}
Expand All @@ -106,21 +90,20 @@ func drawImage(ctrl controller, opts *drawOpts) {
return
}

img := image1bit.NewVerticalLSB(image.Rectangle{Max: s.BufferSize})
draw.Src.Draw(img, s.BufferRect, opts.src, opts.srcPts)
draw.Src.Draw(opts.buffer, s.DstRect, opts.src, opts.srcPts)

setMemoryArea(ctrl, s.MemRect)

ctrl.sendCommand(opts.cmd)

rowData := make([]byte, s.MemRect.Dx())

for y := 0; y < img.Bounds().Dy(); y++ {
for y := s.MemRect.Min.Y; y < s.MemRect.Max.Y; y++ {
for x := 0; x < len(rowData); x++ {
rowData[x] = 0

for bit := 0; bit < 8; bit++ {
if img.BitAt((x*8)+bit, y) {
if opts.buffer.BitAt(((s.MemRect.Min.X+x)*8)+bit, y) {
rowData[x] |= 0x80 >> bit
}
}
Expand Down
Loading