diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2/adafruit_dotstar.py b/ports/esp32s2/boards/unexpectedmaker_feathers2/adafruit_dotstar.py new file mode 100755 index 0000000000..65fbbe8683 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2/adafruit_dotstar.py @@ -0,0 +1,198 @@ +# The MIT License (MIT) +# +# Copyright (c) 2016 Damien P. George (original Neopixel object) +# Copyright (c) 2017 Ladyada +# Copyright (c) 2017 Scott Shawcroft for Adafruit Industries +# Copyright (c) 2019 Roy Hooper +# +# 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. + +""" +`adafruit_dotstar` - DotStar strip driver (for CircuitPython 5.0+ with _pixelbuf) +================================================================================= + +* Author(s): Damien P. George, Limor Fried, Scott Shawcroft & Roy Hooper +""" + +# pylint: disable=ungrouped-imports +import sys +import busio +import digitalio + +if sys.implementation.version[0] < 5: + import adafruit_pypixelbuf as _pixelbuf +else: + try: + import _pixelbuf + except ImportError: + import adafruit_pypixelbuf as _pixelbuf + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DotStar.git" + +START_HEADER_SIZE = 4 + +# Pixel color order constants +RBG = "PRBG" +"""Red Blue Green""" +RGB = "PRGB" +"""Red Green Blue""" +GRB = "PGRB" +"""Green Red Blue""" +GBR = "PGBR" +"""Green Blue Red""" +BRG = "PBRG" +"""Blue Red Green""" +BGR = "PBGR" +"""Blue Green Red""" + + +class DotStar(_pixelbuf.PixelBuf): + """ + A sequence of dotstars. + + :param ~microcontroller.Pin clock: The pin to output dotstar clock on. + :param ~microcontroller.Pin data: The pin to output dotstar data on. + :param int n: The number of dotstars in the chain + :param float brightness: Brightness of the pixels between 0.0 and 1.0 + :param bool auto_write: True if the dotstars should immediately change when + set. If False, `show` must be called explicitly. + :param str pixel_order: Set the pixel order on the strip - different + strips implement this differently. If you send red, and it looks blue + or green on the strip, modify this! It should be one of the values above. + :param int baudrate: Desired clock rate if using hardware SPI (ignored if + using 'soft' SPI). This is only a recommendation; the actual clock + rate may be slightly different depending on what the system hardware + can provide. + + Example for Gemma M0: + + .. code-block:: python + + import adafruit_dotstar + import time + from board import * + + RED = 0x100000 + + with adafruit_dotstar.DotStar(APA102_SCK, APA102_MOSI, 1) as pixels: + pixels[0] = RED + time.sleep(2) + + .. py:method:: DotStar.show() + + Shows the new colors on the dotstars themselves if they haven't already + been autowritten. + + The colors may or may not be showing after this function returns because + it may be done asynchronously. + + .. py:method:: DotStar.fill(color) + + Colors all dotstars the given ***color***. + + .. py:attribute:: brightness + + Overall brightness of all dotstars (0 to 1.0) + """ + + def __init__( + self, + clock, + data, + n, + *, + brightness=1.0, + auto_write=True, + pixel_order=BGR, + baudrate=4000000 + ): + self._spi = None + try: + self._spi = busio.SPI(clock, MOSI=data) + while not self._spi.try_lock(): + pass + self._spi.configure(baudrate=baudrate) + + except (NotImplementedError, ValueError): + self.dpin = digitalio.DigitalInOut(data) + self.cpin = digitalio.DigitalInOut(clock) + self.dpin.direction = digitalio.Direction.OUTPUT + self.cpin.direction = digitalio.Direction.OUTPUT + self.cpin.value = False + + # Supply one extra clock cycle for each two pixels in the strip. + trailer_size = n // 16 + if n % 16 != 0: + trailer_size += 1 + + # Four empty bytes for the header. + header = bytearray(START_HEADER_SIZE) + # 0xff bytes for the trailer. + trailer = bytearray(b"\xff") * trailer_size + + super().__init__( + n, + byteorder=pixel_order, + brightness=brightness, + auto_write=auto_write, + header=header, + trailer=trailer, + ) + + def deinit(self): + """Blank out the DotStars and release the resources.""" + self.fill(0) + self.show() + if self._spi: + self._spi.deinit() + else: + self.dpin.deinit() + self.cpin.deinit() + + def __enter__(self): + return self + + def __exit__(self, exception_type, exception_value, traceback): + self.deinit() + + def __repr__(self): + return "[" + ", ".join([str(x) for x in self]) + "]" + + @property + def n(self): + """ + The number of dotstars in the chain (read-only) + """ + return len(self) + + def _transmit(self, buffer): + if self._spi: + self._spi.write(buffer) + else: + self._ds_writebytes(buffer) + + def _ds_writebytes(self, buffer): + for b in buffer: + for _ in range(8): + self.dpin.value = b & 0x80 + self.cpin.value = True + self.cpin.value = False + b = b << 1 + self.cpin.value = False diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2/adafruit_pypixelbuf.py b/ports/esp32s2/boards/unexpectedmaker_feathers2/adafruit_pypixelbuf.py new file mode 100755 index 0000000000..3cb83e970c --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2/adafruit_pypixelbuf.py @@ -0,0 +1,374 @@ +# The MIT License (MIT) +# +# Based on the Adafruit NeoPixel and Adafruit Dotstar CircuitPython drivers. +# Copyright (c) 2019-2020 Roy Hooper +# +# 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. + +""" +`adafruit_pypixelbuf` - A pure python implementation of _pixelbuf +================================================================= +This class is used when _pixelbuf is not available in CircuitPython. It is based on the work +in neopixel.py and adafruit_dotstar.py. + +* Author(s): Damien P. George & Limor Fried & Scott Shawcroft & Roy Hooper +""" + +DOTSTAR_LED_START_FULL_BRIGHT = 0xFF +DOTSTAR_LED_START = 0b11100000 # Three "1" bits, followed by 5 brightness bits +DOTSTAR_LED_BRIGHTNESS = 0b00011111 + + +class PixelBuf: # pylint: disable=too-many-instance-attributes + """ + A sequence of RGB/RGBW pixels. + + This is the pure python implementation of CircuitPython's _pixelbuf. + + :param ~int n: Number of pixels + :param ~str byteorder: Byte order string constant (also sets bpp) + :param ~float brightness: Brightness (0 to 1.0, default 1.0) + :param ~bool auto_write: Whether to automatically write pixels (Default False) + :param bytes header: Sequence of bytes to always send before pixel values. + :param bytes trailer: Sequence of bytes to always send after pixel values. + """ + + def __init__( # pylint: disable=too-many-locals,too-many-arguments + self, + n, + byteorder="BGR", + brightness=1.0, + auto_write=False, + header=None, + trailer=None, + ): + + bpp, byteorder_tuple, has_white, dotstar_mode = self.parse_byteorder(byteorder) + + self.auto_write = False + + effective_bpp = 4 if dotstar_mode else bpp + _bytes = effective_bpp * n + buf = bytearray(_bytes) + offset = 0 + + if header is not None: + if not isinstance(header, bytearray): + raise TypeError("header must be a bytearray") + buf = header + buf + offset = len(header) + + if trailer is not None: + if not isinstance(trailer, bytearray): + raise TypeError("trailer must be a bytearray") + buf += trailer + + self._pixels = n + self._bytes = _bytes + self._byteorder = byteorder_tuple + self._byteorder_string = byteorder + self._has_white = has_white + self._bpp = bpp + self._pre_brightness_buffer = None + self._post_brightness_buffer = buf + self._offset = offset + self._dotstar_mode = dotstar_mode + self._pixel_step = effective_bpp + + if dotstar_mode: + self._byteorder_tuple = ( + byteorder_tuple[0] + 1, + byteorder_tuple[1] + 1, + byteorder_tuple[2] + 1, + 0, + ) + # Initialize the buffer with the dotstar start bytes. + for i in range(self._offset, self._bytes + self._offset, 4): + self._post_brightness_buffer[i] = DOTSTAR_LED_START_FULL_BRIGHT + + self._brightness = 1.0 + self.brightness = brightness + + self.auto_write = auto_write + + @staticmethod + def parse_byteorder(byteorder): + """ + Parse a Byteorder string for validity and determine bpp, byte order, and + dostar brightness bits. + + Byteorder strings may contain the following characters: + R - Red + G - Green + B - Blue + W - White + P - PWM (PWM Duty cycle for pixel - dotstars 0 - 1.0) + + :param: ~str bpp: bpp string. + :return: ~tuple: bpp, byteorder, has_white, dotstar_mode + """ + bpp = len(byteorder) + dotstar_mode = False + has_white = False + + if byteorder.strip("RGBWP") != "": + raise ValueError("Invalid Byteorder string") + + try: + r = byteorder.index("R") + g = byteorder.index("G") + b = byteorder.index("B") + except ValueError: + raise ValueError("Invalid Byteorder string") + if "W" in byteorder: + w = byteorder.index("W") + byteorder = (r, g, b, w) + has_white = True + elif "P" in byteorder: + lum = byteorder.index("P") + byteorder = (r, g, b, lum) + dotstar_mode = True + else: + byteorder = (r, g, b) + + return bpp, byteorder, has_white, dotstar_mode + + @property + def bpp(self): + """ + The number of bytes per pixel in the buffer (read-only). + """ + return self._bpp + + @property + def brightness(self): + """ + Float value between 0 and 1. Output brightness. + + When brightness is less than 1.0, a second buffer will be used to store the color values + before they are adjusted for brightness. + """ + return self._brightness + + @brightness.setter + def brightness(self, value): + value = min(max(value, 0.0), 1.0) + change = value - self._brightness + if -0.001 < change < 0.001: + return + + self._brightness = value + + if self._pre_brightness_buffer is None: + self._pre_brightness_buffer = bytearray(self._post_brightness_buffer) + + # Adjust brightness of existing pixels + offset_check = self._offset % self._pixel_step + for i in range(self._offset, self._bytes + self._offset): + # Don't adjust per-pixel luminance bytes in dotstar mode + if self._dotstar_mode and (i % 4 != offset_check): + continue + self._post_brightness_buffer[i] = int( + self._pre_brightness_buffer[i] * self._brightness + ) + + if self.auto_write: + self.show() + + @property + def byteorder(self): + """ + ByteOrder string for the buffer (read-only) + """ + return self._byteorder_string + + def __len__(self): + """ + Number of pixels. + """ + return self._pixels + + def show(self): + """ + Call the associated write function to display the pixels + """ + return self._transmit(self._post_brightness_buffer) + + def fill(self, color): + """ + Fills the given pixelbuf with the given color. + :param pixelbuf: A pixel object. + :param color: Color to set. + """ + r, g, b, w = self._parse_color(color) + for i in range(self._pixels): + self._set_item(i, r, g, b, w) + if self.auto_write: + self.show() + + def _parse_color(self, value): + r = 0 + g = 0 + b = 0 + w = 0 + if isinstance(value, int): + r = value >> 16 + g = (value >> 8) & 0xFF + b = value & 0xFF + w = 0 + + if self._dotstar_mode: + w = 1.0 + else: + if len(value) < 3 or len(value) > 4: + raise ValueError( + "Expected tuple of length {}, got {}".format(self._bpp, len(value)) + ) + if len(value) == self._bpp: + if self._bpp == 3: + r, g, b = value + else: + r, g, b, w = value + elif len(value) == 3: + r, g, b = value + if self._dotstar_mode: + w = 1.0 + + if self._bpp == 4: + if self._dotstar_mode: + # LED startframe is three "1" bits, followed by 5 brightness bits + # then 8 bits for each of R, G, and B. The order of those 3 are configurable and + # vary based on hardware + w = (int(w * 31) & 0b00011111) | DOTSTAR_LED_START + elif ( + self._has_white + and (isinstance(value, int) or len(value) == 3) + and r == g + and g == b + ): + # If all components are the same and we have a white pixel then use it + # instead of the individual components when all 4 values aren't explicitly given. + w = r + r = 0 + g = 0 + b = 0 + + return (r, g, b, w) + + def _set_item( + self, index, r, g, b, w + ): # pylint: disable=too-many-locals,too-many-branches,too-many-arguments + if index < 0: + index += len(self) + if index >= self._pixels or index < 0: + raise IndexError + offset = self._offset + (index * self._bpp) + + if self._pre_brightness_buffer is not None: + if self._bpp == 4: + self._pre_brightness_buffer[offset + self._byteorder[3]] = w + self._pre_brightness_buffer[offset + self._byteorder[0]] = r + self._pre_brightness_buffer[offset + self._byteorder[1]] = g + self._pre_brightness_buffer[offset + self._byteorder[2]] = b + + if self._bpp == 4: + # Only apply brightness if w is actually white (aka not DotStar.) + if not self._dotstar_mode: + w = int(w * self._brightness) + self._post_brightness_buffer[offset + self._byteorder[3]] = w + + self._post_brightness_buffer[offset + self._byteorder[0]] = int( + r * self._brightness + ) + self._post_brightness_buffer[offset + self._byteorder[1]] = int( + g * self._brightness + ) + self._post_brightness_buffer[offset + self._byteorder[2]] = int( + b * self._brightness + ) + + def __setitem__(self, index, val): + if isinstance(index, slice): + start, stop, step = index.indices(self._pixels) + for val_i, in_i in enumerate(range(start, stop, step)): + r, g, b, w = self._parse_color(val[val_i]) + self._set_item(in_i, r, g, b, w) + else: + r, g, b, w = self._parse_color(val) + self._set_item(index, r, g, b, w) + + if self.auto_write: + self.show() + + def _getitem(self, index): + start = self._offset + (index * self._bpp) + buffer = ( + self._pre_brightness_buffer + if self._pre_brightness_buffer is not None + else self._post_brightness_buffer + ) + value = [ + buffer[start + self._byteorder[0]], + buffer[start + self._byteorder[1]], + buffer[start + self._byteorder[2]], + ] + if self._has_white: + value.append(buffer[start + self._byteorder[3]]) + elif self._dotstar_mode: + value.append( + (buffer[start + self._byteorder[3]] & DOTSTAR_LED_BRIGHTNESS) / 31.0 + ) + return value + + def __getitem__(self, index): + if isinstance(index, slice): + out = [] + for in_i in range( + *index.indices(len(self._post_brightness_buffer) // self._bpp) + ): + out.append(self._getitem(in_i)) + return out + if index < 0: + index += len(self) + if index >= self._pixels or index < 0: + raise IndexError + return self._getitem(index) + + def _transmit(self, buffer): + raise NotImplementedError("Must be subclassed") + + +def wheel(pos): + """ + Helper to create a colorwheel. + + :param pos: int 0-255 of color value to return + :return: tuple of RGB values + """ + # Input a value 0 to 255 to get a color value. + # The colours are a transition r - g - b - back to r. + if pos < 0 or pos > 255: + return 0, 0, 0 + if pos < 85: + return 255 - pos * 3, pos * 3, 0 + if pos < 170: + pos -= 85 + return 0, 255 - pos * 3, pos * 3 + pos -= 170 + return pos * 3, 0, 255 - pos * 3 diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2/adafruit_requests.py b/ports/esp32s2/boards/unexpectedmaker_feathers2/adafruit_requests.py new file mode 100755 index 0000000000..0888937bf8 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2/adafruit_requests.py @@ -0,0 +1,311 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019 ladyada 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. +""" +`adafruit_requests` +================================================================================ + +A requests-like library for web interfacing + + +* Author(s): ladyada, Paul Sokolovsky + +Implementation Notes +-------------------- + +Adapted from https://github.com/micropython/micropython-lib/tree/master/urequests + +micropython-lib consists of multiple modules from different sources and +authors. Each module comes under its own licensing terms. Short name of +a license can be found in a file within a module directory (usually +metadata.txt or setup.py). Complete text of each license used is provided +at https://github.com/micropython/micropython-lib/blob/master/LICENSE + +author='Paul Sokolovsky' +license='MIT' + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +import gc + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Requests.git" + +_the_interface = None # pylint: disable=invalid-name +_the_sock = None # pylint: disable=invalid-name + + +def set_socket(sock, iface=None): + """Helper to set the global socket and optionally set the global network interface. + :param sock: socket object. + :param iface: internet interface object + + """ + global _the_sock # pylint: disable=invalid-name, global-statement + _the_sock = sock + if iface: + global _the_interface # pylint: disable=invalid-name, global-statement + _the_interface = iface + _the_sock.set_interface(iface) + + +class Response: + """The response from a request, contains all the headers/content""" + + encoding = None + + def __init__(self, sock): + self.socket = sock + self.encoding = "utf-8" + self._cached = None + self.status_code = None + self.reason = None + self._read_so_far = 0 + self.headers = {} + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + def close(self): + """Close, delete and collect the response data""" + if self.socket: + self.socket.close() + del self.socket + del self._cached + gc.collect() + + @property + def content(self): + """The HTTP content direct from the socket, as bytes""" + # print(self.headers) + try: + content_length = int(self.headers["content-length"]) + except KeyError: + content_length = 0 + # print("Content length:", content_length) + if self._cached is None: + try: + self._cached = self.socket.recv(content_length) + finally: + self.socket.close() + self.socket = None + # print("Buffer length:", len(self._cached)) + return self._cached + + @property + def text(self): + """The HTTP content, encoded into a string according to the HTTP + header encoding""" + return str(self.content, self.encoding) + + def json(self): + """The HTTP content, parsed into a json dictionary""" + # pylint: disable=import-outside-toplevel + try: + import json as json_module + except ImportError: + import ujson as json_module + return json_module.loads(self.content) + + def iter_content(self, chunk_size=1, decode_unicode=False): + """An iterator that will stream data by only reading 'chunk_size' + bytes and yielding them, when we can't buffer the whole datastream""" + if decode_unicode: + raise NotImplementedError("Unicode not supported") + + while True: + chunk = self.socket.recv(chunk_size) + if chunk: + yield chunk + else: + return + + +# pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals +def request(method, url, data=None, json=None, headers=None, stream=False, timeout=1): + """Perform an HTTP request to the given url which we will parse to determine + whether to use SSL ('https://') or not. We can also send some provided 'data' + or a json dictionary which we will stringify. 'headers' is optional HTTP headers + sent along. 'stream' will determine if we buffer everything, or whether to only + read only when requested + """ + global _the_interface # pylint: disable=global-statement, invalid-name + global _the_sock # pylint: disable=global-statement, invalid-name + + if not headers: + headers = {} + + try: + proto, dummy, host, path = url.split("/", 3) + # replace spaces in path + path = path.replace(" ", "%20") + except ValueError: + proto, dummy, host = url.split("/", 2) + path = "" + if proto == "http:": + port = 80 + elif proto == "https:": + port = 443 + else: + raise ValueError("Unsupported protocol: " + proto) + + if ":" in host: + host, port = host.split(":", 1) + port = int(port) + + addr_info = _the_sock.getaddrinfo(host, port, 0, _the_sock.SOCK_STREAM)[0] + sock = _the_sock.socket(addr_info[0], addr_info[1], addr_info[2]) + resp = Response(sock) # our response + + sock.settimeout(timeout) # socket read timeout + + try: + if proto == "https:": + conntype = _the_interface.TLS_MODE + sock.connect( + (host, port), conntype + ) # for SSL we need to know the host name + else: + conntype = _the_interface.TCP_MODE + sock.connect(addr_info[-1], conntype) + sock.send( + b"%s /%s HTTP/1.0\r\n" % (bytes(method, "utf-8"), bytes(path, "utf-8")) + ) + if "Host" not in headers: + sock.send(b"Host: %s\r\n" % bytes(host, "utf-8")) + if "User-Agent" not in headers: + sock.send(b"User-Agent: Adafruit CircuitPython\r\n") + # Iterate over keys to avoid tuple alloc + for k in headers: + sock.send(k.encode()) + sock.send(b": ") + sock.send(headers[k].encode()) + sock.send(b"\r\n") + if json is not None: + assert data is None + # pylint: disable=import-outside-toplevel + try: + import json as json_module + except ImportError: + import ujson as json_module + # pylint: enable=import-outside-toplevel + data = json_module.dumps(json) + sock.send(b"Content-Type: application/json\r\n") + if data: + if isinstance(data, dict): + sock.send(b"Content-Type: application/x-www-form-urlencoded\r\n") + _post_data = "" + for k in data: + _post_data = "{}&{}={}".format(_post_data, k, data[k]) + data = _post_data[1:] + sock.send(b"Content-Length: %d\r\n" % len(data)) + sock.send(b"\r\n") + if data: + if isinstance(data, bytearray): + sock.send(bytes(data)) + else: + sock.send(bytes(data, "utf-8")) + + line = sock.readline() + # print(line) + line = line.split(None, 2) + status = int(line[1]) + reason = "" + if len(line) > 2: + reason = line[2].rstrip() + resp.headers = parse_headers(sock) + if resp.headers.get("transfer-encoding"): + if "chunked" in resp.headers.get("transfer-encoding"): + raise ValueError("Unsupported " + resp.headers.get("transfer-encoding")) + elif resp.headers.get("location") and not 200 <= status <= 299: + raise NotImplementedError("Redirects not yet supported") + + except: + sock.close() + raise + + resp.status_code = status + resp.reason = reason + return resp + + +def parse_headers(sock): + """ + Parses the header portion of an HTTP request/response from the socket. + Expects first line of HTTP request/response to have been read already + return: header dictionary + rtype: Dict + """ + headers = {} + while True: + line = sock.readline() + if not line or line == b"\r\n": + break + + # print("**line: ", line) + splits = line.split(b": ", 1) + title = splits[0] + content = "" + if len(splits) > 1: + content = splits[1] + if title and content: + title = str(title.lower(), "utf-8") + content = str(content, "utf-8") + headers[title] = content + return headers + + +def head(url, **kw): + """Send HTTP HEAD request""" + return request("HEAD", url, **kw) + + +def get(url, **kw): + """Send HTTP GET request""" + return request("GET", url, **kw) + + +def post(url, **kw): + """Send HTTP POST request""" + return request("POST", url, **kw) + + +def put(url, **kw): + """Send HTTP PUT request""" + return request("PUT", url, **kw) + + +def patch(url, **kw): + """Send HTTP PATCH request""" + return request("PATCH", url, **kw) + + +def delete(url, **kw): + """Send HTTP DELETE request""" + return request("DELETE", url, **kw) diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2/feathers2.py b/ports/esp32s2/boards/unexpectedmaker_feathers2/feathers2.py new file mode 100644 index 0000000000..0631cad7f0 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2/feathers2.py @@ -0,0 +1,47 @@ +# FeatherS2 Helper Library +# 2020 Seon Rozenblum, Unexpected Maker +# +# Project home: +# https://feathers2.io +# + +# Import required libraries +import time +import board +from digitalio import DigitalInOut, Direction, Pull + + +# Helper functions + +def enable_LDO2(state): + """Set the power for the second on-board LDO to allow no current draw when not needed.""" + + # Grab a reference to the LDO2 IO (21 in this case) + ldo2 = DigitalInOut(board.LDO2) + ldo2.direction = Direction.OUTPUT + + # Set the LDO2 power pin on / off + ldo2.value = state + + # A small delay to let the IO change state + time.sleep(0.035) + + +# Dotstar rainbow colour wheel +def dotstar_color_wheel(wheel_pos): + """Color wheel to allow for cycling through the rainbow of RGB colors.""" + wheel_pos = wheel_pos % 255 + + if wheel_pos < 85: + return 255 - wheel_pos * 3, 0, wheel_pos * 3 + elif wheel_pos < 170: + wheel_pos -= 85 + return 0, wheel_pos * 3, 255 - wheel_pos * 3 + else: + wheel_pos -= 170 + return wheel_pos * 3, 255 - wheel_pos * 3, 0 + + + +# Disable LDO2 by default +enable_LDO2(False) diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2/mpconfigboard.h b/ports/esp32s2/boards/unexpectedmaker_feathers2/mpconfigboard.h index e5fed4e324..a77de15116 100644 --- a/ports/esp32s2/boards/unexpectedmaker_feathers2/mpconfigboard.h +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2/mpconfigboard.h @@ -35,6 +35,5 @@ #define AUTORESET_DELAY_MS 500 -// Doesn't work with this on. -// #define MICROPY_HW_APA102_MOSI (&pin_GPIO44) -// #define MICROPY_HW_APA102_SCK (&pin_GPIO45) +#define MICROPY_HW_APA102_MOSI (&pin_GPIO40) +#define MICROPY_HW_APA102_SCK (&pin_GPIO45) diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2/mpconfigboard.mk b/ports/esp32s2/boards/unexpectedmaker_feathers2/mpconfigboard.mk index 294736d17c..91c321af92 100644 --- a/ports/esp32s2/boards/unexpectedmaker_feathers2/mpconfigboard.mk +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2/mpconfigboard.mk @@ -16,3 +16,7 @@ CIRCUITPY_ESP_FLASH_FREQ=40m CIRCUITPY_ESP_FLASH_SIZE=16MB CIRCUITPY_BITBANG_APA102 = 1 + +# Include these Python libraries in firmware. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DotStar + diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2/pins.c b/ports/esp32s2/boards/unexpectedmaker_feathers2/pins.c index e8dd2edf6a..5a29e029c2 100644 --- a/ports/esp32s2/boards/unexpectedmaker_feathers2/pins.c +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2/pins.c @@ -1,55 +1,103 @@ #include "shared-bindings/board/__init__.h" STATIC const mp_rom_map_elem_t board_global_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_IO17), MP_ROM_PTR(&pin_GPIO17) }, { MP_ROM_QSTR(MP_QSTR_A0), MP_ROM_PTR(&pin_GPIO17) }, { MP_ROM_QSTR(MP_QSTR_D14), MP_ROM_PTR(&pin_GPIO17) }, + { MP_ROM_QSTR(MP_QSTR_IO18), MP_ROM_PTR(&pin_GPIO18) }, { MP_ROM_QSTR(MP_QSTR_A1), MP_ROM_PTR(&pin_GPIO18) }, { MP_ROM_QSTR(MP_QSTR_D15), MP_ROM_PTR(&pin_GPIO18) }, - { MP_ROM_QSTR(MP_QSTR_A2), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_D16), MP_ROM_PTR(&pin_GPIO14) }, + { MP_ROM_QSTR(MP_QSTR_IO13), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_A2), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_D16), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_IO12), MP_ROM_PTR(&pin_GPIO12) }, { MP_ROM_QSTR(MP_QSTR_A3), MP_ROM_PTR(&pin_GPIO12) }, { MP_ROM_QSTR(MP_QSTR_D17), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_IO6), MP_ROM_PTR(&pin_GPIO6) }, { MP_ROM_QSTR(MP_QSTR_A4), MP_ROM_PTR(&pin_GPIO6) }, { MP_ROM_QSTR(MP_QSTR_D18), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_IO5), MP_ROM_PTR(&pin_GPIO5) }, { MP_ROM_QSTR(MP_QSTR_A5), MP_ROM_PTR(&pin_GPIO5) }, { MP_ROM_QSTR(MP_QSTR_D19), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_IO36), MP_ROM_PTR(&pin_GPIO36) }, { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO36) }, { MP_ROM_QSTR(MP_QSTR_D25), MP_ROM_PTR(&pin_GPIO36) }, + { MP_ROM_QSTR(MP_QSTR_IO35), MP_ROM_PTR(&pin_GPIO35) }, { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO35) }, { MP_ROM_QSTR(MP_QSTR_D24), MP_ROM_PTR(&pin_GPIO35) }, + { MP_ROM_QSTR(MP_QSTR_IO37), MP_ROM_PTR(&pin_GPIO37) }, { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO37) }, { MP_ROM_QSTR(MP_QSTR_D23), MP_ROM_PTR(&pin_GPIO37) }, + { MP_ROM_QSTR(MP_QSTR_IO44), MP_ROM_PTR(&pin_GPIO44) }, { MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO44) }, { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO44) }, + { MP_ROM_QSTR(MP_QSTR_IO43), MP_ROM_PTR(&pin_GPIO43) }, { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO43) }, { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO43) }, - // Moving to 9 and 8 - { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO38) }, - { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO33) }, - { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO1) }, - { MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO3) }, - { MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_IO8), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO8) }, { MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_APA102_MOSI), MP_ROM_PTR(&pin_GPIO44) }, // MTDO + { MP_ROM_QSTR(MP_QSTR_IO9), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO9) }, + + { MP_ROM_QSTR(MP_QSTR_IO0), MP_ROM_PTR(&pin_GPIO0) }, + { MP_ROM_QSTR(MP_QSTR_D4), MP_ROM_PTR(&pin_GPIO0) }, + + { MP_ROM_QSTR(MP_QSTR_IO1), MP_ROM_PTR(&pin_GPIO1) }, + { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO1) }, + { MP_ROM_QSTR(MP_QSTR_A10), MP_ROM_PTR(&pin_GPIO1) }, + + { MP_ROM_QSTR(MP_QSTR_IO3), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_A9), MP_ROM_PTR(&pin_GPIO3) }, + + { MP_ROM_QSTR(MP_QSTR_IO7), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_A8), MP_ROM_PTR(&pin_GPIO7) }, + + { MP_ROM_QSTR(MP_QSTR_IO33), MP_ROM_PTR(&pin_GPIO33) }, + { MP_ROM_QSTR(MP_QSTR_D20), MP_ROM_PTR(&pin_GPIO33) }, + + { MP_ROM_QSTR(MP_QSTR_IO38), MP_ROM_PTR(&pin_GPIO38) }, + { MP_ROM_QSTR(MP_QSTR_D21), MP_ROM_PTR(&pin_GPIO38) }, + + { MP_ROM_QSTR(MP_QSTR_IO10), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_A7), MP_ROM_PTR(&pin_GPIO10) }, + + { MP_ROM_QSTR(MP_QSTR_IO11), MP_ROM_PTR(&pin_GPIO11) }, + { MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO11) }, + { MP_ROM_QSTR(MP_QSTR_A6), MP_ROM_PTR(&pin_GPIO11) }, + + // { MP_ROM_QSTR(MP_QSTR_IO40), MP_ROM_PTR(&pin_GPIO40) }, // MTDO + { MP_ROM_QSTR(MP_QSTR_APA102_MOSI), MP_ROM_PTR(&pin_GPIO40) }, // MTDO + + // { MP_ROM_QSTR(MP_QSTR_IO45), MP_ROM_PTR(&pin_GPIO45) }, { MP_ROM_QSTR(MP_QSTR_APA102_SCK), MP_ROM_PTR(&pin_GPIO45) }, + + { MP_ROM_QSTR(MP_QSTR_LDO2), MP_ROM_PTR(&pin_GPIO21) }, + { MP_ROM_QSTR(MP_QSTR_IO21), MP_ROM_PTR(&pin_GPIO21) }, + + { MP_ROM_QSTR(MP_QSTR_IO13), MP_ROM_PTR(&pin_GPIO13) }, { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_IO4), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_AMB), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) }, diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/adafruit_dotstar.py b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/adafruit_dotstar.py new file mode 100755 index 0000000000..65fbbe8683 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/adafruit_dotstar.py @@ -0,0 +1,198 @@ +# The MIT License (MIT) +# +# Copyright (c) 2016 Damien P. George (original Neopixel object) +# Copyright (c) 2017 Ladyada +# Copyright (c) 2017 Scott Shawcroft for Adafruit Industries +# Copyright (c) 2019 Roy Hooper +# +# 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. + +""" +`adafruit_dotstar` - DotStar strip driver (for CircuitPython 5.0+ with _pixelbuf) +================================================================================= + +* Author(s): Damien P. George, Limor Fried, Scott Shawcroft & Roy Hooper +""" + +# pylint: disable=ungrouped-imports +import sys +import busio +import digitalio + +if sys.implementation.version[0] < 5: + import adafruit_pypixelbuf as _pixelbuf +else: + try: + import _pixelbuf + except ImportError: + import adafruit_pypixelbuf as _pixelbuf + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DotStar.git" + +START_HEADER_SIZE = 4 + +# Pixel color order constants +RBG = "PRBG" +"""Red Blue Green""" +RGB = "PRGB" +"""Red Green Blue""" +GRB = "PGRB" +"""Green Red Blue""" +GBR = "PGBR" +"""Green Blue Red""" +BRG = "PBRG" +"""Blue Red Green""" +BGR = "PBGR" +"""Blue Green Red""" + + +class DotStar(_pixelbuf.PixelBuf): + """ + A sequence of dotstars. + + :param ~microcontroller.Pin clock: The pin to output dotstar clock on. + :param ~microcontroller.Pin data: The pin to output dotstar data on. + :param int n: The number of dotstars in the chain + :param float brightness: Brightness of the pixels between 0.0 and 1.0 + :param bool auto_write: True if the dotstars should immediately change when + set. If False, `show` must be called explicitly. + :param str pixel_order: Set the pixel order on the strip - different + strips implement this differently. If you send red, and it looks blue + or green on the strip, modify this! It should be one of the values above. + :param int baudrate: Desired clock rate if using hardware SPI (ignored if + using 'soft' SPI). This is only a recommendation; the actual clock + rate may be slightly different depending on what the system hardware + can provide. + + Example for Gemma M0: + + .. code-block:: python + + import adafruit_dotstar + import time + from board import * + + RED = 0x100000 + + with adafruit_dotstar.DotStar(APA102_SCK, APA102_MOSI, 1) as pixels: + pixels[0] = RED + time.sleep(2) + + .. py:method:: DotStar.show() + + Shows the new colors on the dotstars themselves if they haven't already + been autowritten. + + The colors may or may not be showing after this function returns because + it may be done asynchronously. + + .. py:method:: DotStar.fill(color) + + Colors all dotstars the given ***color***. + + .. py:attribute:: brightness + + Overall brightness of all dotstars (0 to 1.0) + """ + + def __init__( + self, + clock, + data, + n, + *, + brightness=1.0, + auto_write=True, + pixel_order=BGR, + baudrate=4000000 + ): + self._spi = None + try: + self._spi = busio.SPI(clock, MOSI=data) + while not self._spi.try_lock(): + pass + self._spi.configure(baudrate=baudrate) + + except (NotImplementedError, ValueError): + self.dpin = digitalio.DigitalInOut(data) + self.cpin = digitalio.DigitalInOut(clock) + self.dpin.direction = digitalio.Direction.OUTPUT + self.cpin.direction = digitalio.Direction.OUTPUT + self.cpin.value = False + + # Supply one extra clock cycle for each two pixels in the strip. + trailer_size = n // 16 + if n % 16 != 0: + trailer_size += 1 + + # Four empty bytes for the header. + header = bytearray(START_HEADER_SIZE) + # 0xff bytes for the trailer. + trailer = bytearray(b"\xff") * trailer_size + + super().__init__( + n, + byteorder=pixel_order, + brightness=brightness, + auto_write=auto_write, + header=header, + trailer=trailer, + ) + + def deinit(self): + """Blank out the DotStars and release the resources.""" + self.fill(0) + self.show() + if self._spi: + self._spi.deinit() + else: + self.dpin.deinit() + self.cpin.deinit() + + def __enter__(self): + return self + + def __exit__(self, exception_type, exception_value, traceback): + self.deinit() + + def __repr__(self): + return "[" + ", ".join([str(x) for x in self]) + "]" + + @property + def n(self): + """ + The number of dotstars in the chain (read-only) + """ + return len(self) + + def _transmit(self, buffer): + if self._spi: + self._spi.write(buffer) + else: + self._ds_writebytes(buffer) + + def _ds_writebytes(self, buffer): + for b in buffer: + for _ in range(8): + self.dpin.value = b & 0x80 + self.cpin.value = True + self.cpin.value = False + b = b << 1 + self.cpin.value = False diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/adafruit_pypixelbuf.py b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/adafruit_pypixelbuf.py new file mode 100755 index 0000000000..3cb83e970c --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/adafruit_pypixelbuf.py @@ -0,0 +1,374 @@ +# The MIT License (MIT) +# +# Based on the Adafruit NeoPixel and Adafruit Dotstar CircuitPython drivers. +# Copyright (c) 2019-2020 Roy Hooper +# +# 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. + +""" +`adafruit_pypixelbuf` - A pure python implementation of _pixelbuf +================================================================= +This class is used when _pixelbuf is not available in CircuitPython. It is based on the work +in neopixel.py and adafruit_dotstar.py. + +* Author(s): Damien P. George & Limor Fried & Scott Shawcroft & Roy Hooper +""" + +DOTSTAR_LED_START_FULL_BRIGHT = 0xFF +DOTSTAR_LED_START = 0b11100000 # Three "1" bits, followed by 5 brightness bits +DOTSTAR_LED_BRIGHTNESS = 0b00011111 + + +class PixelBuf: # pylint: disable=too-many-instance-attributes + """ + A sequence of RGB/RGBW pixels. + + This is the pure python implementation of CircuitPython's _pixelbuf. + + :param ~int n: Number of pixels + :param ~str byteorder: Byte order string constant (also sets bpp) + :param ~float brightness: Brightness (0 to 1.0, default 1.0) + :param ~bool auto_write: Whether to automatically write pixels (Default False) + :param bytes header: Sequence of bytes to always send before pixel values. + :param bytes trailer: Sequence of bytes to always send after pixel values. + """ + + def __init__( # pylint: disable=too-many-locals,too-many-arguments + self, + n, + byteorder="BGR", + brightness=1.0, + auto_write=False, + header=None, + trailer=None, + ): + + bpp, byteorder_tuple, has_white, dotstar_mode = self.parse_byteorder(byteorder) + + self.auto_write = False + + effective_bpp = 4 if dotstar_mode else bpp + _bytes = effective_bpp * n + buf = bytearray(_bytes) + offset = 0 + + if header is not None: + if not isinstance(header, bytearray): + raise TypeError("header must be a bytearray") + buf = header + buf + offset = len(header) + + if trailer is not None: + if not isinstance(trailer, bytearray): + raise TypeError("trailer must be a bytearray") + buf += trailer + + self._pixels = n + self._bytes = _bytes + self._byteorder = byteorder_tuple + self._byteorder_string = byteorder + self._has_white = has_white + self._bpp = bpp + self._pre_brightness_buffer = None + self._post_brightness_buffer = buf + self._offset = offset + self._dotstar_mode = dotstar_mode + self._pixel_step = effective_bpp + + if dotstar_mode: + self._byteorder_tuple = ( + byteorder_tuple[0] + 1, + byteorder_tuple[1] + 1, + byteorder_tuple[2] + 1, + 0, + ) + # Initialize the buffer with the dotstar start bytes. + for i in range(self._offset, self._bytes + self._offset, 4): + self._post_brightness_buffer[i] = DOTSTAR_LED_START_FULL_BRIGHT + + self._brightness = 1.0 + self.brightness = brightness + + self.auto_write = auto_write + + @staticmethod + def parse_byteorder(byteorder): + """ + Parse a Byteorder string for validity and determine bpp, byte order, and + dostar brightness bits. + + Byteorder strings may contain the following characters: + R - Red + G - Green + B - Blue + W - White + P - PWM (PWM Duty cycle for pixel - dotstars 0 - 1.0) + + :param: ~str bpp: bpp string. + :return: ~tuple: bpp, byteorder, has_white, dotstar_mode + """ + bpp = len(byteorder) + dotstar_mode = False + has_white = False + + if byteorder.strip("RGBWP") != "": + raise ValueError("Invalid Byteorder string") + + try: + r = byteorder.index("R") + g = byteorder.index("G") + b = byteorder.index("B") + except ValueError: + raise ValueError("Invalid Byteorder string") + if "W" in byteorder: + w = byteorder.index("W") + byteorder = (r, g, b, w) + has_white = True + elif "P" in byteorder: + lum = byteorder.index("P") + byteorder = (r, g, b, lum) + dotstar_mode = True + else: + byteorder = (r, g, b) + + return bpp, byteorder, has_white, dotstar_mode + + @property + def bpp(self): + """ + The number of bytes per pixel in the buffer (read-only). + """ + return self._bpp + + @property + def brightness(self): + """ + Float value between 0 and 1. Output brightness. + + When brightness is less than 1.0, a second buffer will be used to store the color values + before they are adjusted for brightness. + """ + return self._brightness + + @brightness.setter + def brightness(self, value): + value = min(max(value, 0.0), 1.0) + change = value - self._brightness + if -0.001 < change < 0.001: + return + + self._brightness = value + + if self._pre_brightness_buffer is None: + self._pre_brightness_buffer = bytearray(self._post_brightness_buffer) + + # Adjust brightness of existing pixels + offset_check = self._offset % self._pixel_step + for i in range(self._offset, self._bytes + self._offset): + # Don't adjust per-pixel luminance bytes in dotstar mode + if self._dotstar_mode and (i % 4 != offset_check): + continue + self._post_brightness_buffer[i] = int( + self._pre_brightness_buffer[i] * self._brightness + ) + + if self.auto_write: + self.show() + + @property + def byteorder(self): + """ + ByteOrder string for the buffer (read-only) + """ + return self._byteorder_string + + def __len__(self): + """ + Number of pixels. + """ + return self._pixels + + def show(self): + """ + Call the associated write function to display the pixels + """ + return self._transmit(self._post_brightness_buffer) + + def fill(self, color): + """ + Fills the given pixelbuf with the given color. + :param pixelbuf: A pixel object. + :param color: Color to set. + """ + r, g, b, w = self._parse_color(color) + for i in range(self._pixels): + self._set_item(i, r, g, b, w) + if self.auto_write: + self.show() + + def _parse_color(self, value): + r = 0 + g = 0 + b = 0 + w = 0 + if isinstance(value, int): + r = value >> 16 + g = (value >> 8) & 0xFF + b = value & 0xFF + w = 0 + + if self._dotstar_mode: + w = 1.0 + else: + if len(value) < 3 or len(value) > 4: + raise ValueError( + "Expected tuple of length {}, got {}".format(self._bpp, len(value)) + ) + if len(value) == self._bpp: + if self._bpp == 3: + r, g, b = value + else: + r, g, b, w = value + elif len(value) == 3: + r, g, b = value + if self._dotstar_mode: + w = 1.0 + + if self._bpp == 4: + if self._dotstar_mode: + # LED startframe is three "1" bits, followed by 5 brightness bits + # then 8 bits for each of R, G, and B. The order of those 3 are configurable and + # vary based on hardware + w = (int(w * 31) & 0b00011111) | DOTSTAR_LED_START + elif ( + self._has_white + and (isinstance(value, int) or len(value) == 3) + and r == g + and g == b + ): + # If all components are the same and we have a white pixel then use it + # instead of the individual components when all 4 values aren't explicitly given. + w = r + r = 0 + g = 0 + b = 0 + + return (r, g, b, w) + + def _set_item( + self, index, r, g, b, w + ): # pylint: disable=too-many-locals,too-many-branches,too-many-arguments + if index < 0: + index += len(self) + if index >= self._pixels or index < 0: + raise IndexError + offset = self._offset + (index * self._bpp) + + if self._pre_brightness_buffer is not None: + if self._bpp == 4: + self._pre_brightness_buffer[offset + self._byteorder[3]] = w + self._pre_brightness_buffer[offset + self._byteorder[0]] = r + self._pre_brightness_buffer[offset + self._byteorder[1]] = g + self._pre_brightness_buffer[offset + self._byteorder[2]] = b + + if self._bpp == 4: + # Only apply brightness if w is actually white (aka not DotStar.) + if not self._dotstar_mode: + w = int(w * self._brightness) + self._post_brightness_buffer[offset + self._byteorder[3]] = w + + self._post_brightness_buffer[offset + self._byteorder[0]] = int( + r * self._brightness + ) + self._post_brightness_buffer[offset + self._byteorder[1]] = int( + g * self._brightness + ) + self._post_brightness_buffer[offset + self._byteorder[2]] = int( + b * self._brightness + ) + + def __setitem__(self, index, val): + if isinstance(index, slice): + start, stop, step = index.indices(self._pixels) + for val_i, in_i in enumerate(range(start, stop, step)): + r, g, b, w = self._parse_color(val[val_i]) + self._set_item(in_i, r, g, b, w) + else: + r, g, b, w = self._parse_color(val) + self._set_item(index, r, g, b, w) + + if self.auto_write: + self.show() + + def _getitem(self, index): + start = self._offset + (index * self._bpp) + buffer = ( + self._pre_brightness_buffer + if self._pre_brightness_buffer is not None + else self._post_brightness_buffer + ) + value = [ + buffer[start + self._byteorder[0]], + buffer[start + self._byteorder[1]], + buffer[start + self._byteorder[2]], + ] + if self._has_white: + value.append(buffer[start + self._byteorder[3]]) + elif self._dotstar_mode: + value.append( + (buffer[start + self._byteorder[3]] & DOTSTAR_LED_BRIGHTNESS) / 31.0 + ) + return value + + def __getitem__(self, index): + if isinstance(index, slice): + out = [] + for in_i in range( + *index.indices(len(self._post_brightness_buffer) // self._bpp) + ): + out.append(self._getitem(in_i)) + return out + if index < 0: + index += len(self) + if index >= self._pixels or index < 0: + raise IndexError + return self._getitem(index) + + def _transmit(self, buffer): + raise NotImplementedError("Must be subclassed") + + +def wheel(pos): + """ + Helper to create a colorwheel. + + :param pos: int 0-255 of color value to return + :return: tuple of RGB values + """ + # Input a value 0 to 255 to get a color value. + # The colours are a transition r - g - b - back to r. + if pos < 0 or pos > 255: + return 0, 0, 0 + if pos < 85: + return 255 - pos * 3, pos * 3, 0 + if pos < 170: + pos -= 85 + return 0, 255 - pos * 3, pos * 3 + pos -= 170 + return pos * 3, 0, 255 - pos * 3 diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/board.c b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/board.c new file mode 100644 index 0000000000..1dc30b5af8 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/board.c @@ -0,0 +1,56 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Scott Shawcroft 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. + */ + +#include "boards/board.h" +#include "mpconfigboard.h" +#include "shared-bindings/microcontroller/Pin.h" + +void board_init(void) { + // USB + common_hal_never_reset_pin(&pin_GPIO19); + common_hal_never_reset_pin(&pin_GPIO20); + + // Debug UART + common_hal_never_reset_pin(&pin_GPIO43); + common_hal_never_reset_pin(&pin_GPIO44); + + // SPI Flash and RAM + common_hal_never_reset_pin(&pin_GPIO26); + common_hal_never_reset_pin(&pin_GPIO27); + common_hal_never_reset_pin(&pin_GPIO28); + common_hal_never_reset_pin(&pin_GPIO29); + common_hal_never_reset_pin(&pin_GPIO30); + common_hal_never_reset_pin(&pin_GPIO31); + common_hal_never_reset_pin(&pin_GPIO32); +} + +bool board_requests_safe_mode(void) { + return false; +} + +void reset_board(void) { + +} diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/feathers2.py b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/feathers2.py new file mode 100644 index 0000000000..0631cad7f0 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/feathers2.py @@ -0,0 +1,47 @@ +# FeatherS2 Helper Library +# 2020 Seon Rozenblum, Unexpected Maker +# +# Project home: +# https://feathers2.io +# + +# Import required libraries +import time +import board +from digitalio import DigitalInOut, Direction, Pull + + +# Helper functions + +def enable_LDO2(state): + """Set the power for the second on-board LDO to allow no current draw when not needed.""" + + # Grab a reference to the LDO2 IO (21 in this case) + ldo2 = DigitalInOut(board.LDO2) + ldo2.direction = Direction.OUTPUT + + # Set the LDO2 power pin on / off + ldo2.value = state + + # A small delay to let the IO change state + time.sleep(0.035) + + +# Dotstar rainbow colour wheel +def dotstar_color_wheel(wheel_pos): + """Color wheel to allow for cycling through the rainbow of RGB colors.""" + wheel_pos = wheel_pos % 255 + + if wheel_pos < 85: + return 255 - wheel_pos * 3, 0, wheel_pos * 3 + elif wheel_pos < 170: + wheel_pos -= 85 + return 0, wheel_pos * 3, 255 - wheel_pos * 3 + else: + wheel_pos -= 170 + return wheel_pos * 3, 255 - wheel_pos * 3, 0 + + + +# Disable LDO2 by default +enable_LDO2(False) diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/mpconfigboard.h b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/mpconfigboard.h new file mode 100644 index 0000000000..a65d00206d --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/mpconfigboard.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Scott Shawcroft 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. + */ + +//Micropython setup + +#define MICROPY_HW_BOARD_NAME "FeatherS2" +#define MICROPY_HW_MCU_NAME "ESP32S2" + +#define CIRCUITPY_BOOT_BUTTON (&pin_GPIO0) + +#define BOARD_USER_SAFE_MODE_ACTION "pressing boot button at start up.\n" + +#define AUTORESET_DELAY_MS 500 + +// Doesn't work with this on. +// #define MICROPY_HW_APA102_MOSI (&pin_GPIO44) +// #define MICROPY_HW_APA102_SCK (&pin_GPIO45) diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/mpconfigboard.mk b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/mpconfigboard.mk new file mode 100644 index 0000000000..4c809471bd --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/mpconfigboard.mk @@ -0,0 +1,21 @@ +USB_VID = 0x239A +USB_PID = 0x80AC +USB_PRODUCT = "FeatherS2" +USB_MANUFACTURER = "UnexpectedMaker" +USB_DEVICES = "CDC,MSC,HID" + +INTERNAL_FLASH_FILESYSTEM = 1 +LONGINT_IMPL = MPZ + +# The default queue depth of 16 overflows on release builds, +# so increase it to 32. +CFLAGS += -DCFG_TUD_TASK_QUEUE_SZ=32 + +CIRCUITPY_ESP_FLASH_MODE=qio +CIRCUITPY_ESP_FLASH_FREQ=40m +CIRCUITPY_ESP_FLASH_SIZE=16MB + +CIRCUITPY_BITBANG_APA102 = 1 + +# Include these Python libraries in firmware. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DotStar \ No newline at end of file diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/pins.c b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/pins.c new file mode 100644 index 0000000000..9476cafdf2 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/pins.c @@ -0,0 +1,56 @@ +#include "shared-bindings/board/__init__.h" + +STATIC const mp_rom_map_elem_t board_global_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_A0), MP_ROM_PTR(&pin_GPIO17) }, + { MP_ROM_QSTR(MP_QSTR_D14), MP_ROM_PTR(&pin_GPIO17) }, + + { MP_ROM_QSTR(MP_QSTR_A1), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_D15), MP_ROM_PTR(&pin_GPIO18) }, + + { MP_ROM_QSTR(MP_QSTR_A3), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_D17), MP_ROM_PTR(&pin_GPIO12) }, + + { MP_ROM_QSTR(MP_QSTR_A4), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_D18), MP_ROM_PTR(&pin_GPIO6) }, + + { MP_ROM_QSTR(MP_QSTR_A5), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_D19), MP_ROM_PTR(&pin_GPIO5) }, + + { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO36) }, + { MP_ROM_QSTR(MP_QSTR_D25), MP_ROM_PTR(&pin_GPIO36) }, + + { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO35) }, + { MP_ROM_QSTR(MP_QSTR_D24), MP_ROM_PTR(&pin_GPIO35) }, + + { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO37) }, + { MP_ROM_QSTR(MP_QSTR_D23), MP_ROM_PTR(&pin_GPIO37) }, + + { MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO44) }, + { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO44) }, + + { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO43) }, + { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO43) }, + + // Moving to 9 and 8 + { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO38) }, + { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO33) }, + { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO1) }, + { MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO11) }, + + { MP_ROM_QSTR(MP_QSTR_APA102_MOSI), MP_ROM_PTR(&pin_GPIO40) }, // MTDO + { MP_ROM_QSTR(MP_QSTR_APA102_SCK), MP_ROM_PTR(&pin_GPIO45) }, + { MP_ROM_QSTR(MP_QSTR_LDO2), MP_ROM_PTR(&pin_GPIO21) }, + + { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_AMB), MP_ROM_PTR(&pin_GPIO4) }, + + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) }, + { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) }, +}; +MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table); diff --git a/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/sdkconfig b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/sdkconfig new file mode 100644 index 0000000000..b73c4a8c20 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_feathers2_prerelease/sdkconfig @@ -0,0 +1,35 @@ +CONFIG_ESP32S2_SPIRAM_SUPPORT=y + +# +# SPI RAM config +# +# CONFIG_SPIRAM_TYPE_AUTO is not set +# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set +# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set +CONFIG_SPIRAM_TYPE_ESPPSRAM64=y +CONFIG_SPIRAM_SIZE=8388608 + +# +# PSRAM clock and cs IO for ESP32S2 +# +CONFIG_DEFAULT_PSRAM_CLK_IO=30 +CONFIG_DEFAULT_PSRAM_CS_IO=26 +# end of PSRAM clock and cs IO for ESP32S2 + +CONFIG_SPIRAM_SPIWP_SD3_PIN=28 +# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set +# CONFIG_SPIRAM_RODATA is not set +# CONFIG_SPIRAM_USE_AHB_DBUS3 is not set +# CONFIG_SPIRAM_SPEED_80M is not set +CONFIG_SPIRAM_SPEED_40M=y +# CONFIG_SPIRAM_SPEED_26M is not set +# CONFIG_SPIRAM_SPEED_20M is not set +CONFIG_SPIRAM=y +CONFIG_SPIRAM_BOOT_INIT=y +# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set +CONFIG_SPIRAM_USE_MEMMAP=y +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +# CONFIG_SPIRAM_USE_MALLOC is not set +CONFIG_SPIRAM_MEMTEST=y +# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set +# end of SPI RAM config diff --git a/ports/esp32s2/boards/unexpectedmaker_pros2/adafruit_dotstar.py b/ports/esp32s2/boards/unexpectedmaker_pros2/adafruit_dotstar.py new file mode 100755 index 0000000000..65fbbe8683 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_pros2/adafruit_dotstar.py @@ -0,0 +1,198 @@ +# The MIT License (MIT) +# +# Copyright (c) 2016 Damien P. George (original Neopixel object) +# Copyright (c) 2017 Ladyada +# Copyright (c) 2017 Scott Shawcroft for Adafruit Industries +# Copyright (c) 2019 Roy Hooper +# +# 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. + +""" +`adafruit_dotstar` - DotStar strip driver (for CircuitPython 5.0+ with _pixelbuf) +================================================================================= + +* Author(s): Damien P. George, Limor Fried, Scott Shawcroft & Roy Hooper +""" + +# pylint: disable=ungrouped-imports +import sys +import busio +import digitalio + +if sys.implementation.version[0] < 5: + import adafruit_pypixelbuf as _pixelbuf +else: + try: + import _pixelbuf + except ImportError: + import adafruit_pypixelbuf as _pixelbuf + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DotStar.git" + +START_HEADER_SIZE = 4 + +# Pixel color order constants +RBG = "PRBG" +"""Red Blue Green""" +RGB = "PRGB" +"""Red Green Blue""" +GRB = "PGRB" +"""Green Red Blue""" +GBR = "PGBR" +"""Green Blue Red""" +BRG = "PBRG" +"""Blue Red Green""" +BGR = "PBGR" +"""Blue Green Red""" + + +class DotStar(_pixelbuf.PixelBuf): + """ + A sequence of dotstars. + + :param ~microcontroller.Pin clock: The pin to output dotstar clock on. + :param ~microcontroller.Pin data: The pin to output dotstar data on. + :param int n: The number of dotstars in the chain + :param float brightness: Brightness of the pixels between 0.0 and 1.0 + :param bool auto_write: True if the dotstars should immediately change when + set. If False, `show` must be called explicitly. + :param str pixel_order: Set the pixel order on the strip - different + strips implement this differently. If you send red, and it looks blue + or green on the strip, modify this! It should be one of the values above. + :param int baudrate: Desired clock rate if using hardware SPI (ignored if + using 'soft' SPI). This is only a recommendation; the actual clock + rate may be slightly different depending on what the system hardware + can provide. + + Example for Gemma M0: + + .. code-block:: python + + import adafruit_dotstar + import time + from board import * + + RED = 0x100000 + + with adafruit_dotstar.DotStar(APA102_SCK, APA102_MOSI, 1) as pixels: + pixels[0] = RED + time.sleep(2) + + .. py:method:: DotStar.show() + + Shows the new colors on the dotstars themselves if they haven't already + been autowritten. + + The colors may or may not be showing after this function returns because + it may be done asynchronously. + + .. py:method:: DotStar.fill(color) + + Colors all dotstars the given ***color***. + + .. py:attribute:: brightness + + Overall brightness of all dotstars (0 to 1.0) + """ + + def __init__( + self, + clock, + data, + n, + *, + brightness=1.0, + auto_write=True, + pixel_order=BGR, + baudrate=4000000 + ): + self._spi = None + try: + self._spi = busio.SPI(clock, MOSI=data) + while not self._spi.try_lock(): + pass + self._spi.configure(baudrate=baudrate) + + except (NotImplementedError, ValueError): + self.dpin = digitalio.DigitalInOut(data) + self.cpin = digitalio.DigitalInOut(clock) + self.dpin.direction = digitalio.Direction.OUTPUT + self.cpin.direction = digitalio.Direction.OUTPUT + self.cpin.value = False + + # Supply one extra clock cycle for each two pixels in the strip. + trailer_size = n // 16 + if n % 16 != 0: + trailer_size += 1 + + # Four empty bytes for the header. + header = bytearray(START_HEADER_SIZE) + # 0xff bytes for the trailer. + trailer = bytearray(b"\xff") * trailer_size + + super().__init__( + n, + byteorder=pixel_order, + brightness=brightness, + auto_write=auto_write, + header=header, + trailer=trailer, + ) + + def deinit(self): + """Blank out the DotStars and release the resources.""" + self.fill(0) + self.show() + if self._spi: + self._spi.deinit() + else: + self.dpin.deinit() + self.cpin.deinit() + + def __enter__(self): + return self + + def __exit__(self, exception_type, exception_value, traceback): + self.deinit() + + def __repr__(self): + return "[" + ", ".join([str(x) for x in self]) + "]" + + @property + def n(self): + """ + The number of dotstars in the chain (read-only) + """ + return len(self) + + def _transmit(self, buffer): + if self._spi: + self._spi.write(buffer) + else: + self._ds_writebytes(buffer) + + def _ds_writebytes(self, buffer): + for b in buffer: + for _ in range(8): + self.dpin.value = b & 0x80 + self.cpin.value = True + self.cpin.value = False + b = b << 1 + self.cpin.value = False diff --git a/ports/esp32s2/boards/unexpectedmaker_pros2/adafruit_pypixelbuf.py b/ports/esp32s2/boards/unexpectedmaker_pros2/adafruit_pypixelbuf.py new file mode 100755 index 0000000000..3cb83e970c --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_pros2/adafruit_pypixelbuf.py @@ -0,0 +1,374 @@ +# The MIT License (MIT) +# +# Based on the Adafruit NeoPixel and Adafruit Dotstar CircuitPython drivers. +# Copyright (c) 2019-2020 Roy Hooper +# +# 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. + +""" +`adafruit_pypixelbuf` - A pure python implementation of _pixelbuf +================================================================= +This class is used when _pixelbuf is not available in CircuitPython. It is based on the work +in neopixel.py and adafruit_dotstar.py. + +* Author(s): Damien P. George & Limor Fried & Scott Shawcroft & Roy Hooper +""" + +DOTSTAR_LED_START_FULL_BRIGHT = 0xFF +DOTSTAR_LED_START = 0b11100000 # Three "1" bits, followed by 5 brightness bits +DOTSTAR_LED_BRIGHTNESS = 0b00011111 + + +class PixelBuf: # pylint: disable=too-many-instance-attributes + """ + A sequence of RGB/RGBW pixels. + + This is the pure python implementation of CircuitPython's _pixelbuf. + + :param ~int n: Number of pixels + :param ~str byteorder: Byte order string constant (also sets bpp) + :param ~float brightness: Brightness (0 to 1.0, default 1.0) + :param ~bool auto_write: Whether to automatically write pixels (Default False) + :param bytes header: Sequence of bytes to always send before pixel values. + :param bytes trailer: Sequence of bytes to always send after pixel values. + """ + + def __init__( # pylint: disable=too-many-locals,too-many-arguments + self, + n, + byteorder="BGR", + brightness=1.0, + auto_write=False, + header=None, + trailer=None, + ): + + bpp, byteorder_tuple, has_white, dotstar_mode = self.parse_byteorder(byteorder) + + self.auto_write = False + + effective_bpp = 4 if dotstar_mode else bpp + _bytes = effective_bpp * n + buf = bytearray(_bytes) + offset = 0 + + if header is not None: + if not isinstance(header, bytearray): + raise TypeError("header must be a bytearray") + buf = header + buf + offset = len(header) + + if trailer is not None: + if not isinstance(trailer, bytearray): + raise TypeError("trailer must be a bytearray") + buf += trailer + + self._pixels = n + self._bytes = _bytes + self._byteorder = byteorder_tuple + self._byteorder_string = byteorder + self._has_white = has_white + self._bpp = bpp + self._pre_brightness_buffer = None + self._post_brightness_buffer = buf + self._offset = offset + self._dotstar_mode = dotstar_mode + self._pixel_step = effective_bpp + + if dotstar_mode: + self._byteorder_tuple = ( + byteorder_tuple[0] + 1, + byteorder_tuple[1] + 1, + byteorder_tuple[2] + 1, + 0, + ) + # Initialize the buffer with the dotstar start bytes. + for i in range(self._offset, self._bytes + self._offset, 4): + self._post_brightness_buffer[i] = DOTSTAR_LED_START_FULL_BRIGHT + + self._brightness = 1.0 + self.brightness = brightness + + self.auto_write = auto_write + + @staticmethod + def parse_byteorder(byteorder): + """ + Parse a Byteorder string for validity and determine bpp, byte order, and + dostar brightness bits. + + Byteorder strings may contain the following characters: + R - Red + G - Green + B - Blue + W - White + P - PWM (PWM Duty cycle for pixel - dotstars 0 - 1.0) + + :param: ~str bpp: bpp string. + :return: ~tuple: bpp, byteorder, has_white, dotstar_mode + """ + bpp = len(byteorder) + dotstar_mode = False + has_white = False + + if byteorder.strip("RGBWP") != "": + raise ValueError("Invalid Byteorder string") + + try: + r = byteorder.index("R") + g = byteorder.index("G") + b = byteorder.index("B") + except ValueError: + raise ValueError("Invalid Byteorder string") + if "W" in byteorder: + w = byteorder.index("W") + byteorder = (r, g, b, w) + has_white = True + elif "P" in byteorder: + lum = byteorder.index("P") + byteorder = (r, g, b, lum) + dotstar_mode = True + else: + byteorder = (r, g, b) + + return bpp, byteorder, has_white, dotstar_mode + + @property + def bpp(self): + """ + The number of bytes per pixel in the buffer (read-only). + """ + return self._bpp + + @property + def brightness(self): + """ + Float value between 0 and 1. Output brightness. + + When brightness is less than 1.0, a second buffer will be used to store the color values + before they are adjusted for brightness. + """ + return self._brightness + + @brightness.setter + def brightness(self, value): + value = min(max(value, 0.0), 1.0) + change = value - self._brightness + if -0.001 < change < 0.001: + return + + self._brightness = value + + if self._pre_brightness_buffer is None: + self._pre_brightness_buffer = bytearray(self._post_brightness_buffer) + + # Adjust brightness of existing pixels + offset_check = self._offset % self._pixel_step + for i in range(self._offset, self._bytes + self._offset): + # Don't adjust per-pixel luminance bytes in dotstar mode + if self._dotstar_mode and (i % 4 != offset_check): + continue + self._post_brightness_buffer[i] = int( + self._pre_brightness_buffer[i] * self._brightness + ) + + if self.auto_write: + self.show() + + @property + def byteorder(self): + """ + ByteOrder string for the buffer (read-only) + """ + return self._byteorder_string + + def __len__(self): + """ + Number of pixels. + """ + return self._pixels + + def show(self): + """ + Call the associated write function to display the pixels + """ + return self._transmit(self._post_brightness_buffer) + + def fill(self, color): + """ + Fills the given pixelbuf with the given color. + :param pixelbuf: A pixel object. + :param color: Color to set. + """ + r, g, b, w = self._parse_color(color) + for i in range(self._pixels): + self._set_item(i, r, g, b, w) + if self.auto_write: + self.show() + + def _parse_color(self, value): + r = 0 + g = 0 + b = 0 + w = 0 + if isinstance(value, int): + r = value >> 16 + g = (value >> 8) & 0xFF + b = value & 0xFF + w = 0 + + if self._dotstar_mode: + w = 1.0 + else: + if len(value) < 3 or len(value) > 4: + raise ValueError( + "Expected tuple of length {}, got {}".format(self._bpp, len(value)) + ) + if len(value) == self._bpp: + if self._bpp == 3: + r, g, b = value + else: + r, g, b, w = value + elif len(value) == 3: + r, g, b = value + if self._dotstar_mode: + w = 1.0 + + if self._bpp == 4: + if self._dotstar_mode: + # LED startframe is three "1" bits, followed by 5 brightness bits + # then 8 bits for each of R, G, and B. The order of those 3 are configurable and + # vary based on hardware + w = (int(w * 31) & 0b00011111) | DOTSTAR_LED_START + elif ( + self._has_white + and (isinstance(value, int) or len(value) == 3) + and r == g + and g == b + ): + # If all components are the same and we have a white pixel then use it + # instead of the individual components when all 4 values aren't explicitly given. + w = r + r = 0 + g = 0 + b = 0 + + return (r, g, b, w) + + def _set_item( + self, index, r, g, b, w + ): # pylint: disable=too-many-locals,too-many-branches,too-many-arguments + if index < 0: + index += len(self) + if index >= self._pixels or index < 0: + raise IndexError + offset = self._offset + (index * self._bpp) + + if self._pre_brightness_buffer is not None: + if self._bpp == 4: + self._pre_brightness_buffer[offset + self._byteorder[3]] = w + self._pre_brightness_buffer[offset + self._byteorder[0]] = r + self._pre_brightness_buffer[offset + self._byteorder[1]] = g + self._pre_brightness_buffer[offset + self._byteorder[2]] = b + + if self._bpp == 4: + # Only apply brightness if w is actually white (aka not DotStar.) + if not self._dotstar_mode: + w = int(w * self._brightness) + self._post_brightness_buffer[offset + self._byteorder[3]] = w + + self._post_brightness_buffer[offset + self._byteorder[0]] = int( + r * self._brightness + ) + self._post_brightness_buffer[offset + self._byteorder[1]] = int( + g * self._brightness + ) + self._post_brightness_buffer[offset + self._byteorder[2]] = int( + b * self._brightness + ) + + def __setitem__(self, index, val): + if isinstance(index, slice): + start, stop, step = index.indices(self._pixels) + for val_i, in_i in enumerate(range(start, stop, step)): + r, g, b, w = self._parse_color(val[val_i]) + self._set_item(in_i, r, g, b, w) + else: + r, g, b, w = self._parse_color(val) + self._set_item(index, r, g, b, w) + + if self.auto_write: + self.show() + + def _getitem(self, index): + start = self._offset + (index * self._bpp) + buffer = ( + self._pre_brightness_buffer + if self._pre_brightness_buffer is not None + else self._post_brightness_buffer + ) + value = [ + buffer[start + self._byteorder[0]], + buffer[start + self._byteorder[1]], + buffer[start + self._byteorder[2]], + ] + if self._has_white: + value.append(buffer[start + self._byteorder[3]]) + elif self._dotstar_mode: + value.append( + (buffer[start + self._byteorder[3]] & DOTSTAR_LED_BRIGHTNESS) / 31.0 + ) + return value + + def __getitem__(self, index): + if isinstance(index, slice): + out = [] + for in_i in range( + *index.indices(len(self._post_brightness_buffer) // self._bpp) + ): + out.append(self._getitem(in_i)) + return out + if index < 0: + index += len(self) + if index >= self._pixels or index < 0: + raise IndexError + return self._getitem(index) + + def _transmit(self, buffer): + raise NotImplementedError("Must be subclassed") + + +def wheel(pos): + """ + Helper to create a colorwheel. + + :param pos: int 0-255 of color value to return + :return: tuple of RGB values + """ + # Input a value 0 to 255 to get a color value. + # The colours are a transition r - g - b - back to r. + if pos < 0 or pos > 255: + return 0, 0, 0 + if pos < 85: + return 255 - pos * 3, pos * 3, 0 + if pos < 170: + pos -= 85 + return 0, 255 - pos * 3, pos * 3 + pos -= 170 + return pos * 3, 0, 255 - pos * 3 diff --git a/ports/esp32s2/boards/unexpectedmaker_pros2/board.c b/ports/esp32s2/boards/unexpectedmaker_pros2/board.c new file mode 100644 index 0000000000..1dc30b5af8 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_pros2/board.c @@ -0,0 +1,56 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Scott Shawcroft 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. + */ + +#include "boards/board.h" +#include "mpconfigboard.h" +#include "shared-bindings/microcontroller/Pin.h" + +void board_init(void) { + // USB + common_hal_never_reset_pin(&pin_GPIO19); + common_hal_never_reset_pin(&pin_GPIO20); + + // Debug UART + common_hal_never_reset_pin(&pin_GPIO43); + common_hal_never_reset_pin(&pin_GPIO44); + + // SPI Flash and RAM + common_hal_never_reset_pin(&pin_GPIO26); + common_hal_never_reset_pin(&pin_GPIO27); + common_hal_never_reset_pin(&pin_GPIO28); + common_hal_never_reset_pin(&pin_GPIO29); + common_hal_never_reset_pin(&pin_GPIO30); + common_hal_never_reset_pin(&pin_GPIO31); + common_hal_never_reset_pin(&pin_GPIO32); +} + +bool board_requests_safe_mode(void) { + return false; +} + +void reset_board(void) { + +} diff --git a/ports/esp32s2/boards/unexpectedmaker_pros2/mpconfigboard.h b/ports/esp32s2/boards/unexpectedmaker_pros2/mpconfigboard.h new file mode 100644 index 0000000000..2cfb7b6a0f --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_pros2/mpconfigboard.h @@ -0,0 +1,35 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Scott Shawcroft 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. + */ + +//Micropython setup + +#define MICROPY_HW_BOARD_NAME "ProS2" +#define MICROPY_HW_MCU_NAME "ESP32S2" + +#define AUTORESET_DELAY_MS 500 + +#define MICROPY_HW_APA102_MOSI (&pin_GPIO40) +#define MICROPY_HW_APA102_SCK (&pin_GPIO45) diff --git a/ports/esp32s2/boards/unexpectedmaker_pros2/mpconfigboard.mk b/ports/esp32s2/boards/unexpectedmaker_pros2/mpconfigboard.mk new file mode 100644 index 0000000000..cb6823ba67 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_pros2/mpconfigboard.mk @@ -0,0 +1,21 @@ +USB_VID = 0x239A +USB_PID = 0x80AA +USB_PRODUCT = "ProS2" +USB_MANUFACTURER = "UnexpectedMaker" +USB_DEVICES = "CDC,MSC,HID" + +INTERNAL_FLASH_FILESYSTEM = 1 +LONGINT_IMPL = MPZ + +# The default queue depth of 16 overflows on release builds, +# so increase it to 32. +CFLAGS += -DCFG_TUD_TASK_QUEUE_SZ=32 + +CIRCUITPY_ESP_FLASH_MODE=qio +CIRCUITPY_ESP_FLASH_FREQ=40m +CIRCUITPY_ESP_FLASH_SIZE=16MB + +CIRCUITPY_BITBANG_APA102 = 1 + +# Include these Python libraries in firmware. +FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_DotStar diff --git a/ports/esp32s2/boards/unexpectedmaker_pros2/pins.c b/ports/esp32s2/boards/unexpectedmaker_pros2/pins.c new file mode 100644 index 0000000000..349ad1bd5d --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_pros2/pins.c @@ -0,0 +1,58 @@ +#include "shared-bindings/board/__init__.h" + +STATIC const mp_rom_map_elem_t board_global_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_A0), MP_ROM_PTR(&pin_GPIO17) }, + { MP_ROM_QSTR(MP_QSTR_D14), MP_ROM_PTR(&pin_GPIO17) }, + + { MP_ROM_QSTR(MP_QSTR_A1), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_D15), MP_ROM_PTR(&pin_GPIO18) }, + + { MP_ROM_QSTR(MP_QSTR_A2), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_D16), MP_ROM_PTR(&pin_GPIO4) }, + + { MP_ROM_QSTR(MP_QSTR_A3), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_D17), MP_ROM_PTR(&pin_GPIO12) }, + + { MP_ROM_QSTR(MP_QSTR_A4), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_D18), MP_ROM_PTR(&pin_GPIO6) }, + + { MP_ROM_QSTR(MP_QSTR_A5), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_D19), MP_ROM_PTR(&pin_GPIO5) }, + + { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO36) }, + { MP_ROM_QSTR(MP_QSTR_D25), MP_ROM_PTR(&pin_GPIO36) }, + + { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO35) }, + { MP_ROM_QSTR(MP_QSTR_D24), MP_ROM_PTR(&pin_GPIO35) }, + + { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO37) }, + { MP_ROM_QSTR(MP_QSTR_D23), MP_ROM_PTR(&pin_GPIO37) }, + + { MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_GPIO44) }, + { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO44) }, + + { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_GPIO43) }, + { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO43) }, + + { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_GPIO1) }, + { MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_GPIO11) }, + + { MP_ROM_QSTR(MP_QSTR_APA102_MOSI), MP_ROM_PTR(&pin_GPIO40) }, // MTDO + { MP_ROM_QSTR(MP_QSTR_APA102_SCK), MP_ROM_PTR(&pin_GPIO45) }, + + { MP_ROM_QSTR(MP_QSTR_LDO2), MP_ROM_PTR(&pin_GPIO21) }, + + { MP_ROM_QSTR(MP_QSTR_VBAT_SENSE), MP_ROM_PTR(&pin_GPIO14) }, + + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) }, + { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) }, +}; +MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table); diff --git a/ports/esp32s2/boards/unexpectedmaker_pros2/pros2.py b/ports/esp32s2/boards/unexpectedmaker_pros2/pros2.py new file mode 100644 index 0000000000..3d2397d5d4 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_pros2/pros2.py @@ -0,0 +1,63 @@ +# Pros2 Helper Library +# 2020 Seon Rozenblum, Unexpected Maker +# +# Project home: +# https://pros2.io +# + +# Import required libraries +import time +import board +from digitalio import DigitalInOut, Direction, Pull +from analogio import AnalogIn + + +# Helper functions + +def enable_LDO2(state): + """Set the power for the second on-board LDO to allow no current draw when not needed.""" + + # Grab a reference to the LDO2 IO (21 in this case) + ldo2 = DigitalInOut(board.LDO2) + ldo2.direction = Direction.OUTPUT + + # Set the LDO2 power pin on / off + ldo2.value = state + + # A small delay to let the IO change state + time.sleep(0.035) + + +# Dotstar rainbow colour wheel +def dotstar_color_wheel(wheel_pos): + """Color wheel to allow for cycling through the rainbow of RGB colors.""" + wheel_pos = wheel_pos % 255 + + if wheel_pos < 85: + return 255 - wheel_pos * 3, 0, wheel_pos * 3 + elif wheel_pos < 170: + wheel_pos -= 85 + return 0, wheel_pos * 3, 255 - wheel_pos * 3 + else: + wheel_pos -= 170 + return wheel_pos * 3, 255 - wheel_pos * 3, 0 + + +# Get a *rough* estimate of the current battery voltage +# If the battery is not present, the charge IC will still report it's trying to charge at X voltage +# so it will still show a voltage. +def get_battery_voltage(): + """ + Returns the current battery voltage. If no battery is connected, returns 3.7V + This is an approximation only, but useful to detect of the charge state of the battery is getting low. + """ + adc = AnalogIn(board.VBAT_SENSE) + + measured_vbat = adc.value # Read the value + measured_vbat /= 4095 # divide by 4095 as we are using the default ADC voltage range of 0-1V + measured_vbat *= 3.7 # Multiply by 3.7V, our reference voltage + return measured_vbat + + +# Disable LDO2 by default +enable_LDO2(False) diff --git a/ports/esp32s2/boards/unexpectedmaker_pros2/sdkconfig b/ports/esp32s2/boards/unexpectedmaker_pros2/sdkconfig new file mode 100644 index 0000000000..b73c4a8c20 --- /dev/null +++ b/ports/esp32s2/boards/unexpectedmaker_pros2/sdkconfig @@ -0,0 +1,35 @@ +CONFIG_ESP32S2_SPIRAM_SUPPORT=y + +# +# SPI RAM config +# +# CONFIG_SPIRAM_TYPE_AUTO is not set +# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set +# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set +CONFIG_SPIRAM_TYPE_ESPPSRAM64=y +CONFIG_SPIRAM_SIZE=8388608 + +# +# PSRAM clock and cs IO for ESP32S2 +# +CONFIG_DEFAULT_PSRAM_CLK_IO=30 +CONFIG_DEFAULT_PSRAM_CS_IO=26 +# end of PSRAM clock and cs IO for ESP32S2 + +CONFIG_SPIRAM_SPIWP_SD3_PIN=28 +# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set +# CONFIG_SPIRAM_RODATA is not set +# CONFIG_SPIRAM_USE_AHB_DBUS3 is not set +# CONFIG_SPIRAM_SPEED_80M is not set +CONFIG_SPIRAM_SPEED_40M=y +# CONFIG_SPIRAM_SPEED_26M is not set +# CONFIG_SPIRAM_SPEED_20M is not set +CONFIG_SPIRAM=y +CONFIG_SPIRAM_BOOT_INIT=y +# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set +CONFIG_SPIRAM_USE_MEMMAP=y +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +# CONFIG_SPIRAM_USE_MALLOC is not set +CONFIG_SPIRAM_MEMTEST=y +# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set +# end of SPI RAM config