From 41b26497a9f646ed03f7f5ef3838b89893675299 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Fri, 24 Dec 2021 23:18:03 +0100 Subject: [PATCH 1/5] waveshare2in13v2: Document command 0x37 and always apply it The datasheets of the comparable SSD1680, SSD1675B and GDEH0213B73 controllers describe this command as "Write register for display option". Signed-off-by: Michael Hanselmann --- waveshare2in13v2/controller.go | 7 +++---- waveshare2in13v2/controller_test.go | 1 + waveshare2in13v2/waveshare213v2.go | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/waveshare2in13v2/controller.go b/waveshare2in13v2/controller.go index ad35077..adcb53a 100644 --- a/waveshare2in13v2/controller.go +++ b/waveshare2in13v2/controller.go @@ -63,11 +63,10 @@ func configDisplayMode(ctrl controller, mode PartialUpdate, lut LUT) { 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(writeDisplayOptionRegister) + ctrl.sendData([]byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00}) + if mode == Partial { ctrl.sendCommand(displayUpdateControl2) ctrl.sendData([]byte{ displayUpdateEnableClock | diff --git a/waveshare2in13v2/controller_test.go b/waveshare2in13v2/controller_test.go index c536ea7..5c6436f 100644 --- a/waveshare2in13v2/controller_test.go +++ b/waveshare2in13v2/controller_test.go @@ -91,6 +91,7 @@ func TestConfigDisplayMode(t *testing.T) { {cmd: writeVcomRegister, data: []byte{0x55}}, {cmd: borderWaveformControl, data: []byte{0x03}}, {cmd: writeLutRegister, data: bytes.Repeat([]byte{'F'}, 70)}, + {cmd: 0x37, data: []byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00}}, }, }, { diff --git a/waveshare2in13v2/waveshare213v2.go b/waveshare2in13v2/waveshare213v2.go index b67a636..8187bb4 100644 --- a/waveshare2in13v2/waveshare213v2.go +++ b/waveshare2in13v2/waveshare213v2.go @@ -37,6 +37,7 @@ const ( setDummyLinePeriod byte = 0x3A setGateTime byte = 0x3B borderWaveformControl byte = 0x3C + writeDisplayOptionRegister byte = 0x37 setRAMXAddressStartEndPosition byte = 0x44 setRAMYAddressStartEndPosition byte = 0x45 setRAMXAddressCounter byte = 0x4E From f0dd1cdeabea6e128f7e84222a62d63373a4a4d3 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Fri, 24 Dec 2021 16:54:52 +0100 Subject: [PATCH 2/5] waveshare2in13v2: Add function to put controller to sleep --- waveshare2in13v2/waveshare213v2.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/waveshare2in13v2/waveshare213v2.go b/waveshare2in13v2/waveshare213v2.go index 8187bb4..60d2666 100644 --- a/waveshare2in13v2/waveshare213v2.go +++ b/waveshare2in13v2/waveshare213v2.go @@ -25,6 +25,7 @@ const ( driverOutputControl byte = 0x01 gateDrivingVoltageControl byte = 0x03 sourceDrivingVoltageControl byte = 0x04 + deepSleepMode byte = 0x10 dataEntryModeSetting byte = 0x11 swReset byte = 0x12 masterActivation byte = 0x20 @@ -301,6 +302,19 @@ func (d *Dev) String() string { return fmt.Sprintf("epd.Dev{%s, %s, Height: %d, Width: %d}", d.c, d.dc, d.opts.Height, d.opts.Width) } +// 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} From 4571ffd856054ab08f8518b30b66c23f37f7b38f Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Fri, 24 Dec 2021 17:10:14 +0100 Subject: [PATCH 3/5] waveshare2in13v2: Enable hardware early Enable the clock and analog parts when configuring the mode. In probably all use cases a drawing operation is following soon after anyway. Putting the device to sleep disables these hardware parts again. --- waveshare2in13v2/controller.go | 13 ++++--------- waveshare2in13v2/controller_test.go | 2 ++ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/waveshare2in13v2/controller.go b/waveshare2in13v2/controller.go index adcb53a..cd65897 100644 --- a/waveshare2in13v2/controller.go +++ b/waveshare2in13v2/controller.go @@ -66,16 +66,11 @@ func configDisplayMode(ctrl controller, mode PartialUpdate, lut LUT) { ctrl.sendCommand(writeDisplayOptionRegister) ctrl.sendData([]byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00}) - if mode == Partial { - ctrl.sendCommand(displayUpdateControl2) - ctrl.sendData([]byte{ - displayUpdateEnableClock | - displayUpdateEnableAnalog, - }) - - ctrl.sendCommand(masterActivation) - } + // Start up the parts likely used by a draw operation soon. + ctrl.sendCommand(displayUpdateControl2) + ctrl.sendData([]byte{displayUpdateEnableClock | displayUpdateEnableAnalog}) + ctrl.sendCommand(masterActivation) ctrl.waitUntilIdle() } diff --git a/waveshare2in13v2/controller_test.go b/waveshare2in13v2/controller_test.go index 5c6436f..16496ae 100644 --- a/waveshare2in13v2/controller_test.go +++ b/waveshare2in13v2/controller_test.go @@ -92,6 +92,8 @@ func TestConfigDisplayMode(t *testing.T) { {cmd: borderWaveformControl, data: []byte{0x03}}, {cmd: writeLutRegister, data: bytes.Repeat([]byte{'F'}, 70)}, {cmd: 0x37, data: []byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00}}, + {cmd: displayUpdateControl2, data: []byte{0xc0}}, + {cmd: masterActivation}, }, }, { From cc3d0faf03bdf02ccb4caea049297c3624b2dba4 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Sat, 25 Dec 2021 16:12:28 +0100 Subject: [PATCH 4/5] waveshare2in13v2: Test updates to both buffers Move the logic applying display content changes to both buffers to the "drawImage" function. --- waveshare2in13v2/drawing.go | 54 +++++++++----- waveshare2in13v2/drawing_test.go | 113 +++++++++++++++++++++++++---- waveshare2in13v2/waveshare213v2.go | 11 +-- 3 files changed, 134 insertions(+), 44 deletions(-) diff --git a/waveshare2in13v2/drawing.go b/waveshare2in13v2/drawing.go index 05ef1ae..24e8a0b 100644 --- a/waveshare2in13v2/drawing.go +++ b/waveshare2in13v2/drawing.go @@ -52,12 +52,12 @@ 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 + commands []byte + devSize image.Point + buffer *image1bit.VerticalLSB + dstRect image.Rectangle + src image.Image + srcPts image.Point } type drawSpec struct { @@ -82,28 +82,25 @@ func (o *drawOpts) spec() drawSpec { return s } -// drawImage sends an image to the controller after setting up the registers. -func drawImage(ctrl controller, opts *drawOpts) { - s := opts.spec() - - if s.MemRect.Empty() { +// sendImage sends an image to the controller after setting up the registers. +// The area is in bytes on the horizontal axis. +func sendImage(ctrl controller, cmd byte, area image.Rectangle, img *image1bit.VerticalLSB) { + if area.Empty() { return } - draw.Src.Draw(opts.buffer, s.DstRect, opts.src, opts.srcPts) - - setMemoryArea(ctrl, s.MemRect) + setMemoryArea(ctrl, area) - ctrl.sendCommand(opts.cmd) + ctrl.sendCommand(cmd) - rowData := make([]byte, s.MemRect.Dx()) + rowData := make([]byte, area.Dx()) - for y := s.MemRect.Min.Y; y < s.MemRect.Max.Y; y++ { + for y := area.Min.Y; y < area.Max.Y; y++ { for x := 0; x < len(rowData); x++ { rowData[x] = 0 for bit := 0; bit < 8; bit++ { - if opts.buffer.BitAt(((s.MemRect.Min.X+x)*8)+bit, y) { + if img.BitAt(((area.Min.X+x)*8)+bit, y) { rowData[x] |= 0x80 >> bit } } @@ -113,6 +110,27 @@ func drawImage(ctrl controller, opts *drawOpts) { } } +func drawImage(ctrl controller, opts *drawOpts) { + s := opts.spec() + + if s.MemRect.Empty() { + return + } + + draw.Src.Draw(opts.buffer, s.DstRect, opts.src, opts.srcPts) + + commands := opts.commands + + if len(commands) == 0 { + commands = []byte{writeRAMBW, writeRAMRed} + } + + // Keep the two buffers in sync. + for _, cmd := range commands { + sendImage(ctrl, cmd, s.MemRect, opts.buffer) + } +} + func clearDisplay(ctrl controller, size image.Point, color image1bit.Bit) { var colorValue byte diff --git a/waveshare2in13v2/drawing_test.go b/waveshare2in13v2/drawing_test.go index dc9a384..9ecfff9 100644 --- a/waveshare2in13v2/drawing_test.go +++ b/waveshare2in13v2/drawing_test.go @@ -7,6 +7,7 @@ package waveshare2in13v2 import ( "bytes" "image" + "image/draw" "testing" "github.com/google/go-cmp/cmp" @@ -58,6 +59,89 @@ func TestDrawSpec(t *testing.T) { } } +func TestSendImage(t *testing.T) { + for _, tc := range []struct { + name string + cmd byte + area image.Rectangle + img *image1bit.VerticalLSB + want []record + }{ + { + name: "empty", + }, + { + name: "partial", + cmd: writeRAMBW, + area: image.Rect(2, 20, 4, 40), + img: image1bit.NewVerticalLSB(image.Rect(0, 0, 64, 64)), + want: []record{ + {cmd: dataEntryModeSetting, data: []byte{0x3}}, + {cmd: setRAMXAddressStartEndPosition, data: []byte{2, 4 - 1}}, + {cmd: setRAMYAddressStartEndPosition, data: []byte{20, 0, 40 - 1, 0}}, + {cmd: setRAMXAddressCounter, data: []byte{2}}, + {cmd: setRAMYAddressCounter, data: []byte{20, 0}}, + { + cmd: writeRAMBW, + data: bytes.Repeat([]byte{0}, 2*(30-10)), + }, + }, + }, + { + name: "partial non-aligned", + cmd: writeRAMRed, + area: image.Rect(2, 4, 6, 8), + img: func() *image1bit.VerticalLSB { + img := image1bit.NewVerticalLSB(image.Rect(0, 0, 64, 64)) + draw.Src.Draw(img, image.Rect(17, 4, 41, 8), &image.Uniform{image1bit.On}, image.Point{}) + return img + }(), + want: []record{ + {cmd: dataEntryModeSetting, data: []byte{0x3}}, + {cmd: setRAMXAddressStartEndPosition, data: []byte{2, 6 - 1}}, + {cmd: setRAMYAddressStartEndPosition, data: []byte{4, 0, 8 - 1, 0}}, + {cmd: setRAMXAddressCounter, data: []byte{2}}, + {cmd: setRAMYAddressCounter, data: []byte{4, 0}}, + { + cmd: writeRAMRed, + data: bytes.Repeat([]byte{0x7f, 0xff, 0xff, 0x80}, 4), + }, + }, + }, + { + name: "full", + cmd: writeRAMBW, + area: image.Rect(0, 0, 10, 120), + img: func() *image1bit.VerticalLSB { + img := image1bit.NewVerticalLSB(image.Rect(0, 0, 80, 120)) + draw.Src.Draw(img, image.Rect(0, 0, 80, 120), &image.Uniform{image1bit.On}, image.Point{}) + return img + }(), + want: []record{ + {cmd: dataEntryModeSetting, data: []byte{0x3}}, + {cmd: setRAMXAddressStartEndPosition, data: []byte{0, 10 - 1}}, + {cmd: setRAMYAddressStartEndPosition, data: []byte{0, 0, 120 - 1, 0}}, + {cmd: setRAMXAddressCounter, data: []byte{0}}, + {cmd: setRAMYAddressCounter, data: []byte{0, 0}}, + { + cmd: writeRAMBW, + data: bytes.Repeat([]byte{0xff}, 80/8*120), + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var got fakeController + + sendImage(&got, tc.cmd, tc.area, tc.img) + + if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" { + t.Errorf("sendImage() difference (-got +want):\n%s", diff) + } + }) + } +} + func TestDrawImage(t *testing.T) { for _, tc := range []struct { name string @@ -66,19 +150,16 @@ func TestDrawImage(t *testing.T) { }{ { name: "empty", - opts: drawOpts{ - src: &image.Uniform{image1bit.On}, - }, }, { name: "partial", opts: drawOpts{ - cmd: writeRAMBW, - devSize: image.Pt(64, 64), - buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 64, 64)), - dstRect: image.Rect(17, 4, 41, 8), - src: &image.Uniform{image1bit.On}, - srcPts: image.Pt(0, 0), + commands: []byte{writeRAMBW}, + devSize: image.Pt(64, 64), + buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 64, 64)), + dstRect: image.Rect(17, 4, 41, 8), + src: &image.Uniform{image1bit.On}, + srcPts: image.Pt(0, 0), }, want: []record{ {cmd: dataEntryModeSetting, data: []byte{0x3}}, @@ -95,12 +176,12 @@ func TestDrawImage(t *testing.T) { { name: "full", opts: drawOpts{ - cmd: writeRAMBW, - devSize: image.Pt(80, 120), - buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 80, 120)), - dstRect: image.Rect(0, 0, 80, 120), - src: &image.Uniform{image1bit.On}, - srcPts: image.Pt(33, 44), + commands: []byte{writeRAMRed}, + devSize: image.Pt(80, 120), + buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 80, 120)), + dstRect: image.Rect(0, 0, 80, 120), + src: &image.Uniform{image1bit.On}, + srcPts: image.Pt(33, 44), }, want: []record{ {cmd: dataEntryModeSetting, data: []byte{0x3}}, @@ -109,7 +190,7 @@ func TestDrawImage(t *testing.T) { {cmd: setRAMXAddressCounter, data: []byte{0}}, {cmd: setRAMYAddressCounter, data: []byte{0, 0}}, { - cmd: writeRAMBW, + cmd: writeRAMRed, data: bytes.Repeat([]byte{0xff}, 80/8*120), }, }, diff --git a/waveshare2in13v2/waveshare213v2.go b/waveshare2in13v2/waveshare213v2.go index 60d2666..fddfdcf 100644 --- a/waveshare2in13v2/waveshare213v2.go +++ b/waveshare2in13v2/waveshare213v2.go @@ -267,16 +267,7 @@ func (d *Dev) Draw(dstRect image.Rectangle, src image.Image, srcPts image.Point) eh := errorHandler{d: *d} - // Keep the two buffers in sync. - for _, cmd := range []byte{writeRAMBW, writeRAMRed} { - opts.cmd = cmd - - drawImage(&eh, &opts) - - if eh.err != nil { - break - } - } + drawImage(&eh, &opts) if eh.err == nil { updateDisplay(&eh, d.mode) From 8d6bc97e98378eaaa70d91a7e1119f17db1fc67b Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Sat, 25 Dec 2021 16:17:20 +0100 Subject: [PATCH 5/5] waveshare2in13v2: Use drawing functionality for clearing With the introduction of the shadow buffer in commit 4f99575 there's always a sufficiently large memory buffer to keep the bytes to send for clearing and the dedicated code using a smaller buffer is no longer needed. This also has the advantage of automatically updating both on-device buffers. --- waveshare2in13v2/drawing.go | 28 --------------- waveshare2in13v2/drawing_test.go | 55 ------------------------------ waveshare2in13v2/waveshare213v2.go | 15 ++------ 3 files changed, 3 insertions(+), 95 deletions(-) diff --git a/waveshare2in13v2/drawing.go b/waveshare2in13v2/drawing.go index 24e8a0b..038e34e 100644 --- a/waveshare2in13v2/drawing.go +++ b/waveshare2in13v2/drawing.go @@ -5,7 +5,6 @@ package waveshare2in13v2 import ( - "bytes" "image" "image/draw" @@ -130,30 +129,3 @@ func drawImage(ctrl controller, opts *drawOpts) { sendImage(ctrl, cmd, s.MemRect, opts.buffer) } } - -func clearDisplay(ctrl controller, size image.Point, color image1bit.Bit) { - var colorValue byte - - if color == image1bit.On { - colorValue = 0xff - } - - spec := (&drawOpts{ - devSize: size, - dstRect: image.Rectangle{Max: size}, - }).spec() - - if spec.MemRect.Empty() { - return - } - - setMemoryArea(ctrl, spec.MemRect) - - ctrl.sendCommand(writeRAMBW) - - data := bytes.Repeat([]byte{colorValue}, spec.MemRect.Dx()) - - for y := 0; y < spec.MemRect.Max.Y; y++ { - ctrl.sendData(data) - } -} diff --git a/waveshare2in13v2/drawing_test.go b/waveshare2in13v2/drawing_test.go index 9ecfff9..30feb32 100644 --- a/waveshare2in13v2/drawing_test.go +++ b/waveshare2in13v2/drawing_test.go @@ -207,58 +207,3 @@ func TestDrawImage(t *testing.T) { }) } } - -func TestClearDisplay(t *testing.T) { - for _, tc := range []struct { - name string - size image.Point - color image1bit.Bit - want []record - }{ - { - name: "empty", - }, - { - name: "off", - size: image.Pt(100, 10), - color: image1bit.Off, - want: []record{ - {cmd: dataEntryModeSetting, data: []byte{0x3}}, - {cmd: setRAMXAddressStartEndPosition, data: []byte{0, (100+7)/8 - 1}}, - {cmd: setRAMYAddressStartEndPosition, data: []byte{0, 0, 10 - 1, 0}}, - {cmd: setRAMXAddressCounter, data: []byte{0}}, - {cmd: setRAMYAddressCounter, data: []byte{0, 0}}, - { - cmd: writeRAMBW, - data: bytes.Repeat([]byte{0}, 13*10), - }, - }, - }, - { - name: "on", - size: image.Pt(32, 20), - color: image1bit.On, - want: []record{ - {cmd: dataEntryModeSetting, data: []byte{0x3}}, - {cmd: setRAMXAddressStartEndPosition, data: []byte{0, 32/8 - 1}}, - {cmd: setRAMYAddressStartEndPosition, data: []byte{0, 0, 20 - 1, 0}}, - {cmd: setRAMXAddressCounter, data: []byte{0}}, - {cmd: setRAMYAddressCounter, data: []byte{0, 0}}, - { - cmd: writeRAMBW, - data: bytes.Repeat([]byte{0xff}, 4*20), - }, - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - var got fakeController - - clearDisplay(&got, tc.size, tc.color) - - if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" { - t.Errorf("clearDisplay() difference (-got +want):\n%s", diff) - } - }) - } -} diff --git a/waveshare2in13v2/waveshare213v2.go b/waveshare2in13v2/waveshare213v2.go index fddfdcf..5d1849e 100644 --- a/waveshare2in13v2/waveshare213v2.go +++ b/waveshare2in13v2/waveshare213v2.go @@ -229,18 +229,9 @@ func (d *Dev) SetUpdateMode(mode PartialUpdate) error { // Clear clears the display. func (d *Dev) Clear(color color.Color) error { - eh := errorHandler{d: *d} - - c := image1bit.BitModel.Convert(color).(image1bit.Bit) - draw.Src.Draw(d.buffer, d.buffer.Bounds(), &image.Uniform{c}, image.Point{}) - - clearDisplay(&eh, image.Pt(d.opts.Width, d.opts.Height), c) - - if eh.err == nil { - updateDisplay(&eh, Full) - } - - return eh.err + return d.Draw(d.buffer.Bounds(), &image.Uniform{ + C: image1bit.BitModel.Convert(color).(image1bit.Bit), + }, image.Point{}) } // ColorModel returns a 1Bit color model.