mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
waveshare(ssd1680): Add driver for waveshare 2.9 inch v2 epaper
- Waveshare 2.9in v2 epaper uses a different IC (SSD1680) than the v1. This commit
takes the code from v1 and modifies them for v2.
- Datasheets:
- https://files.waveshare.com/upload/7/79/2.9inch-e-paper-v2-specification.pdf
- https://cdn-learn.adafruit.com/assets/assets/000/097/631/original/SSD1680_Datasheet.pdf?1607625960
- Code reference: https://github.com/waveshareteam/e-Paper/raw/refs/heads/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in9_V2.c
- Fixes #627
Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
Assisted-by: Claude Code
This commit is contained in:
@@ -0,0 +1,137 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/waveshare-epd/epd2in9v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var display epd2in9v2.Device
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
|
Frequency: 12000000,
|
||||||
|
SCK: machine.EPD_SCK_PIN,
|
||||||
|
SDO: machine.EPD_SDO_PIN,
|
||||||
|
})
|
||||||
|
|
||||||
|
display = epd2in9v2.New(
|
||||||
|
machine.SPI0,
|
||||||
|
machine.EPD_CS_PIN,
|
||||||
|
machine.EPD_DC_PIN,
|
||||||
|
machine.EPD_RESET_PIN,
|
||||||
|
machine.EPD_BUSY_PIN,
|
||||||
|
)
|
||||||
|
display.Configure(epd2in9v2.Config{
|
||||||
|
Rotation: epd2in9v2.ROTATION_270,
|
||||||
|
Speed: epd2in9v2.SPEED_DEFAULT,
|
||||||
|
Blocking: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
black := color.RGBA{0, 0, 0, 255}
|
||||||
|
white := color.RGBA{255, 255, 255, 255}
|
||||||
|
|
||||||
|
// --- Step 1: clear to white ---
|
||||||
|
println("epd2in9v2: clearing display")
|
||||||
|
display.ClearBuffer()
|
||||||
|
display.Display()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
// --- Step 2: full refresh checkerboard ---
|
||||||
|
println("epd2in9v2: drawing checkerboard (full refresh)")
|
||||||
|
w, h := display.Size()
|
||||||
|
for i := int16(0); i < w/8; i++ {
|
||||||
|
for j := int16(0); j < h/8; j++ {
|
||||||
|
if (i+j)%2 == 0 {
|
||||||
|
fillRect(i*8, j*8, 8, 8, black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
display.Display()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
// --- Step 3: fast refresh - draw border and diagonal cross ---
|
||||||
|
println("epd2in9v2: switching to fast refresh")
|
||||||
|
display.SetSpeed(epd2in9v2.SPEED_FAST)
|
||||||
|
display.ClearBuffer()
|
||||||
|
|
||||||
|
for x := int16(0); x < w; x++ {
|
||||||
|
display.SetPixel(x, 0, black)
|
||||||
|
display.SetPixel(x, h-1, black)
|
||||||
|
}
|
||||||
|
for y := int16(0); y < h; y++ {
|
||||||
|
display.SetPixel(0, y, black)
|
||||||
|
display.SetPixel(w-1, y, black)
|
||||||
|
}
|
||||||
|
for i := int16(0); i < w && i < h; i++ {
|
||||||
|
display.SetPixel(i, i*h/w, black)
|
||||||
|
display.SetPixel(w-1-i, i*h/w, black)
|
||||||
|
}
|
||||||
|
|
||||||
|
display.Display()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
// --- Step 4: partial refresh counter ---
|
||||||
|
println("epd2in9v2: partial refresh demo")
|
||||||
|
display.SetSpeed(epd2in9v2.SPEED_DEFAULT)
|
||||||
|
display.ClearBuffer()
|
||||||
|
|
||||||
|
println("epd2in9v2: setting base image")
|
||||||
|
display.DisplayWithBase()
|
||||||
|
|
||||||
|
for count := 0; count < 10; count++ {
|
||||||
|
cx := int16(120)
|
||||||
|
cy := int16(50)
|
||||||
|
fillRect(cx, cy, 60, 20, white)
|
||||||
|
|
||||||
|
digit := int16(count % 10)
|
||||||
|
drawDigit(cx+22, cy+2, digit, black)
|
||||||
|
|
||||||
|
display.DisplayPartial()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
// --- Step 5: sleep ---
|
||||||
|
println("epd2in9v2: entering deep sleep")
|
||||||
|
display.ClearBuffer()
|
||||||
|
display.Display()
|
||||||
|
display.Sleep()
|
||||||
|
println("epd2in9v2: done, you can remove power")
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillRect(x, y, w, h int16, c color.RGBA) {
|
||||||
|
for i := x; i < x+w; i++ {
|
||||||
|
for j := y; j < y+h; j++ {
|
||||||
|
display.SetPixel(i, j, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawDigit draws a simple 3x5-pixel-block digit (each block 4x3 px) at position (x,y).
|
||||||
|
func drawDigit(x, y, digit int16, c color.RGBA) {
|
||||||
|
segments := [10][5]uint8{
|
||||||
|
{0x7, 0x5, 0x5, 0x5, 0x7}, // 0
|
||||||
|
{0x2, 0x2, 0x2, 0x2, 0x2}, // 1
|
||||||
|
{0x7, 0x1, 0x7, 0x4, 0x7}, // 2
|
||||||
|
{0x7, 0x1, 0x7, 0x1, 0x7}, // 3
|
||||||
|
{0x5, 0x5, 0x7, 0x1, 0x1}, // 4
|
||||||
|
{0x7, 0x4, 0x7, 0x1, 0x7}, // 5
|
||||||
|
{0x7, 0x4, 0x7, 0x5, 0x7}, // 6
|
||||||
|
{0x7, 0x1, 0x1, 0x1, 0x1}, // 7
|
||||||
|
{0x7, 0x5, 0x7, 0x5, 0x7}, // 8
|
||||||
|
{0x7, 0x5, 0x7, 0x1, 0x7}, // 9
|
||||||
|
}
|
||||||
|
if digit < 0 || digit > 9 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for row := int16(0); row < 5; row++ {
|
||||||
|
for col := int16(0); col < 3; col++ {
|
||||||
|
if segments[digit][row]&(0x4>>uint(col)) != 0 {
|
||||||
|
fillRect(x+col*4, y+row*3, 4, 3, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -128,6 +128,7 @@ tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/sx127x/l
|
|||||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/ssd1289/main.go
|
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/ssd1289/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/irremote/main.go
|
tinygo build -size short -o ./build/test.hex -target=pico ./examples/irremote/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=badger2040 ./examples/uc8151/main.go
|
tinygo build -size short -o ./build/test.hex -target=badger2040 ./examples/uc8151/main.go
|
||||||
|
tinygo build -size short -o ./build/test.hex -target=badger2040 ./examples/waveshare-epd/epd2in9v2/main.go
|
||||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/scd4x/main.go
|
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/scd4x/main.go
|
||||||
tinygo build -size short -o ./build/test.uf2 -target=circuitplay-express ./examples/makeybutton/main.go
|
tinygo build -size short -o ./build/test.uf2 -target=circuitplay-express ./examples/makeybutton/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ds18b20/main.go
|
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ds18b20/main.go
|
||||||
|
|||||||
@@ -0,0 +1,465 @@
|
|||||||
|
// Package epd2in9v2 implements a driver for the Waveshare 2.9in V2 black and white e-paper display.
|
||||||
|
//
|
||||||
|
// This is for the V2 device using the SSD1680 chipset. For the V1 device (using IL3820),
|
||||||
|
// use the epd2in9 package instead.
|
||||||
|
//
|
||||||
|
// Datasheet:
|
||||||
|
// https://files.waveshare.com/upload/7/79/2.9inch-e-paper-v2-specification.pdf
|
||||||
|
// https://cdn-learn.adafruit.com/assets/assets/000/097/631/original/SSD1680_Datasheet.pdf?1607625960
|
||||||
|
//
|
||||||
|
// Reference: https://github.com/waveshareteam/e-Paper/tree/master/RaspberryPi_JetsonNano/c/lib/e-Paper
|
||||||
|
package epd2in9v2 // import "tinygo.org/x/drivers/waveshare-epd/epd2in9v2"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Width int16
|
||||||
|
Height int16
|
||||||
|
Rotation Rotation
|
||||||
|
Speed Speed
|
||||||
|
Blocking bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
bus drivers.SPI
|
||||||
|
cs machine.Pin
|
||||||
|
dc machine.Pin
|
||||||
|
rst machine.Pin
|
||||||
|
busy machine.Pin
|
||||||
|
width int16
|
||||||
|
height int16
|
||||||
|
buffer []uint8
|
||||||
|
bufferLength uint32
|
||||||
|
rotation Rotation
|
||||||
|
speed Speed
|
||||||
|
blocking bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rotation uint8
|
||||||
|
type Speed uint8
|
||||||
|
|
||||||
|
// LUT for normal full refresh (~2s)
|
||||||
|
var lutDefault = [159]uint8{
|
||||||
|
0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0,
|
||||||
|
0x10, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0,
|
||||||
|
0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0,
|
||||||
|
0x10, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x14, 0x8, 0x0, 0x0, 0x0, 0x0, 0x1,
|
||||||
|
0xA, 0xA, 0x0, 0xA, 0xA, 0x0, 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,
|
||||||
|
0x14, 0x8, 0x0, 0x1, 0x0, 0x0, 0x1,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x17, 0x41, 0x0, 0x32, 0x36,
|
||||||
|
}
|
||||||
|
|
||||||
|
// LUT for fast full refresh (~1s)
|
||||||
|
var lutFast = [159]uint8{
|
||||||
|
0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x19, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x24, 0x42, 0x22, 0x22, 0x23, 0x32, 0x00, 0x00, 0x00,
|
||||||
|
0x22, 0x17, 0x41, 0xAE, 0x32, 0x38,
|
||||||
|
}
|
||||||
|
|
||||||
|
// LUT for partial refresh
|
||||||
|
var lutPartial = [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,
|
||||||
|
0x0A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
|
||||||
|
0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||||
|
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,
|
||||||
|
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,
|
||||||
|
0x22, 0x17, 0x41, 0xB0, 0x32, 0x36,
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new epd2in9v2 driver. Pass in a fully configured SPI bus.
|
||||||
|
func New(bus drivers.SPI, csPin, dcPin, rstPin, busyPin machine.Pin) Device {
|
||||||
|
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||||
|
return Device{
|
||||||
|
bus: bus,
|
||||||
|
cs: csPin,
|
||||||
|
dc: dcPin,
|
||||||
|
rst: rstPin,
|
||||||
|
busy: busyPin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure sets up the device.
|
||||||
|
func (d *Device) Configure(cfg Config) {
|
||||||
|
if cfg.Width != 0 {
|
||||||
|
d.width = cfg.Width
|
||||||
|
} else {
|
||||||
|
d.width = EPD_WIDTH
|
||||||
|
}
|
||||||
|
if cfg.Height != 0 {
|
||||||
|
d.height = cfg.Height
|
||||||
|
} else {
|
||||||
|
d.height = EPD_HEIGHT
|
||||||
|
}
|
||||||
|
d.rotation = cfg.Rotation
|
||||||
|
d.speed = cfg.Speed
|
||||||
|
d.blocking = cfg.Blocking
|
||||||
|
d.bufferLength = (uint32(d.width) * uint32(d.height)) / 8
|
||||||
|
d.buffer = make([]uint8, d.bufferLength)
|
||||||
|
for i := uint32(0); i < d.bufferLength; i++ {
|
||||||
|
d.buffer[i] = 0xFF
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Reset()
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
d.SendCommand(SW_RESET)
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
|
||||||
|
d.SendCommand(DRIVER_OUTPUT_CONTROL)
|
||||||
|
d.SendData(uint8((d.height - 1) & 0xFF))
|
||||||
|
d.SendData(uint8((d.height - 1) >> 8))
|
||||||
|
d.SendData(0x00)
|
||||||
|
|
||||||
|
d.SendCommand(DATA_ENTRY_MODE)
|
||||||
|
d.SendData(0x03)
|
||||||
|
|
||||||
|
d.setWindow(0, 0, d.width-1, d.height-1)
|
||||||
|
|
||||||
|
if cfg.Speed == SPEED_FAST {
|
||||||
|
d.SendCommand(BORDER_WAVEFORM_CONTROL)
|
||||||
|
d.SendData(0x05)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SendCommand(DISPLAY_UPDATE_CONTROL_1)
|
||||||
|
d.SendData(0x00)
|
||||||
|
d.SendData(0x80)
|
||||||
|
|
||||||
|
d.setCursor(0, 0)
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
|
||||||
|
switch cfg.Speed {
|
||||||
|
case SPEED_FAST:
|
||||||
|
d.setLUTByHost(&lutFast)
|
||||||
|
default:
|
||||||
|
d.setLUTByHost(&lutDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HardwareReset resets the device via the RST pin.
|
||||||
|
func (d *Device) Reset() {
|
||||||
|
d.rst.High()
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
d.rst.Low()
|
||||||
|
time.Sleep(2 * time.Millisecond)
|
||||||
|
d.rst.High()
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendCommand sends a command byte to the display.
|
||||||
|
func (d *Device) SendCommand(command uint8) {
|
||||||
|
d.dc.Low()
|
||||||
|
d.cs.Low()
|
||||||
|
d.bus.Transfer(command)
|
||||||
|
d.cs.High()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendData sends a data byte to the display.
|
||||||
|
func (d *Device) SendData(data uint8) {
|
||||||
|
d.dc.High()
|
||||||
|
d.cs.Low()
|
||||||
|
d.bus.Transfer(data)
|
||||||
|
d.cs.High()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitUntilIdle waits until the display is ready.
|
||||||
|
// On SSD1680, BUSY pin is HIGH when busy, LOW when idle.
|
||||||
|
func (d *Device) WaitUntilIdle() {
|
||||||
|
for d.busy.Get() {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBusy returns the busy status of the display.
|
||||||
|
func (d *Device) IsBusy() bool {
|
||||||
|
return d.busy.Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPixel modifies the internal buffer in a single pixel.
|
||||||
|
// Uses color.RGBA where black (0,0,0) = black pixel, anything else = white pixel.
|
||||||
|
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||||
|
x, y = d.xy(x, y)
|
||||||
|
|
||||||
|
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
byteIndex := (y * (d.width / 8)) + (x / 8)
|
||||||
|
if c.R == 0 && c.G == 0 && c.B == 0 {
|
||||||
|
d.buffer[byteIndex] &^= 0x80 >> uint8(x%8)
|
||||||
|
} else {
|
||||||
|
d.buffer[byteIndex] |= 0x80 >> uint8(x%8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display sends the buffer to the screen.
|
||||||
|
func (d *Device) Display() error {
|
||||||
|
if d.blocking {
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
d.setCursor(0, 0)
|
||||||
|
d.SendCommand(WRITE_RAM_BW)
|
||||||
|
for i := uint32(0); i < d.bufferLength; i++ {
|
||||||
|
d.SendData(d.buffer[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
d.turnOnDisplay()
|
||||||
|
|
||||||
|
if d.blocking {
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisplayWithBase writes the buffer to both BW and RED RAM then refreshes.
|
||||||
|
// This is useful before partial updates to set the base image.
|
||||||
|
func (d *Device) DisplayWithBase() error {
|
||||||
|
if d.blocking {
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
d.setCursor(0, 0)
|
||||||
|
d.SendCommand(WRITE_RAM_BW)
|
||||||
|
for i := uint32(0); i < d.bufferLength; i++ {
|
||||||
|
d.SendData(d.buffer[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
d.setCursor(0, 0)
|
||||||
|
d.SendCommand(WRITE_RAM_RED)
|
||||||
|
for i := uint32(0); i < d.bufferLength; i++ {
|
||||||
|
d.SendData(d.buffer[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
d.turnOnDisplay()
|
||||||
|
|
||||||
|
if d.blocking {
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisplayPartial performs a partial refresh of the display.
|
||||||
|
// Call DisplayWithBase first to set the base image before using partial updates.
|
||||||
|
func (d *Device) DisplayPartial() error {
|
||||||
|
d.rst.Low()
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
d.rst.High()
|
||||||
|
time.Sleep(2 * time.Millisecond)
|
||||||
|
|
||||||
|
d.setLUT(&lutPartial)
|
||||||
|
|
||||||
|
d.SendCommand(OTP_SELECTION_CONTROL)
|
||||||
|
d.SendData(0x00)
|
||||||
|
d.SendData(0x00)
|
||||||
|
d.SendData(0x00)
|
||||||
|
d.SendData(0x00)
|
||||||
|
d.SendData(0x00)
|
||||||
|
d.SendData(0x40)
|
||||||
|
d.SendData(0x00)
|
||||||
|
d.SendData(0x00)
|
||||||
|
d.SendData(0x00)
|
||||||
|
d.SendData(0x00)
|
||||||
|
|
||||||
|
d.SendCommand(BORDER_WAVEFORM_CONTROL)
|
||||||
|
d.SendData(0x80)
|
||||||
|
|
||||||
|
d.SendCommand(DISPLAY_UPDATE_CONTROL_2)
|
||||||
|
d.SendData(0xC0)
|
||||||
|
d.SendCommand(MASTER_ACTIVATION)
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
|
||||||
|
d.setWindow(0, 0, d.width-1, d.height-1)
|
||||||
|
d.setCursor(0, 0)
|
||||||
|
|
||||||
|
d.SendCommand(WRITE_RAM_BW)
|
||||||
|
for i := uint32(0); i < d.bufferLength; i++ {
|
||||||
|
d.SendData(d.buffer[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
d.turnOnDisplayPartial()
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearDisplay erases the display.
|
||||||
|
func (d *Device) ClearDisplay() {
|
||||||
|
d.ClearBuffer()
|
||||||
|
d.Display()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBuffer sets the buffer to 0xFF (white).
|
||||||
|
func (d *Device) ClearBuffer() {
|
||||||
|
for i := uint32(0); i < d.bufferLength; 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 d.height, d.width
|
||||||
|
}
|
||||||
|
return d.width, d.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRotation changes the rotation (clock-wise) of the device.
|
||||||
|
func (d *Device) SetRotation(rotation Rotation) {
|
||||||
|
d.rotation = rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBlocking changes the blocking flag of the device.
|
||||||
|
func (d *Device) SetBlocking(blocking bool) {
|
||||||
|
d.blocking = blocking
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSpeed changes the refresh speed and reconfigures the device.
|
||||||
|
func (d *Device) SetSpeed(speed Speed) {
|
||||||
|
d.Configure(Config{
|
||||||
|
Width: d.width,
|
||||||
|
Height: d.height,
|
||||||
|
Rotation: d.rotation,
|
||||||
|
Speed: speed,
|
||||||
|
Blocking: d.blocking,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep puts the display into deep sleep mode. A hardware reset is needed to wake it.
|
||||||
|
func (d *Device) Sleep() {
|
||||||
|
d.SendCommand(DEEP_SLEEP_MODE)
|
||||||
|
d.SendData(0x01)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PowerOff disables the display analog/clock. Lighter than Sleep.
|
||||||
|
func (d *Device) PowerOff() {
|
||||||
|
d.SendCommand(DISPLAY_UPDATE_CONTROL_2)
|
||||||
|
d.SendData(0x03)
|
||||||
|
d.SendCommand(MASTER_ACTIVATION)
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) xy(x, y int16) (int16, int16) {
|
||||||
|
switch d.rotation {
|
||||||
|
case NO_ROTATION:
|
||||||
|
return x, y
|
||||||
|
case ROTATION_90:
|
||||||
|
return d.width - y - 1, x
|
||||||
|
case ROTATION_180:
|
||||||
|
return d.width - x - 1, d.height - y - 1
|
||||||
|
case ROTATION_270:
|
||||||
|
return y, d.height - x - 1
|
||||||
|
}
|
||||||
|
return x, y
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) setWindow(xStart, yStart, xEnd, yEnd int16) {
|
||||||
|
d.SendCommand(SET_RAM_X_ADDRESS)
|
||||||
|
d.SendData(uint8((xStart >> 3) & 0xFF))
|
||||||
|
d.SendData(uint8((xEnd >> 3) & 0xFF))
|
||||||
|
|
||||||
|
d.SendCommand(SET_RAM_Y_ADDRESS)
|
||||||
|
d.SendData(uint8(yStart & 0xFF))
|
||||||
|
d.SendData(uint8((yStart >> 8) & 0xFF))
|
||||||
|
d.SendData(uint8(yEnd & 0xFF))
|
||||||
|
d.SendData(uint8((yEnd >> 8) & 0xFF))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) setCursor(x, y int16) {
|
||||||
|
d.SendCommand(SET_RAM_X_COUNTER)
|
||||||
|
d.SendData(uint8(x & 0xFF))
|
||||||
|
|
||||||
|
d.SendCommand(SET_RAM_Y_COUNTER)
|
||||||
|
d.SendData(uint8(y & 0xFF))
|
||||||
|
d.SendData(uint8((y >> 8) & 0xFF))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) turnOnDisplay() {
|
||||||
|
d.SendCommand(DISPLAY_UPDATE_CONTROL_2)
|
||||||
|
d.SendData(0xC7)
|
||||||
|
d.SendCommand(MASTER_ACTIVATION)
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) turnOnDisplayPartial() {
|
||||||
|
d.SendCommand(DISPLAY_UPDATE_CONTROL_2)
|
||||||
|
d.SendData(0x0F)
|
||||||
|
d.SendCommand(MASTER_ACTIVATION)
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) setLUT(lut *[159]uint8) {
|
||||||
|
d.SendCommand(WRITE_LUT_REGISTER)
|
||||||
|
for i := 0; i < 153; i++ {
|
||||||
|
d.SendData(lut[i])
|
||||||
|
}
|
||||||
|
d.WaitUntilIdle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) setLUTByHost(lut *[159]uint8) {
|
||||||
|
d.setLUT(lut)
|
||||||
|
|
||||||
|
d.SendCommand(END_OPTION)
|
||||||
|
d.SendData(lut[153])
|
||||||
|
|
||||||
|
d.SendCommand(GATE_DRIVING_VOLTAGE)
|
||||||
|
d.SendData(lut[154])
|
||||||
|
|
||||||
|
d.SendCommand(SOURCE_DRIVING_VOLTAGE)
|
||||||
|
d.SendData(lut[155])
|
||||||
|
d.SendData(lut[156])
|
||||||
|
d.SendData(lut[157])
|
||||||
|
|
||||||
|
d.SendCommand(WRITE_VCOM_REGISTER)
|
||||||
|
d.SendData(lut[158])
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package epd2in9v2
|
||||||
|
|
||||||
|
// Commands from SSD1680 datasheet
|
||||||
|
const (
|
||||||
|
EPD_WIDTH = 128
|
||||||
|
EPD_HEIGHT = 296
|
||||||
|
|
||||||
|
DRIVER_OUTPUT_CONTROL = 0x01
|
||||||
|
GATE_DRIVING_VOLTAGE = 0x03
|
||||||
|
SOURCE_DRIVING_VOLTAGE = 0x04
|
||||||
|
DEEP_SLEEP_MODE = 0x10
|
||||||
|
DATA_ENTRY_MODE = 0x11
|
||||||
|
SW_RESET = 0x12
|
||||||
|
MASTER_ACTIVATION = 0x20
|
||||||
|
DISPLAY_UPDATE_CONTROL_1 = 0x21
|
||||||
|
DISPLAY_UPDATE_CONTROL_2 = 0x22
|
||||||
|
WRITE_RAM_BW = 0x24
|
||||||
|
WRITE_RAM_RED = 0x26
|
||||||
|
VCOM_SENSE = 0x28
|
||||||
|
VCOM_SENSE_DURATION = 0x29
|
||||||
|
PROGRAM_VCOM_OTP = 0x2A
|
||||||
|
WRITE_VCOM_CONTROL = 0x2B
|
||||||
|
WRITE_VCOM_REGISTER = 0x2C
|
||||||
|
OTP_READ_DISPLAY_OPTION = 0x2D
|
||||||
|
USER_ID_READ = 0x2E
|
||||||
|
PROGRAM_WS_OTP = 0x30
|
||||||
|
LOAD_WS_OTP = 0x31
|
||||||
|
WRITE_LUT_REGISTER = 0x32
|
||||||
|
PROGRAM_OTP_SELECTION = 0x36
|
||||||
|
OTP_SELECTION_CONTROL = 0x37
|
||||||
|
WRITE_USER_ID = 0x38
|
||||||
|
OTP_PROGRAM_MODE = 0x39
|
||||||
|
BORDER_WAVEFORM_CONTROL = 0x3C
|
||||||
|
END_OPTION = 0x3F
|
||||||
|
SET_RAM_X_ADDRESS = 0x44
|
||||||
|
SET_RAM_Y_ADDRESS = 0x45
|
||||||
|
SET_RAM_X_COUNTER = 0x4E
|
||||||
|
SET_RAM_Y_COUNTER = 0x4F
|
||||||
|
SET_ANALOG_BLOCK_CONTROL = 0x74
|
||||||
|
SET_DIGITAL_BLOCK_CONTROL = 0x7E
|
||||||
|
|
||||||
|
NO_ROTATION Rotation = 0
|
||||||
|
ROTATION_90 Rotation = 1
|
||||||
|
ROTATION_180 Rotation = 2
|
||||||
|
ROTATION_270 Rotation = 3
|
||||||
|
|
||||||
|
SPEED_DEFAULT Speed = 0
|
||||||
|
SPEED_FAST Speed = 1
|
||||||
|
SPEED_PARTIAL Speed = 2
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user