added files
parent
9e3c8ca05d
commit
d840ec58d8
|
|
@ -1 +0,0 @@
|
||||||
/files/
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,612 @@
|
||||||
|
# SPDX-FileCopyrightText: <text> 2018 Kattni Rembor, Melissa LeBlanc-Williams
|
||||||
|
# and Tony DiCola, for Adafruit Industries.
|
||||||
|
# Original file created by Damien P. George </text>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
`adafruit_framebuf`
|
||||||
|
====================================================
|
||||||
|
|
||||||
|
CircuitPython pure-python framebuf module, based on the micropython framebuf module.
|
||||||
|
|
||||||
|
Implementation Notes
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
**Hardware:**
|
||||||
|
|
||||||
|
* `Adafruit SSD1306 OLED displays <https://www.adafruit.com/?q=ssd1306>`_
|
||||||
|
* `Adafruit HT16K33 Matrix displays <https://www.adafruit.com/?q=ht16k33>`_
|
||||||
|
|
||||||
|
**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."""
|
||||||
|
|
@ -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)
|
||||||
|
<https://en.wikipedia.org/wiki/Speed_of_sound>`_, 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
|
||||||
|
<https://www.adafruit.com/product/2653?q=level%20shifter&>`_ 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
|
||||||
Binary file not shown.
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue