diff --git a/ports/esp32/boards/INKPLATE6/README.md b/ports/esp32/boards/INKPLATE6/README.md new file mode 100644 index 0000000000..fb098bc8da --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/README.md @@ -0,0 +1,20 @@ +# MicroPython for the Inkplate6 + +![](https://www.crowdsupply.com/img/040a/inkplate-6-angle-01_png_project-main.jpg) + +Inkplate is a powerful, Wi-Fi enabled ESP32 based e-paper display – recycled from a Kindle e-reader. Learn more about Inkplate on [official website](https://inkplate.io/). Inkplate6 was crowdfunded on [Crowd Supply Inkplate 6](https://www.crowdsupply.com/e-radionica/inkplate-6). + +This board definition was based on [e-radionica/Inkplate-micropython](https://github.com/e-radionicacom/Inkplate-micropython) though the original software effort was by [tve](https://github.com/tve/micropython-inkplate6). + +### Features + +- Simple graphics class for monochrome use of the e-paper display +- Simple graphics class for 2 bits per pixel greyscale use of the e-paper display +- Support for partial updates +- Access to touch sensors +- Everything in pure python with screen updates virtually as fast as the Arduino C driver +- Bitmap drawing, although really slow + +### Getting started with MicroPython on Inkplate + +Download and flash the MicroPython firmware for the [INKPLATE6](https://micropython.org/download/inkplate6/) board. diff --git a/ports/esp32/boards/INKPLATE6/board.json b/ports/esp32/boards/INKPLATE6/board.json new file mode 100644 index 0000000000..0469bb95e6 --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/board.json @@ -0,0 +1,27 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi", + "Display", + "e-ink", + "Micro USB", + "MicroSD", + "SPIRAM" + ], + "id": "inkplate6", + "images": [ + "inkplate6.jpg" + ], + "mcu": "esp32", + "product": "ESP32", + "thumbnail": "", + "url": "https://inkplate.io/", + "variants": { + "idf3": "Compiled with IDF 3.x" + }, + "vendor": "e-radonica" +} diff --git a/ports/esp32/boards/INKPLATE6/board.md b/ports/esp32/boards/INKPLATE6/board.md new file mode 100644 index 0000000000..863ad65d6d --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/board.md @@ -0,0 +1 @@ +The following files are daily firmware builds for the Inkplate6. diff --git a/ports/esp32/boards/INKPLATE6/examples/README.md b/ports/esp32/boards/INKPLATE6/examples/README.md new file mode 100644 index 0000000000..9f8a7f94de --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/examples/README.md @@ -0,0 +1,17 @@ +# Examples + +These examples, originally from [e-radionica/Inkplate/Examples/Inkplate6](https://github.com/e-radionicacom/Inkplate-micropython/tree/master/Examples/Inkplate6) are _not_ frozen into the board firmware; they're here for reference. Execute them on your Inkplate to try them out. + +- basicBW.py -> demonstrates basic drawing capabilities, as well as drawing some images. +- basicGrayscale.py -> demonstrates basic drawing capabilities, as well as drawing some images. +- exampleNetwork.py -> demonstrates connection to WiFi network while drawing the HTTP request response on the screen. +- exampleSd.py -> demonstrates reading files and images from SD card. +- batteryAndTemperatureRead.py -> demonstrates how to read temperature and voltage from internal sensors. +- touchpads.py -> demonstrates how to use built in touchpads. + +## To execute the examples +The recommended way to execute the examples is with `mpremote`. Install `mpremote` with `pipx` (`pipx install mpremote`) then run a script on the Inkplate, eg: + +```bash +> mpremote connect [SERIALPORT] run basicBW.py +``` \ No newline at end of file diff --git a/ports/esp32/boards/INKPLATE6/examples/basicBW.py b/ports/esp32/boards/INKPLATE6/examples/basicBW.py new file mode 100644 index 0000000000..1bc50c822b --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/examples/basicBW.py @@ -0,0 +1,39 @@ +from inkplate6 import Inkplate +from image import * +import time + +display = Inkplate(Inkplate.INKPLATE_1BIT) + +if __name__ == "__main__": + # Must be called before using, line in Arduino + display.begin() + display.clearDisplay() + display.display() + + for r in range(4): + # Sets the screen rotation + display.setRotation(r) + + # All drawing functions + display.drawPixel(100, 100, display.BLACK) + display.drawRect(50, 50, 75, 75, display.BLACK) + display.drawCircle(200, 200, 30, display.BLACK) + display.fillCircle(300, 300, 30, display.BLACK) + display.drawFastHLine(20, 100, 50, display.BLACK) + display.drawFastVLine(100, 20, 50, display.BLACK) + display.drawLine(100, 100, 400, 400, display.BLACK) + display.drawRoundRect(100, 10, 100, 100, 10, display.BLACK) + display.fillRoundRect(10, 100, 100, 100, 10, display.BLACK) + display.drawTriangle(300, 100, 400, 150, 400, 100, display.BLACK) + + if display.rotation % 2 == 0: + display.fillTriangle(500, 101, 400, 150, 400, 100, display.BLACK) + display.display() + time.sleep(5) + + # Draws image from bytearray + display.setRotation(0) + display.drawBitmap(120, 200, image, 576, 100) + + #Use display.partialUpdate instead of display.display() to draw only updated pixels + display.partialUpdate() \ No newline at end of file diff --git a/ports/esp32/boards/INKPLATE6/examples/basicGrayscale.py b/ports/esp32/boards/INKPLATE6/examples/basicGrayscale.py new file mode 100644 index 0000000000..b090bd359a --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/examples/basicGrayscale.py @@ -0,0 +1,33 @@ +# Include needed libraries + +from inkplate6 import Inkplate +from image import * +import time + + +# Initialize inkplate display +display = Inkplate(Inkplate.INKPLATE_2BIT) + + +# Main function, you can make infinite while loop inside this to run code indefinitely +if __name__ == "__main__": + # Must be called before using, line in Arduino + display.begin() + display.clearDisplay() + display.display() + + # Draw palet of posible colors + #use color values 0, 1, 2, 3 + display.writeFillRect(0, 0, 25, 600, 3) + display.writeFillRect(25, 0, 25, 600, 2) + display.writeFillRect(50, 0, 25, 600, 1) + display.writeFillRect(75, 0, 25, 600, 0) + + display.display() + time.sleep(3) + + # Draws image from bytearray + display.setRotation(0) + display.drawBitmap(120, 200, image, 576, 100) + display.display() + time.sleep(10) diff --git a/ports/esp32/boards/INKPLATE6/examples/batteryAndTemperatureRead.py b/ports/esp32/boards/INKPLATE6/examples/batteryAndTemperatureRead.py new file mode 100644 index 0000000000..213cddf660 --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/examples/batteryAndTemperatureRead.py @@ -0,0 +1,22 @@ +from inkplate6 import Inkplate +from image import * + +display = Inkplate(Inkplate.INKPLATE_1BIT) + +if __name__ == "__main__": + display.begin() + display.clearDisplay() + display.display() + + battery = str(display.readBattery()) + + display.setTextSize(2) + display.printText(100, 100, "batt: " + battery + "V") + display.display() + + + temperature = str(display.readTemperature()) + + display.setTextSize(2) + display.printText(100, 150, "TEMP: " + temperature + "C") + display.display() \ No newline at end of file diff --git a/ports/esp32/boards/INKPLATE6/examples/exampleNetwork.py b/ports/esp32/boards/INKPLATE6/examples/exampleNetwork.py new file mode 100644 index 0000000000..2d4705513b --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/examples/exampleNetwork.py @@ -0,0 +1,60 @@ +import network +import time +from inkplate6 import Inkplate + +ssid = "e-radionica.com" +password = "croduino" + +# More info here: https://docs.micropython.org/en/latest/esp8266/tutorial/network_basics.html +def do_connect(): + import network + + sta_if = network.WLAN(network.STA_IF) + if not sta_if.isconnected(): + print("connecting to network...") + sta_if.active(True) + sta_if.connect(ssid, password) + while not sta_if.isconnected(): + pass + print("network config:", sta_if.ifconfig()) + + +# More info here: https://docs.micropython.org/en/latest/esp8266/tutorial/network_tcp.html +def http_get(url): + import socket + + res = "" + _, _, host, path = url.split("/", 3) + addr = socket.getaddrinfo(host, 80)[0][-1] + s = socket.socket() + s.connect(addr) + s.send(bytes("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n" % (path, host), "utf8")) + while True: + data = s.recv(100) + if data: + res += str(data, "utf8") + else: + break + s.close() + + return res + + +# Calling functions defined above +do_connect() +response = http_get("http://micropython.org/ks/test.html") + +# Initialise our Inkplate object +display = Inkplate(Inkplate.INKPLATE_1BIT) +display.begin() + +# Print response in lines +cnt = 0 +for x in response.split("\n"): + display.printText( + 10, 10 + cnt, x.upper() + ) # Default font has only upper case letters + cnt += 10 + +# Display image from buffer +display.display() diff --git a/ports/esp32/boards/INKPLATE6/examples/exampleSd.py b/ports/esp32/boards/INKPLATE6/examples/exampleSd.py new file mode 100644 index 0000000000..ec895abe10 --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/examples/exampleSd.py @@ -0,0 +1,21 @@ +import os, time +from inkplate6 import Inkplate + +display = Inkplate(Inkplate.INKPLATE_2BIT) +display.begin() + +# This prints all the files on card +print(os.listdir("/sd")) + +f = open("sd/text.txt", "r") + +# Print file contents +print(f.read()) +f.close() + +time.sleep(5) + +# Utterly slow, can take minutes :( +display.drawImageFile(0, 0, "sd/1.bmp") + +display.display() diff --git a/ports/esp32/boards/INKPLATE6/examples/touchpads.py b/ports/esp32/boards/INKPLATE6/examples/touchpads.py new file mode 100644 index 0000000000..bf9e5f47aa --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/examples/touchpads.py @@ -0,0 +1,57 @@ +from inkplate6 import Inkplate +from image import * + +display = Inkplate(Inkplate.INKPLATE_1BIT) + +circle_x = 400 +circle_y = 300 +circle_r = 40 + +#main function used by micropython +if __name__ == "__main__": + display.begin() + display.clearDisplay() + display.display() + + # function to show text at the top of the screen + # need to be called every time we clear display + def topText(): + display.setTextSize(2) + display.printText(100, 10, "TOUCHPADS EXAMPLE! 1, 3 TO MOVE CIRCLE, 2 TO RESET") + + topText() + + # Touchpads definitions + touch1, touch2, touch3 = display.TOUCH1, display.TOUCH2, display.TOUCH3 + + #draw initial circle for touchpad demonstration + display.drawCircle(circle_x, circle_y, circle_r, display.BLACK) + display.display() + + #Main loop that will run forever or until battery is dead + while True: + #check if touchpad is pressed + if touch1(): + circle_x -= 40 + display.clearDisplay() + topText() + display.drawCircle(circle_x, circle_y, circle_r, display.BLACK) + display.partialUpdate() + + if touch3(): + circle_x += 40 + display.clearDisplay() + topText() + display.drawCircle(circle_x, circle_y, circle_r, display.BLACK) + display.partialUpdate() + + if touch2(): + circle_x = 400 + circle_y = 300 + circle_r = 40 + + display.clearDisplay() + topText() + display.drawCircle(circle_x, circle_y, circle_r, display.BLACK) + display.partialUpdate() + diff --git a/ports/esp32/boards/INKPLATE6/manifest.py b/ports/esp32/boards/INKPLATE6/manifest.py new file mode 100644 index 0000000000..7ae2ed15d9 --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/manifest.py @@ -0,0 +1,2 @@ +include("$(PORT_DIR)/boards/manifest.py") +freeze("modules") diff --git a/ports/esp32/boards/INKPLATE6/modules/gfx.py b/ports/esp32/boards/INKPLATE6/modules/gfx.py new file mode 100644 index 0000000000..9b86ab7fab --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/modules/gfx.py @@ -0,0 +1,545 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 Kattni Rembor for Adafruit Industries +# +# 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. +""" +`gfx` +==================================================== +CircuitPython pixel graphics drawing library. +* Author(s): Kattni Rembor, Tony DiCola, Jonah Yolles-Murphy, based on code by Phil Burgess +Implementation Notes +-------------------- +**Hardware:** +**Software and Dependencies:** +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases +""" + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_GFX.git" + +# pylint: disable=invalid-name + + +class GFX: + # pylint: disable=too-many-instance-attributes + """Create an instance of the GFX drawing class. + :param width: The width of the drawing area in pixels. + :param height: The height of the drawing area in pixels. + :param pixel: A function to call when a pixel is drawn on the display. This function + should take at least an x and y position and then any number of optional + color or other parameters. + :param hline: A function to quickly draw a horizontal line on the display. + This should take at least an x, y, and width parameter and + any number of optional color or other parameters. + :param vline: A function to quickly draw a vertical line on the display. + This should take at least an x, y, and height paraemter and + any number of optional color or other parameters. + :param fill_rect: A funtion to quickly draw a solid rectangle with four + input parameters: x,y, width, and height. Any number of other + parameters for color or screen specific data. + :param text: A function to quickly place text on the screen. The inputs include: + x, y data(top left as starting point). + :param font: An optional input to augment the default text method with a new font. + The input shoudl be a properly formatted dict. + """ + # pylint: disable=too-many-arguments + + def __init__( + self, + width, + height, + pixel, + hline=None, + vline=None, + fill_rect=None, + text=None, + font=None, + ): + # pylint: disable=too-many-instance-attributes + self.width = width + self.height = height + self._pixel = pixel + # Default to slow horizontal & vertical line implementations if no + # faster versions are provided. + if hline is None: + self.hline = self._slow_hline + else: + self.hline = hline + if vline is None: + self.vline = self._slow_vline + else: + self.vline = vline + if fill_rect is None: + self.fill_rect = self._fill_rect + else: + self.fill_rect = fill_rect + if text is None: + self.text = self._very_slow_text + # if no supplied font set to std + if font is None: + from gfx_standard_font_01 import ( # pylint: disable=import-outside-toplevel # changed + text_dict as std_font, + ) + + self.font = std_font + self.set_text_background() + else: + self.font = font + if not isinstance(self.font, dict): + raise ValueError( + "Font definitions must be contained in a dictionary object." + ) + del self.set_text_background + + else: + self.text = text + + def pixel(self, x0, y0, *args, **kwargs): + """A function to pass through in input pixel functionality.""" + # This was added to mainitatn the abstrtion between gfx and the dislay library + self._pixel(x0, y0, *args, **kwargs) + + def _slow_hline(self, x0, y0, width, *args, **kwargs): + """Slow implementation of a horizontal line using pixel drawing. + This is used as the default horizontal line if no faster override + is provided.""" + if y0 < 0 or y0 > self.height or x0 < -width or x0 > self.width: + return + for i in range(width): + self._pixel(x0 + i, y0, *args, **kwargs) + + def _slow_vline(self, x0, y0, height, *args, **kwargs): + """Slow implementation of a vertical line using pixel drawing. + This is used as the default vertical line if no faster override + is provided.""" + if y0 < -height or y0 > self.height or x0 < 0 or x0 > self.width: + return + for i in range(height): + self._pixel(x0, y0 + i, *args, **kwargs) + + def rect(self, x0, y0, width, height, *args, **kwargs): + """Rectangle drawing function. Will draw a single pixel wide rectangle + starting in the upper left x0, y0 position and width, height pixels in + size.""" + if y0 < -height or y0 > self.height or x0 < -width or x0 > self.width: + return + self.hline(x0, y0, width, *args, **kwargs) + self.hline(x0, y0 + height - 1, width, *args, **kwargs) + self.vline(x0, y0, height, *args, **kwargs) + self.vline(x0 + width - 1, y0, height, *args, **kwargs) + + def _fill_rect(self, x0, y0, width, height, *args, **kwargs): + """Filled rectangle drawing function. Will draw a single pixel wide + rectangle starting in the upper left x0, y0 position and width, height + pixels in size.""" + if y0 < -height or y0 > self.height or x0 < -width or x0 > self.width: + return + for i in range(x0, x0 + width): + self.vline(i, y0, height, *args, **kwargs) + + def line(self, x0, y0, x1, y1, *args, **kwargs): + """Line drawing function. Will draw a single pixel wide line starting at + x0, y0 and ending at x1, y1.""" + steep = abs(y1 - y0) > abs(x1 - x0) + if steep: + x0, y0 = y0, x0 + x1, y1 = y1, x1 + if x0 > x1: + x0, x1 = x1, x0 + y0, y1 = y1, y0 + dx = x1 - x0 + dy = abs(y1 - y0) + err = dx // 2 + ystep = 0 + if y0 < y1: + ystep = 1 + else: + ystep = -1 + while x0 <= x1: + if steep: + self._pixel(y0, x0, *args, **kwargs) + else: + self._pixel(x0, y0, *args, **kwargs) + err -= dy + if err < 0: + y0 += ystep + err += dx + x0 += 1 + + def circle(self, x0, y0, radius, *args, **kwargs): + """Circle drawing function. Will draw a single pixel wide circle with + center at x0, y0 and the specified radius.""" + f = 1 - radius + ddF_x = 1 + ddF_y = -2 * radius + x = 0 + y = radius + self._pixel(x0, y0 + radius, *args, **kwargs) # bottom + self._pixel(x0, y0 - radius, *args, **kwargs) # top + self._pixel(x0 + radius, y0, *args, **kwargs) # right + self._pixel(x0 - radius, y0, *args, **kwargs) # left + while x < y: + if f >= 0: + y -= 1 + ddF_y += 2 + f += ddF_y + x += 1 + ddF_x += 2 + f += ddF_x + # angle notations are based on the unit circle and in diection of being drawn + self._pixel(x0 + x, y0 + y, *args, **kwargs) # 270 to 315 + self._pixel(x0 - x, y0 + y, *args, **kwargs) # 270 to 255 + self._pixel(x0 + x, y0 - y, *args, **kwargs) # 90 to 45 + self._pixel(x0 - x, y0 - y, *args, **kwargs) # 90 to 135 + self._pixel(x0 + y, y0 + x, *args, **kwargs) # 0 to 315 + self._pixel(x0 - y, y0 + x, *args, **kwargs) # 180 to 225 + self._pixel(x0 + y, y0 - x, *args, **kwargs) # 0 to 45 + self._pixel(x0 - y, y0 - x, *args, **kwargs) # 180 to 135 + + def fill_circle(self, x0, y0, radius, *args, **kwargs): + """Filled circle drawing function. Will draw a filled circule with + center at x0, y0 and the specified radius.""" + self.vline(x0, y0 - radius, 2 * radius + 1, *args, **kwargs) + f = 1 - radius + ddF_x = 1 + ddF_y = -2 * radius + x = 0 + y = radius + while x < y: + if f >= 0: + y -= 1 + ddF_y += 2 + f += ddF_y + x += 1 + ddF_x += 2 + f += ddF_x + self.vline(x0 + x, y0 - y, 2 * y + 1, *args, **kwargs) + self.vline(x0 + y, y0 - x, 2 * x + 1, *args, **kwargs) + self.vline(x0 - x, y0 - y, 2 * y + 1, *args, **kwargs) + self.vline(x0 - y, y0 - x, 2 * x + 1, *args, **kwargs) + + def triangle(self, x0, y0, x1, y1, x2, y2, *args, **kwargs): + # pylint: disable=too-many-arguments + """Triangle drawing function. Will draw a single pixel wide triangle + around the points (x0, y0), (x1, y1), and (x2, y2).""" + self.line(x0, y0, x1, y1, *args, **kwargs) + self.line(x1, y1, x2, y2, *args, **kwargs) + self.line(x2, y2, x0, y0, *args, **kwargs) + + def fill_triangle(self, x0, y0, x1, y1, x2, y2, *args, **kwargs): + # Filled triangle drawing function. Will draw a filled triangle around + # the points (x0, y0), (x1, y1), and (x2, y2). + if y0 > y1: + y0, y1 = y1, y0 + x0, x1 = x1, x0 + if y1 > y2: + y2, y1 = y1, y2 + x2, x1 = x1, x2 + if y0 > y1: + y0, y1 = y1, y0 + x0, x1 = x1, x0 + a = 0 + b = 0 + y = 0 + last = 0 + + if y0 == y2: + a = x0 + b = x0 + if x1 < a: + a = x1 + elif x1 > b: + b = x1 + if x2 < a: + a = x2 + elif x2 > b: + b = x2 + self.hline(a, y0, b-a+1, *args, **kwargs) + return + dx01 = x1 - x0 + dy01 = y1 - y0 + dx02 = x2 - x0 + dy02 = y2 - y0 + dx12 = x2 - x1 + dy12 = y2 - y1 + if dy01 == 0: + dy01 = 1 + if dy02 == 0: + dy02 = 1 + if dy12 == 0: + dy12 = 1 + sa = 0 + sb = 0 + if y1 == y2: + last = y1 + else: + last = y1-1 + + for y in range(y0, last+1): + a = x0 + sa // dy01 + b = x0 + sb // dy02 + sa += dx01 + sb += dx02 + if a > b: + a, b = b, a + self.hline(a, y, b-a+1, *args, **kwargs) + + y = last + + sa = dx12 * (y - y1) + sb = dx02 * (y - y0) + while y <= y2: + a = x1 + sa // dy12 + b = x0 + sb // dy02 + sa += dx12 + sb += dx02 + if a > b: + a, b = b, a + self.hline(a, y, b-a+1, *args, **kwargs) + y += 1 + + def round_rect(self, x0, y0, width, height, radius, *args, **kwargs): + """Rectangle with rounded corners drawing function. + This works like a regular rect though! if radius = 0 + Will draw the outline of a rextabgle with rounded corners with (x0,y0) at the top left""" + # shift to correct for start point location + x0 += radius + y0 += radius + + # ensure that the radius will only ever half of the shortest side or less + radius = int(min(radius, width / 2, height / 2)) + + if radius: + f = 1 - radius + ddF_x = 1 + ddF_y = -2 * radius + x = 0 + y = radius + self.vline( + x0 - radius, y0, height - 2 * radius + 1, *args, **kwargs + ) # left + self.vline( + x0 + width - radius, y0, height - 2 * radius + 1, *args, **kwargs + ) # right + self.hline( + x0, y0 + height - radius + 1, width - 2 * radius + 1, *args, **kwargs + ) # bottom + self.hline(x0, y0 - radius, width - 2 * + radius + 1, *args, **kwargs) # top + while x < y: + if f >= 0: + y -= 1 + ddF_y += 2 + f += ddF_y + x += 1 + ddF_x += 2 + f += ddF_x + # angle notations are based on the unit circle and in diection of being drawn + + # top left + self._pixel(x0 - y, y0 - x, *args, **kwargs) # 180 to 135 + self._pixel(x0 - x, y0 - y, *args, **kwargs) # 90 to 135 + # top right + self._pixel( + x0 + x + width - 2 * radius, y0 - y, *args, **kwargs + ) # 90 to 45 + self._pixel( + x0 + y + width - 2 * radius, y0 - x, *args, **kwargs + ) # 0 to 45 + # bottom right + self._pixel( + x0 + y + width - 2 * radius, + y0 + x + height - 2 * radius, + *args, + **kwargs, + ) # 0 to 315 + self._pixel( + x0 + x + width - 2 * radius, + y0 + y + height - 2 * radius, + *args, + **kwargs, + ) # 270 to 315 + # bottom left + self._pixel( + x0 - x, y0 + y + height - 2 * radius, *args, **kwargs + ) # 270 to 255 + self._pixel( + x0 - y, y0 + x + height - 2 * radius, *args, **kwargs + ) # 180 to 225 + + def fill_round_rect(self, x0, y0, width, height, radius, *args, **kwargs): + """Filled circle drawing function. Will draw a filled circule with + center at x0, y0 and the specified radius.""" + # shift to correct for start point location + x0 += radius + y0 += radius + + # ensure that the radius will only ever half of the shortest side or less + radius = int(min(radius, width / 2, height / 2)) + + self.fill_rect( + x0, y0 - radius, width - 2 * radius + 2, height + 2, *args, **kwargs + ) + + if radius: + f = 1 - radius + ddF_x = 1 + ddF_y = -2 * radius + x = 0 + y = radius + while x < y: + if f >= 0: + y -= 1 + ddF_y += 2 + f += ddF_y + x += 1 + ddF_x += 2 + f += ddF_x + # part notation starts with 0 on left and 1 on right, and direction is noted + # top left + self.vline( + x0 - y, y0 - x, 2 * x + 1 + height - 2 * radius, *args, **kwargs + ) # 0 to .25 + self.vline( + x0 - x, y0 - y, 2 * y + 1 + height - 2 * radius, *args, **kwargs + ) # .5 to .25 + # top right + self.vline( + x0 + x + width - 2 * radius, + y0 - y, + 2 * y + 1 + height - 2 * radius, + *args, + **kwargs, + ) # .5 to .75 + self.vline( + x0 + y + width - 2 * radius, + y0 - x, + 2 * x + 1 + height - 2 * radius, + *args, + **kwargs, + ) # 1 to .75 + + def _place_char(self, x0, y0, char, size, *args, **kwargs): + """A sub class used for placing a single character on the screen""" + # pylint: disable=undefined-loop-variable + arr = self.font[char] + width = arr[0] + height = arr[1] + # extract the char section of the data + data = arr[2:] + for x in range(width): + for y in range(height): + bit = bool(data[x] & 2 ** y) + # char pixel + if bit: + self.fill_rect( + size * x + x0, + size * (height - y - 1) + y0, + size, + size, + *args, + **kwargs, + ) + # else background pixel + else: + try: + self.fill_rect( + size * x + x0, + size * (height - y - 1) + y0, + size, + size, + *self.text_bkgnd_args, + **self.text_bkgnd_kwargs, + ) + except TypeError: + pass + del arr, width, height, data, x, y, x0, y0, char, size + + def _very_slow_text(self, x0, y0, string, size, *args, **kwargs): + """a function to place text on the display.(temporary) + to use special characters put "__" on either side of the desired characters. + letter format: + {'character_here' : bytearray(b',WIDTH,HEIGHT,right-most-data, + more-bytes-here,left-most-data') ,} + (replace the "," with backslashes!!) + each byte: + | lower most bit(lowest on display) + V + x0110100 + ^c + | top most bit (highest on display)""" + + x_roll = x0 # rolling x + y_roll = y0 # rolling y + + # highest_height = 0#wrap + sep_string = string.split("__") + + for chunk in sep_string: + # print(chunk) + try: + self._place_char(x_roll, y_roll, chunk, size, *args, **kwargs) + x_roll += size * self.font[chunk][0] + size + # highest_height = max(highest_height, size*self.font[chunk][1] + 1) #wrap + except KeyError: + while chunk: + char = chunk[0] + + # make sure something is sent even if not in font dict + try: + self._place_char(x_roll, y_roll, char, + size, *args, **kwargs) + except KeyError: + self._place_char( + x_roll, y_roll, "?CHAR?", size, *args, **kwargs + ) + char = "?CHAR?" + + x_roll += size * self.font[char][0] + + # gap between letters + try: + self.fill_rect( + x_roll, + y_roll, + size, + size * self.font[char][1], + *self.text_bkgnd_args, + **self.text_bkgnd_kwargs, + ) + except TypeError: + pass + + x_roll += size + # highest_height = max(highest_height, size*self.font[char][1] + 1) #wrap + chunk = chunk[1:] # wrap + # if (x_roll >= self.width) or (chunk[0:2] == """\n"""): #wrap + # self._text(x0,y0+highest_height,"__".join(sep_string),size) #wrap + # print(highest_height) #wrap + + def set_text_background(self, *args, **kwargs): + """A method to change the background color of text, input any and all color paramsself. + run without any inputs to return to "clear" background + """ + self.text_bkgnd_args = args + self.text_bkgnd_kwargs = kwargs + + # pylint: enable=too-many-arguments diff --git a/ports/esp32/boards/INKPLATE6/modules/gfx_standard_font_01.py b/ports/esp32/boards/INKPLATE6/modules/gfx_standard_font_01.py new file mode 100644 index 0000000000..28b1a37521 --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/modules/gfx_standard_font_01.py @@ -0,0 +1,161 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 Jonah Yolles-Murphy for Makers Anywhere! +# +# 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. +""" +`gfx_standard_font_01` +==================================================== + +CircuitPython pixel graphics drawing library. + +* Author(s): Jonah Yolles-Murphy + +Implementation Notes +-------------------- + +**Hardware:** + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +* letter format: + { 'character_here' : bytearray(WIDTH,HEIGHT,right-most-data,more-bytes-here,left-most-data')} + the right most bit is the top most bit of each vertical stripe of a char +* Key format: + keys of one length only represent one character. longer then one is either + extended characters or special characters like the degree sign. + all extended or special charaters have all capitalized keys. + "?CHAR?" is used when an input character is not in the font dictionary + +""" +# pylint: disable=invalid-name +text_dict = { + "A": bytearray(b"\x05\x07?DDD?"), + "B": bytearray(b"\x05\x07\x7fAII6"), + "C": bytearray(b'\x05\x07>AAA"'), + "D": bytearray(b'\x05\x07\x7fAA"\x1c'), + "E": bytearray(b"\x05\x07\x7fIIIA"), + "F": bytearray(b"\x05\x07\x7fHH@@"), + "G": bytearray(b"\x05\x07>AII."), + "H": bytearray(b"\x05\x07\x7f\x08\x08\x08\x7f"), + "I": bytearray(b"\x05\x07AA\x7fAA"), + "J": bytearray(b"\x05\x07FA~@@"), + "K": bytearray(b"\x05\x07\x7f\x08\x08t\x03"), + "L": bytearray(b"\x05\x07\x7f\x01\x01\x01\x01"), + "M": bytearray(b"\x05\x07\x7f \x10 \x7f"), + "N": bytearray(b"\x05\x07\x7f \x1c\x02\x7f"), + "O": bytearray(b"\x05\x07>AAA>"), + "P": bytearray(b"\x05\x07\x7fHHH0"), + "Q": bytearray(b"\x05\x07>AEB="), + "R": bytearray(b"\x05\x07\x7fHLJ1"), + "S": bytearray(b"\x05\x072III&"), + "T": bytearray(b"\x05\x07@@\x7f@@"), + "U": bytearray(b"\x05\x07~\x01\x01\x01~"), + "V": bytearray(b"\x05\x07p\x0e\x01\x0ep"), + "W": bytearray(b"\x05\x07|\x03\x04\x03|"), + "X": bytearray(b"\x05\x07c\x14\x08\x14c"), + "Y": bytearray(b"\x05\x07`\x10\x0f\x10`"), + "Z": bytearray(b"\x05\x07CEIQa"), + + #until we get proper support for lowercase letters + "a": bytearray(b"\x05\x07?DDD?"), + "b": bytearray(b"\x05\x07\x7fAII6"), + "c": bytearray(b'\x05\x07>AAA"'), + "d": bytearray(b'\x05\x07\x7fAA"\x1c'), + "e": bytearray(b"\x05\x07\x7fIIIA"), + "f": bytearray(b"\x05\x07\x7fHH@@"), + "g": bytearray(b"\x05\x07>AII."), + "h": bytearray(b"\x05\x07\x7f\x08\x08\x08\x7f"), + "i": bytearray(b"\x05\x07AA\x7fAA"), + "j": bytearray(b"\x05\x07FA~@@"), + "k": bytearray(b"\x05\x07\x7f\x08\x08t\x03"), + "l": bytearray(b"\x05\x07\x7f\x01\x01\x01\x01"), + "m": bytearray(b"\x05\x07\x7f \x10 \x7f"), + "n": bytearray(b"\x05\x07\x7f \x1c\x02\x7f"), + "o": bytearray(b"\x05\x07>AAA>"), + "p": bytearray(b"\x05\x07\x7fHHH0"), + "q": bytearray(b"\x05\x07>AEB="), + "r": bytearray(b"\x05\x07\x7fHLJ1"), + "s": bytearray(b"\x05\x072III&"), + "t": bytearray(b"\x05\x07@@\x7f@@"), + "u": bytearray(b"\x05\x07~\x01\x01\x01~"), + "v": bytearray(b"\x05\x07p\x0e\x01\x0ep"), + "w": bytearray(b"\x05\x07|\x03\x04\x03|"), + "x": bytearray(b"\x05\x07c\x14\x08\x14c"), + "y": bytearray(b"\x05\x07`\x10\x0f\x10`"), + "z": bytearray(b"\x05\x07CEIQa"), + + "0": bytearray(b"\x05\x07>EIQ>"), + "1": bytearray(b"\x05\x07\x11!\x7f\x01\x01"), + "2": bytearray(b"\x05\x07!CEI1"), + "3": bytearray(b"\x05\x07FAQiF"), + "4": bytearray(b"\x05\x07x\x08\x08\x08\x7f"), + "5": bytearray(b"\x05\x07rQQQN"), + "6": bytearray(b"\x05\x07\x1e)II\x06"), + "7": bytearray(b"\x05\x07@GHP`"), + "8": bytearray(b"\x05\x076III6"), + "9": bytearray(b"\x05\x070IIJ<"), + ")": bytearray(b"\x05\x07\x00A>\x00\x00"), + "(": bytearray(b"\x05\x07\x00\x00>A\x00"), + "[": bytearray(b"\x05\x07\x00\x00\x7fA\x00"), + "]": bytearray(b"\x05\x07\x00A\x7f\x00\x00"), + ".": bytearray(b"\x05\x07\x00\x03\x03\x00\x00"), + "'": bytearray(b"\x05\x07\x00\x000\x00\x00"), + ":": bytearray(b"\x05\x07\x00\x0066\x00"), + "?CHAR?": bytearray(b"\x05\x07\x7f_RG\x7f"), + "!": bytearray(b"\x05\x07\x00{{\x00\x00"), + "?": bytearray(b"\x05\x07 @EH0"), + ",": bytearray(b"\x05\x07\x00\x05\x06\x00\x00"), + ";": bytearray(b"\x05\x07\x0056\x00\x00"), + "/": bytearray(b"\x05\x07\x01\x06\x080@"), + ">": bytearray(b"\x05\x07Ac6\x1c\x08"), + "<": bytearray(b"\x05\x07\x08\x1c6cA"), + "%": bytearray(b"\x05\x07af\x083C"), + "@": bytearray(b"\x05\x07&IOA>"), + "#": bytearray(b"\x05\x07\x14\x7f\x14\x7f\x14"), + "$": bytearray(b"\x05\x072I\x7fI&"), + "&": bytearray(b'\x05\x076IU"\x05'), + "*": bytearray(b"\x05\x07(\x10|\x10("), + "-": bytearray(b"\x05\x07\x00\x08\x08\x08\x00"), + "_": bytearray(b"\x05\x07\x01\x01\x01\x01\x01"), + "+": bytearray(b"\x05\x07\x08\x08>\x08\x08"), + "=": bytearray(b"\x05\x07\x00\x14\x14\x14\x00"), + '"': bytearray(b"\x05\x07\x00p\x00p\x00"), + "`": bytearray(b"\x05\x07\x00\x00 \x10\x00"), + "~": bytearray(b"\x05\x07\x08\x10\x08\x04\x08"), + " ": bytearray(b"\x05\x07\x00\x00\x00\x00\x00"), + "^": bytearray(b"\x05\x07\x10 @ \x10"), + "NONE": bytearray(b"\x00\x07"), + "BLANK": bytearray(b"\x05\x07\x00\x00\x00\x00\x00"), + "BATA0": bytearray(b"\x0b\x07\x7fAAAAAAAA\x7f\x1c"), + "BATA1": bytearray(b"\x0b\x07\x7fA]AAAAAA\x7f\x1c"), + "BATA2": bytearray(b"\x0b\x07\x7fA]]AAAAA\x7f\x1c"), + "BATA3": bytearray(b"\x0b\x07\x7fA]]]AAAA\x7f\x1c"), + "BATA4": bytearray(b"\x0b\x07\x7fA]]]]AAA\x7f\x1c"), + "BATA5": bytearray(b"\x0b\x07\x7fA]]]]]AA\x7f\x1c"), + "BATA6": bytearray(b"\x0b\x07\x7fA]]]]]]A\x7f\x1c"), + "BATACHRG": bytearray(b"\x07\x08\x7fAIYyOMIA\x7f\x1c"), + "BATB0": bytearray(b"\x0b\x07\x7fAAAAAAAA\x7f\x1c"), + "FULL": bytearray(b"\x05\x07\x7f\x7f\x7f\x7f\x7f"), + "\n": bytearray(b"\x05\x07\x00\x00\x00\x00\x00"), + "DEGREESIGN": bytearray(b"\x05\x07\x18$$\x18\x00"), +} diff --git a/ports/esp32/boards/INKPLATE6/modules/image.py b/ports/esp32/boards/INKPLATE6/modules/image.py new file mode 100644 index 0000000000..4a529eb343 --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/modules/image.py @@ -0,0 +1,3 @@ +image = bytearray( + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xff\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xff\xff\xff\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xff\xff\xff\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xff\xff\x00\x00\x3f\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xff\xf8\x00\x00\x0f\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xff\x80\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x7f\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xf8\x00\x00\x00\x00\x0f\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xff\xf0\x00\x00\x00\x00\x07\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xff\xc0\x00\x00\x00\x00\x01\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xff\x80\x00\x00\x00\x00\x00\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xfe\x00\x00\x00\x00\x00\x00\x3f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xfc\x00\x00\x00\x00\x00\x00\x1f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf8\x00\x00\x00\x00\x00\x00\x0f\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\x00\x00\x00\x00\x00\x07\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xe0\x00\x00\x00\x00\x00\x00\x03\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc0\x00\x00\x00\x00\x00\x00\x01\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x80\x00\x00\x00\x00\x00\x00\x00\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xff\x80\x00\x00\x00\x00\x00\x00\x00\x7f\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xff\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x00\x00\x7f\xff\xff\xf8\x00\x00\x0f\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf8\x00\x07\xff\xff\xff\xfc\x00\x00\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc3\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf8\x00\x0f\xff\xff\xff\xfc\x00\x00\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc3\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xf0\x00\x3f\xff\xff\xff\xfc\x00\x00\x07\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc3\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x03\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xf0\x00\x7f\xff\xff\xff\xfc\x00\x00\x03\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc3\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x03\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xe0\x01\xff\xff\xff\xff\xff\xff\x80\x03\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc3\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xe0\x03\xff\xfc\x00\x7d\xff\xff\x80\x03\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc3\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xc0\x07\xfe\x30\x00\x30\xff\xff\x80\x01\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xc0\x0f\xfc\x30\x00\x30\xff\xff\x80\x01\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xc0\x1f\xf0\x38\x70\x20\xfe\x1f\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xc0\x1f\xe0\x3f\xf8\x78\xfc\x00\x00\x00\xff\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x1f\xff\x80\x01\xff\xfc\x00\x00\x1f\xff\xff\xc1\xc0\x00\xff\xfe\x00\x0f\xff\xfe\x00\x00\xe0\x00\x7f\xff\x00\x00\x07\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x80\x3f\xc0\x3f\xf8\xf8\xfc\x00\x00\x00\xff\x00\x00\x00\x0f\xff\xff\xf0\x00\x00\x00\x03\xff\xff\xc0\x1f\xff\xff\xc0\x01\xff\xff\xff\xc3\xe0\x0f\xff\xff\xe0\x1f\xff\xff\xf0\x00\xf0\x07\xff\xff\xf0\x00\x7f\xff\xff\x00\x00\x00\x7f\xff\xfe\x00\x07\xff\xff\xe0\x0f\xff\xff\x07\xff\xfc\x00\xff\x80\x3f\x80\x3f\xf8\xf8\xfc\x00\x00\x00\xff\x80\x00\x00\x1f\xff\xff\xf8\x00\x00\x00\x0f\xff\xff\xc0\x3f\xff\xff\xe0\x03\xff\xff\xff\xc3\xf0\x1f\xff\xff\xf0\x1f\xff\xff\xfc\x01\xf0\x0f\xff\xff\xf8\x00\xff\xff\xff\x80\x00\x00\xff\xff\xff\x00\x0f\xff\xff\xf0\x0f\xff\xff\x8f\xff\xff\x00\xff\x00\x7f\x00\x00\xf0\x78\xfc\x00\x00\x00\xff\x80\x00\x00\x3f\xff\xff\xfc\x00\x00\x00\x1f\xff\xff\xc0\xff\xff\xff\xf0\x0f\xff\xff\xff\xc3\xf0\x3f\xff\xff\xf8\x1f\xff\xff\xfe\x01\xf0\x3f\xff\xff\xfc\x03\xff\xff\xff\xc0\x00\x03\xff\xff\xff\xc0\x1f\xff\xff\xfc\x0f\xff\xff\xff\xff\xff\xc0\xff\x00\xff\x00\x00\x00\x30\xfc\x00\x00\x00\x7f\x80\x00\x00\x3f\xff\xff\xfe\x00\x00\x00\x3f\xff\xff\xc0\xff\xff\xff\xf8\x0f\xff\xff\xff\xc3\xf0\x7f\xff\xff\xfc\x1f\xff\xff\xff\x01\xf0\x3f\xff\xff\xfe\x03\xff\xff\xff\xe0\x00\x03\xff\xff\xff\xc0\x3f\xff\xff\xfc\x0f\xff\xff\xff\xff\xff\xc0\xff\x00\xfe\x00\x00\x00\x00\xfc\x00\x00\x00\x7f\xc0\x00\x00\x7f\x80\x01\xff\x00\x00\x00\x7f\xe0\x00\x01\xfe\x00\x07\xf8\x1f\xe0\x00\x0f\xc3\xf0\xff\x00\x03\xfc\x1f\x80\x01\xff\x81\xf0\x7f\x80\x01\xff\x07\xf8\x00\x1f\xf0\x00\x03\xf0\x00\x0f\xe0\x3f\x00\x00\xfe\x0f\xc0\x07\xff\x00\x1f\xf0\xff\x80\xfc\x00\x00\x00\x00\xfc\x00\x00\x00\x7f\xc0\x00\x00\xfe\x00\x00\x7f\x00\x00\x00\xff\x80\x00\x01\xfc\x00\x01\xfc\x1f\xc0\x00\x07\xc3\xf0\xfe\x00\x00\xfe\x1f\x00\x00\x7f\xc1\xf0\x7f\x00\x00\x7f\x07\xf0\x00\x07\xf0\x00\x07\xe0\x00\x07\xf0\x7e\x00\x00\x7f\x0f\x80\x03\xff\x00\x07\xf0\xff\xfc\xfc\xfc\x00\x00\x00\xfc\x00\x00\x00\x7f\xc0\x00\x00\xfc\x00\x00\x3f\x00\x00\x00\xfe\x00\x00\x03\xf0\x00\x00\xfc\x3f\x00\x00\x07\xc3\xf0\xfc\x00\x00\x7e\x1f\x00\x00\x1f\xc1\xf0\xfe\x00\x00\x3f\x0f\xe0\x00\x03\xf0\x00\x07\xc0\x00\x03\xf0\x7c\x00\x00\x3f\x0f\x80\x00\xff\x00\x03\xf0\xff\xff\xfd\xfe\x00\x00\x00\xfe\x00\x00\x00\x3f\xc0\x00\x00\xfc\x00\x00\x3f\x00\x00\x00\xfc\x00\x00\x03\xf0\x00\x00\xfc\x3f\x00\x00\x07\xc3\xf1\xf8\x00\x00\x3e\x1f\x00\x00\x0f\xc1\xf0\xfc\x00\x00\x1f\x0f\xc0\x00\x01\xf0\x00\x0f\xc0\x00\x01\xf0\xfc\x00\x00\x1f\x0f\x80\x00\x7f\x00\x01\xf8\xff\xff\xff\xff\x01\xc0\x00\xff\xfc\x00\x00\x3f\xc0\x00\x00\xf8\x00\x00\x1f\x00\x00\x01\xf8\x00\x00\x03\xf0\x00\x00\x7c\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x07\xe1\xf0\xfc\x00\x00\x1f\x0f\xc0\x00\x01\xf0\x00\x0f\x80\x00\x01\xf0\xf8\x00\x00\x1f\x0f\x80\x00\x3f\x00\x00\xfc\xff\xff\xff\xff\x83\xe0\x00\xff\xfc\x00\x00\x3f\xc0\x00\x00\xf8\x00\x00\x1f\x00\x00\x01\xf0\x00\x00\x03\xe0\x00\x00\xfc\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xe1\xf0\xfc\x00\x00\x0f\x0f\xc0\x00\x03\xf0\x00\x0f\x80\x00\x00\xf0\xf8\x00\x00\x0f\x0f\x80\x00\x3f\x00\x00\xfc\xff\xff\xff\xff\xff\xe0\x00\xff\xfc\x00\x00\x3f\xc0\x00\x00\xf8\x00\x00\x3f\x00\x00\x01\xf0\x00\x00\x00\x00\x00\x3f\xfc\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xe1\xf0\xfc\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\x0f\x80\x00\x00\x00\xf8\x00\x00\x0f\x0f\x80\x00\x3f\x00\x00\x7e\xff\xff\xff\xff\xff\xe0\x00\xff\xfc\x00\x00\x3f\xc0\x00\x00\xf8\x00\x00\x7f\x00\x00\x03\xf0\x00\x00\x00\x00\x00\xff\xfc\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x00\x00\x00\x03\xff\xf0\x00\x0f\x80\x00\x00\x00\xf8\x00\x00\x0f\x0f\x80\x00\x3f\x00\x00\x7e\x3f\xfd\xfd\xfe\x03\xc0\x00\xfe\x00\x00\x00\x3f\xc0\x00\x00\xf8\x00\x0f\xff\x00\x00\x03\xf0\x00\x00\x00\x00\x3f\xff\xfc\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x00\x00\x00\xff\xff\xf0\x00\x0f\x80\x00\x00\x00\xf8\x00\x00\x0f\x0f\x80\x00\x1f\x00\x00\x3f\x3f\xfc\xfc\xfc\x00\x80\x00\xfc\x00\x00\x00\x3f\xc0\x00\x00\xf8\x00\x7f\xfe\x00\x00\x03\xf0\x00\x00\x00\x01\xff\xff\xfc\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x00\x00\x07\xff\xff\xf0\x00\x0f\x80\x00\x00\x00\xf8\x00\x00\x0f\x0f\x80\x00\x1f\x00\x00\x3f\x00\x00\xfc\x00\x00\x00\x00\xfc\x00\x00\x00\x7f\xc0\x00\x00\xf8\x1f\xff\xfc\x07\xff\xc3\xf0\x00\x00\x00\x3f\xff\xff\xfc\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x00\x00\xff\xff\xff\xf0\x00\x0f\x80\x00\x00\x00\xf8\x00\x00\x0f\x0f\x80\x00\x1f\x00\x00\x3f\x00\x00\xfe\x00\x00\x00\x00\xfc\x00\x00\x00\x7f\xc0\x00\x00\xfc\xff\xff\xfc\x0f\xff\xc3\xf0\x00\x00\x00\xff\xff\xfc\x7c\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x00\x01\xff\xff\xf1\xf0\x00\x0f\x80\x00\x00\x00\xf8\x00\x00\x0f\x0f\x80\x00\x1f\x00\x00\x3f\x00\x00\xff\x00\x70\x00\x00\xfc\x00\x00\x00\x7f\xc0\x00\x00\xff\xff\xff\xc0\x0f\xff\xc3\xf0\x00\x00\x00\xff\xff\x00\x7c\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x00\x03\xff\xfc\x00\xf0\x00\x0f\x80\x00\x00\x00\xf8\x00\x00\x0f\x0f\x80\x00\x1f\x00\x00\x3f\x00\x00\x7f\x00\xf8\x02\x00\xfc\x00\x00\x00\x7f\x80\x00\x00\xff\xff\xfe\x00\x0f\xff\xc3\xf0\x00\x00\x01\xff\xf8\x00\x7c\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x00\x07\xff\xe0\x00\xf0\x00\x0f\x80\x00\x00\x00\xf8\x00\x00\x0f\x0f\x80\x00\x1f\x00\x00\x3f\x00\x00\x3f\x80\xf8\x0f\x00\xfc\x00\x00\x00\xff\x80\x00\x00\xff\xff\xc0\x00\x07\xff\xc3\xf0\x00\x00\x03\xff\x00\x00\x7c\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x00\x07\xfc\x00\x00\xf0\x00\x0f\x80\x00\x00\x00\xf8\x00\x00\x0f\x0f\x80\x00\x1f\x00\x00\x3f\x00\x00\x3f\xc0\xf8\x0f\x80\xfc\x00\x00\x00\xff\x80\x00\x00\xff\xfe\x00\x00\x00\x00\x03\xf0\x00\x00\x03\xf8\x00\x00\x7c\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x00\x0f\xe0\x00\x00\xf0\x00\x0f\x80\x00\x00\x00\xf8\x00\x00\x0f\x0f\x80\x00\x1f\x00\x00\x3f\x00\x00\x1f\xe0\xf0\x0f\x80\xfc\x00\x00\x00\xff\x80\x00\x00\xff\x80\x00\x0f\x00\x00\x03\xf0\x00\x00\x03\xf0\x00\x00\x7c\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x0f\x0f\xc0\x00\x00\xf0\x00\x0f\x80\x00\x00\xe0\xf8\x00\x00\x0f\x0f\x80\x00\x1f\x00\x00\x3f\x00\x00\x1f\xf0\x60\x0f\x80\xfc\x00\x00\x00\xff\x00\x00\x00\xfc\x00\x00\x1f\x00\x00\x03\xf0\x00\x00\x03\xe0\x00\x00\x7c\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x0f\x0f\xc0\x00\x01\xf0\x00\x0f\x80\x00\x00\xf0\xf8\x00\x00\x0f\x0f\x80\x00\x1f\x00\x00\x3f\x00\x00\x0f\xfc\x20\x06\x00\xff\xff\x80\x00\xff\x00\x00\x00\xfc\x00\x00\x3f\x00\x00\x03\xf0\x00\x00\x03\xf0\x00\x00\x7c\x3f\x00\x00\x07\xc3\xf1\xf0\x00\x00\x3f\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x1f\x0f\xc0\x00\x01\xf0\x00\x0f\x80\x00\x01\xf0\xf8\x00\x00\x1f\x0f\x80\x00\x1f\x00\x00\x3f\x00\x00\x07\xfe\x70\x0e\x00\xff\xff\x80\x00\xff\x00\x00\x00\xfc\x00\x00\x3f\x00\x00\x03\xf0\x00\x00\x03\xf0\x00\x00\xfc\x3f\x00\x00\x0f\xc3\xf0\xf8\x00\x00\x3e\x1f\x00\x00\x03\xf1\xf0\xfc\x00\x00\x1f\x0f\xc0\x00\x01\xf0\x00\x0f\xc0\x00\x01\xf0\xfc\x00\x00\x1f\x0f\x80\x00\x1f\x00\x00\x3f\x07\x80\x03\xff\xf8\x3e\x01\xff\xff\x80\x01\xff\x00\x00\x00\xfc\x00\x00\x3f\x00\x00\x03\xf0\x00\x00\x03\xf8\x00\x00\xfc\x3f\x80\x00\x0f\xc3\xf0\xfc\x00\x00\x7e\x1f\x00\x00\x03\xf1\xf0\x7e\x00\x00\x3f\x0f\xe0\x00\x03\xf0\x00\x07\xc0\x00\x03\xf0\x7c\x00\x00\x3f\x0f\x80\x00\x1f\x00\x00\x3f\x0f\xc0\x01\xff\xff\xff\xff\xff\xff\x80\x03\xff\x00\x00\x00\xfe\x00\x00\xfe\x00\x00\x03\xf0\x00\x00\x03\xfc\x00\x01\xfc\x1f\xc0\x00\x1f\xc3\xf0\xfe\x00\x00\xfe\x1f\x00\x00\x03\xf1\xf0\x7f\x00\x00\x7f\x07\xf0\x00\x07\xf0\x0e\x07\xe0\x00\x07\xe0\x7e\x00\x00\x7e\x0f\x80\x00\x1f\x00\x00\x3f\x0f\xe0\x00\xff\xff\xff\xff\xfe\xff\x00\x03\xfe\x00\x00\x00\x7f\xff\xff\xfe\x00\x00\x03\xf0\x00\x00\x01\xff\xff\xff\xf8\x1f\xff\xff\xff\x83\xf0\xff\xff\xff\xfc\x1f\x00\x00\x03\xf1\xf0\x3f\xff\xff\xfe\x07\xff\xff\xff\xe0\x1f\x03\xff\xff\xff\xe0\x3f\xff\xff\xfe\x0f\x80\x00\x1f\x00\x00\x3f\x1f\xf0\x00\x7f\xff\xff\xff\xfc\x00\x00\x03\xfe\x00\x00\x00\x3f\xff\xff\xfc\x00\x00\x03\xf0\x00\x00\x00\xff\xff\xff\xf0\x0f\xff\xff\xff\x83\xf0\x7f\xff\xff\xfc\x1f\x00\x00\x03\xf1\xf0\x3f\xff\xff\xfe\x03\xff\xff\xff\xe0\x3f\x03\xff\xff\xff\xc0\x3f\xff\xff\xfc\x0f\x80\x00\x1f\x00\x00\x3f\x1f\xf0\x00\x1f\xff\xff\xff\xfc\x00\x00\x03\xfc\x00\x00\x00\x3f\xff\xff\xf8\x00\x00\x03\xf0\x00\x00\x00\xff\xff\xff\xf0\x07\xff\xff\xff\x03\xf0\x3f\xff\xff\xf8\x1f\x00\x00\x03\xf1\xf0\x1f\xff\xff\xfc\x01\xff\xff\xff\xc0\x3f\x01\xff\xff\xff\x80\x1f\xff\xff\xf8\x0f\x80\x00\x1f\x00\x00\x3f\x0f\xf0\x00\x07\xff\xff\xff\xfc\x00\x00\x07\xfc\x00\x00\x00\x0f\xff\xff\xf0\x00\x00\x03\xf0\x00\x00\x00\x7f\xff\xff\xe0\x03\xff\xff\xfe\x03\xf0\x1f\xff\xff\xf0\x1f\x00\x00\x03\xf1\xf0\x0f\xff\xff\xf8\x00\xff\xff\xff\x80\x3f\x00\xff\xff\xff\x00\x0f\xff\xff\xf0\x0f\x80\x00\x1f\x00\x00\x3f\x0f\xf8\x00\x00\x7f\xff\xff\xf8\x00\x00\x0f\xfc\x00\x00\x00\x03\xff\xff\xc0\x00\x00\x01\xe0\x00\x00\x00\x0f\xff\xff\x00\x00\xff\xff\xf0\x03\xe0\x07\xff\xff\xc0\x0f\x00\x00\x03\xe0\xf0\x03\xff\xff\xe0\x00\x3f\xff\xfe\x00\x0e\x00\x1f\xff\xf8\x00\x01\xff\xff\x80\x07\x00\x00\x0e\x00\x00\x3c\x0f\xfc\x00\x00\x0f\xf8\x00\x00\x00\x00\x0f\xf8\x00\x00\x00\x00\xff\xfe\x00\x00\x00\x00\xc0\x00\x00\x00\x01\xff\xfc\x00\x00\x1f\xff\xc0\x00\xc0\x00\xff\xfe\x00\x06\x00\x00\x00\xc0\x60\x00\x7f\xff\x00\x00\x07\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xff\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xff\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x80\x00\x00\x00\x00\x00\x00\x00\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc0\x00\x00\x00\x00\x00\x00\x00\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xe0\x00\x00\x00\x00\x00\x00\x03\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\x00\x00\x00\x00\x00\x03\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xf8\x00\x00\x00\x00\x00\x00\x0f\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xfc\x00\x00\x00\x00\x00\x00\x0f\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfe\x00\x00\x00\x00\x00\x00\x3f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xff\x00\x00\x00\x00\x00\x00\x7f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xff\xc0\x00\x00\x00\x00\x00\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xff\xe0\x00\x00\x00\x00\x03\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xf8\x00\x00\x00\x00\x0f\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfc\x00\x00\x00\x00\x1f\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xff\xc0\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xff\xe0\x00\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xff\xff\x00\x00\x3f\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xff\xff\xe0\x03\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xff\xff\xff\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xff\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xff\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +) diff --git a/ports/esp32/boards/INKPLATE6/modules/inkplate6.py b/ports/esp32/boards/INKPLATE6/modules/inkplate6.py new file mode 100644 index 0000000000..98440c82b9 --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/modules/inkplate6.py @@ -0,0 +1,1044 @@ +# Copyright © 2020 by Thorsten von Eicken. +import time +import micropython +import framebuf +import os +from machine import ADC, I2C, Pin, SDCard +from uarray import array +from mcp23017 import MCP23017 +from micropython import const +from shapes import Shapes + +from gfx import GFX +from gfx_standard_font_01 import text_dict as std_font +# ===== Constants that change between the Inkplate 6 and 10 + +# Raw display constants for Inkplate 6 +D_ROWS = const(600) +D_COLS = const(800) + +# Waveforms for 2 bits per pixel grey-scale. +# Order of 4 values in each tuple: blk, dk-grey, light-grey, white +# Meaning of values: 0=dischg, 1=black, 2=white, 3=skip +# Uses "colors" 0 (black), 3, 5, and 7 (white) from 3-bit waveforms below + +# add discharge to waveforms to try to fix them +WAVE_2B = ( # original mpy driver for Ink 6, differs from arduino driver below + (0, 0, 0, 0), + (0, 0, 0, 0), + (0, 1, 1, 0), + (0, 1, 1, 0), + (1, 2, 1, 0), + (1, 1, 2, 0), + (1, 2, 2, 2), +) +# Ink6 WAVEFORM3BIT from arduino driver +# {{0,1,1,0,0,1,1,0},{0,1,2,1,1,2,1,0},{1,1,1,2,2,1,0,0},{0,0,0,1,1,1,2,0}, +# {2,1,1,1,2,1,2,0},{2,2,1,1,2,1,2,0},{1,1,1,2,1,2,2,0},{0,0,0,0,0,0,2,0}}; + +TPS65186_addr = const(0x48) # I2C address + +# ESP32 GPIO set and clear registers to twiddle 32 gpio bits at once +# from esp-idf: +# define DR_REG_GPIO_BASE 0x3ff44000 +# define GPIO_OUT_W1TS_REG (DR_REG_GPIO_BASE + 0x0008) +# define GPIO_OUT_W1TC_REG (DR_REG_GPIO_BASE + 0x000c) +ESP32_GPIO = const(0x3FF44000) # ESP32 GPIO base +# register offsets from ESP32_GPIO +W1TS0 = const(2) # offset for "write one to set" register for gpios 0..31 +W1TC0 = const(3) # offset for "write one to clear" register for gpios 0..31 +W1TS1 = const(5) # offset for "write one to set" register for gpios 32..39 +W1TC1 = const(6) # offset for "write one to clear" register for gpios 32..39 +# bit masks in W1TS/W1TC registers +EPD_DATA = const(0x0E8C0030) # EPD_D0..EPD_D7 +EPD_CL = const(0x00000001) # in W1Tx0 +EPD_LE = const(0x00000004) # in W1Tx0 +EPD_CKV = const(0x00000001) # in W1Tx1 +EPD_SPH = const(0x00000002) # in W1Tx1 + +# Inkplate provides access to the pins of the Inkplate 6 as well as to low-level display +# functions. + + +class _Inkplate: + @classmethod + def init(cls, i2c): + cls._i2c = i2c + cls._mcp23017 = MCP23017(i2c) + # Display control lines + cls.EPD_CL = Pin(0, Pin.OUT, value=0) + cls.EPD_LE = Pin(2, Pin.OUT, value=0) + cls.EPD_CKV = Pin(32, Pin.OUT, value=0) + cls.EPD_SPH = Pin(33, Pin.OUT, value=1) + cls.EPD_OE = cls._mcp23017.pin(0, Pin.OUT, value=0) + cls.EPD_GMODE = cls._mcp23017.pin(1, Pin.OUT, value=0) + cls.EPD_SPV = cls._mcp23017.pin(2, Pin.OUT, value=1) + # Display data lines - we only use the Pin class to init the pins + Pin(4, Pin.OUT) + Pin(5, Pin.OUT) + Pin(18, Pin.OUT) + Pin(19, Pin.OUT) + Pin(23, Pin.OUT) + Pin(25, Pin.OUT) + Pin(26, Pin.OUT) + Pin(27, Pin.OUT) + # TPS65186 power regulator control + cls.TPS_WAKEUP = cls._mcp23017.pin(3, Pin.OUT, value=0) + cls.TPS_PWRUP = cls._mcp23017.pin(4, Pin.OUT, value=0) + cls.TPS_VCOM = cls._mcp23017.pin(5, Pin.OUT, value=0) + cls.TPS_INT = cls._mcp23017.pin(6, Pin.IN) + cls.TPS_PWR_GOOD = cls._mcp23017.pin(7, Pin.IN) + # Misc + cls.GPIO0_PUP = cls._mcp23017.pin(8, Pin.OUT, value=0) + cls.VBAT_EN = cls._mcp23017.pin(9, Pin.OUT, value=1) + cls.VBAT = ADC(Pin(35)) + cls.VBAT.atten(ADC.ATTN_11DB) + cls.VBAT.width(ADC.WIDTH_12BIT) + # Touch sensors + cls.TOUCH1 = cls._mcp23017.pin(10, Pin.IN) + cls.TOUCH2 = cls._mcp23017.pin(11, Pin.IN) + cls.TOUCH3 = cls._mcp23017.pin(12, Pin.IN) + + cls._on = False # whether panel is powered on or not + + if len(_Inkplate.byte2gpio) == 0: + _Inkplate.gen_byte2gpio() + + @classmethod + def begin(self): + _Inkplate.init(I2C(0, scl=Pin(22), sda=Pin(21))) + + self.ipg = InkplateGS2() + self.ipm = InkplateMono() + self.ipp = InkplatePartial(self.ipm) + + self.GFX = GFX( + D_COLS, + D_ROWS, + self.writePixel, + self.writeFastHLine, + self.writeFastVLine, + self.writeFillRect, + None, + None, + ) + + @classmethod + def clearDisplay(self): + self.ipg.clear() + self.ipm.clear() + + @classmethod + def display(self): + if self.displayMode == 0: + self.ipm.display() + elif self.displayMode == 1: + self.ipg.display() + + @classmethod + def partialUpdate(self): + if self.displayMode == 1: + return + self.ipp.display() + + # Read the battery voltage. Note that the result depends on the ADC calibration, and be a bit off. + @classmethod + def read_battery(cls): + cls.VBAT_EN.value(0) + # Probably don't need to delay since Micropython is slow, but we do it anyway + time.sleep_ms(1) + value = cls.VBAT.read() + cls.VBAT_EN.value(1) + result = (value / 4095.0) * 1.1 * 3.548133892 * 2 + return result + + # Read panel temperature. I varies +- 2 degree + @classmethod + def read_temperature(cls): + # start temperature measurement and wait 5 ms + cls._i2c.writeto_mem(TPS65186_addr, 0x0D, bytes((0x80,))) + time.sleep_ms(5) + + # request temperature data from panel + cls._i2c.writeto(TPS65186_addr, bytearray((0x00,))) + cls._temperature = cls._i2c.readfrom(TPS65186_addr, 1) + + # convert data from bytes to integer + cls.temperatureInt = int.from_bytes(cls._temperature, "big", True) + return cls.temperatureInt + + # _tps65186_write writes an 8-bit value to a register + @classmethod + def _tps65186_write(cls, reg, v): + cls._i2c.writeto_mem(TPS65186_addr, reg, bytes((v,))) + + # _tps65186_read reads an 8-bit value from a register + @classmethod + def _tps65186_read(cls, reg): + cls._i2c.readfrom_mem(TPS65186_addr, reg, 1)[0] + + # power_on turns the voltage regulator on and wakes up the display (GMODE and OE) + @classmethod + def power_on(cls): + if cls._on: + return + cls._on = True + # turn on power regulator + cls.TPS_WAKEUP(1) + cls.TPS_PWRUP(1) + cls.TPS_VCOM(1) + # enable all rails + cls._tps65186_write(0x01, 0x3F) # ??? + time.sleep_ms(40) + cls._tps65186_write(0x0D, 0x80) # ??? + time.sleep_ms(2) + cls._temperature = cls._tps65186_read(1) + # wake-up display + cls.EPD_GMODE(1) + cls.EPD_OE(1) + + # power_off puts the display to sleep and cuts the power + # TODO: also tri-state gpio pins to avoid current leakage during deep-sleep + @classmethod + def power_off(cls): + if not cls._on: + return + cls._on = False + # put display to sleep + cls.EPD_GMODE(0) + cls.EPD_OE(0) + # turn off power regulator + cls.TPS_PWRUP(0) + cls.TPS_WAKEUP(0) + cls.TPS_VCOM(0) + + # ===== Methods that are independent of pixel bit depth + + # vscan_start begins a vertical scan by toggling CKV and SPV + # sleep_us calls are commented out 'cause MP is slow enough... + @classmethod + def vscan_start(cls): + def ckv_pulse(): + cls.EPD_CKV(0) + cls.EPD_CKV(1) + + # start a vertical scan pulse + cls.EPD_CKV(1) # time.sleep_us(7) + cls.EPD_SPV(0) # time.sleep_us(10) + ckv_pulse() # time.sleep_us(8) + cls.EPD_SPV(1) # time.sleep_us(10) + # pulse through 3 scan lines that end up being invisible + ckv_pulse() # time.sleep_us(18) + ckv_pulse() # time.sleep_us(18) + ckv_pulse() + + # vscan_write latches the row into the display pixels and moves to the next row + @micropython.viper + @staticmethod + def vscan_write(): + w1ts0 = ptr32(int(ESP32_GPIO + 4 * W1TS0)) + w1tc0 = ptr32(int(ESP32_GPIO + 4 * W1TC0)) + w1tc0[W1TC1 - W1TC0] = EPD_CKV # remove gate drive + w1ts0[0] = EPD_LE # pulse to latch row -- + w1ts0[0] = EPD_LE # delay a tiny bit + w1tc0[0] = EPD_LE + w1tc0[0] = EPD_LE # delay a tiny bit + w1ts0[W1TS1 - W1TS0] = EPD_CKV # apply gate drive to next row + + # byte2gpio converts a byte of data for the screen to 32 bits of gpio0..31 + # (oh, e-radionica, why didn't you group the gpios better?!) + byte2gpio = [] + + @classmethod + def gen_byte2gpio(cls): + cls.byte2gpio = array("L", bytes(4 * 256)) + for b in range(256): + cls.byte2gpio[b] = ( + (b & 0x3) << 4 | (b & 0xC) << 16 | ( + b & 0x10) << 19 | (b & 0xE0) << 20 + ) + # sanity check that all EPD_DATA bits got set at some point and no more + union = 0 + for i in range(256): + union |= cls.byte2gpio[i] + assert union == EPD_DATA + + # fill_screen writes the same value to all bytes of the screen, it is used for cleaning + @micropython.viper + @staticmethod + def fill_screen(data: int): + w1ts0 = ptr32(int(ESP32_GPIO + 4 * W1TS0)) + w1tc0 = ptr32(int(ESP32_GPIO + 4 * W1TC0)) + # set the data output gpios + w1tc0[0] = EPD_DATA | EPD_CL + w1ts0[0] = data + vscan_write = _Inkplate.vscan_write + epd_cl = EPD_CL + + # send all rows + for r in range(D_ROWS): + # send first byte of row with start-row signal + w1tc0[W1TC1 - W1TC0] = EPD_SPH + w1ts0[0] = epd_cl + w1tc0[0] = epd_cl + w1ts0[W1TS1 - W1TS0] = EPD_SPH + + # send remaining bytes (we overshoot by one, which is OK) + i = int(D_COLS >> 3) + while i > 0: + w1ts0[0] = epd_cl + w1tc0[0] = epd_cl + w1ts0[0] = epd_cl + w1tc0[0] = epd_cl + i -= 1 + + # latch row and increment to next + # inlined vscan_write() + w1tc0[W1TC1 - W1TC0] = EPD_CKV # remove gate drive + w1ts0[0] = EPD_LE # pulse to latch row -- + w1ts0[0] = EPD_LE # delay a tiny bit + w1tc0[0] = EPD_LE + w1tc0[0] = EPD_LE # delay a tiny bit + w1ts0[W1TS1 - W1TS0] = EPD_CKV # apply gate drive to next row + + # clean fills the screen with one of the four possible pixel patterns + @classmethod + def clean(cls, patt, rep): + c = [0xAA, 0x55, 0x00, 0xFF][patt] + data = _Inkplate.byte2gpio[c] & ~EPD_CL + for i in range(rep): + cls.vscan_start() + cls.fill_screen(data) + + +class InkplateMono(framebuf.FrameBuffer): + def __init__(self): + self._framebuf = bytearray(D_ROWS * D_COLS // 8) + super().__init__(self._framebuf, D_COLS, D_ROWS, framebuf.MONO_HMSB) + ip = InkplateMono + ip._gen_luts() + ip._wave = [ip.lut_blk, ip.lut_blk, ip.lut_blk, + ip.lut_blk, ip.lut_blk, ip.lut_bw] + + # gen_luts generates the look-up tables to convert a nibble (4 bits) of pixels to the + # 32-bits that need to be pushed into the gpio port. + # The LUTs used here were copied from the e-Radionica Inkplate-6-Arduino-library. + @classmethod + def _gen_luts(cls): + # is there a better way to init an array with 16 words??? + b16 = bytes(4 * 16) + # bits to ship to gpio to make pixels white + cls.lut_wht = array("L", b16) + # bits to ship to gpio to make pixels black + cls.lut_blk = array("L", b16) + # bits to ship to gpio to make pixels black and white + cls.lut_bw = array("L", b16) + for i in range(16): + wht = 0 + blk = 0 + bw = 0 + # display uses 2 bits per pixel: 00=discharge, 01=black, 10=white, 11=skip + for bit in range(4): + wht = wht | ((2 if (i >> bit) & 1 == 0 else 3) << (2 * bit)) + blk = blk | ((1 if (i >> bit) & 1 == 1 else 3) << (2 * bit)) + bw = bw | ((1 if (i >> bit) & 1 == 1 else 2) << (2 * bit)) + cls.lut_wht[i] = _Inkplate.byte2gpio[wht] | EPD_CL + cls.lut_blk[i] = _Inkplate.byte2gpio[blk] | EPD_CL + cls.lut_bw[i] = _Inkplate.byte2gpio[bw] | EPD_CL + # print("Black: %08x, White:%08x Data:%08x" % (cls.lut_bw[0xF], cls.lut_bw[0], EPD_DATA)) + + # _send_row writes a row of data to the display + @micropython.viper + @staticmethod + def _send_row(lut_in, framebuf, row: int): + ROW_LEN = D_COLS >> 3 # length of row in bytes + # cache vars into locals + w1ts0 = ptr32(int(ESP32_GPIO + 4 * W1TS0)) + w1tc0 = ptr32(int(ESP32_GPIO + 4 * W1TC0)) + off = int(EPD_DATA | EPD_CL) # mask with all data bits and clock bit + fb = ptr8(framebuf) + ix = int(row * ROW_LEN + ROW_LEN - 1) # index into framebuffer + lut = ptr32(lut_in) + # send first byte + data = int(fb[ix]) + ix -= 1 + w1tc0[0] = off + w1tc0[W1TC1 - W1TC0] = EPD_SPH + w1ts0[0] = lut[data >> 4] # set data bits and assert clock + # w1tc0[0] = EPD_CL # clear clock, leaving data bits (unreliable if data also cleared) + w1tc0[0] = off # clear data bits as well ready for next byte + w1ts0[W1TS1 - W1TS0] = EPD_SPH + w1ts0[0] = lut[data & 0xF] + # w1tc0[0] = EPD_CL + w1tc0[0] = off + # send the remaining bytes + for c in range(ROW_LEN - 1): + data = int(fb[ix]) + ix -= 1 + w1ts0[0] = lut[data >> 4] + # w1tc0[0] = EPD_CL + w1tc0[0] = off + w1ts0[0] = lut[data & 0xF] + # w1tc0[0] = EPD_CL + w1tc0[0] = off + + # display_mono sends the monochrome buffer to the display, clearing it first + def display(self): + ip = _Inkplate + ip.power_on() + + # clean the display + t0 = time.ticks_ms() + ip.clean(0, 1) + ip.clean(1, 12) + ip.clean(2, 1) + ip.clean(0, 11) + ip.clean(2, 1) + ip.clean(1, 12) + ip.clean(2, 1) + ip.clean(0, 11) + + # the display gets written N times + t1 = time.ticks_ms() + n = 0 + send_row = InkplateMono._send_row + vscan_write = ip.vscan_write + fb = self._framebuf + for lut in self._wave: + ip.vscan_start() + # write all rows + r = D_ROWS - 1 + while r >= 0: + send_row(lut, fb, r) + vscan_write() + r -= 1 + n += 1 + + t2 = time.ticks_ms() + tc = time.ticks_diff(t1, t0) + td = time.ticks_diff(t2, t1) + tt = time.ticks_diff(t2, t0) + print( + "Mono: clean %dms (%dms ea), draw %dms (%dms ea), total %dms" + % (tc, tc // (4 + 22 + 24), td, td // len(self._wave), tt) + ) + + ip.clean(2, 2) + ip.clean(3, 1) + ip.power_off() + + # @micropython.viper + def clear(self): + self.fill(0) + # fb = ptr8(self._framebuf) + # for ix in range(D_ROWS * D_COLS // 8): + # fb[ix] = 0 + + +Shapes.__mix_me_in(InkplateMono) + +# Inkplate display with 2 bits of gray scale (4 levels) + + +class InkplateGS2(framebuf.FrameBuffer): + _wave = None + + def __init__(self): + self._framebuf = bytearray(D_ROWS * D_COLS // 4) + super().__init__(self._framebuf, D_COLS, D_ROWS, framebuf.GS2_HMSB) + if not InkplateGS2._wave: + InkplateGS2._gen_wave() + + # _gen_wave generates the waveform table. The table consists of N phases or steps during + # each of which the entire display gets written. The array in each phase gets indexed with + # a nibble of data and contains the 32-bits that need to be pushed into the gpio port. + # The waveform used here was adapted from the e-Radionica Inkplate-6-Arduino-library + # by taking colors 0 (black), 3, 5, and 7 (white) from "waveform3Bit[8][7]". + @classmethod + def _gen_wave(cls): + # genlut generates the lookup table that maps a nibble (2 pixels, 4 bits) to a 32-bit + # word to push into the GPIO port + def genlut(op): + return bytes([op[j] | op[i] << 2 for i in range(4) for j in range(4)]) + + cls._wave = [genlut(w) for w in WAVE_2B] + + # _send_row writes a row of data to the display + @micropython.viper + @staticmethod + def _send_row(lut_in, framebuf, row: int): + ROW_LEN = D_COLS >> 2 # length of row in bytes + # cache vars into locals + w1ts0 = ptr32(int(ESP32_GPIO + 4 * W1TS0)) + w1tc0 = ptr32(int(ESP32_GPIO + 4 * W1TC0)) + off = int(EPD_DATA | EPD_CL) # mask with all data bits and clock bit + fb = ptr8(framebuf) + ix = int(row * ROW_LEN + (ROW_LEN - 1)) # index into framebuffer + lut = ptr8(lut_in) + b2g = ptr32(_Inkplate.byte2gpio) + # send first byte + data = int(fb[ix]) + ix -= 1 + w1tc0[0] = off + w1tc0[W1TC1 - W1TC0] = EPD_SPH + w1ts0[0] = b2g[lut[data >> 4] << 4 | lut[data & 0xF] + ] | EPD_CL # set data bits and clock + # w1tc0[0] = EPD_CL # clear clock, leaving data bits (unreliable if data also cleared) + w1tc0[0] = off # clear data bits as well ready for next byte + w1ts0[W1TS1 - W1TS0] = EPD_SPH + # send the remaining bytes + for c in range(ROW_LEN - 1): + data = int(fb[ix]) + ix -= 1 + w1ts0[0] = b2g[lut[data >> 4] << 4 | lut[data & 0xF]] | EPD_CL + # w1tc0[0] = EPD_CL + w1tc0[0] = off + + # display_mono sends the monochrome buffer to the display, clearing it first + def display(self): + ip = _Inkplate + ip.power_on() + + # clean the display + t0 = time.ticks_ms() + ip.clean(0, 1) + ip.clean(1, 12) + ip.clean(2, 1) + ip.clean(0, 11) + ip.clean(2, 1) + ip.clean(1, 12) + ip.clean(2, 1) + ip.clean(0, 11) + + # the display gets written N times + t1 = time.ticks_ms() + n = 0 + send_row = InkplateGS2._send_row + vscan_write = ip.vscan_write + fb = self._framebuf + for lut in InkplateGS2._wave: + ip.vscan_start() + # write all rows + r = D_ROWS - 1 + while r >= 0: + send_row(lut, fb, r) + vscan_write() + r -= 1 + n += 1 + + t2 = time.ticks_ms() + tc = time.ticks_diff(t1, t0) + td = time.ticks_diff(t2, t1) + tt = time.ticks_diff(t2, t0) + print( + "GS2: clean %dms (%dms ea), draw %dms (%dms ea), total %dms" + % (tc, tc // (4 + 22 + 24), td, td // len(InkplateGS2._wave), tt) + ) + + ip.clean(2, 1) # ?? + ip.clean(3, 1) + ip.power_off() + + # @micropython.viper + def clear(self): + self.fill(3) + # fb = ptr8(self._framebuf) + # for ix in range(int(len(self._framebuf))): + # fb[ix] = 0xFF + + +Shapes.__mix_me_in(InkplateGS2) + +# InkplatePartial managed partial updates. It starts by making a copy of the current framebuffer +# and then when asked to draw it renders the differences between the copy and the new framebuffer +# state. The constructor needs a reference to the current/main display object (InkplateMono). +# Only InkplateMono is supported at the moment. + + +class InkplatePartial: + def __init__(self, base): + self._base = base + self._framebuf = bytearray(len(base._framebuf)) + InkplatePartial._gen_lut_mono() + + # start makes a reference copy of the current framebuffer + def start(self): + self._framebuf[:] = self._base._framebuf[:] + + # display the changes between our reference copy and the current framebuffer contents + def display(self, x=0, y=0, w=D_COLS, h=D_ROWS): + ip = _Inkplate + ip.power_on() + + # the display gets written a couple of times + t0 = time.ticks_ms() + n = 0 + send_row = InkplatePartial._send_row + skip_rows = InkplatePartial._skip_rows + vscan_write = ip.vscan_write + nfb = self._base._framebuf # new framebuffer + ofb = self._framebuf # old framebuffer + lut = InkplatePartial._lut_mono + h -= 1 + for _ in range(5): + ip.vscan_start() + r = D_ROWS - 1 + # skip rows that supposedly have no change + if r > y + h: + skip_rows(r - (y + h)) + r = y + h + # write changed rows + while r >= y: + send_row(lut, ofb, nfb, r) + vscan_write() + r -= 1 + # skip remaining rows (doesn't seem to be necessary for Inkplate 6 but it is for 10) + if r > 0: + skip_rows(r) + n += 1 + + t1 = time.ticks_ms() + td = time.ticks_diff(t1, t0) + print( + "Partial: draw %dms (%dms/frame %dus/row) (y=%d..%d)" + % (td, td // n, td * 1000 // n // (D_ROWS - y), y, y + h + 1) + ) + + ip.clean(2, 2) + ip.clean(3, 1) + ip.power_off() + + # gen_lut_mono generates a look-up tables to change the display from a nibble of old + # pixels (4 bits = 4 pixels) to a nibble of new pixels. The LUT contains the + # 32-bits that need to be pushed into the gpio port to effect the change. + @classmethod + def _gen_lut_mono(cls): + lut = cls._lut_mono = array("L", bytes(4 * 256)) + for o in range(16): # iterate through all old-pixels combos + for n in range(16): # iterate through all new-pixels combos + bw = 0 + for bit in range(4): + # value to send to display: turns out that if we juxtapose the old and new + # bits we get the right value except for the 00 combination... + val = (((o >> bit) << 1) & 2) | ((n >> bit) & 1) + if val == 0: + val = 3 + bw = bw | (val << (2 * bit)) + lut[o * 16 + n] = _Inkplate.byte2gpio[bw] | EPD_CL + # print("Black: %08x, White:%08x Data:%08x" % (cls.lut_bw[0xF], cls.lut_bw[0], EPD_DATA)) + + # _skip_rows skips N rows + @micropython.viper + @staticmethod + def _skip_rows(rows: int): + if rows <= 0: + return + # cache vars into locals + w1ts0 = ptr32(int(ESP32_GPIO + 4 * W1TS0)) + w1tc0 = ptr32(int(ESP32_GPIO + 4 * W1TC0)) + + # need to fill the column latches with "no-change" values (all ones) + epd_cl = EPD_CL + w1tc0[0] = epd_cl + w1ts0[0] = EPD_DATA + # send first byte of row with start-row signal + w1tc0[W1TC1 - W1TC0] = EPD_SPH + w1ts0[0] = epd_cl + w1tc0[0] = epd_cl + w1ts0[W1TS1 - W1TS0] = EPD_SPH + # send remaining bytes + i = int(D_COLS >> 3) + while i > 0: + w1ts0[0] = epd_cl + w1tc0[0] = epd_cl + w1ts0[0] = epd_cl + w1tc0[0] = epd_cl + i -= 1 + + # write the same row over and over, weird thing is that we need the sleep otherwise + # the rows we subsequently draw don't draw proper whites leaving ghosts behind - hard to + # understand why the speed at which we "skip" rows affects rows that are drawn later... + while rows > 0: + _Inkplate.vscan_write() + rows -= 1 + time.sleep_us(50) + + # _send_row writes a row of data to the display + @micropython.viper + @staticmethod + def _send_row(lut_in, old_framebuf, new_framebuf, row: int): + ROW_LEN = D_COLS >> 3 # length of row in bytes + # cache vars into locals + w1ts0 = ptr32(int(ESP32_GPIO + 4 * W1TS0)) + w1tc0 = ptr32(int(ESP32_GPIO + 4 * W1TC0)) + off = int(EPD_DATA | EPD_CL) # mask with all data bits and clock bit + ofb = ptr8(old_framebuf) + nfb = ptr8(new_framebuf) + ix = int(row * ROW_LEN + (ROW_LEN - 1)) # index into framebuffer + lut = ptr32(lut_in) + # send first byte + odata = int(ofb[ix]) + ndata = int(nfb[ix]) + ix -= 1 + w1tc0[0] = off + w1tc0[W1TC1 - W1TC0] = EPD_SPH + if odata == ndata: + w1ts0[0] = off # send all-ones: no change to any of the pixels + w1tc0[0] = EPD_CL + w1ts0[W1TS1 - W1TS0] = EPD_SPH + w1ts0[0] = EPD_CL + w1tc0[0] = off + else: + w1ts0[0] = lut[(odata & 0xF0) + (ndata >> 4)] + w1tc0[0] = off # clear data bits as well ready for next byte + w1ts0[W1TS1 - W1TS0] = EPD_SPH + w1ts0[0] = lut[((odata & 0xF) << 4) + (ndata & 0xF)] + w1tc0[0] = off + # send the remaining bytes + for c in range(ROW_LEN - 1): + odata = int(ofb[ix]) + ndata = int(nfb[ix]) + ix -= 1 + if odata == ndata: + w1ts0[0] = off # send all-ones: no change to any of the pixels + w1tc0[0] = EPD_CL + w1ts0[0] = EPD_CL + w1tc0[0] = off + else: + w1ts0[0] = lut[(odata & 0xF0) + ((ndata >> 4) & 0xF)] + w1tc0[0] = off + w1ts0[0] = lut[((odata & 0xF) << 4) + (ndata & 0xF)] + w1tc0[0] = off + +# Inkplate wraper to make it more easy for use + + +class Inkplate: + INKPLATE_1BIT = 0 + INKPLATE_2BIT = 1 + + BLACK = 1 + WHITE = 0 + + _width = D_COLS + _height = D_ROWS + + rotation = 0 + displayMode = 0 + textSize = 1 + + def __init__(self, mode): + self.displayMode = mode + # try: + # os.mount( + # SDCard( + # slot=3, + # miso=Pin(12), + # mosi=Pin(13), + # sck=Pin(14), + # cs=Pin(15)), + # "/sd" + # ) + # except: + # print("Sd card could not be read") + + def begin(self): + _Inkplate.init(I2C(0, scl=Pin(22), sda=Pin(21))) + + self.ipg = InkplateGS2() + self.ipm = InkplateMono() + self.ipp = InkplatePartial(self.ipm) + + self.TOUCH1 = _Inkplate.TOUCH1 + self.TOUCH2 = _Inkplate.TOUCH2 + self.TOUCH3 = _Inkplate.TOUCH3 + + self.GFX = GFX( + D_COLS, + D_ROWS, + self.writePixel, + self.writeFastHLine, + self.writeFastVLine, + self.writeFillRect, + None, + None, + ) + + def clearDisplay(self): + self.ipm.clear() + self.ipg.clear() + + def display(self): + if self.displayMode == 0: + self.ipm.display() + elif self.displayMode == 1: + self.ipg.display() + + self.ipp.start() # making framebuffer copy for partial update + + def partialUpdate(self): + if self.displayMode == self.INKPLATE_2BIT: + return + self.ipp.display() + self.ipp.start() # making framebuffer copy for partial update + + def clean(self): + self.einkOn() + _Inkplate.clean(0, 1) + _Inkplate.clean(1, 12) + _Inkplate.clean(2, 1) + _Inkplate.clean(0, 11) + _Inkplate.clean(2, 1) + _Inkplate.clean(1, 12) + _Inkplate.clean(2, 1) + _Inkplate.clean(0, 11) + self.einkOff() + + def einkOn(self): + _Inkplate.power_on() + + def einkOff(self): + _Inkplate.power_off() + + def readBattery(self): + return _Inkplate.read_battery() + + def readTemperature(self): + return _Inkplate.read_temperature() + + def width(self): + return self._width + + def height(self): + return self._height + + # Arduino compatibility functions + def setRotation(self, x): + self.rotation = x % 4 + if self.rotation == 0 or self.rotation == 2: + self._width = D_COLS + self._height = D_ROWS + elif self.rotation == 1 or self.rotation == 3: + self._width = D_ROWS + self._height = D_COLS + + def getRotation(self): + return self.rotation + + def drawPixel(self, x, y, c): + self.startWrite() + self.writePixel(x, y, c) + self.endWrite() + + def startWrite(self): + pass + + def writePixel(self, x, y, c): + if x > self.width() - 1 or y > self.height() - 1 or x < 0 or y < 0: + return + if self.rotation == 1: + x, y = y, x + x = self.height() - x - 1 + elif self.rotation == 2: + x = self.width() - x - 1 + y = self.height() - y - 1 + elif self.rotation == 3: + x, y = y, x + y = self.width() - y - 1 + (self.ipm.pixel if self.displayMode == self.INKPLATE_1BIT else self.ipg.pixel)( + x, y, c + ) + + def writeFillRect(self, x, y, w, h, c): + for j in range(w): + for i in range(h): + self.writePixel(x + j, y + i, c) + + def writeFastVLine(self, x, y, h, c): + for i in range(h): + self.writePixel(x, y + i, c) + + def writeFastHLine(self, x, y, w, c): + for i in range(w): + self.writePixel(x + i, y, c) + + def writeLine(self, x0, y0, x1, y1, c): + self.GFX.line(x0, y0, x1, y1, c) + + def endWrite(self): + pass + + def drawFastVLine(self, x, y, h, c): + self.startWrite() + self.writeFastVLine(x, y, h, c) + self.endWrite() + + def drawFastHLine(self, x, y, w, c): + self.startWrite() + self.writeFastHLine(x, y, w, c) + self.endWrite() + + def fillRect(self, x, y, w, h, c): + self.startWrite() + self.writeFillRect(x, y, w, h, c) + self.endWrite() + + def fillScreen(self, c): + self.fillRect(0, 0, self.width(), self.height(), c) + + def drawLine(self, x0, y0, x1, y1, c): + self.startWrite() + self.writeLine(x0, y0, x1, y1, c) + self.endWrite() + + def drawRect(self, x, y, w, h, c): + self.GFX.rect(x, y, w, h, c) + + def drawCircle(self, x, y, r, c): + self.GFX.circle(x, y, r, c) + + def fillCircle(self, x, y, r, c): + self.GFX.fill_circle(x, y, r, c) + + def drawTriangle(self, x0, y0, x1, y1, x2, y2, c): + self.GFX.triangle(x0, y0, x1, y1, x2, y2, c) + + def fillTriangle(self, x0, y0, x1, y1, x2, y2, c): + self.GFX.fill_triangle(x0, y0, x1, y1, x2, y2, c) + + def drawRoundRect(self, x, y, q, h, r, c): + self.GFX.round_rect(x, y, q, h, r, c) + + def fillRoundRect(self, x, y, q, h, r, c): + self.GFX.fill_round_rect(x, y, q, h, r, c) + + def setDisplayMode(self, mode): + self.displayMode = mode + + def selectDisplayMode(self, mode): + self.displayMode = mode + + def getDisplayMode(self): + return self.displayMode + + def setTextSize(self, s): + self.textSize = s + + def setFont(self, f): + self.GFX.font = f + + def printText(self, x, y, s): + self.GFX._very_slow_text(x, y, s, self.textSize, 1) + + def drawBitmap(self, x, y, data, w, h): + byteWidth = (w + 7) // 8 + byte = 0 + self.startWrite() + for j in range(h): + for i in range(w): + if i & 7: + byte <<= 1 + else: + byte = data[j * byteWidth + i // 8] + if byte & 0x80: + self.writePixel(x + i, y + j, 1) + self.endWrite() + + def drawImageFile(self, x, y, path, invert=False): + with open(path, "rb") as f: + header14 = f.read(14) + if header14[0] != 0x42 or header14[1] != 0x4D: + return 0 + header40 = f.read(40) + + w = int( + (header40[7] << 24) + + (header40[6] << 16) + + (header40[5] << 8) + + header40[4] + ) + h = int( + (header40[11] << 24) + + (header40[10] << 16) + + (header40[9] << 8) + + header40[8] + ) + dataStart = int((header14[11] << 8) + header14[10]) + + depth = int((header40[15] << 8) + header40[14]) + totalColors = int((header40[33] << 8) + header40[32]) + + rowSize = 4 * ((depth * w + 31) // 32) + + if totalColors == 0: + totalColors = 1 << depth + + palette = None + + if depth <= 8: + palette = [0 for i in range(totalColors)] + p = f.read(totalColors * 4) + for i in range(totalColors): + palette[i] = ( + 54 * p[i * 4] + 183 * p[i * 4 + 1] + 19 * p[i * 4 + 2] + ) >> 14 + # print(palette) + f.seek(dataStart) + for j in range(h): + # print(100 * j // h, "% complete") + buffer = f.read(rowSize) + for i in range(w): + val = 0 + if depth == 1: + px = int( + invert + ^ (palette[0] < palette[1]) + ^ bool(buffer[i >> 3] & (1 << (7 - i & 7))) + ) + val = palette[px] + elif depth == 4: + px = (buffer[i >> 1] & (0x0F if i & 1 == 1 else 0xF0)) >> ( + 0 if i & 1 else 4 + ) + val = palette[px] + if invert: + val = 3 - val + elif depth == 8: + px = buffer[i] + val = palette[px] + if invert: + val = 3 - val + elif depth == 16: + px = (buffer[(i << 1) | 1] << 8) | buffer[(i << 1)] + + r = (px & 0x7C00) >> 7 + g = (px & 0x3E0) >> 2 + b = (px & 0x1F) << 3 + + val = (54 * r + 183 * g + 19 * b) >> 14 + + if invert: + val = 3 - val + elif depth == 24: + r = buffer[i * 3] + g = buffer[i * 3 + 1] + b = buffer[i * 3 + 2] + + val = (54 * r + 183 * g + 19 * b) >> 14 + + if invert: + val = 3 - val + elif depth == 32: + r = buffer[i * 4] + g = buffer[i * 4 + 1] + b = buffer[i * 4 + 2] + + val = (54 * r + 183 * g + 19 * b) >> 14 + + if invert: + val = 3 - val + + if self.getDisplayMode() == self.INKPLATE_1BIT: + val >>= 1 + + self.drawPixel(x + i, y + h - j, val) diff --git a/ports/esp32/boards/INKPLATE6/modules/mcp23017.py b/ports/esp32/boards/INKPLATE6/modules/mcp23017.py new file mode 100644 index 0000000000..e576735a48 --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/modules/mcp23017.py @@ -0,0 +1,84 @@ +# Copyright © 2020 by Thorsten von Eicken. +from machine import Pin as mPin +from micropython import const + +# MCP23017 registers - defined as const(), which makes them module-global +IODIR = const(0) +IOCON = const(0xA) +GPPU = const(0xC) +GPIO = const(0x12) +OLAT = const(0x14) + + +# MCP23017 is a minimal driver for an 16-bit I2C I/O expander +class MCP23017: + def __init__(self, i2c, addr=0x20): + self.i2c = i2c + self.addr = addr + self.write(IOCON, 0x00) + self.write2(IODIR, 0xFF, 0xFF) # all inputs + self.gpio0 = 0 + self.gpio1 = 0 + self.write2(GPIO, 0, 0) + + # read an 8-bit register, internal method + def read(self, reg): + return self.i2c.readfrom_mem(self.addr, reg, 1)[0] + + # write an 8-bit register, internal method + def write(self, reg, v): + self.i2c.writeto_mem(self.addr, reg, bytes((v,))) + + # write two 8-bit registers, internal method + def write2(self, reg, v1, v2): + self.i2c.writeto_mem(self.addr, reg, bytes((v1, v2))) + + # writebuf writes multiple bytes to the same register + def writebuf(self, reg, v): + self.i2c.writeto_mem(self.addr, reg, v) + + # bit reads or sets a bit in a register, caching the gpio register for performance + def bit(self, reg, num, v=None): + if v is None: + data = self.read(reg) + if reg == GPIO: + self.gpio0 = data + elif reg == GPIO + 1: + self.gpio1 = data + return (data >> num) & 1 + else: + mask = 0xFF ^ (1 << num) + if reg == GPIO: + self.gpio0 = (self.gpio0 & mask) | ((v & 1) << num) + self.write(reg, self.gpio0) + elif reg == GPIO + 1: + self.gpio1 = (self.gpio1 & mask) | ((v & 1) << num) + self.write(reg, self.gpio1) + else: + data = (self.read(reg) & mask) | ((v & 1) << num) + self.write(reg, data) + + def pin(self, num, mode=mPin.IN, pull=None, value=None): + return Pin(self, num, mode, pull, value) + + +# Pin implements a minimal machine.Pin look-alike for pins on the MCP23017 +class Pin: + def __init__(self, mcp23017, num, mode=mPin.IN, pull=None, value=None): + self.bit = mcp23017.bit + incr = num >> 3 # bank selector + self.gpio = GPIO + incr + self.num = num = num & 0x7 + if value is not None: + self.bit(self.gpio, num, value) + self.bit(IODIR + incr, num, 1 if mode == mPin.IN else 0) + self.bit(GPPU + incr, num, 1 if pull == mPin.PULL_UP else 0) + + # value reads or write a pin value (0 or 1) + def value(self, v=None): + if v is None: + return self.bit(self.gpio, self.num) + else: + self.bit(self.gpio, self.num, v) + + __call__ = value diff --git a/ports/esp32/boards/INKPLATE6/modules/shapes.py b/ports/esp32/boards/INKPLATE6/modules/shapes.py new file mode 100644 index 0000000000..32870ad327 --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/modules/shapes.py @@ -0,0 +1,252 @@ +# Copyright © 2020 by Thorsten von Eicken. + +# Shapes is intended to be a mix-in that adds methods to a class that is derived from the +# MicroPython framebuf.FrameBuffer class. It adds methods to draw circles, rounded rectangles +# etc. All the code is cobbled together from elsewhere, please refer to the embedded copyright +# notices for the exact provenance. +# Methods in Shapes call self.pixel, self.line, self.hline, and self.vline, so these must be +# provided by the class that Shapes is mixed into. +class Shapes: + + # __mix_me_in adds the methods of this class to another class. Typical usage is to put + # something like Shapes.__mix_me_in(MyClass) after the class definition. + @classmethod + def __mix_me_in(cls, target): + for func in dir(cls): + if not callable(getattr(cls, func)) or func.startswith("__"): + continue + setattr(target, func, getattr(cls, func)) + + # The following shapes are adapted from https://github.com/peterhinch/micropython-epaper + # Copyright 2015 Peter Hinch + # + # Licensed under the Apache License, Version 2.0 (the "License") you may not use this + # file except in compliance with the License. You may obtain a copy of the License at: + # + # http:#www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software distributed under + # the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + # KIND, either express or implied. See the License for the specific language governing + # permissions and limitations under the License. + # Code translated and developed from https://developer.mbed.org/users/dreschpe/code/EaEpaper/ + + def circle(self, x0, y0, r, color): # Single pixel circle + x = -r + y = 0 + err = 2 - 2 * r + while x <= 0: + self.pixel(x0 - x, y0 + y, color) + self.pixel(x0 + x, y0 + y, color) + self.pixel(x0 + x, y0 - y, color) + self.pixel(x0 - x, y0 - y, color) + e2 = err + if e2 <= y: + y += 1 + err += y * 2 + 1 + if -x == y and e2 <= x: + e2 = 0 + if e2 > x: + x += 1 + err += x * 2 + 1 + + def fill_circle(self, x0, y0, r, color): # Draw filled circle + x0, y0, r = int(x0), int(y0), int(r) + x = -r + y = 0 + err = 2 - 2 * r + while x <= 0: + self.vline(x0 - x, y0 - y, 2*y+1, color) + self.vline(x0 + x, y0 - y, 2*y+1, color) + e2 = err + if e2 <= y: + y += 1 + err += y * 2 + 1 + if -x == y and e2 <= x: + e2 = 0 + if e2 > x: + x += 1 + err += x * 2 + 1 + + # The following shapes are adapted from https://github.com/adafruit/Adafruit_CircuitPython_GFX + # The MIT License (MIT) + # + # Copyright (c) 2018 Kattni Rembor for Adafruit Industries + # + # 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. + + def triangle(self, x0, y0, x1, y1, x2, y2, color): + """Triangle drawing function. Will draw a single pixel wide triangle + around the points (x0, y0), (x1, y1), and (x2, y2).""" + self.line(x0, y0, x1, y1, color) + self.line(x1, y1, x2, y2, color) + self.line(x2, y2, x0, y0, color) + + def fill_triangle(self, x0, y0, x1, y1, x2, y2, color): + """Filled triangle drawing function. Will draw a filled triangle around + the points (x0, y0), (x1, y1), and (x2, y2).""" + if y0 > y1: + y0, y1 = y1, y0 + x0, x1 = x1, x0 + if y1 > y2: + y2, y1 = y1, y2 + x2, x1 = x1, x2 + if y0 > y1: + y0, y1 = y1, y0 + x0, x1 = x1, x0 + a = 0 + b = 0 + y = 0 + last = 0 + if y0 == y2: + a = x0 + b = x0 + if x1 < a: + a = x1 + elif x1 > b: + b = x1 + if x2 < a: + a = x2 + elif x2 > b: + b = x2 + self.hline(a, y0, b - a + 1, color) + return + dx01 = x1 - x0 + dy01 = y1 - y0 + dx02 = x2 - x0 + dy02 = y2 - y0 + dx12 = x2 - x1 + dy12 = y2 - y1 + if dy01 == 0: + dy01 = 1 + if dy02 == 0: + dy02 = 1 + if dy12 == 0: + dy12 = 1 + sa = 0 + sb = 0 + if y1 == y2: + last = y1 + else: + last = y1 - 1 + for y in range(y0, last + 1): + a = x0 + sa // dy01 + b = x0 + sb // dy02 + sa += dx01 + sb += dx02 + if a > b: + a, b = b, a + self.hline(a, y, b - a + 1, color) + sa = dx12 * (y - y1) + sb = dx02 * (y - y0) + while y <= y2: + a = x1 + sa // dy12 + b = x0 + sb // dy02 + sa += dx12 + sb += dx02 + if a > b: + a, b = b, a + self.hline(a, y, b - a + 1, color) + y += 1 + + def round_rect(self, x0, y0, width, height, radius, color): + """Rectangle with rounded corners drawing function. + This works like a regular rect though! if radius = 0 + Will draw the outline of a rextabgle with rounded corners with (x0,y0) at the top left""" + # shift to correct for start point location + x0 += radius + y0 += radius + + # ensure that the radius will only ever half of the shortest side or less + radius = int(min(radius, width / 2, height / 2)) + + if radius: + f = 1 - radius + ddF_x = 1 + ddF_y = -2 * radius + x = 0 + y = radius + self.vline(x0 - radius, y0, height - 2 * radius + 1, color) # left + self.vline(x0 + width - radius, y0, height - 2 * radius + 1, color) # right + self.hline(x0, y0 + height - radius + 1, width - 2 * radius + 1, color) # bottom + self.hline(x0, y0 - radius, width - 2 * radius + 1, color) # top + while x < y: + if f >= 0: + y -= 1 + ddF_y += 2 + f += ddF_y + x += 1 + ddF_x += 2 + f += ddF_x + # angle notations are based on the unit circle and in diection of being drawn + + # top left + self.pixel(x0 - y, y0 - x, color) # 180 to 135 + self.pixel(x0 - x, y0 - y, color) # 90 to 135 + # top right + self.pixel(x0 + x + width - 2 * radius, y0 - y, color) # 90 to 45 + self.pixel(x0 + y + width - 2 * radius, y0 - x, color) # 0 to 45 + # bottom right + self.pixel( + x0 + y + width - 2 * radius, y0 + x + height - 2 * radius, color + ) # 0 to 315 + self.pixel( + x0 + x + width - 2 * radius, y0 + y + height - 2 * radius, color + ) # 270 to 315 + # bottom left + self.pixel(x0 - x, y0 + y + height - 2 * radius, color) # 270 to 255 + self.pixel(x0 - y, y0 + x + height - 2 * radius, color) # 180 to 225 + + def fill_round_rect(self, x0, y0, width, height, radius, color): + """Filled circle drawing function. Will draw a filled circule with + center at x0, y0 and the specified radius.""" + # shift to correct for start point location + x0 += radius + y0 += radius + + # ensure that the radius will only ever half of the shortest side or less + radius = int(min(radius, width / 2, height / 2)) + + self.fill_rect(x0, y0 - radius, width - 2 * radius + 2, height + 2, color) + + if radius: + f = 1 - radius + ddF_x = 1 + ddF_y = -2 * radius + x = 0 + y = radius + while x < y: + if f >= 0: + y -= 1 + ddF_y += 2 + f += ddF_y + x += 1 + ddF_x += 2 + f += ddF_x + # part notation starts with 0 on left and 1 on right, and direction is noted + # top left + self.vline(x0 - y, y0 - x, 2 * x + 1 + height - 2 * radius, color) # 0 to .25 + self.vline(x0 - x, y0 - y, 2 * y + 1 + height - 2 * radius, color) # .5 to .25 + # top right + self.vline( + x0 + x + width - 2 * radius, y0 - y, 2 * y + 1 + height - 2 * radius, color + ) # .5 to .75 + self.vline( + x0 + y + width - 2 * radius, y0 - x, 2 * x + 1 + height - 2 * radius, color + ) # 1 to .75 diff --git a/ports/esp32/boards/INKPLATE6/mpconfigboard.cmake b/ports/esp32/boards/INKPLATE6/mpconfigboard.cmake new file mode 100644 index 0000000000..bc996a312f --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/mpconfigboard.cmake @@ -0,0 +1,8 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base + boards/sdkconfig.ble + boards/sdkconfig.spiram +) +if(NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) +endif() diff --git a/ports/esp32/boards/INKPLATE6/mpconfigboard.h b/ports/esp32/boards/INKPLATE6/mpconfigboard.h new file mode 100644 index 0000000000..0803a3b4c7 --- /dev/null +++ b/ports/esp32/boards/INKPLATE6/mpconfigboard.h @@ -0,0 +1,8 @@ +#define MICROPY_HW_BOARD_NAME "Inkplate6" +#define MICROPY_HW_MCU_NAME "ESP32" + +// easyC I2C port +#define MICROPY_HW_I2C0_SCL (22) +#define MICROPY_HW_I2C0_SDA (21) + +