diff --git a/waveshare2in13v2/controller.go b/waveshare2in13v2/controller.go index ad35077..cd65897 100644 --- a/waveshare2in13v2/controller.go +++ b/waveshare2in13v2/controller.go @@ -63,20 +63,14 @@ 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(displayUpdateControl2) - ctrl.sendData([]byte{ - displayUpdateEnableClock | - displayUpdateEnableAnalog, - }) + ctrl.sendCommand(writeDisplayOptionRegister) + ctrl.sendData([]byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00}) - 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 c536ea7..16496ae 100644 --- a/waveshare2in13v2/controller_test.go +++ b/waveshare2in13v2/controller_test.go @@ -91,6 +91,9 @@ 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}}, + {cmd: displayUpdateControl2, data: []byte{0xc0}}, + {cmd: masterActivation}, }, }, { diff --git a/waveshare2in13v2/drawing.go b/waveshare2in13v2/drawing.go index 05ef1ae..038e34e 100644 --- a/waveshare2in13v2/drawing.go +++ b/waveshare2in13v2/drawing.go @@ -5,7 +5,6 @@ package waveshare2in13v2 import ( - "bytes" "image" "image/draw" @@ -52,12 +51,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 +81,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,29 +109,23 @@ func drawImage(ctrl controller, opts *drawOpts) { } } -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() +func drawImage(ctrl controller, opts *drawOpts) { + s := opts.spec() - if spec.MemRect.Empty() { + if s.MemRect.Empty() { return } - setMemoryArea(ctrl, spec.MemRect) + draw.Src.Draw(opts.buffer, s.DstRect, opts.src, opts.srcPts) - ctrl.sendCommand(writeRAMBW) + commands := opts.commands - data := bytes.Repeat([]byte{colorValue}, spec.MemRect.Dx()) + if len(commands) == 0 { + commands = []byte{writeRAMBW, writeRAMRed} + } - for y := 0; y < spec.MemRect.Max.Y; y++ { - ctrl.sendData(data) + // Keep the two buffers in sync. + for _, cmd := range commands { + sendImage(ctrl, cmd, s.MemRect, opts.buffer) } } diff --git a/waveshare2in13v2/drawing_test.go b/waveshare2in13v2/drawing_test.go index dc9a384..30feb32 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,28 +59,43 @@ func TestDrawSpec(t *testing.T) { } } -func TestDrawImage(t *testing.T) { +func TestSendImage(t *testing.T) { for _, tc := range []struct { name string - opts drawOpts + cmd byte + area image.Rectangle + img *image1bit.VerticalLSB want []record }{ { 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), + 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}}, @@ -87,21 +103,20 @@ func TestDrawImage(t *testing.T) { {cmd: setRAMXAddressCounter, data: []byte{2}}, {cmd: setRAMYAddressCounter, data: []byte{4, 0}}, { - cmd: writeRAMBW, + cmd: writeRAMRed, data: bytes.Repeat([]byte{0x7f, 0xff, 0xff, 0x80}, 4), }, }, }, { 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), - }, + 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}}, @@ -118,54 +133,65 @@ func TestDrawImage(t *testing.T) { t.Run(tc.name, func(t *testing.T) { var got fakeController - drawImage(&got, &tc.opts) + sendImage(&got, tc.cmd, tc.area, tc.img) if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" { - t.Errorf("drawImage() difference (-got +want):\n%s", diff) + t.Errorf("sendImage() difference (-got +want):\n%s", diff) } }) } } -func TestClearDisplay(t *testing.T) { +func TestDrawImage(t *testing.T) { for _, tc := range []struct { - name string - size image.Point - color image1bit.Bit - want []record + name string + opts drawOpts + want []record }{ { name: "empty", }, { - name: "off", - size: image.Pt(100, 10), - color: image1bit.Off, + name: "partial", + opts: drawOpts{ + 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}}, - {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: 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: writeRAMBW, - data: bytes.Repeat([]byte{0}, 13*10), + data: bytes.Repeat([]byte{0x7f, 0xff, 0xff, 0x80}, 4), }, }, }, { - name: "on", - size: image.Pt(32, 20), - color: image1bit.On, + name: "full", + opts: drawOpts{ + 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}}, - {cmd: setRAMXAddressStartEndPosition, data: []byte{0, 32/8 - 1}}, - {cmd: setRAMYAddressStartEndPosition, data: []byte{0, 0, 20 - 1, 0}}, + {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}, 4*20), + cmd: writeRAMRed, + data: bytes.Repeat([]byte{0xff}, 80/8*120), }, }, }, @@ -173,10 +199,10 @@ func TestClearDisplay(t *testing.T) { t.Run(tc.name, func(t *testing.T) { var got fakeController - clearDisplay(&got, tc.size, tc.color) + drawImage(&got, &tc.opts) if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" { - t.Errorf("clearDisplay() difference (-got +want):\n%s", diff) + t.Errorf("drawImage() difference (-got +want):\n%s", diff) } }) } diff --git a/waveshare2in13v2/waveshare213v2.go b/waveshare2in13v2/waveshare213v2.go index b67a636..5d1849e 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 @@ -37,6 +38,7 @@ const ( setDummyLinePeriod byte = 0x3A setGateTime byte = 0x3B borderWaveformControl byte = 0x3C + writeDisplayOptionRegister byte = 0x37 setRAMXAddressStartEndPosition byte = 0x44 setRAMYAddressStartEndPosition byte = 0x45 setRAMXAddressCounter byte = 0x4E @@ -227,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. @@ -265,16 +258,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) @@ -300,6 +284,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}