epd1in54: add Waveshare 1.54inch B/W e-Paper display (#704)

epd1in54: add Waveshare 1.54inch B/W e-Paper display
This commit is contained in:
Marcin Białoń
2024-10-23 13:43:30 +02:00
committed by GitHub
parent 109cab4f3b
commit ce80e7582f
4 changed files with 513 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
package main
import (
"image/color"
"machine"
"tinygo.org/x/drivers/waveshare-epd/epd1in54"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/gophers"
)
var (
spi0 = machine.SPI0
cs = machine.D10
dc = machine.D9
rst = machine.D6
busy = machine.D5
black = color.RGBA{R: 1, G: 1, B: 1, A: 255}
)
func main() {
display := epd1in54.New(spi0, cs, dc, rst, busy)
display.LDirInit(epd1in54.Config{})
display.Clear()
display.ClearBuffer()
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 150, 0, "A B C", black, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 100, 0, "D E F", black, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 50, 0, "G H I", black, tinyfont.ROTATION_90)
tinyfont.WriteLineRotated(&display, &gophers.Regular58pt, 0, 0, "J K L", black, tinyfont.ROTATION_90)
display.Display()
display.Sleep()
}
+1
View File
@@ -77,6 +77,7 @@ tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/r
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/pyportal_touchpaint/main.go
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl53l1x/main.go
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl6180x/main.go
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840-sense ./examples/waveshare-epd/epd1in54/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd4in2/main.go
+424
View File
@@ -0,0 +1,424 @@
// Package epd1in54 implements a driver for Waveshare 1.54in black and white e-paper device.
//
// Derived from:
//
// https://github.com/tinygo-org/drivers/tree/master/waveshare-epd
// https://github.com/waveshare/e-Paper/blob/master/Arduino/epd1in54_V2/epd1in54_V2.cpp
//
// Datasheet: https://www.waveshare.com/w/upload/e/e5/1.54inch_e-paper_V2_Datasheet.pdf
package epd1in54
import (
"image/color"
"machine"
"time"
)
type Config struct {
Width int16
Height int16
LogicalWidth int16
Rotation Rotation
}
type Device struct {
bus machine.SPI
cs machine.Pin
dc machine.Pin
rst machine.Pin
busy machine.Pin
buffer []uint8
rotation Rotation
}
type Rotation uint8
var fullRefresh = [159]uint8{
0x80, 0x48, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x40, 0x48, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x80, 0x48, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x40, 0x48, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x8, 0x1, 0x0, 0x8, 0x1, 0x0, 0x2,
0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
0x22, 0x17, 0x41, 0x0, 0x32, 0x20,
}
var partialRefresh = [159]uint8{
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
0x02, 0x17, 0x41, 0xB0, 0x32, 0x28,
}
// New returns a new epd1in54 driver. Pass in a fully configured SPI bus.
func New(bus machine.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
return Device{
buffer: make([]uint8, (uint32(Width)*uint32(Height))/8),
bus: bus,
cs: csPin,
dc: dcPin,
rst: rstPin,
busy: busyPin,
}
}
func (d *Device) LDirInit(cfg Config) {
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
d.bus.Configure(machine.SPIConfig{
Frequency: 2000000,
Mode: 0,
LSBFirst: false,
})
d.Reset()
d.WaitUntilIdle()
d.SendCommand(0x12)
d.WaitUntilIdle()
d.SendCommand(0x01)
d.SendData(0xC7)
d.SendData(0x00)
d.SendData(0x00)
d.SendCommand(0x11)
d.SendData(0x03)
d.SendCommand(0x44)
/* x point must be the multiple of 8 or the last 3 bits will be ignored */
d.SendData((0 >> 3) & 0xFF)
d.SendData((199 >> 3) & 0xFF)
d.SendCommand(0x45)
d.SendData(0 & 0xFF)
d.SendData((0 >> 8) & 0xFF)
d.SendData(199 & 0xFF)
d.SendData((199 >> 8) & 0xFF)
d.SendCommand(0x3C)
d.SendData(0x01)
d.SendCommand(0x18)
d.SendData(0x80)
d.SendCommand(0x22)
d.SendData(0xB1)
d.SendCommand(0x20)
d.SendCommand(0x4E)
d.SendData(0x00)
d.SendCommand(0x4F)
d.SendData(0xC7)
d.SendData(0x00)
d.WaitUntilIdle()
d.setLUT(fullRefresh)
}
func (d *Device) HDirInit(cfg Config) {
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.rst.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.dc.Configure(machine.PinConfig{Mode: machine.PinOutput})
d.busy.Configure(machine.PinConfig{Mode: machine.PinInput})
d.bus.Configure(machine.SPIConfig{
Frequency: 2000000,
Mode: 0,
LSBFirst: false,
})
d.Reset()
d.WaitUntilIdle()
d.SendCommand(0x12)
d.WaitUntilIdle()
d.SendCommand(0x01)
d.SendData(0xC7)
d.SendData(0x00)
d.SendData(0x01)
d.SendCommand(0x11)
d.SendData(0x01)
d.SendCommand(0x44)
d.SendData(0x00)
d.SendData(0x18)
d.SendCommand(0x45)
d.SendData(0xC7)
d.SendData(0x00)
d.SendData(0x00)
d.SendData(0x00)
d.SendCommand(0x3C)
d.SendData(0x01)
d.SendCommand(0x18)
d.SendData(0x80)
d.SendCommand(0x22)
d.SendData(0xB1)
d.SendCommand(0x20)
d.SendCommand(0x4E)
d.SendData(0x00)
d.SendCommand(0x4F)
d.SendData(0xC7)
d.SendData(0x00)
d.WaitUntilIdle()
d.setLUT(fullRefresh)
}
func (d *Device) setLUT(lut [159]uint8) {
d.SendCommand(0x32)
for i := 0; i < 153; i++ {
d.SendData(lut[i])
}
d.WaitUntilIdle()
d.SendCommand(0x3F)
d.SendData(lut[153])
d.SendCommand(0x03)
d.SendData(lut[154])
d.SendCommand(0x04)
d.SendData(lut[155])
d.SendData(lut[156])
d.SendData(lut[157])
d.SendCommand(0x2C)
d.SendData(lut[158])
}
// Reset resets the display.
func (d *Device) Reset() {
d.rst.High()
time.Sleep(20 * time.Millisecond)
d.rst.Low()
time.Sleep(5 * time.Millisecond)
d.rst.High()
time.Sleep(20 * time.Millisecond)
}
// SendCommand sends a command to the display
func (d *Device) SendCommand(command uint8) {
d.sendDataCommand(true, command)
}
// SendData sends a data byte to the display
func (d *Device) SendData(data uint8) {
d.sendDataCommand(false, data)
}
// sendDataCommand sends image data or a command to the screen
func (d *Device) sendDataCommand(isCommand bool, data uint8) {
if isCommand {
d.dc.Low()
} else {
d.dc.High()
}
d.cs.Low()
d.bus.Transfer(data)
d.cs.High()
}
// SetPixel modifies the internal buffer in a single pixel.
// The display have 2 colors: black and white
// We use RGBA(0,0,0, 255) as white (transparent)
// Anything else as black
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
x, y = d.xy(x, y)
if x < 0 || x >= Width || y < 0 || y >= Height {
return
}
byteIndex := (uint32(x) + uint32(y)*uint32(Width)) / 8
if c.R == 0 && c.G == 0 && c.B == 0 { // TRANSPARENT / WHITE
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
} else { // WHITE / EMPTY
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
}
}
func (d *Device) DisplayImage(image []uint8) {
var w, h int
if Width%8 == 0 {
w = int(Width / 8)
} else {
w = int(Width/8 + 1)
}
h = int(Height)
d.SendCommand(0x24)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
d.SendData(image[i+j*w])
}
}
d.SendCommand(0x26)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
d.SendData(image[i+j*w])
}
}
d.displayFrame()
}
func (d *Device) Display() error {
var w, h int
if Width%8 == 0 {
w = int(Width / 8)
} else {
w = int(Width/8 + 1)
}
h = int(Height)
d.SendCommand(0x24)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
x := i + j*w
d.SendData(d.buffer[x])
}
}
d.SendCommand(0x26)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
x := i + j*w
d.SendData(d.buffer[x])
}
}
d.displayFrame()
return nil
}
func (d *Device) displayFrame() {
d.SendCommand(0x22)
d.SendData(0xC7)
d.SendCommand(0x20)
d.WaitUntilIdle()
}
func (d *Device) Clear() {
var w, h int
if Width%8 == 0 {
w = int(Width / 8)
} else {
w = int(Width/8 + 1)
}
h = int(Height)
d.SendCommand(0x24)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
d.SendData(0xff)
}
}
d.SendCommand(0x26)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
d.SendData(0xff)
}
}
d.displayFrame()
}
// WaitUntilIdle waits until the display is ready
func (d *Device) WaitUntilIdle() {
for d.busy.Get() {
time.Sleep(100 * time.Millisecond)
}
time.Sleep(200 * time.Millisecond)
}
// IsBusy returns the busy status of the display
func (d *Device) IsBusy() bool {
return d.busy.Get()
}
// ClearBuffer sets the buffer to 0xFF (white)
func (d *Device) ClearBuffer() {
for i := 0; i < len(d.buffer); i++ {
d.buffer[i] = 0xFF
}
}
// Size returns the current size of the display.
func (d *Device) Size() (w, h int16) {
if d.rotation == ROTATION_90 || d.rotation == ROTATION_270 {
return Height, Width
}
return Width, Height
}
// SetRotation changes the rotation (clock-wise) of the device
func (d *Device) SetRotation(rotation Rotation) {
d.rotation = rotation
}
// xy chages the coordinates according to the rotation
func (d *Device) xy(x, y int16) (int16, int16) {
switch d.rotation {
case NO_ROTATION:
return x, y
case ROTATION_90:
return Width - y - 1, x
case ROTATION_180:
return Width - x - 1, Height - y - 1
case ROTATION_270:
return y, Height - x - 1
}
return x, y
}
func (d *Device) Sleep() {
d.SendCommand(0x10)
d.SendData(0x01)
time.Sleep(200 * time.Millisecond)
d.rst.Low()
}
+52
View File
@@ -0,0 +1,52 @@
package epd1in54
// Derived from https://github.com/waveshare/e-Paper/blob/master/Arduino/epd4in2/epd4in2.h
const (
Width = 200
Height = 200
PANEL_SETTING = 0x00
POWER_SETTING = 0x01
POWER_OFF = 0x02
POWER_OFF_SEQUENCE_SETTING = 0x03
POWER_ON = 0x04
POWER_ON_MEASURE = 0x05
BOOSTER_SOFT_START = 0x06
DEEP_SLEEP = 0x07
DATA_START_TRANSMISSION_1 = 0x10
DATA_STOP = 0x11
DISPLAY_REFRESH = 0x12
DATA_START_TRANSMISSION_2 = 0x13
LUT_FOR_VCOM = 0x20
LUT_WHITE_TO_WHITE = 0x21
LUT_BLACK_TO_WHITE = 0x22
LUT_WHITE_TO_BLACK = 0x23
LUT_BLACK_TO_BLACK = 0x24
PLL_CONTROL = 0x30
TEMPERATURE_SENSOR_COMMAND = 0x40
TEMPERATURE_SENSOR_SELECTION = 0x41
TEMPERATURE_SENSOR_WRITE = 0x42
TEMPERATURE_SENSOR_READ = 0x43
VCOM_AND_DATA_INTERVAL_SETTING = 0x50
LOW_POWER_DETECTION = 0x51
TCON_SETTING = 0x60
RESOLUTION_SETTING = 0x61
GSST_SETTING = 0x65
GET_STATUS = 0x71
AUTO_MEASUREMENT_VCOM = 0x80
READ_VCOM_VALUE = 0x81
VCM_DC_SETTING = 0x82
PARTIAL_WINDOW = 0x90
PARTIAL_IN = 0x91
PARTIAL_OUT = 0x92
PROGRAM_MODE = 0xA0
ACTIVE_PROGRAMMING = 0xA1
READ_OTP = 0xA2
POWER_SAVING = 0xE3
NO_ROTATION Rotation = 0
ROTATION_90 Rotation = 1
ROTATION_180 Rotation = 2
ROTATION_270 Rotation = 3
)