diff --git a/nrf5/modules/epaper.py b/nrf5/modules/epaper.py new file mode 100644 index 0000000000..13c0d754a9 --- /dev/null +++ b/nrf5/modules/epaper.py @@ -0,0 +1,451 @@ +# This file is part of the Micro Python project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2017 Glenn Ruben Bakke +# +# 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. + +""" +E-paper SLD00200. + +Pin layout on pca10040 (nrf52): + +EPAPER_PANEL_ON 13 (Arduino D2) +EPAPER_BORDER 14 (Arduino D3) +EPAPER_PWM 16 (Arduino D5) +EPAPER_RESET 17 (Arduino D6) +EPAPER_BUSY 18 (Arduino D7) +EPAPER_DISCHARGE 19 (Arduino D8) +EPAPER_TEMP_SENSOR 03 (Arduino A0) +EPAPER_CS 22 (Arduino D10) +EPAPER_DIN 23 (Arduino D11) +EPAPER_DOUT 24 (Arduino D12) +EPAPER_CLK 25 (Arduino D13) + +Example usage on pca10040: + + from epaper import Epaper + + epd = Epaper() + epd.fill(0) + epd.text("Hello World!", 50, 50) + epd.show() + + epd.refresh() + epd.refresh() +""" + +import os +import time +import lcd_mono_fb + +from machine import SPI, Pin, PWM + +EPD_STATE_COMP = const(0x1) +EPD_STATE_WHITE = const(0x2) +EPD_STATE_INV = const(0x3) +EPD_STATE_NORM = const(0x4) + +class Epaper: + def __init__(self, width=264, height=176, vertical=False): + + self.width = width + self.height = height + self.vertical = vertical + self.framebuf = lcd_mono_fb.MonoFB(self.line_update, self.width, self.height, True) + + self.reset = Pin("A17", mode=Pin.OUT, pull=Pin.PULL_UP) + self.panel_on = Pin("A13", mode=Pin.OUT, pull=Pin.PULL_UP) + self.discharge = Pin("A19", mode=Pin.OUT, pull=Pin.PULL_UP) + self.border = Pin("A14", mode=Pin.OUT, pull=Pin.PULL_UP) + self.busy = Pin("A18", mode=Pin.IN, pull=Pin.PULL_DISABLED) + self.cs = Pin("A22", mode=Pin.OUT, pull=Pin.PULL_UP) + + self.reset.low() + self.panel_on.low() + self.discharge.low() + self.border.low() + + self.pwm = PWM(0, Pin("A16", mode=Pin.OUT), freq=PWM.FREQ_250KHZ, duty=50, period=2) + + # Min baudrate 4M, max 12M + self.spi = SPI(0, baudrate=80000000) + # self.spi.init(baudrate=8000000, phase=0, polarity=0) + + def line_update(self, o, line, new_bytes, old_bytes): + if new_bytes: + self._update_line(line, old_bytes, EPD_STATE_COMP) + self._update_line(line, old_bytes, EPD_STATE_WHITE) + self._update_line(line, new_bytes, EPD_STATE_INV) + self._update_line(line, new_bytes, EPD_STATE_NORM) + else: + self._update_line(line, old_bytes, EPD_STATE_NORM) + + def clear(self): + line_count = self.height; + for i in range(0, line_count): + self._update_line(i, None, EPD_STATE_COMP, 0xFF) + + time.sleep_ms(500) + + for i in range(0, line_count): + self._update_line(i, None, EPD_STATE_WHITE, 0xAA) + + time.sleep_ms(500) + + for i in range(0, line_count): + self._update_line(i, None, EPD_STATE_INV, 0xFF) + + time.sleep_ms(500) + + for i in range(0, line_count): + self._update_line(i, None, EPD_STATE_NORM, 0xAA) + + time.sleep_ms(500) + + def init_display(self): + + self.pwm.init() # start the pwm + + print("sleep") + time.sleep_ms(5) + print("wakeup") + self.panel_on.high() + time.sleep_ms(10) + + self.reset.high() + self.border.high() + self.cs.high() + + time.sleep_ms(5) + + self.reset.low() + + time.sleep_ms(5) + + self.reset.high() + + time.sleep_ms(5) + print("Wait for busy") + self.wait_for_busy_release() + + time.sleep_us(10) + # channel select + self.write_data(bytearray([0x70, 0x01])) + time.sleep_us(10) + + # CS + self.write_data(bytearray([0x72, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x00])) + + # DC/DC frequency + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x06])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0xff])) + + # high power mode osc + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x07])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x9d])) + + # disable ADC + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x08])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x00])) + + # Vcom level + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x09])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0xd0, 0x00])) + + # gate and source voltage levels + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x04])) + + # GS + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x00])) + + time.sleep_ms(5) + + # driver latch on + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x03])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x01])) + + # driver latch off + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x03])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x00])) + + time.sleep_ms(5) + + # charge pump positive voltage on + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x05])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x01])) + + # final delay before PWM off + time.sleep_us(30) + + # stop PWM + self.pwm.deinit() + + # charge pump negative voltage on + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x05])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x03])) + + time.sleep_us(30) + + # Vcom driver on + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x05])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x0f])) + + time.sleep_ms(30); + + # output enable to disable + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x02])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x24])) + + def wait_for_busy_release(self): + # wait for COG to become ready + while (self.busy.value() == 1): + pass + + def _update_line(self, line, data, state, fixed=None): + time.sleep_us(10) + + self.write_data(bytearray([0x70, 0x04])) + time.sleep_us(10) + + # gate source + self.write_data(bytearray([0x72, 0x00])) + time.sleep_us(10) + + self.write_data(bytearray([0x70, 0x0a])) + time.sleep_us(10) + + self.cs.low() + + self.write_data_wait(bytearray([0x72])) + + bytes_per_line = self.width // 8; + + # even pixels + if data: + for i in range(bytes_per_line - 1, -1, -1): + + pixels = data[i] & 0xaa; + + if state == EPD_STATE_COMP: + # B -> W, W -> B (current image) + pixels = 0xaa | ((pixels ^ 0xaa) >> 1) + + elif state == EPD_STATE_WHITE: + # B -> N, W -> W (current image) + pixels = 0x55 + ((pixels ^ 0xaa) >> 1) + + elif state == EPD_STATE_INV: + # B -> N, W -> B (new image) + pixels = 0x55 | (pixels ^ 0xaa) + + elif state == EPD_STATE_NORM: + # B -> B, W -> W (new image) + pixels = 0xaa | (pixels >> 1) + + self.write_data_wait(bytearray([pixels])); + else: + self.write_data_wait(bytearray([fixed] * bytes_per_line)); + + bytes_per_scan = 176 // 4; + # scan line + for i in range(0, bytes_per_scan): + if (line // 4 == i): + self.write_data_wait(bytearray([0xc0 >> (2 * (line & 0x03))])) + else: + self.write_data_wait(bytearray([0x00])) + + # odd pixels + if data: + for i in range (0, bytes_per_line): + pixels = data[i] & 0x55 + + if state == EPD_STATE_COMP: + pixels = 0xaa | (pixels ^ 0x55) + + elif state == EPD_STATE_WHITE: + pixels = 0x55 + (pixels ^ 0x55) + + elif state == EPD_STATE_INV: + pixels = 0x55 | ((pixels ^ 0x55) << 1) + + elif state == EPD_STATE_NORM: + pixels = 0xaa | pixels + + p1 = (pixels >> 6) & 0x03; + p2 = (pixels >> 4) & 0x03; + p3 = (pixels >> 2) & 0x03; + p4 = (pixels >> 0) & 0x03; + + pixels = (p1 << 0) | (p2 << 2) | (p3 << 4) | (p4 << 6); + + self.write_data_wait(bytearray([pixels])) + else: + self.write_data_wait(bytearray([fixed] * bytes_per_line)) + + # Complete line + self.write_data_wait(bytearray([0x00])) + + self.cs.high() + + time.sleep_us(10) + + + self.write_data(bytearray([0x70, 0x02])) + time.sleep_us(10) + + self.write_data(bytearray([0x72, 0x2f])) + + def deinit_display(self): + # all display sizes + self._update_line(0x7fff, None, EPD_STATE_NORM, 0x55) + time.sleep_ms(25) + self.border.low() + time.sleep_ms(250) + self.border.high() + + # latch reset turn on + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x03])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x01])) + + # output enable off + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x02])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x05])) + + # Vcom power off + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x05])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x0e])) + + # power off negative charge pump + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x05])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x02])) + + # discharge + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x04])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x0c])) + time.sleep_us(120) + + # all charge pumps off + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x05])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x00])) + + # turn of osc + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x07])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x0d])) + + # discharge internal - 1 + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x04])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x50])) + time.sleep_us(40) + + # discharge internal - 2 + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x04])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0xA0])) + time.sleep_us(40) + + # discharge internal - 3 + time.sleep_us(10) + self.write_data(bytearray([0x70, 0x04])) + time.sleep_us(10) + self.write_data(bytearray([0x72, 0x00])) + + # turn of power and all signals + time.sleep_ms(10) + self.reset.low() + self.panel_on.low() + self.border.low() + + # discharge pulse + self.discharge.high() + time.sleep_us(250) + self.discharge.low() + + self.cs.high() + + def show(self): + self.init_display() + self.framebuf.show() + self.deinit_display() + + def refresh(self): + self.init_display() + self.framebuf.refresh() + self.deinit_display() + + def fill(self, col): + self.framebuf.fill(col) + + def pixel(self, x, y, col): + self.framebuf.pixel(x, y, col) + + def scroll(self, dx, dy): + self.framebuf.scroll(dx, dy) + + def text(self, string, x, y, col=1): + self.framebuf.text(string, x, y, col) + + def write_data_wait(self, buf): + self.spi.write(buf) + self.wait_for_busy_release() + + def write_data(self, buf): + self.cs.low() + self.spi.write(buf) + self.cs.high()