# 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 EM027AS011. 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()