Skip to content
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
69 changes: 69 additions & 0 deletions examples/gdew0154m09/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package main

import (
"device/esp"
"image/color"
"machine"
"time"

"tinygo.org/x/drivers/gdew0154m09"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freemono"
)

func main() {
// Hold the M5Stack CoreInk power rail on.
machine.GPIO12.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.GPIO12.High()

cs := machine.IO9
dc := machine.GPIO15
rst := machine.IO0
busy := machine.IO4
cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
busy.Configure(machine.PinConfig{Mode: machine.PinInput})
cs.High()
dc.High()
rst.High()

// Enable and reset the ESP32 VSPI peripheral before configuring SPI3.
esp.DPORT.SetPERIP_RST_EN_SPI3_RST(1)
esp.DPORT.SetPERIP_CLK_EN_SPI3_CLK_EN(1)
esp.DPORT.SetPERIP_RST_EN_SPI3_RST(0)
if err := machine.SPI3.Configure(machine.SPIConfig{
Frequency: gdew0154m09.Baudrate,
SCK: machine.IO18,
SDO: machine.IO23,
SDI: machine.NoPin,
Mode: gdew0154m09.SPIMode,
}); err != nil {
println("could not configure SPI:", err.Error())
return
}

display := gdew0154m09.New(machine.SPI3, cs, dc, rst, busy)
if err := display.Configure(gdew0154m09.DefaultConfig); err != nil {
println("could not configure display:", err.Error())
return
}

black := color.RGBA{A: 0xff}
tinyfont.WriteLine(display, &freemono.Bold18pt7b, 15, 90, "TinyGo", black)
for x := int16(0); x < gdew0154m09.Width; x++ {
display.SetPixel(x, 0, black)
display.SetPixel(x, gdew0154m09.Height-1, black)
}
for y := int16(0); y < gdew0154m09.Height; y++ {
display.SetPixel(0, y, black)
display.SetPixel(gdew0154m09.Width-1, y, black)
}

if err := display.Display(); err != nil {
println("could not refresh display:", err.Error())
}
for {
time.Sleep(time.Hour)
}
}
297 changes: 297 additions & 0 deletions gdew0154m09/gdew0154m09.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
// Package gdew0154m09 provides a driver for the Good Display GDEW0154M09
// 1.54-inch 200x200 monochrome e-paper panel used by the M5Stack CoreInk.
//
// The initialization sequence is based on the MIT-licensed M5Stack
// M5Core-Ink library:
// https://github.com/m5stack/M5Core-Ink
package gdew0154m09 // import "tinygo.org/x/drivers/gdew0154m09"

import (
"errors"
"image/color"
"time"

"tinygo.org/x/drivers"
)

const (
// Width and Height are the physical panel dimensions in pixels.
Width = 200
Height = 200

// Baudrate and SPIMode are the recommended SPI bus configuration.
Baudrate = 10_000_000
SPIMode = 3

frameBufferSize = Width * Height / 8
)

var (
ErrBusyTimeout = errors.New("gdew0154m09: busy timeout")
ErrInvalidBusyTimeout = errors.New("gdew0154m09: busy timeout must not be negative")
)

// OutputPin is the interface required from an output pin. Pins must be
// configured by the caller before they are passed to New.
type OutputPin interface {
Set(level bool)
}

// InputPin is the interface required from an input pin. The pin must be
// configured by the caller before it is passed to New.
type InputPin interface {
Get() bool
}

// Config contains the display configuration.
type Config struct {
// BusyTimeout is the maximum time to wait for a panel operation. A zero
// duration selects the default timeout.
BusyTimeout time.Duration
}

// DefaultConfig contains the recommended display configuration.
var DefaultConfig = Config{
BusyTimeout: 5 * time.Second,
}

// Device is a GDEW0154M09 display connected over SPI.
type Device struct {
bus drivers.SPI
cs OutputPin
dc OutputPin
rst OutputPin
busy InputPin

buffer [frameBufferSize]byte
previous [frameBufferSize]byte
tx [3]byte
busyTimeout time.Duration
}

var _ drivers.Displayer = (*Device)(nil)

// New returns a new GDEW0154M09 driver. The SPI bus and pins must already be
// configured. New performs no I/O.
func New(bus drivers.SPI, cs, dc, rst OutputPin, busy InputPin) *Device {
d := &Device{
bus: bus,
cs: cs,
dc: dc,
rst: rst,
busy: busy,
busyTimeout: DefaultConfig.BusyTimeout,
}
d.ClearBuffer()
fill(d.previous[:], 0xff)
return d
}

// Configure resets and initializes the display controller. It also clears the
// current and previous framebuffers to white without refreshing the panel.
func (d *Device) Configure(config Config) error {
if config.BusyTimeout < 0 {
return ErrInvalidBusyTimeout
}
if config.BusyTimeout == 0 {
config.BusyTimeout = DefaultConfig.BusyTimeout
}
d.busyTimeout = config.BusyTimeout

d.hardwareReset()
if err := d.waitUntilIdle(); err != nil {
return err
}

if err := d.sendCommand2(commandPanelSetting, 0xdf, 0x0e); err != nil {
return err
}
if err := d.sendCommand1(commandFITIInternalCode, 0x55); err != nil {
return err
}
if err := d.sendCommand1(commandUnknownAA, 0x0f); err != nil {
return err
}
if err := d.sendCommand1(commandUnknownE9, 0x02); err != nil {
return err
}
if err := d.sendCommand1(commandBoosterSoftStart, 0x11); err != nil {
return err
}
if err := d.sendCommand1(commandPowerSequence, 0x0a); err != nil {
return err
}
if err := d.sendCommand3(commandResolutionSetting, 0xc8, 0x00, 0xc8); err != nil {
return err
}
if err := d.sendCommand1(commandTCONSetting, 0x00); err != nil {
return err
}
if err := d.sendCommand1(commandVCOMDataInterval, defaultVCOMDataInterval); err != nil {
return err
}
if err := d.sendCommand1(commandPowerSaving, 0x00); err != nil {
return err
}
if err := d.sendCommand(commandPowerOn); err != nil {
return err
}

time.Sleep(100 * time.Millisecond)
if err := d.waitUntilIdle(); err != nil {
return err
}
d.ClearBuffer()
fill(d.previous[:], 0xff)
return nil
}

// Size returns the dimensions of the display in pixels.
func (d *Device) Size() (width, height int16) {
return Width, Height
}

// SetPixel updates one pixel in the framebuffer. Transparent and light colors
// are rendered white; opaque dark colors are rendered black.
func (d *Device) SetPixel(x, y int16, c color.RGBA) {
if x < 0 || x >= Width || y < 0 || y >= Height {
return
}
index := int(y)*(Width/8) + int(x)/8
mask := byte(0x80 >> uint(x%8))
brightness := uint16(c.R) + uint16(c.G) + uint16(c.B)
if c.A == 0 || brightness >= 3*128 {
d.buffer[index] |= mask
} else {
d.buffer[index] &^= mask
}
}

// ClearBuffer sets every pixel in the framebuffer to white without refreshing
// the panel.
func (d *Device) ClearBuffer() {
fill(d.buffer[:], 0xff)
}

// Display sends the previous and current framebuffers to the panel and starts
// a full refresh. It blocks until the refresh completes or the busy timeout is
// reached.
func (d *Device) Display() error {
if err := d.sendCommand(commandDataStartOld); err != nil {
return err
}
if err := d.sendData(d.previous[:]); err != nil {
return err
}
time.Sleep(2 * time.Millisecond)

if err := d.sendCommand(commandDataStartNew); err != nil {
return err
}
if err := d.sendData(d.buffer[:]); err != nil {
return err
}
time.Sleep(2 * time.Millisecond)

if err := d.sendCommand(commandDisplayRefresh); err != nil {
return err
}
if err := d.waitUntilIdle(); err != nil {
return err
}
copy(d.previous[:], d.buffer[:])
return nil
}

// ClearDisplay clears the framebuffer and refreshes the panel.
func (d *Device) ClearDisplay() error {
d.ClearBuffer()
return d.Display()
}

// DeepSleep powers off the panel and enters deep sleep. Configure must be
// called to wake and reinitialize the controller.
func (d *Device) DeepSleep() error {
if err := d.sendCommand1(commandVCOMDataInterval, deepSleepVCOMDataInterval); err != nil {
return err
}
if err := d.waitUntilIdle(); err != nil {
return err
}
if err := d.sendCommand(commandPowerOff); err != nil {
return err
}
return d.sendCommand1(commandDeepSleep, deepSleepCheckCode)
}

// IsBusy reports whether the active-low busy signal is asserted.
func (d *Device) IsBusy() bool {
return !d.busy.Get()
}

func (d *Device) hardwareReset() {
d.rst.Set(true)
time.Sleep(10 * time.Millisecond)
d.rst.Set(false)
time.Sleep(100 * time.Millisecond)
d.rst.Set(true)
time.Sleep(100 * time.Millisecond)
}

func (d *Device) waitUntilIdle() error {
deadline := time.Now().Add(d.busyTimeout)
for d.IsBusy() {
if time.Now().After(deadline) {
return ErrBusyTimeout
}
time.Sleep(time.Millisecond)
}
return nil
}

func (d *Device) sendCommandWithData(command byte, data []byte) error {
if err := d.sendCommand(command); err != nil {
return err
}
return d.sendData(data)
}

func (d *Device) sendCommand1(command, data byte) error {
d.tx[0] = data
return d.sendCommandWithData(command, d.tx[:1])
}

func (d *Device) sendCommand2(command, data0, data1 byte) error {
d.tx[0] = data0
d.tx[1] = data1
return d.sendCommandWithData(command, d.tx[:2])
}

func (d *Device) sendCommand3(command, data0, data1, data2 byte) error {
d.tx[0] = data0
d.tx[1] = data1
d.tx[2] = data2
return d.sendCommandWithData(command, d.tx[:3])
}

func (d *Device) sendCommand(command byte) error {
d.dc.Set(false)
d.cs.Set(false)
_, err := d.bus.Transfer(command)
d.cs.Set(true)
return err
}

func (d *Device) sendData(data []byte) error {
d.dc.Set(true)
d.cs.Set(false)
err := d.bus.Tx(data, nil)
d.cs.Set(true)
return err
}

func fill(buffer []byte, value byte) {
for index := range buffer {
buffer[index] = value
}
}
Loading