2018-05-23 02:18:06 +00:00
|
|
|
"""
|
|
|
|
MicroPython Waveshare 2.9" Black/White GDEH029A1 e-paper display driver
|
|
|
|
https://github.com/mcauser/micropython-waveshare-epaper
|
|
|
|
|
|
|
|
MIT License
|
|
|
|
Copyright (c) 2017 Waveshare
|
|
|
|
Copyright (c) 2018 Mike Causer
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
|
|
copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
SOFTWARE.
|
|
|
|
"""
|
2018-02-23 15:38:02 +00:00
|
|
|
|
|
|
|
from micropython import const
|
|
|
|
from time import sleep_ms
|
|
|
|
import ustruct
|
|
|
|
|
|
|
|
# Display resolution
|
|
|
|
EPD_WIDTH = const(128)
|
|
|
|
EPD_HEIGHT = const(296)
|
|
|
|
|
|
|
|
# Display commands
|
|
|
|
DRIVER_OUTPUT_CONTROL = const(0x01)
|
|
|
|
BOOSTER_SOFT_START_CONTROL = const(0x0C)
|
|
|
|
#GATE_SCAN_START_POSITION = const(0x0F)
|
|
|
|
DEEP_SLEEP_MODE = const(0x10)
|
|
|
|
DATA_ENTRY_MODE_SETTING = const(0x11)
|
|
|
|
#SW_RESET = const(0x12)
|
|
|
|
#TEMPERATURE_SENSOR_CONTROL = const(0x1A)
|
|
|
|
MASTER_ACTIVATION = const(0x20)
|
|
|
|
#DISPLAY_UPDATE_CONTROL_1 = const(0x21)
|
|
|
|
DISPLAY_UPDATE_CONTROL_2 = const(0x22)
|
|
|
|
WRITE_RAM = const(0x24)
|
|
|
|
WRITE_VCOM_REGISTER = const(0x2C)
|
|
|
|
WRITE_LUT_REGISTER = const(0x32)
|
|
|
|
SET_DUMMY_LINE_PERIOD = const(0x3A)
|
|
|
|
SET_GATE_TIME = const(0x3B)
|
|
|
|
#BORDER_WAVEFORM_CONTROL = const(0x3C)
|
|
|
|
SET_RAM_X_ADDRESS_START_END_POSITION = const(0x44)
|
|
|
|
SET_RAM_Y_ADDRESS_START_END_POSITION = const(0x45)
|
|
|
|
SET_RAM_X_ADDRESS_COUNTER = const(0x4E)
|
|
|
|
SET_RAM_Y_ADDRESS_COUNTER = const(0x4F)
|
|
|
|
TERMINATE_FRAME_READ_WRITE = const(0xFF)
|
|
|
|
|
2018-06-07 09:45:06 +00:00
|
|
|
BUSY = const(1) # 1=busy, 0=idle
|
|
|
|
|
2018-02-23 15:38:02 +00:00
|
|
|
class EPD:
|
|
|
|
def __init__(self, spi, cs, dc, rst, busy):
|
|
|
|
self.spi = spi
|
|
|
|
self.cs = cs
|
|
|
|
self.dc = dc
|
|
|
|
self.rst = rst
|
|
|
|
self.busy = busy
|
|
|
|
self.cs.init(self.cs.OUT, value=1)
|
|
|
|
self.dc.init(self.dc.OUT, value=0)
|
|
|
|
self.rst.init(self.rst.OUT, value=0)
|
|
|
|
self.busy.init(self.busy.IN)
|
|
|
|
self.width = EPD_WIDTH
|
|
|
|
self.height = EPD_HEIGHT
|
|
|
|
|
|
|
|
# 30 bytes (look up tables)
|
|
|
|
# original waveshare example
|
|
|
|
LUT_FULL_UPDATE = bytearray(b'\x02\x02\x01\x11\x12\x12\x22\x22\x66\x69\x69\x59\x58\x99\x99\x88\x00\x00\x00\x00\xF8\xB4\x13\x51\x35\x51\x51\x19\x01\x00')
|
|
|
|
LUT_PARTIAL_UPDATE = bytearray(b'\x10\x18\x18\x08\x18\x18\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x14\x44\x12\x00\x00\x00\x00\x00\x00')
|
|
|
|
|
|
|
|
# https://github.com/ZinggJM/GxEPD/blob/master/GxGDEH029A1/GxGDEH029A1.cpp
|
|
|
|
#LUT_FULL_UPDATE = bytearray(b'\x50\xAA\x55\xAA\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\x1F\x00\x00\x00\x00\x00\x00\x00')
|
|
|
|
#LUT_PARTIAL_UPDATE = bytearray(b'\x10\x18\x18\x08\x18\x18\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x14\x44\x12\x00\x00\x00\x00\x00\x00')
|
|
|
|
|
|
|
|
def _command(self, command, data=None):
|
2018-06-07 09:46:00 +00:00
|
|
|
self.dc(0)
|
|
|
|
self.cs(0)
|
2018-02-23 15:38:02 +00:00
|
|
|
self.spi.write(bytearray([command]))
|
2018-06-07 09:46:00 +00:00
|
|
|
self.cs(1)
|
2018-02-23 15:38:02 +00:00
|
|
|
if data is not None:
|
|
|
|
self._data(data)
|
|
|
|
|
|
|
|
def _data(self, data):
|
2018-06-07 09:46:00 +00:00
|
|
|
self.dc(1)
|
|
|
|
self.cs(0)
|
2018-02-23 15:38:02 +00:00
|
|
|
self.spi.write(data)
|
2018-06-07 09:46:00 +00:00
|
|
|
self.cs(1)
|
2018-02-23 15:38:02 +00:00
|
|
|
|
|
|
|
def init(self):
|
|
|
|
self.reset()
|
|
|
|
self._command(DRIVER_OUTPUT_CONTROL, ustruct.pack("<HB", EPD_HEIGHT-1, 0x00))
|
|
|
|
self._command(BOOSTER_SOFT_START_CONTROL, b'\xD7\xD6\x9D')
|
|
|
|
self._command(WRITE_VCOM_REGISTER, b'\xA8') # VCOM 7C
|
|
|
|
self._command(SET_DUMMY_LINE_PERIOD, b'\x1A') # 4 dummy lines per gate
|
|
|
|
self._command(SET_GATE_TIME, b'\x08') # 2us per line
|
|
|
|
self._command(DATA_ENTRY_MODE_SETTING, b'\x03') # X increment Y increment
|
|
|
|
self.set_lut(self.LUT_FULL_UPDATE)
|
|
|
|
|
|
|
|
def wait_until_idle(self):
|
2018-06-07 09:45:06 +00:00
|
|
|
while self.busy.value() == BUSY:
|
2018-02-23 15:38:02 +00:00
|
|
|
sleep_ms(100)
|
|
|
|
|
|
|
|
def reset(self):
|
2018-06-07 09:46:00 +00:00
|
|
|
self.rst(0)
|
2018-02-23 15:38:02 +00:00
|
|
|
sleep_ms(200)
|
2018-06-07 09:46:00 +00:00
|
|
|
self.rst(1)
|
2018-02-23 15:38:02 +00:00
|
|
|
sleep_ms(200)
|
|
|
|
|
|
|
|
def set_lut(self, lut):
|
|
|
|
self._command(WRITE_LUT_REGISTER, lut)
|
|
|
|
|
|
|
|
# put an image in the frame memory
|
|
|
|
def set_frame_memory(self, image, x, y, w, h):
|
|
|
|
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
|
|
|
x = x & 0xF8
|
|
|
|
w = w & 0xF8
|
|
|
|
|
|
|
|
if (x + w >= self.width):
|
|
|
|
x_end = self.width - 1
|
|
|
|
else:
|
|
|
|
x_end = x + w - 1
|
|
|
|
|
|
|
|
if (y + h >= self.height):
|
|
|
|
y_end = self.height - 1
|
|
|
|
else:
|
|
|
|
y_end = y + h - 1
|
|
|
|
|
|
|
|
self.set_memory_area(x, y, x_end, y_end)
|
|
|
|
self.set_memory_pointer(x, y)
|
|
|
|
self._command(WRITE_RAM, image)
|
|
|
|
|
|
|
|
# replace the frame memory with the specified color
|
|
|
|
def clear_frame_memory(self, color):
|
|
|
|
self.set_memory_area(0, 0, self.width - 1, self.height - 1)
|
|
|
|
self.set_memory_pointer(0, 0)
|
|
|
|
self._command(WRITE_RAM)
|
|
|
|
# send the color data
|
|
|
|
for i in range(0, self.width // 8 * self.height):
|
|
|
|
self._data(bytearray([color]))
|
|
|
|
|
|
|
|
# draw the current frame memory and switch to the next memory area
|
|
|
|
def display_frame(self):
|
|
|
|
self._command(DISPLAY_UPDATE_CONTROL_2, b'\xC4')
|
|
|
|
self._command(MASTER_ACTIVATION)
|
|
|
|
self._command(TERMINATE_FRAME_READ_WRITE)
|
|
|
|
self.wait_until_idle()
|
|
|
|
|
|
|
|
# specify the memory area for data R/W
|
|
|
|
def set_memory_area(self, x_start, y_start, x_end, y_end):
|
|
|
|
self._command(SET_RAM_X_ADDRESS_START_END_POSITION)
|
|
|
|
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
|
|
|
self._data(bytearray([(x_start >> 3) & 0xFF]))
|
|
|
|
self._data(bytearray([(x_end >> 3) & 0xFF]))
|
|
|
|
self._command(SET_RAM_Y_ADDRESS_START_END_POSITION, ustruct.pack("<HH", y_start, y_end))
|
|
|
|
|
|
|
|
# specify the start point for data R/W
|
|
|
|
def set_memory_pointer(self, x, y):
|
|
|
|
self._command(SET_RAM_X_ADDRESS_COUNTER)
|
|
|
|
# x point must be the multiple of 8 or the last 3 bits will be ignored
|
|
|
|
self._data(bytearray([(x >> 3) & 0xFF]))
|
|
|
|
self._command(SET_RAM_Y_ADDRESS_COUNTER, ustruct.pack("<H", y))
|
|
|
|
self.wait_until_idle()
|
|
|
|
|
|
|
|
# to wake call reset() or init()
|
|
|
|
def sleep(self):
|
|
|
|
self._command(DEEP_SLEEP_MODE)
|
|
|
|
self.wait_until_idle()
|