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

waveshare2in13v3/waveshare2in13v4: adding v3 and v4 waveshare 2.13" ePaper displays #65

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions waveshare2in13v3/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// 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 waveshare2in13v3

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

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

ctrl.sendCommand(driverOutputControl)
ctrl.sendData([]byte{0xf9, 0x00, 0x00})

ctrl.sendCommand(dataEntryModeSetting)
ctrl.sendData([]byte{0x03})

setWindow(ctrl, 0, 0, opts.Width-1, opts.Height-1)
setCursor(ctrl, 0, 0)

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

ctrl.sendCommand(displayUpdateControl1)
ctrl.sendData([]byte{0x00, 0x80})

ctrl.sendCommand(tempSensorSelect)
ctrl.sendData([]byte{0x80})

ctrl.waitUntilIdle()

setLut(ctrl, opts.FullUpdate)
}

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])

ctrl.sendCommand(writeDisplayOptionRegister)
ctrl.sendData([]byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00})

// Start up the parts likely used by a draw operation soon.
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()
}

// new

// turnOnDisplay turns on the display if mode = true it does a partial display
func turnOnDisplay(ctrl controller, mode PartialUpdate) {
var upMode byte = 0xC7
if mode {
upMode = 0x0f
}
ctrl.sendCommand(displayUpdateControl2)
ctrl.sendData([]byte{upMode})
ctrl.sendCommand(masterActivation)
ctrl.waitUntilIdle()
}

func lookUpTable(ctrl controller, lut LUT) {
ctrl.sendCommand(writeLutRegister)
ctrl.sendData(lut[:153])
ctrl.waitUntilIdle()
}

func setLut(ctrl controller, lut LUT) {
lookUpTable(ctrl, lut)
ctrl.sendCommand(endOptionEOPT)
ctrl.sendData([]byte{lut[153]})
ctrl.sendCommand(gateDrivingVoltageControl)
ctrl.sendData([]byte{lut[154]})
ctrl.sendCommand(sourceDrivingVoltageControl)
ctrl.sendData(lut[155:157])
ctrl.sendCommand(writeVcomRegister)
ctrl.sendData([]byte{lut[158]})
}

func setWindow(ctrl controller, x_start int, y_start int, x_end int, y_end int) {
ctrl.sendCommand(setRAMXAddressStartEndPosition)
ctrl.sendData([]byte{byte((x_start >> 3) & 0xFF), byte((x_end >> 3) & 0xFF)})

ctrl.sendCommand(setRAMYAddressStartEndPosition)
ctrl.sendData([]byte{byte(y_start & 0xFF), byte((y_start >> 8) & 0xFF), byte(y_end & 0xFF), byte((y_end >> 8) & 0xFF)})
}

func setCursor(ctrl controller, x int, y int) {
ctrl.sendCommand(setRAMXAddressCounter)
// x point must be the multiple of 8 or the last 3 bits will be ignored
ctrl.sendData([]byte{byte(x & 0xFF)})

ctrl.sendCommand(setRAMYAddressCounter)
ctrl.sendData([]byte{byte(y & 0xFF), byte((y >> 8) & 0xFF)})
}

func clear(ctrl controller, color byte, opts *Opts) {
var linewidth int
if opts.Width%8 == 0 {
linewidth = int(opts.Width / 8)

Check warning on line 150 in waveshare2in13v3/controller.go

View check run for this annotation

Codecov / codecov/patch

waveshare2in13v3/controller.go#L150

Added line #L150 was not covered by tests
} else {
linewidth = int(opts.Width/8) + 1
}

var buff []byte
ctrl.sendCommand(writeRAMBW)
for j := 0; j < opts.Height; j++ {
for i := 0; i < linewidth; i++ {
buff = append(buff, color)
}
}
ctrl.sendData(buff)

turnOnDisplay(ctrl, false)
}
196 changes: 196 additions & 0 deletions waveshare2in13v3/controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// 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 waveshare2in13v3

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: "epd2in13v3",
opts: EPD2in13v3,
want: []record{
{cmd: swReset},
{
cmd: driverOutputControl,
data: []byte{250 - 1, 0, 0},
},
{cmd: dataEntryModeSetting, data: []uint8{0x03}},
{cmd: setRAMXAddressStartEndPosition, data: []uint8{0x00, 0x0f}},
{cmd: setRAMYAddressStartEndPosition, data: []uint8{0x00, 0x00, 0xf9, 0x00}},
{cmd: setRAMXAddressCounter, data: []uint8{0x00}},
{cmd: setRAMYAddressCounter, data: []uint8{0x00, 0x00}},
{cmd: borderWaveformControl, data: []uint8{0x05}},
{cmd: displayUpdateControl1, data: []uint8{0x00, 0x80}},
{cmd: tempSensorSelect, data: []uint8{0x80}},
{cmd: writeLutRegister, data: EPD2in13v3.FullUpdate[:153]},
{cmd: endOptionEOPT, data: []uint8{EPD2in13v3.FullUpdate[153]}},
{cmd: gateDrivingVoltageControl, data: []uint8{EPD2in13v3.FullUpdate[154]}},
{cmd: sourceDrivingVoltageControl, data: EPD2in13v3.FullUpdate[155:157]},
{cmd: writeVcomRegister, data: []uint8{EPD2in13v3.FullUpdate[158]}},
},
},
} {
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)},
{cmd: 0x37, data: []byte{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00}},
{cmd: displayUpdateControl2, data: []byte{0xc0}},
{cmd: masterActivation},
},
},
{
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)
}
})
}
}

func TestClear(t *testing.T) {
var buff []byte
const linewidth = int(122/8) + 1
for j := 0; j < 250; j++ {
for i := 0; i < linewidth; i++ {
buff = append(buff, 0x00)
}
}
for _, tc := range []struct {
name string
opts Opts
color byte
want []record
}{
{
name: "clear",
opts: EPD2in13v3,
want: []record{
{cmd: writeRAMBW, data: buff},
{cmd: displayUpdateControl2, data: []byte{0xC7}},
{cmd: masterActivation},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var got fakeController

clear(&got, tc.color, &tc.opts)

if diff := cmp.Diff([]record(got), tc.want, cmpopts.EquateEmpty(), cmp.AllowUnexported(record{})); diff != "" {
t.Errorf("updateDisplay() difference (-got +want):\n%s", diff)
}
})
}
}
19 changes: 19 additions & 0 deletions waveshare2in13v3/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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 waveshare2in13v3 controls Waveshare 2.13 v3 e-paper displays.
//
// Datasheet:
// https://files.waveshare.com/upload/5/59/2.13inch_e-Paper_V3_Specificition.pdf
//
// Product page:
// 2.13 inch version 3: https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT_Manual#Resources
// This display is an Active Matrix Electrophoretic Display (AM EPD), with
// interface and a reference system design. The display is capable to display
// imagesat 1-bit white, black full display capabilities. The 2.13inch active area
// contains 250×122 pixels. The module is a TFT-array driving electrophoresis
// display, withintegrated circuits including gate driver, source driver, MCU
// interface, timingcontroller, oscillator, DC-DC, SRAM, LUT, VCOM. Module can be
// used in portableelectronic devices, such as Electronic Shelf Label (ESL) System.
package waveshare2in13v3
Loading
Loading