Skip to content

Commit

Permalink
waveshare2in13v2: Deduplicate and simplify logic for sending data (#33)
Browse files Browse the repository at this point in the history
Dev.Clear: Avoid writing one beyond the end of the line. Also build the
row data only once before sending it for each line.

Dev.Draw, Dev.DrawPartial: Deduplicate logic for iterating over pixels
and simplify it such that a whole row of bits is built before sending
them in one go.

Tested using a Waveshare e-Paper 2.13in V2 display.

Signed-off-by: Michael Hanselmann <public@hansmi.ch>
  • Loading branch information
hansmi authored Dec 10, 2021
1 parent 37f977f commit 5b35a3f
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 71 deletions.
120 changes: 49 additions & 71 deletions waveshare2in13v2/waveshare213v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package waveshare2in13v2

import (
"bytes"
"fmt"
"image"
"image/color"
Expand Down Expand Up @@ -124,6 +125,12 @@ var EPD2in13v2 = Opts{
},
}

// dataDimensions returns the size in terms of bytes needed to fill the
// display.
func dataDimensions(opts *Opts) (int, int) {
return opts.Height, (opts.Width + 7) / 8
}

func (eh *errorHandler) rstOut(l gpio.Level) {
if eh.err != nil {
return
Expand Down Expand Up @@ -307,21 +314,16 @@ func (d *Dev) Init(partialUpdate PartialUpdate) error {

// Clear clears the display.
func (d *Dev) Clear(color byte) error {
linewidth := 0
if d.opts.Width%8 == 0 {
linewidth = d.opts.Width / 8
} else {
linewidth = d.opts.Width/8 + 1
}
rows, cols := dataDimensions(d.opts)
data := bytes.Repeat([]byte{color}, cols)

if err := d.sendCommand([]byte{writeRAMBW}); err != nil {
return err
}

for i := 0; i <= d.opts.Height; i++ {
for i := 0; i <= linewidth; i++ {
if err := d.sendData([]byte{color}); err != nil {
return err
}
for y := 0; y < rows; y++ {
if err := d.sendData(data); err != nil {
return err
}
}

Expand All @@ -338,32 +340,44 @@ func (d *Dev) Bounds() image.Rectangle {
return image.Rect(0, 0, d.opts.Width, d.opts.Height)
}

func (d *Dev) sendImage(cmd []byte, dstRect image.Rectangle, src *image1bit.VerticalLSB) error {
// TODO: Handle dstRect not matching the device bounds.

if err := d.setMemoryPointer(0, 0); err != nil {
return err
}

eh := errorHandler{d: *d}
eh.sendCommand(cmd)

rows, cols := dataDimensions(d.opts)

for y := 0; y < rows; y++ {
data := make([]byte, cols)

for x := 0; x < cols; x++ {
for bit := 0; bit < 8; bit++ {
if src.BitAt((x*8)+bit, y) {
data[x] |= 0x80 >> bit
}
}
}

eh.sendData(data)
}

return eh.err
}

// Draw draws the given image to the display.
func (d *Dev) Draw(dstRect image.Rectangle, src image.Image, srcPts image.Point) error {
next := image1bit.NewVerticalLSB(dstRect)
draw.Src.Draw(next, dstRect, src, srcPts)

var byteToSend byte = 0x00
for y := 0; y <= d.opts.Height; y++ {
if err := d.setMemoryPointer(0, y); err != nil {
return err
}
if err := d.sendCommand([]byte{writeRAMBW}); err != nil {
return err
}
for x := 0; x <= d.opts.Width; x++ {
bit := next.BitAt(x, y)
if bit {
byteToSend |= 0x80 >> (uint32(x) % 8)
}
if x%8 == 7 {
if err := d.sendData([]byte{byteToSend}); err != nil {
return err
}
byteToSend = 0x00
}
}
if err := d.sendImage([]byte{writeRAMBW}, dstRect, next); err != nil {
return err
}

return d.turnOnDisplay()
}

Expand All @@ -372,48 +386,12 @@ func (d *Dev) DrawPartial(dstRect image.Rectangle, src image.Image, srcPts image
next := image1bit.NewVerticalLSB(dstRect)
draw.Src.Draw(next, dstRect, src, srcPts)

var byteToSend byte = 0x00
for y := 0; y < d.opts.Height; y++ {
if err := d.setMemoryPointer(0, y); err != nil {
return err
}
if err := d.sendCommand([]byte{writeRAMBW}); err != nil {
return err
}
for x := 0; x < d.opts.Width; x++ {
bit := next.BitAt(x, y)
if bit {
byteToSend |= 0x80 >> (uint32(x) % 8)
}
if x%8 == 7 {
if err := d.sendData([]byte{byteToSend}); err != nil {
return err
}
byteToSend = 0x00
}
}
if err := d.sendImage([]byte{writeRAMBW}, dstRect, next); err != nil {
return err
}

byteToSend = 0x00
for y := 0; y < d.opts.Height; y++ {
if err := d.setMemoryPointer(0, y); err != nil {
return err
}
if err := d.sendCommand([]byte{writeRAMRed}); err != nil {
return err
}
for x := 0; x < d.opts.Width; x++ {
bit := next.BitAt(x, y)
if bit {
byteToSend |= 0x80 >> (uint32(x) % 8)
}
if x%8 == 7 {
if err := d.sendData([]byte{^byteToSend}); err != nil {
return err
}
byteToSend = 0x00
}
}
if err := d.sendImage([]byte{writeRAMRed}, dstRect, next); err != nil {
return err
}

return d.turnOnDisplay()
Expand Down
38 changes: 38 additions & 0 deletions waveshare2in13v2/waveshare213v2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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"
"testing"
)

func TestDataDimensions(t *testing.T) {
for _, tc := range []struct {
opts *Opts
wantHeight int
wantWidth int
}{
{opts: &Opts{Width: 0, Height: 0}},
{
opts: &Opts{Height: 48, Width: 16},
wantHeight: 48,
wantWidth: 2,
},
{
opts: &Opts{Height: 250, Width: 122},
wantHeight: 250,
wantWidth: 16,
},
} {
t.Run(fmt.Sprintf("%+v", *tc.opts), func(t *testing.T) {
gotHeight, gotWidth := dataDimensions(tc.opts)

if !(gotHeight == tc.wantHeight && gotWidth == tc.wantWidth) {
t.Errorf("dataDimensions(%#v) returned %d, %d; want %d, %d", tc.opts, gotHeight, gotWidth, tc.wantHeight, tc.wantWidth)
}
})
}
}

0 comments on commit 5b35a3f

Please sign in to comment.