diff --git a/.gitignore b/.gitignore index 1f1c8ed..e69de29 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +0,0 @@ -/files/ \ No newline at end of file diff --git a/files/MuEditor-Linux-1.2.0-x86_64.tar b/files/MuEditor-Linux-1.2.0-x86_64.tar new file mode 100644 index 0000000..9e181a9 Binary files /dev/null and b/files/MuEditor-Linux-1.2.0-x86_64.tar differ diff --git a/files/MuEditor-OSX-1.2.0.dmg b/files/MuEditor-OSX-1.2.0.dmg new file mode 100644 index 0000000..805da4a Binary files /dev/null and b/files/MuEditor-OSX-1.2.0.dmg differ diff --git a/files/MuEditor-win64-1.2.0.msi b/files/MuEditor-win64-1.2.0.msi new file mode 100644 index 0000000..457642a Binary files /dev/null and b/files/MuEditor-win64-1.2.0.msi differ diff --git a/files/adafruit-circuitpython-waveshare_rp2040_zero-en_GB-9.2.8.uf2 b/files/adafruit-circuitpython-waveshare_rp2040_zero-en_GB-9.2.8.uf2 new file mode 100644 index 0000000..e0d891a Binary files /dev/null and b/files/adafruit-circuitpython-waveshare_rp2040_zero-en_GB-9.2.8.uf2 differ diff --git a/files/adafruit_framebuf.py b/files/adafruit_framebuf.py new file mode 100644 index 0000000..ef45f5c --- /dev/null +++ b/files/adafruit_framebuf.py @@ -0,0 +1,612 @@ +# SPDX-FileCopyrightText: 2018 Kattni Rembor, Melissa LeBlanc-Williams +# and Tony DiCola, for Adafruit Industries. +# Original file created by Damien P. George +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_framebuf` +==================================================== + +CircuitPython pure-python framebuf module, based on the micropython framebuf module. + +Implementation Notes +-------------------- + +**Hardware:** + +* `Adafruit SSD1306 OLED displays `_ +* `Adafruit HT16K33 Matrix displays `_ + +**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_framebuf.git" + +import os +import struct + +# Framebuf format constants: +MVLSB = 0 # Single bit displays (like SSD1306 OLED) +RGB565 = 1 # 16-bit color displays +GS4_HMSB = 2 # Unimplemented! +MHMSB = 3 # Single bit displays like the Sharp Memory +RGB888 = 4 # Neopixels and Dotstars +GS2_HMSB = 5 # 2-bit color displays like the HT16K33 8x8 Matrix + + +class GS2HMSBFormat: + """GS2HMSBFormat""" + + @staticmethod + def set_pixel(framebuf, x, y, color): + """Set a given pixel to a color.""" + index = (y * framebuf.stride + x) >> 2 + pixel = framebuf.buf[index] + + shift = (x & 0b11) << 1 + mask = 0b11 << shift + color = (color & 0b11) << shift + + framebuf.buf[index] = color | (pixel & (~mask)) + + @staticmethod + def get_pixel(framebuf, x, y): + """Get the color of a given pixel""" + index = (y * framebuf.stride + x) >> 2 + pixel = framebuf.buf[index] + + shift = (x & 0b11) << 1 + return (pixel >> shift) & 0b11 + + @staticmethod + def fill(framebuf, color): + """completely fill/clear the buffer with a color""" + if color: + bits = color & 0b11 + fill = (bits << 6) | (bits << 4) | (bits << 2) | (bits << 0) + else: + fill = 0x00 + + framebuf.buf = [fill for i in range(len(framebuf.buf))] + + @staticmethod + def rect(framebuf, x, y, width, height, color): + """Draw the outline of a rectangle at the given location, size and color.""" + for _x in range(x, x + width): + for _y in range(y, y + height): + if _x in {x, x + width} or _y in {y, y + height}: + GS2HMSBFormat.set_pixel(framebuf, _x, _y, color) + + @staticmethod + def fill_rect(framebuf, x, y, width, height, color): + """Draw the outline and interior of a rectangle at the given location, size and color.""" + for _x in range(x, x + width): + for _y in range(y, y + height): + GS2HMSBFormat.set_pixel(framebuf, _x, _y, color) + + +class MHMSBFormat: + """MHMSBFormat""" + + @staticmethod + def set_pixel(framebuf, x, y, color): + """Set a given pixel to a color.""" + index = (y * framebuf.stride + x) // 8 + offset = 7 - x & 0x07 + framebuf.buf[index] = (framebuf.buf[index] & ~(0x01 << offset)) | ((color != 0) << offset) + + @staticmethod + def get_pixel(framebuf, x, y): + """Get the color of a given pixel""" + index = (y * framebuf.stride + x) // 8 + offset = 7 - x & 0x07 + return (framebuf.buf[index] >> offset) & 0x01 + + @staticmethod + def fill(framebuf, color): + """completely fill/clear the buffer with a color""" + if color: + fill = 0xFF + else: + fill = 0x00 + for i in range(len(framebuf.buf)): + framebuf.buf[i] = fill + + @staticmethod + def fill_rect(framebuf, x, y, width, height, color): + """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws + both the outline and interior.""" + for _x in range(x, x + width): + offset = 7 - _x & 0x07 + for _y in range(y, y + height): + index = (_y * framebuf.stride + _x) // 8 + framebuf.buf[index] = (framebuf.buf[index] & ~(0x01 << offset)) | ( + (color != 0) << offset + ) + + +class MVLSBFormat: + """MVLSBFormat""" + + @staticmethod + def set_pixel(framebuf, x, y, color): + """Set a given pixel to a color.""" + index = (y >> 3) * framebuf.stride + x + offset = y & 0x07 + framebuf.buf[index] = (framebuf.buf[index] & ~(0x01 << offset)) | ((color != 0) << offset) + + @staticmethod + def get_pixel(framebuf, x, y): + """Get the color of a given pixel""" + index = (y >> 3) * framebuf.stride + x + offset = y & 0x07 + return (framebuf.buf[index] >> offset) & 0x01 + + @staticmethod + def fill(framebuf, color): + """completely fill/clear the buffer with a color""" + if color: + fill = 0xFF + else: + fill = 0x00 + for i in range(len(framebuf.buf)): + framebuf.buf[i] = fill + + @staticmethod + def fill_rect(framebuf, x, y, width, height, color): + """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws + both the outline and interior.""" + while height > 0: + index = (y >> 3) * framebuf.stride + x + offset = y & 0x07 + for w_w in range(width): + framebuf.buf[index + w_w] = (framebuf.buf[index + w_w] & ~(0x01 << offset)) | ( + (color != 0) << offset + ) + y += 1 + height -= 1 + + +class RGB565Format: + """ + This class implements the RGB565 format + It assumes a little-endian byte order in the frame buffer + """ + + @staticmethod + def color_to_rgb565(color): + """Convert a color in either tuple or 24 bit integer form to RGB565, + and return as two bytes""" + if isinstance(color, tuple): + hibyte = (color[0] & 0xF8) | (color[1] >> 5) + lobyte = ((color[1] << 5) & 0xE0) | (color[2] >> 3) + else: + hibyte = ((color >> 16) & 0xF8) | ((color >> 13) & 0x07) + lobyte = ((color >> 5) & 0xE0) | ((color >> 3) & 0x1F) + return bytes([lobyte, hibyte]) + + def set_pixel(self, framebuf, x, y, color): + """Set a given pixel to a color.""" + index = (y * framebuf.stride + x) * 2 + framebuf.buf[index : index + 2] = self.color_to_rgb565(color) + + @staticmethod + def get_pixel(framebuf, x, y): + """Get the color of a given pixel""" + index = (y * framebuf.stride + x) * 2 + lobyte, hibyte = framebuf.buf[index : index + 2] + r = hibyte & 0xF8 + g = ((hibyte & 0x07) << 5) | ((lobyte & 0xE0) >> 5) + b = (lobyte & 0x1F) << 3 + return (r << 16) | (g << 8) | b + + def fill(self, framebuf, color): + """completely fill/clear the buffer with a color""" + rgb565_color = self.color_to_rgb565(color) + for i in range(0, len(framebuf.buf), 2): + framebuf.buf[i : i + 2] = rgb565_color + + def fill_rect(self, framebuf, x, y, width, height, color): + """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws + both the outline and interior.""" + rgb565_color = self.color_to_rgb565(color) + for _y in range(2 * y, 2 * (y + height), 2): + offset2 = _y * framebuf.stride + for _x in range(2 * x, 2 * (x + width), 2): + index = offset2 + _x + framebuf.buf[index : index + 2] = rgb565_color + + +class RGB888Format: + """RGB888Format""" + + @staticmethod + def set_pixel(framebuf, x, y, color): + """Set a given pixel to a color.""" + index = (y * framebuf.stride + x) * 3 + if isinstance(color, tuple): + framebuf.buf[index : index + 3] = bytes(color) + else: + framebuf.buf[index : index + 3] = bytes( + ((color >> 16) & 255, (color >> 8) & 255, color & 255) + ) + + @staticmethod + def get_pixel(framebuf, x, y): + """Get the color of a given pixel""" + index = (y * framebuf.stride + x) * 3 + return ( + (framebuf.buf[index] << 16) | (framebuf.buf[index + 1] << 8) | framebuf.buf[index + 2] + ) + + @staticmethod + def fill(framebuf, color): + """completely fill/clear the buffer with a color""" + fill = (color >> 16) & 255, (color >> 8) & 255, color & 255 + for i in range(0, len(framebuf.buf), 3): + framebuf.buf[i : i + 3] = bytes(fill) + + @staticmethod + def fill_rect(framebuf, x, y, width, height, color): + """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws + both the outline and interior.""" + fill = (color >> 16) & 255, (color >> 8) & 255, color & 255 + for _x in range(x, x + width): + for _y in range(y, y + height): + index = (_y * framebuf.stride + _x) * 3 + framebuf.buf[index : index + 3] = bytes(fill) + + +class FrameBuffer: + """FrameBuffer object. + + :param buf: An object with a buffer protocol which must be large enough to contain every + pixel defined by the width, height and format of the FrameBuffer. + :param width: The width of the FrameBuffer in pixel + :param height: The height of the FrameBuffer in pixel + :param buf_format: Specifies the type of pixel used in the FrameBuffer; permissible values + are listed under Constants below. These set the number of bits used to + encode a color value and the layout of these bits in ``buf``. Where a + color value c is passed to a method, c is a small integer with an encoding + that is dependent on the format of the FrameBuffer. + :param stride: The number of pixels between each horizontal line of pixels in the + FrameBuffer. This defaults to ``width`` but may need adjustments when + implementing a FrameBuffer within another larger FrameBuffer or screen. The + ``buf`` size must accommodate an increased step size. + + """ + + def __init__(self, buf, width, height, buf_format=MVLSB, stride=None): + self.buf = buf + self.width = width + self.height = height + self.stride = stride + self._font = None + if self.stride is None: + self.stride = width + if buf_format == MVLSB: + self.format = MVLSBFormat() + elif buf_format == MHMSB: + self.format = MHMSBFormat() + elif buf_format == RGB888: + self.format = RGB888Format() + elif buf_format == RGB565: + self.format = RGB565Format() + elif buf_format == GS2_HMSB: + self.format = GS2HMSBFormat() + else: + raise ValueError("invalid format") + self._rotation = 0 + + @property + def rotation(self): + """The rotation setting of the display, can be one of (0, 1, 2, 3)""" + return self._rotation + + @rotation.setter + def rotation(self, val): + if val not in {0, 1, 2, 3}: + raise RuntimeError("Bad rotation setting") + self._rotation = val + + def fill(self, color): + """Fill the entire FrameBuffer with the specified color.""" + self.format.fill(self, color) + + def fill_rect(self, x, y, width, height, color): + """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws + both the outline and interior.""" + self.rect(x, y, width, height, color, fill=True) + + def pixel(self, x, y, color=None): + """If ``color`` is not given, get the color value of the specified pixel. If ``color`` is + given, set the specified pixel to the given color.""" + if self.rotation == 1: + x, y = y, x + x = self.width - x - 1 + if self.rotation == 2: + x = self.width - x - 1 + y = self.height - y - 1 + if self.rotation == 3: + x, y = y, x + y = self.height - y - 1 + + if x < 0 or x >= self.width or y < 0 or y >= self.height: + return None + if color is None: + return self.format.get_pixel(self, x, y) + self.format.set_pixel(self, x, y, color) + return None + + def hline(self, x, y, width, color): + """Draw a horizontal line up to a given length.""" + self.rect(x, y, width, 1, color, fill=True) + + def vline(self, x, y, height, color): + """Draw a vertical line up to a given length.""" + self.rect(x, y, 1, height, color, fill=True) + + def circle(self, center_x, center_y, radius, color): + """Draw a circle at the given midpoint location, radius and color. + The ```circle``` method draws only a 1 pixel outline.""" + x = radius - 1 + y = 0 + d_x = 1 + d_y = 1 + err = d_x - (radius << 1) + while x >= y: + self.pixel(center_x + x, center_y + y, color) + self.pixel(center_x + y, center_y + x, color) + self.pixel(center_x - y, center_y + x, color) + self.pixel(center_x - x, center_y + y, color) + self.pixel(center_x - x, center_y - y, color) + self.pixel(center_x - y, center_y - x, color) + self.pixel(center_x + y, center_y - x, color) + self.pixel(center_x + x, center_y - y, color) + if err <= 0: + y += 1 + err += d_y + d_y += 2 + if err > 0: + x -= 1 + d_x += 2 + err += d_x - (radius << 1) + + def rect(self, x, y, width, height, color, *, fill=False): + """Draw a rectangle at the given location, size and color. The ```rect``` method draws only + a 1 pixel outline.""" + # pylint: disable=too-many-arguments + if self.rotation == 1: + x, y = y, x + width, height = height, width + x = self.width - x - width + if self.rotation == 2: + x = self.width - x - width + y = self.height - y - height + if self.rotation == 3: + x, y = y, x + width, height = height, width + y = self.height - y - height + + if ( + width < 1 + or height < 1 + or (x + width) <= 0 + or (y + height) <= 0 + or y >= self.height + or x >= self.width + ): + return + x_end = min(self.width - 1, x + width - 1) + y_end = min(self.height - 1, y + height - 1) + x = max(x, 0) + y = max(y, 0) + if fill: + self.format.fill_rect(self, x, y, x_end - x + 1, y_end - y + 1, color) + else: + self.format.fill_rect(self, x, y, x_end - x + 1, 1, color) + self.format.fill_rect(self, x, y, 1, y_end - y + 1, color) + self.format.fill_rect(self, x, y_end, x_end - x + 1, 1, color) + self.format.fill_rect(self, x_end, y, 1, y_end - y + 1, color) + + def line(self, x_0, y_0, x_1, y_1, color): + """Bresenham's line algorithm""" + d_x = abs(x_1 - x_0) + d_y = abs(y_1 - y_0) + x, y = x_0, y_0 + s_x = -1 if x_0 > x_1 else 1 + s_y = -1 if y_0 > y_1 else 1 + if d_x > d_y: + err = d_x / 2.0 + while x != x_1: + self.pixel(x, y, color) + err -= d_y + if err < 0: + y += s_y + err += d_x + x += s_x + else: + err = d_y / 2.0 + while y != y_1: + self.pixel(x, y, color) + err -= d_x + if err < 0: + x += s_x + err += d_y + y += s_y + self.pixel(x, y, color) + + def blit(self): + """blit is not yet implemented""" + raise NotImplementedError() + + def scroll(self, delta_x, delta_y): + """shifts framebuf in x and y direction""" + if delta_x < 0: + shift_x = 0 + xend = self.width + delta_x + dt_x = 1 + else: + shift_x = self.width - 1 + xend = delta_x - 1 + dt_x = -1 + if delta_y < 0: + y = 0 + yend = self.height + delta_y + dt_y = 1 + else: + y = self.height - 1 + yend = delta_y - 1 + dt_y = -1 + while y != yend: + x = shift_x + while x != xend: + self.format.set_pixel( + self, x, y, self.format.get_pixel(self, x - delta_x, y - delta_y) + ) + x += dt_x + y += dt_y + + def text(self, string, x, y, color, *, font_name="font5x8.bin", size=1): + """Place text on the screen in variables sizes. Breaks on \n to next line. + + Does not break on line going off screen. + """ + # determine our effective width/height, taking rotation into account + frame_width = self.width + frame_height = self.height + if self.rotation in {1, 3}: + frame_width, frame_height = frame_height, frame_width + + for chunk in string.split("\n"): + if not self._font or self._font.font_name != font_name: + # load the font! + self._font = BitmapFont(font_name) + width = self._font.font_width + height = self._font.font_height + for i, char in enumerate(chunk): + char_x = x + (i * (width + 1)) * size + if ( + char_x + (width * size) > 0 + and char_x < frame_width + and y + (height * size) > 0 + and y < frame_height + ): + self._font.draw_char(char, char_x, y, self, color, size=size) + y += height * size + + def image(self, img): + """Set buffer to value of Python Imaging Library image. The image should + be in 1 bit mode and a size equal to the display size.""" + # determine our effective width/height, taking rotation into account + width = self.width + height = self.height + if self.rotation in {1, 3}: + width, height = height, width + + if isinstance(self.format, (RGB565Format, RGB888Format)) and img.mode != "RGB": + raise ValueError("Image must be in mode RGB.") + if isinstance(self.format, (MHMSBFormat, MVLSBFormat)) and img.mode != "1": + raise ValueError("Image must be in mode 1.") + + imwidth, imheight = img.size + if imwidth != width or imheight != height: + raise ValueError(f"Image must be same dimensions as display ({width}x{height}).") + # Grab all the pixels from the image, faster than getpixel. + pixels = img.load() + # Clear buffer + for i in range(len(self.buf)): + self.buf[i] = 0 + # Iterate through the pixels + for x in range(width): # yes this double loop is slow, + for y in range(height): # but these displays are small! + if img.mode == "RGB": + self.pixel(x, y, pixels[(x, y)]) + elif pixels[(x, y)]: + self.pixel(x, y, 1) # only write if pixel is true + + +# MicroPython basic bitmap font renderer. +# Author: Tony DiCola +# License: MIT License (https://opensource.org/licenses/MIT) +class BitmapFont: + """A helper class to read binary font tiles and 'seek' through them as a + file to display in a framebuffer. We use file access so we dont waste 1KB + of RAM on a font!""" + + def __init__(self, font_name="font5x8.bin"): + # Specify the drawing area width and height, and the pixel function to + # call when drawing pixels (should take an x and y param at least). + # Optionally specify font_name to override the font file to use (default + # is font5x8.bin). The font format is a binary file with the following + # format: + # - 1 unsigned byte: font character width in pixels + # - 1 unsigned byte: font character height in pixels + # - x bytes: font data, in ASCII order covering all 255 characters. + # Each character should have a byte for each pixel column of + # data (i.e. a 5x8 font has 5 bytes per character). + self.font_name = font_name + + # Open the font file and grab the character width and height values. + # Note that only fonts up to 8 pixels tall are currently supported. + try: + self._font = open(self.font_name, "rb") + self.font_width, self.font_height = struct.unpack("BB", self._font.read(2)) + # simple font file validation check based on expected file size + if 2 + 256 * self.font_width != os.stat(font_name)[6]: + raise RuntimeError("Invalid font file: " + font_name) + except OSError: + print("Could not find font file", font_name) + raise + except OverflowError: + # os.stat can throw this on boards without long int support + # just hope the font file is valid and press on + pass + + def deinit(self): + """Close the font file as cleanup.""" + self._font.close() + + def __enter__(self): + """Initialize/open the font file""" + self.__init__() + return self + + def __exit__(self, exception_type, exception_value, traceback): + """cleanup on exit""" + self.deinit() + + def draw_char(self, char, x, y, framebuffer, color, size=1): + """Draw one character at position (x,y) to a framebuffer in a given color""" + size = max(size, 1) + # Don't draw the character if it will be clipped off the visible area. + # if x < -self.font_width or x >= framebuffer.width or \ + # y < -self.font_height or y >= framebuffer.height: + # return + # Go through each column of the character. + for char_x in range(self.font_width): + # Grab the byte for the current column of font data. + self._font.seek(2 + (ord(char) * self.font_width) + char_x) + try: + line = struct.unpack("B", self._font.read(1))[0] + except RuntimeError: + continue # maybe character isnt there? go to next + # Go through each row in the column byte. + for char_y in range(self.font_height): + # Draw a pixel for each bit that's flipped on. + if (line >> char_y) & 0x1: + framebuffer.fill_rect(x + char_x * size, y + char_y * size, size, size, color) + + def width(self, text): + """Return the pixel width of the specified text message.""" + return len(text) * (self.font_width + 1) + + +class FrameBuffer1(FrameBuffer): + """FrameBuffer1 object. Inherits from FrameBuffer.""" diff --git a/files/adafruit_hcsr04.py b/files/adafruit_hcsr04.py new file mode 100644 index 0000000..445f240 --- /dev/null +++ b/files/adafruit_hcsr04.py @@ -0,0 +1,180 @@ +# SPDX-FileCopyrightText: 2017 Mike Mabey +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_hcsr04` +==================================================== + +A CircuitPython library for the HC-SR04 ultrasonic range sensor. + +The HC-SR04 functions by sending an ultrasonic signal, which is reflected by +many materials, and then sensing when the signal returns to the sensor. Knowing +that sound travels through dry air at `343.2 meters per second (at 20 °C) +`_, it's pretty straightforward +to calculate how far away the object is by timing how long the signal took to +go round-trip and do some simple arithmetic, which is handled for you by this +library. + +.. warning:: + + The HC-SR04 uses 5V logic, so you will have to use a `level shifter + `_ or simple + voltage divider between it and your CircuitPython board (which uses 3.3V logic) + +* Authors: + + - Mike Mabey + - Jerry Needell - modified to add timeout while waiting for echo (2/26/2018) + - ladyada - compatible with `distance` property standard, renaming, Pi compat +""" + +import time + +from digitalio import DigitalInOut, Direction + +try: + from types import TracebackType + from typing import Optional, Type + + from microcontroller import Pin +except ImportError: + pass + +_USE_PULSEIO = False +try: + from pulseio import PulseIn + + _USE_PULSEIO = True +except (ImportError, NotImplementedError): + pass # This is OK, we'll try to bitbang it! + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HCSR04.git" + + +class HCSR04: + """Control a HC-SR04 ultrasonic range sensor. + + Example use: + + :: + + import time + import board + + import adafruit_hcsr04 + + sonar = adafruit_hcsr04.HCSR04(trigger_pin=board.D2, echo_pin=board.D3) + + + while True: + try: + print((sonar.distance,)) + except RuntimeError: + print("Retrying!") + pass + time.sleep(0.1) + """ + + def __init__(self, trigger_pin: Pin, echo_pin: Pin, *, timeout: float = 0.1) -> None: + """ + :param trigger_pin: The pin on the microcontroller that's connected to the + ``Trig`` pin on the HC-SR04. + :type trig_pin: microcontroller.Pin + :param echo_pin: The pin on the microcontroller that's connected to the + ``Echo`` pin on the HC-SR04. + :type echo_pin: microcontroller.Pin + :param float timeout: Max seconds to wait for a response from the + sensor before assuming it isn't going to answer. Should *not* be + set to less than 0.05 seconds! + """ + self._timeout = timeout + self._trig = DigitalInOut(trigger_pin) + self._trig.direction = Direction.OUTPUT + + if _USE_PULSEIO: + self._echo = PulseIn(echo_pin) + self._echo.pause() + self._echo.clear() + else: + self._echo = DigitalInOut(echo_pin) + self._echo.direction = Direction.INPUT + + def __enter__(self) -> "HCSR04": + """Allows for use in context managers.""" + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + """Automatically de-initialize after a context manager.""" + self.deinit() + + def deinit(self) -> None: + """De-initialize the trigger and echo pins.""" + self._trig.deinit() + self._echo.deinit() + + @property + def distance(self) -> float: + """Return the distance measured by the sensor in cm. + + This is the function that will be called most often in user code. The + distance is calculated by timing a pulse from the sensor, indicating + how long between when the sensor sent out an ultrasonic signal and when + it bounced back and was received again. + + If no signal is received, we'll throw a RuntimeError exception. This means + either the sensor was moving too fast to be pointing in the right + direction to pick up the ultrasonic signal when it bounced back (less + likely), or the object off of which the signal bounced is too far away + for the sensor to handle. In my experience, the sensor can detect + objects over 460 cm away. + + :return: Distance in centimeters. + :rtype: float + """ + return self._dist_two_wire() # at this time we only support 2-wire meausre + + def _dist_two_wire(self) -> float: + if _USE_PULSEIO: + self._echo.clear() # Discard any previous pulse values + self._trig.value = True # Set trig high + time.sleep(0.00001) # 10 micro seconds 10/1000/1000 + self._trig.value = False # Set trig low + + pulselen = None + timestamp = time.monotonic() + if _USE_PULSEIO: + self._echo.resume() + while not self._echo: + # Wait for a pulse + if (time.monotonic() - timestamp) > self._timeout: + self._echo.pause() + raise RuntimeError("Timed out") + self._echo.pause() + pulselen = self._echo[0] + else: + # OK no hardware pulse support, we'll just do it by hand! + # hang out while the pin is low + while not self._echo.value: + if time.monotonic() - timestamp > self._timeout: + raise RuntimeError("Timed out") + timestamp = time.monotonic() + # track how long pin is high + while self._echo.value: + if time.monotonic() - timestamp > self._timeout: + raise RuntimeError("Timed out") + pulselen = time.monotonic() - timestamp + pulselen *= 1000000 # convert to us to match pulseio + if pulselen >= 65535: + raise RuntimeError("Timed out") + + # positive pulse time, in seconds, times 340 meters/sec, then + # divided by 2 gives meters. Multiply by 100 for cm + # 1/1000000 s/us * 340 m/s * 100 cm/m * 2 = 0.017 + return pulselen * 0.017 diff --git a/files/adafruit_ssd1306.mpy b/files/adafruit_ssd1306.mpy new file mode 100644 index 0000000..0922219 Binary files /dev/null and b/files/adafruit_ssd1306.mpy differ diff --git a/files/neopixel.py b/files/neopixel.py new file mode 100644 index 0000000..80fab71 --- /dev/null +++ b/files/neopixel.py @@ -0,0 +1,177 @@ +# SPDX-FileCopyrightText: 2016 Damien P. George +# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries +# SPDX-FileCopyrightText: 2019 Carter Nelson +# SPDX-FileCopyrightText: 2019 Roy Hooper +# +# SPDX-License-Identifier: MIT + +""" +`neopixel` - NeoPixel strip driver +==================================================== + +* Author(s): Damien P. George, Scott Shawcroft, Carter Nelson, Rose Hooper +""" + +import sys + +import adafruit_pixelbuf +import board +import digitalio +from neopixel_write import neopixel_write + +try: + # Used only for typing + from types import TracebackType + from typing import Optional, Type + + import microcontroller +except ImportError: + pass + + +__version__ = "0.0.0+auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel.git" + + +# Pixel color order constants +RGB = "RGB" +"""Red Green Blue""" +GRB = "GRB" +"""Green Red Blue""" +RGBW = "RGBW" +"""Red Green Blue White""" +GRBW = "GRBW" +"""Green Red Blue White""" + + +class NeoPixel(adafruit_pixelbuf.PixelBuf): + """ + A sequence of neopixels. + + :param ~microcontroller.Pin pin: The pin to output neopixel data on. + :param int n: The number of neopixels in the chain + :param int bpp: Bytes per pixel. 3 for RGB and 4 for RGBW pixels. + :param float brightness: Brightness of the pixels between 0.0 and 1.0 where 1.0 is full + brightness + :param bool auto_write: True if the neopixels should immediately change when set. If False, + `show` must be called explicitly. + :param str pixel_order: Set the pixel color channel order. The default is GRB if bpp is set + to 3, otherwise GRBW is used as the default. + + Example for Circuit Playground Express: + + .. code-block:: python + + import neopixel + from board import * + + RED = 0x100000 # (0x10, 0, 0) also works + + pixels = neopixel.NeoPixel(NEOPIXEL, 10) + for i in range(len(pixels)): + pixels[i] = RED + + Example for Circuit Playground Express setting every other pixel red using a slice: + + .. code-block:: python + + import neopixel + from board import * + import time + + RED = 0x100000 # (0x10, 0, 0) also works + + # Using ``with`` ensures pixels are cleared after we're done. + with neopixel.NeoPixel(NEOPIXEL, 10) as pixels: + pixels[::2] = [RED] * (len(pixels) // 2) + time.sleep(2) + + .. py:method:: NeoPixel.show() + + Shows the new colors on the pixels 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:: NeoPixel.fill(color) + + Colors all pixels the given ***color***. + + .. py:attribute:: brightness + + Overall brightness of the pixel (0 to 1.0) + + """ + + def __init__( + self, + pin: microcontroller.Pin, + n: int, + *, + bpp: int = 3, + brightness: float = 1.0, + auto_write: bool = True, + pixel_order: str = None, + ): + if not pixel_order: + pixel_order = GRB if bpp == 3 else GRBW + elif isinstance(pixel_order, tuple): + order_list = [RGBW[order] for order in pixel_order] + pixel_order = "".join(order_list) + + self._power = None + if sys.implementation.version[0] >= 7 and getattr(board, "NEOPIXEL", None) == pin: + power = getattr(board, "NEOPIXEL_POWER_INVERTED", None) + polarity = power is None + if not power: + power = getattr(board, "NEOPIXEL_POWER", None) + if power: + try: + self._power = digitalio.DigitalInOut(power) + self._power.switch_to_output(value=polarity) + except ValueError: + pass + + super().__init__(n, brightness=brightness, byteorder=pixel_order, auto_write=auto_write) + + self.pin = digitalio.DigitalInOut(pin) + self.pin.direction = digitalio.Direction.OUTPUT + + def deinit(self) -> None: + """Blank out the NeoPixels and release the pin.""" + self.fill(0) + self.show() + self.pin.deinit() + if self._power: + self._power.deinit() + + def __enter__(self): + return self + + def __exit__( + self, + exception_type: Optional[Type[BaseException]], + exception_value: Optional[BaseException], + traceback: Optional[TracebackType], + ): + self.deinit() + + def __repr__(self): + return "[" + ", ".join([str(x) for x in self]) + "]" + + @property + def n(self) -> int: + """ + The number of neopixels in the chain (read-only) + """ + return len(self) + + def write(self) -> None: + """.. deprecated: 1.0.0 + + Use ``show`` instead. It matches Micro:Bit and Arduino APIs.""" + self.show() + + def _transmit(self, buffer: bytearray) -> None: + neopixel_write(self.pin, buffer) diff --git a/files/rr_sonar.py b/files/rr_sonar.py new file mode 100644 index 0000000..cd7cf85 --- /dev/null +++ b/files/rr_sonar.py @@ -0,0 +1,53 @@ +import board +import digitalio +import time + +SPEED_OF_SOUND = 343 # m/s +TRIGGER_DURATION = 0.00001 # 10 µs + +# Internal cache to track history per echo pin +sensor_history = {} + +def measure_distance(trigger_pin, echo_pin, sample_window=5): + global sensor_history + + with digitalio.DigitalInOut(trigger_pin) as trigger, digitalio.DigitalInOut(echo_pin) as echo: + trigger.direction = digitalio.Direction.OUTPUT + echo.direction = digitalio.Direction.INPUT + + # Send trigger pulse + trigger.value = False + time.sleep(0.0001) + trigger.value = True + time.sleep(TRIGGER_DURATION) + trigger.value = False + + # Timeout setup + start_time = time.monotonic_ns() + timeout = start_time + 3_000_000 # 3 ms + + while not echo.value: + if time.monotonic_ns() > timeout: + break + pulse_start = time.monotonic_ns() + + while echo.value: + if time.monotonic_ns() > timeout: + return None + pulse_end = time.monotonic_ns() + + # Calculate raw distance + pulse_duration = (pulse_end - pulse_start) / 1_000_000_000 + distance_cm = (pulse_duration * SPEED_OF_SOUND * 100) / 2 + + # Store reading in history buffer + key = str(echo_pin) # Use pin identity as key + if key not in sensor_history: + sensor_history[key] = [] + history = sensor_history[key] + history.append(distance_cm) + if len(history) > sample_window: + history.pop(0) + + smoothed = sum(history) / len(history) + return smoothed