aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZoltan Szabatin <[email protected]>2025-03-05 20:07:54 -0800
committerZoltan Szabatin <[email protected]>2025-03-05 20:07:54 -0800
commit513235a9953648026aba927d96b93b0e80b84083 (patch)
tree3f37eaf9ed4fc8257f148def2f928de93f4d046f
parentfix(Input): Controller button indexes (diff)
downloadsplitscreen-duo-513235a9953648026aba927d96b93b0e80b84083.tar.xz
splitscreen-duo-513235a9953648026aba927d96b93b0e80b84083.zip
feat: Add Pico-specific modules
-rwxr-xr-xsrc/pico/System Volume Information/IndexerVolumeGuidbin0 -> 76 bytes
-rwxr-xr-xsrc/pico/System Volume Information/WPSettings.datbin0 -> 12 bytes
-rwxr-xr-xsrc/pico/adafruit_bus_device/__init__.py0
-rwxr-xr-xsrc/pico/adafruit_bus_device/i2c_device.py187
-rwxr-xr-xsrc/pico/adafruit_bus_device/spi_device.py121
-rwxr-xr-xsrc/pico/adafruit_mpu6050.py401
-rwxr-xr-xsrc/pico/adafruit_register/__init__.py0
-rwxr-xr-xsrc/pico/adafruit_register/i2c_bcd_alarm.py202
-rwxr-xr-xsrc/pico/adafruit_register/i2c_bcd_datetime.py114
-rwxr-xr-xsrc/pico/adafruit_register/i2c_bit.py84
-rwxr-xr-xsrc/pico/adafruit_register/i2c_bits.py114
-rwxr-xr-xsrc/pico/adafruit_register/i2c_struct.py104
-rwxr-xr-xsrc/pico/adafruit_register/i2c_struct_array.py114
-rwxr-xr-xsrc/pico/boot.py42
-rwxr-xr-xsrc/pico/boot_out.txt5
-rwxr-xr-xsrc/pico/code.py160
-rwxr-xr-xsrc/pico/gamepad.py96
-rwxr-xr-xsrc/pico/lib/adafruit_display_shapes/__init__.py0
-rwxr-xr-xsrc/pico/lib/adafruit_display_shapes/arc.mpybin0 -> 1614 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_shapes/circle.mpybin0 -> 585 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_shapes/filled_polygon.mpybin0 -> 1112 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_shapes/line.mpybin0 -> 460 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_shapes/multisparkline.mpybin0 -> 2185 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_shapes/polygon.mpybin0 -> 1628 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_shapes/rect.mpybin0 -> 993 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_shapes/roundrect.mpybin0 -> 1800 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_shapes/sparkline.mpy/placeholder.txt1
-rwxr-xr-xsrc/pico/lib/adafruit_display_shapes/triangle.mpybin0 -> 1152 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_text/__init__.mpybin0 -> 4158 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_text/bitmap_label.mpybin0 -> 3979 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_text/label.mpybin0 -> 3597 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_text/outlined_label.mpybin0 -> 1735 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_text/scrolling_label.mpybin0 -> 1189 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_display_text/text_box.mpybin0 -> 3323 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_hid/__init__.mpybin0 -> 747 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_hid/consumer_control.mpybin0 -> 629 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_hid/consumer_control_code.mpybin0 -> 379 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_hid/keyboard.mpybin0 -> 1204 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_hid/keyboard_layout_base.mpybin0 -> 1216 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_hid/keyboard_layout_us.mpybin0 -> 338 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_hid/keycode.mpybin0 -> 1979 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_hid/mouse.mpybin0 -> 856 bytes
-rwxr-xr-xsrc/pico/lib/adafruit_ili9341.mpybin0 -> 627 bytes
-rwxr-xr-xsrc/pico/sd/placeholder.txt1
-rwxr-xr-xsrc/pico/settings.toml0
45 files changed, 1746 insertions, 0 deletions
diff --git a/src/pico/System Volume Information/IndexerVolumeGuid b/src/pico/System Volume Information/IndexerVolumeGuid
new file mode 100755
index 0000000..de78ebf
--- /dev/null
+++ b/src/pico/System Volume Information/IndexerVolumeGuid
Binary files differ
diff --git a/src/pico/System Volume Information/WPSettings.dat b/src/pico/System Volume Information/WPSettings.dat
new file mode 100755
index 0000000..e96e427
--- /dev/null
+++ b/src/pico/System Volume Information/WPSettings.dat
Binary files differ
diff --git a/src/pico/adafruit_bus_device/__init__.py b/src/pico/adafruit_bus_device/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/src/pico/adafruit_bus_device/__init__.py
diff --git a/src/pico/adafruit_bus_device/i2c_device.py b/src/pico/adafruit_bus_device/i2c_device.py
new file mode 100755
index 0000000..8f4fc9d
--- /dev/null
+++ b/src/pico/adafruit_bus_device/i2c_device.py
@@ -0,0 +1,187 @@
+# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`adafruit_bus_device.i2c_device` - I2C Bus Device
+====================================================
+"""
+
+import time
+
+try:
+ from typing import Optional, Type
+ from types import TracebackType
+ from circuitpython_typing import ReadableBuffer, WriteableBuffer
+
+ # Used only for type annotations.
+ from busio import I2C
+except ImportError:
+ pass
+
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git"
+
+
+class I2CDevice:
+ """
+ Represents a single I2C device and manages locking the bus and the device
+ address.
+
+ :param ~busio.I2C i2c: The I2C bus the device is on
+ :param int device_address: The 7 bit device address
+ :param bool probe: Probe for the device upon object creation, default is true
+
+ .. note:: This class is **NOT** built into CircuitPython. See
+ :ref:`here for install instructions <bus_device_installation>`.
+
+ Example:
+
+ .. code-block:: python
+
+ import busio
+ from board import *
+ from adafruit_bus_device.i2c_device import I2CDevice
+
+ with busio.I2C(SCL, SDA) as i2c:
+ device = I2CDevice(i2c, 0x70)
+ bytes_read = bytearray(4)
+ with device:
+ device.readinto(bytes_read)
+ # A second transaction
+ with device:
+ device.write(bytes_read)
+ """
+
+ def __init__(self, i2c: I2C, device_address: int, probe: bool = True) -> None:
+ self.i2c = i2c
+ self.device_address = device_address
+
+ if probe:
+ self.__probe_for_device()
+
+ def readinto(
+ self, buf: WriteableBuffer, *, start: int = 0, end: Optional[int] = None
+ ) -> None:
+ """
+ Read into ``buf`` from the device. The number of bytes read will be the
+ length of ``buf``.
+
+ If ``start`` or ``end`` is provided, then the buffer will be sliced
+ as if ``buf[start:end]``. This will not cause an allocation like
+ ``buf[start:end]`` will so it saves memory.
+
+ :param ~WriteableBuffer buffer: buffer to write into
+ :param int start: Index to start writing at
+ :param int end: Index to write up to but not include; if None, use ``len(buf)``
+ """
+ if end is None:
+ end = len(buf)
+ self.i2c.readfrom_into(self.device_address, buf, start=start, end=end)
+
+ def write(
+ self, buf: ReadableBuffer, *, start: int = 0, end: Optional[int] = None
+ ) -> None:
+ """
+ Write the bytes from ``buffer`` to the device, then transmit a stop
+ bit.
+
+ If ``start`` or ``end`` is provided, then the buffer will be sliced
+ as if ``buffer[start:end]``. This will not cause an allocation like
+ ``buffer[start:end]`` will so it saves memory.
+
+ :param ~ReadableBuffer buffer: buffer containing the bytes to write
+ :param int start: Index to start writing from
+ :param int end: Index to read up to but not include; if None, use ``len(buf)``
+ """
+ if end is None:
+ end = len(buf)
+ self.i2c.writeto(self.device_address, buf, start=start, end=end)
+
+ # pylint: disable-msg=too-many-arguments
+ def write_then_readinto(
+ self,
+ out_buffer: ReadableBuffer,
+ in_buffer: WriteableBuffer,
+ *,
+ out_start: int = 0,
+ out_end: Optional[int] = None,
+ in_start: int = 0,
+ in_end: Optional[int] = None
+ ) -> None:
+ """
+ Write the bytes from ``out_buffer`` to the device, then immediately
+ reads into ``in_buffer`` from the device. The number of bytes read
+ will be the length of ``in_buffer``.
+
+ If ``out_start`` or ``out_end`` is provided, then the output buffer
+ will be sliced as if ``out_buffer[out_start:out_end]``. This will
+ not cause an allocation like ``buffer[out_start:out_end]`` will so
+ it saves memory.
+
+ If ``in_start`` or ``in_end`` is provided, then the input buffer
+ will be sliced as if ``in_buffer[in_start:in_end]``. This will not
+ cause an allocation like ``in_buffer[in_start:in_end]`` will so
+ it saves memory.
+
+ :param ~ReadableBuffer out_buffer: buffer containing the bytes to write
+ :param ~WriteableBuffer in_buffer: buffer containing the bytes to read into
+ :param int out_start: Index to start writing from
+ :param int out_end: Index to read up to but not include; if None, use ``len(out_buffer)``
+ :param int in_start: Index to start writing at
+ :param int in_end: Index to write up to but not include; if None, use ``len(in_buffer)``
+ """
+ if out_end is None:
+ out_end = len(out_buffer)
+ if in_end is None:
+ in_end = len(in_buffer)
+
+ self.i2c.writeto_then_readfrom(
+ self.device_address,
+ out_buffer,
+ in_buffer,
+ out_start=out_start,
+ out_end=out_end,
+ in_start=in_start,
+ in_end=in_end,
+ )
+
+ # pylint: enable-msg=too-many-arguments
+
+ def __enter__(self) -> "I2CDevice":
+ while not self.i2c.try_lock():
+ time.sleep(0)
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[type]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> bool:
+ self.i2c.unlock()
+ return False
+
+ def __probe_for_device(self) -> None:
+ """
+ Try to read a byte from an address,
+ if you get an OSError it means the device is not there
+ or that the device does not support these means of probing
+ """
+ while not self.i2c.try_lock():
+ time.sleep(0)
+ try:
+ self.i2c.writeto(self.device_address, b"")
+ except OSError:
+ # some OS's dont like writing an empty bytesting...
+ # Retry by reading a byte
+ try:
+ result = bytearray(1)
+ self.i2c.readfrom_into(self.device_address, result)
+ except OSError:
+ # pylint: disable=raise-missing-from
+ raise ValueError("No I2C device at address: 0x%x" % self.device_address)
+ # pylint: enable=raise-missing-from
+ finally:
+ self.i2c.unlock()
diff --git a/src/pico/adafruit_bus_device/spi_device.py b/src/pico/adafruit_bus_device/spi_device.py
new file mode 100755
index 0000000..ea8515e
--- /dev/null
+++ b/src/pico/adafruit_bus_device/spi_device.py
@@ -0,0 +1,121 @@
+# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+# pylint: disable=too-few-public-methods
+
+"""
+`adafruit_bus_device.spi_device` - SPI Bus Device
+====================================================
+"""
+
+import time
+
+try:
+ from typing import Optional, Type
+ from types import TracebackType
+
+ # Used only for type annotations.
+ from busio import SPI
+ from digitalio import DigitalInOut
+except ImportError:
+ pass
+
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BusDevice.git"
+
+
+class SPIDevice:
+ """
+ Represents a single SPI device and manages locking the bus and the device
+ address.
+
+ :param ~busio.SPI spi: The SPI bus the device is on
+ :param ~digitalio.DigitalInOut chip_select: The chip select pin object that implements the
+ DigitalInOut API.
+ :param bool cs_active_value: Set to True if your device requires CS to be active high.
+ Defaults to False.
+ :param int baudrate: The desired SCK clock rate in Hertz. The actual clock rate may be
+ higher or lower due to the granularity of available clock settings (MCU dependent).
+ :param int polarity: The base state of the SCK clock pin (0 or 1).
+ :param int phase: The edge of the clock that data is captured. First (0) or second (1).
+ Rising or falling depends on SCK clock polarity.
+ :param int extra_clocks: The minimum number of clock cycles to cycle the bus after CS is high.
+ (Used for SD cards.)
+
+ .. note:: This class is **NOT** built into CircuitPython. See
+ :ref:`here for install instructions <bus_device_installation>`.
+
+ Example:
+
+ .. code-block:: python
+
+ import busio
+ import digitalio
+ from board import *
+ from adafruit_bus_device.spi_device import SPIDevice
+
+ with busio.SPI(SCK, MOSI, MISO) as spi_bus:
+ cs = digitalio.DigitalInOut(D10)
+ device = SPIDevice(spi_bus, cs)
+ bytes_read = bytearray(4)
+ # The object assigned to spi in the with statements below
+ # is the original spi_bus object. We are using the busio.SPI
+ # operations busio.SPI.readinto() and busio.SPI.write().
+ with device as spi:
+ spi.readinto(bytes_read)
+ # A second transaction
+ with device as spi:
+ spi.write(bytes_read)
+ """
+
+ def __init__(
+ self,
+ spi: SPI,
+ chip_select: Optional[DigitalInOut] = None,
+ *,
+ cs_active_value: bool = False,
+ baudrate: int = 100000,
+ polarity: int = 0,
+ phase: int = 0,
+ extra_clocks: int = 0
+ ) -> None:
+ self.spi = spi
+ self.baudrate = baudrate
+ self.polarity = polarity
+ self.phase = phase
+ self.extra_clocks = extra_clocks
+ self.chip_select = chip_select
+ self.cs_active_value = cs_active_value
+ if self.chip_select:
+ self.chip_select.switch_to_output(value=not self.cs_active_value)
+
+ def __enter__(self) -> SPI:
+ while not self.spi.try_lock():
+ time.sleep(0)
+ self.spi.configure(
+ baudrate=self.baudrate, polarity=self.polarity, phase=self.phase
+ )
+ if self.chip_select:
+ self.chip_select.value = self.cs_active_value
+ return self.spi
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[type]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> bool:
+ if self.chip_select:
+ self.chip_select.value = not self.cs_active_value
+ if self.extra_clocks > 0:
+ buf = bytearray(1)
+ buf[0] = 0xFF
+ clocks = self.extra_clocks // 8
+ if self.extra_clocks % 8 != 0:
+ clocks += 1
+ for _ in range(clocks):
+ self.spi.write(buf)
+ self.spi.unlock()
+ return False
diff --git a/src/pico/adafruit_mpu6050.py b/src/pico/adafruit_mpu6050.py
new file mode 100755
index 0000000..cfc7971
--- /dev/null
+++ b/src/pico/adafruit_mpu6050.py
@@ -0,0 +1,401 @@
+# SPDX-FileCopyrightText: 2019 Bryan Siepert for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`adafruit_mpu6050`
+================================================================================
+
+CircuitPython helper library for the MPU6050 6-DoF Accelerometer and Gyroscope.
+
+This driver requires too much RAM to be used on SAMD21 based devices.
+
+
+* Author(s): Bryan Siepert
+
+Implementation Notes
+--------------------
+
+**Hardware:**
+
+* `Adafruit MPU-6050 6-DoF Accel and Gyro Sensor
+ <https://www.adafruit.com/product/3886>`_ (Product ID: 3886)
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+ https://circuitpython.org/downloads
+
+* Adafruit's Bus Device library:
+ https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
+
+* Adafruit's Register library:
+ https://github.com/adafruit/Adafruit_CircuitPython_Register
+
+"""
+
+# imports
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MPU6050.git"
+
+from math import radians
+from time import sleep
+from adafruit_bus_device import i2c_device
+from adafruit_register.i2c_struct import UnaryStruct, ROUnaryStruct
+from adafruit_register.i2c_struct_array import StructArray
+from adafruit_register.i2c_bit import RWBit
+from adafruit_register.i2c_bits import RWBits
+
+try:
+ from typing import Tuple
+ from busio import I2C
+except ImportError:
+ pass
+
+_MPU6050_DEFAULT_ADDRESS = 0x68 # MPU6050 default i2c address w/ AD0 low
+_MPU6050_DEVICE_ID = 0x68 # The correct MPU6050_WHO_AM_I value
+
+_MPU6050_SELF_TEST_X = 0x0D # Self test factory calibrated values register
+_MPU6050_SELF_TEST_Y = 0x0E # Self test factory calibrated values register
+_MPU6050_SELF_TEST_Z = 0x0F # Self test factory calibrated values register
+_MPU6050_SELF_TEST_A = 0x10 # Self test factory calibrated values register
+_MPU6050_SMPLRT_DIV = 0x19 # sample rate divisor register
+_MPU6050_CONFIG = 0x1A # General configuration register
+_MPU6050_GYRO_CONFIG = 0x1B # Gyro specfic configuration register
+_MPU6050_ACCEL_CONFIG = 0x1C # Accelerometer specific configration register
+_MPU6050_FIFO_EN = 0x23 # FIFO Enable
+_MPU6050_INT_PIN_CONFIG = 0x37 # Interrupt pin configuration register
+_MPU6050_ACCEL_OUT = 0x3B # base address for sensor data reads
+_MPU6050_TEMP_OUT = 0x41 # Temperature data high byte register
+_MPU6050_GYRO_OUT = 0x43 # base address for sensor data reads
+_MPU6050_SIG_PATH_RESET = 0x68 # register to reset sensor signal paths
+_MPU6050_USER_CTRL = 0x6A # FIFO and I2C Master control register
+_MPU6050_PWR_MGMT_1 = 0x6B # Primary power/sleep control register
+_MPU6050_PWR_MGMT_2 = 0x6C # Secondary power/sleep control register
+_MPU6050_FIFO_COUNT = 0x72 # FIFO byte count register (high half)
+_MPU6050_FIFO_R_W = 0x74 # FIFO data register
+_MPU6050_WHO_AM_I = 0x75 # Divice ID register
+
+STANDARD_GRAVITY = 9.80665
+
+
+class ClockSource: # pylint: disable=too-few-public-methods
+ """Allowed values for :py:attr:`clock_source`.
+
+ * :py:attr:'ClockSource.CLKSEL_INTERNAL_8MHz
+ * :py:attr:'ClockSource.CLKSEL_INTERNAL_X
+ * :py:attr:'ClockSource.CLKSEL_INTERNAL_Y
+ * :py:attr:'ClockSource.CLKSEL_INTERNAL_Z
+ * :py:attr:'ClockSource.CLKSEL_EXTERNAL_32
+ * :py:attr:'ClockSource.CLKSEL_EXTERNAL_19
+ * :py:attr:'ClockSource.CLKSEL_RESERVED
+ * :py:attr:'ClockSource.CLKSEL_STOP
+ """
+
+ CLKSEL_INTERNAL_8MHz = 0 # Internal 8MHz oscillator
+ CLKSEL_INTERNAL_X = 1 # PLL with X Axis gyroscope reference
+ CLKSEL_INTERNAL_Y = 2 # PLL with Y Axis gyroscope reference
+ CLKSEL_INTERNAL_Z = 3 # PLL with Z Axis gyroscope reference
+ CLKSEL_EXTERNAL_32 = 4 # External 32.768 kHz reference
+ CLKSEL_EXTERNAL_19 = 5 # External 19.2 MHz reference
+ CLKSEL_RESERVED = 6 # Reserved
+ CLKSEL_STOP = 7 # Stops the clock, constant reset mode
+
+
+class Range: # pylint: disable=too-few-public-methods
+ """Allowed values for :py:attr:`accelerometer_range`.
+
+ * :py:attr:`Range.RANGE_2_G`
+ * :py:attr:`Range.RANGE_4_G`
+ * :py:attr:`Range.RANGE_8_G`
+ * :py:attr:`Range.RANGE_16_G`
+
+ """
+
+ RANGE_2_G = 0 # +/- 2g (default value)
+ RANGE_4_G = 1 # +/- 4g
+ RANGE_8_G = 2 # +/- 8g
+ RANGE_16_G = 3 # +/- 16g
+
+
+class GyroRange: # pylint: disable=too-few-public-methods
+ """Allowed values for :py:attr:`gyro_range`.
+
+ * :py:attr:`GyroRange.RANGE_250_DPS`
+ * :py:attr:`GyroRange.RANGE_500_DPS`
+ * :py:attr:`GyroRange.RANGE_1000_DPS`
+ * :py:attr:`GyroRange.RANGE_2000_DPS`
+
+ """
+
+ RANGE_250_DPS = 0 # +/- 250 deg/s (default value)
+ RANGE_500_DPS = 1 # +/- 500 deg/s
+ RANGE_1000_DPS = 2 # +/- 1000 deg/s
+ RANGE_2000_DPS = 3 # +/- 2000 deg/s
+
+
+class Bandwidth: # pylint: disable=too-few-public-methods
+ """Allowed values for :py:attr:`filter_bandwidth`.
+
+ * :py:attr:`Bandwidth.BAND_260_HZ`
+ * :py:attr:`Bandwidth.BAND_184_HZ`
+ * :py:attr:`Bandwidth.BAND_94_HZ`
+ * :py:attr:`Bandwidth.BAND_44_HZ`
+ * :py:attr:`Bandwidth.BAND_21_HZ`
+ * :py:attr:`Bandwidth.BAND_10_HZ`
+ * :py:attr:`Bandwidth.BAND_5_HZ`
+
+ """
+
+ BAND_260_HZ = 0 # Docs imply this disables the filter
+ BAND_184_HZ = 1 # 184 Hz
+ BAND_94_HZ = 2 # 94 Hz
+ BAND_44_HZ = 3 # 44 Hz
+ BAND_21_HZ = 4 # 21 Hz
+ BAND_10_HZ = 5 # 10 Hz
+ BAND_5_HZ = 6 # 5 Hz
+
+
+class Rate: # pylint: disable=too-few-public-methods
+ """Allowed values for :py:attr:`cycle_rate`.
+
+ * :py:attr:`Rate.CYCLE_1_25_HZ`
+ * :py:attr:`Rate.CYCLE_5_HZ`
+ * :py:attr:`Rate.CYCLE_20_HZ`
+ * :py:attr:`Rate.CYCLE_40_HZ`
+
+ """
+
+ CYCLE_1_25_HZ = 0 # 1.25 Hz
+ CYCLE_5_HZ = 1 # 5 Hz
+ CYCLE_20_HZ = 2 # 20 Hz
+ CYCLE_40_HZ = 3 # 40 Hz
+
+
+class MPU6050: # pylint: disable=too-many-instance-attributes
+ """Driver for the MPU6050 6-DoF accelerometer and gyroscope.
+
+ :param ~busio.I2C i2c_bus: The I2C bus the device is connected to
+ :param int address: The I2C device address. Defaults to :const:`0x68`
+
+ **Quickstart: Importing and using the device**
+
+ Here is an example of using the :class:`MPU6050` class.
+ First you will need to import the libraries to use the sensor
+
+ .. code-block:: python
+
+ import board
+ import adafruit_mpu6050
+
+ Once this is done you can define your `board.I2C` object and define your sensor object
+
+ .. code-block:: python
+
+ i2c = board.I2C() # uses board.SCL and board.SDA
+ mpu = adafruit_mpu6050.MPU6050(i2c)
+
+ Now you have access to the :attr:`acceleration`, :attr:`gyro`
+ and :attr:`temperature` attributes
+
+ .. code-block:: python
+
+ acc_x, acc_y, acc_z = sensor.acceleration
+ gyro_x, gyro_y, gyro_z = sensor.gyro
+ temperature = sensor.temperature
+ """
+
+ def __init__(self, i2c_bus: I2C, address: int = _MPU6050_DEFAULT_ADDRESS) -> None:
+ self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)
+
+ if self._device_id != _MPU6050_DEVICE_ID:
+ raise RuntimeError("Failed to find MPU6050 - check your wiring!")
+
+ self.reset()
+
+ self._sample_rate_divisor = 0
+ self._filter_bandwidth = Bandwidth.BAND_260_HZ
+ self._gyro_range = GyroRange.RANGE_500_DPS
+ self._accel_range = Range.RANGE_2_G
+ self._accel_scale = 1.0 / [16384, 8192, 4096, 2048][self._accel_range]
+ sleep(0.100)
+ self.clock_source = (
+ ClockSource.CLKSEL_INTERNAL_X
+ ) # set to use gyro x-axis as reference
+ sleep(0.100)
+ self.sleep = False
+ sleep(0.010)
+
+ def reset(self) -> None:
+ """Reinitialize the sensor"""
+ self._reset = True
+ while self._reset is True:
+ sleep(0.001)
+ sleep(0.100)
+
+ _signal_path_reset = 0b111 # reset all sensors
+ sleep(0.100)
+
+ _clksel = RWBits(3, _MPU6050_PWR_MGMT_1, 0)
+ _device_id = ROUnaryStruct(_MPU6050_WHO_AM_I, ">B")
+
+ _reset = RWBit(_MPU6050_PWR_MGMT_1, 7, 1)
+ _signal_path_reset = RWBits(3, _MPU6050_SIG_PATH_RESET, 3)
+
+ _gyro_range = RWBits(2, _MPU6050_GYRO_CONFIG, 3)
+ _accel_range = RWBits(2, _MPU6050_ACCEL_CONFIG, 3)
+
+ _filter_bandwidth = RWBits(2, _MPU6050_CONFIG, 3)
+
+ _raw_accel_data = StructArray(_MPU6050_ACCEL_OUT, ">h", 3)
+ _raw_gyro_data = StructArray(_MPU6050_GYRO_OUT, ">h", 3)
+ _raw_temp_data = ROUnaryStruct(_MPU6050_TEMP_OUT, ">h")
+
+ _cycle = RWBit(_MPU6050_PWR_MGMT_1, 5)
+ _cycle_rate = RWBits(2, _MPU6050_PWR_MGMT_2, 6, 1)
+
+ sleep = RWBit(_MPU6050_PWR_MGMT_1, 6, 1)
+ """Shuts down the accelerometers and gyroscopes, saving power. No new data will
+ be recorded until the sensor is taken out of sleep by setting to `False`"""
+ sample_rate_divisor = UnaryStruct(_MPU6050_SMPLRT_DIV, ">B")
+ """The sample rate divisor. See the datasheet for additional detail"""
+
+ fifo_en = RWBit(_MPU6050_USER_CTRL, 6)
+ fiforst = RWBit(_MPU6050_USER_CTRL, 2)
+ accel_fifo_en = RWBit(_MPU6050_FIFO_EN, 3)
+ fifo_count = ROUnaryStruct(_MPU6050_FIFO_COUNT, ">h")
+
+ @property
+ def temperature(self) -> float:
+ """The current temperature in º Celsius"""
+ raw_temperature = self._raw_temp_data
+ temp = (raw_temperature / 340.0) + 36.53
+ return temp
+
+ @property
+ def acceleration(self) -> Tuple[float, float, float]:
+ """Acceleration X, Y, and Z axis data in :math:`m/s^2`"""
+ raw_data = self._raw_accel_data
+ return self.scale_accel([raw_data[0][0], raw_data[1][0], raw_data[2][0]])
+
+ def scale_accel(self, raw_data) -> Tuple[float, float, float]:
+ """Scale raw X, Y, and Z axis data to :math:`m/s^2`"""
+ accel_x = (raw_data[0] * self._accel_scale) * STANDARD_GRAVITY
+ accel_y = (raw_data[1] * self._accel_scale) * STANDARD_GRAVITY
+ accel_z = (raw_data[2] * self._accel_scale) * STANDARD_GRAVITY
+ return (accel_x, accel_y, accel_z)
+
+ @property
+ def gyro(self) -> Tuple[float, float, float]:
+ """Gyroscope X, Y, and Z axis data in :math:`º/s`"""
+ raw_data = self._raw_gyro_data
+ return self.scale_gyro((raw_data[0][0], raw_data[1][0], raw_data[2][0]))
+
+ def scale_gyro(self, raw_data) -> Tuple[float, float, float]:
+ """Scale raw gyro data to :math:`º/s`"""
+ raw_x = raw_data[0]
+ raw_y = raw_data[1]
+ raw_z = raw_data[2]
+
+ gyro_scale = 1
+ gyro_range = self._gyro_range
+ if gyro_range == GyroRange.RANGE_250_DPS:
+ gyro_scale = 131
+ if gyro_range == GyroRange.RANGE_500_DPS:
+ gyro_scale = 65.5
+ if gyro_range == GyroRange.RANGE_1000_DPS:
+ gyro_scale = 32.8
+ if gyro_range == GyroRange.RANGE_2000_DPS:
+ gyro_scale = 16.4
+
+ # setup range dependent scaling
+ gyro_x = radians(raw_x / gyro_scale)
+ gyro_y = radians(raw_y / gyro_scale)
+ gyro_z = radians(raw_z / gyro_scale)
+
+ return (gyro_x, gyro_y, gyro_z)
+
+ @property
+ def cycle(self) -> bool:
+ """Enable or disable periodic measurement at a rate set by :meth:`cycle_rate`.
+ If the sensor was in sleep mode, it will be waken up to cycle"""
+ return self._cycle
+
+ @cycle.setter
+ def cycle(self, value: bool) -> None:
+ self.sleep = not value
+ self._cycle = value
+
+ @property
+ def gyro_range(self) -> int:
+ """The measurement range of all gyroscope axes. Must be a `GyroRange`"""
+ return self._gyro_range
+
+ @gyro_range.setter
+ def gyro_range(self, value: int) -> None:
+ if (value < 0) or (value > 3):
+ raise ValueError("gyro_range must be a GyroRange")
+ self._gyro_range = value
+ sleep(0.01)
+
+ @property
+ def accelerometer_range(self) -> int:
+ """The measurement range of all accelerometer axes. Must be a `Range`"""
+ return self._accel_range
+
+ @accelerometer_range.setter
+ def accelerometer_range(self, value: int) -> None:
+ if (value < 0) or (value > 3):
+ raise ValueError("accelerometer_range must be a Range")
+ self._accel_range = value
+ self._accel_scale = 1.0 / [16384, 8192, 4096, 2048][value]
+ sleep(0.01)
+
+ @property
+ def filter_bandwidth(self) -> int:
+ """The bandwidth of the gyroscope Digital Low Pass Filter. Must be a `GyroRange`"""
+ return self._filter_bandwidth
+
+ @filter_bandwidth.setter
+ def filter_bandwidth(self, value: int) -> None:
+ if (value < 0) or (value > 6):
+ raise ValueError("filter_bandwidth must be a Bandwidth")
+ self._filter_bandwidth = value
+ sleep(0.01)
+
+ @property
+ def cycle_rate(self) -> int:
+ """The rate that measurements are taken while in `cycle` mode. Must be a `Rate`"""
+ return self._cycle_rate
+
+ @cycle_rate.setter
+ def cycle_rate(self, value: int) -> None:
+ if (value < 0) or (value > 3):
+ raise ValueError("cycle_rate must be a Rate")
+ self._cycle_rate = value
+ sleep(0.01)
+
+ @property
+ def clock_source(self) -> int:
+ """The clock source for the sensor"""
+ return self._clksel
+
+ @clock_source.setter
+ def clock_source(self, value: int) -> None:
+ """Select between Internal/External clock sources"""
+ if value not in range(8):
+ raise ValueError(
+ "clock_source must be ClockSource value, integer from 0 - 7."
+ )
+ self._clksel = value
+
+ def read_whole_fifo(self):
+ """Return raw FIFO bytes"""
+ # This code must be fast to ensure samples are contiguous
+ count = self.fifo_count
+ buf = bytearray(count)
+ buf[0] = _MPU6050_FIFO_R_W
+ with self.i2c_device:
+ self.i2c_device.write_then_readinto(buf, buf, out_end=1, in_start=0)
+ return buf
diff --git a/src/pico/adafruit_register/__init__.py b/src/pico/adafruit_register/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/src/pico/adafruit_register/__init__.py
diff --git a/src/pico/adafruit_register/i2c_bcd_alarm.py b/src/pico/adafruit_register/i2c_bcd_alarm.py
new file mode 100755
index 0000000..74c8a77
--- /dev/null
+++ b/src/pico/adafruit_register/i2c_bcd_alarm.py
@@ -0,0 +1,202 @@
+# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+# pylint: disable=too-few-public-methods
+# pylint: disable=too-many-branches
+
+"""
+`adafruit_register.i2c_bcd_alarm`
+====================================================
+
+Binary Coded Decimal alarm register
+
+* Author(s): Scott Shawcroft
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
+
+import time
+
+try:
+ from typing import Optional, Type, Tuple
+ from typing_extensions import Literal
+ from circuitpython_typing.device_drivers import I2CDeviceDriver
+
+ FREQUENCY_T = Literal[
+ "monthly", "weekly", "daily", "hourly", "minutely", "secondly"
+ ]
+except ImportError:
+ pass
+
+
+def _bcd2bin(value: int) -> int:
+ """Convert binary coded decimal to Binary
+
+ :param value: the BCD value to convert to binary (required, no default)
+ """
+ return value - 6 * (value >> 4)
+
+
+def _bin2bcd(value: int) -> int:
+ """Convert a binary value to binary coded decimal.
+
+ :param value: the binary value to convert to BCD. (required, no default)
+ """
+ return value + 6 * (value // 10)
+
+
+ALARM_COMPONENT_DISABLED = 0x80
+FREQUENCY = ["secondly", "minutely", "hourly", "daily", "weekly", "monthly"]
+
+
+class BCDAlarmTimeRegister:
+ """
+ Alarm date and time register using binary coded decimal structure.
+
+ The byte order of the registers must* be: [second], minute, hour, day,
+ weekday. Each byte must also have a high enable bit where 1 is disabled and
+ 0 is enabled.
+
+ * If weekday_shared is True, then weekday and day share a register.
+ * If has_seconds is True, then there is a seconds register.
+
+ Values are a tuple of (`time.struct_time`, `str`) where the struct represents
+ a date and time that would alarm. The string is the frequency:
+
+ * "secondly", once a second (only if alarm has_seconds)
+ * "minutely", once a minute when seconds match (if alarm doesn't seconds then when seconds = 0)
+ * "hourly", once an hour when ``tm_min`` and ``tm_sec`` match
+ * "daily", once a day when ``tm_hour``, ``tm_min`` and ``tm_sec`` match
+ * "weekly", once a week when ``tm_wday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match
+ * "monthly", once a month when ``tm_mday``, ``tm_hour``, ``tm_min``, ``tm_sec`` match
+
+ :param int register_address: The register address to start the read
+ :param bool has_seconds: True if the alarm can happen minutely.
+ :param bool weekday_shared: True if weekday and day share the same register
+ :param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the
+ week (Monday)
+ """
+
+ # Defaults are based on alarm1 of the DS3231.
+ def __init__(
+ self,
+ register_address: int,
+ has_seconds: bool = True,
+ weekday_shared: bool = True,
+ weekday_start: Literal[0, 1] = 1,
+ ) -> None:
+ buffer_size = 5
+ if weekday_shared:
+ buffer_size -= 1
+ if has_seconds:
+ buffer_size += 1
+ self.has_seconds = has_seconds
+ self.buffer = bytearray(buffer_size)
+ self.buffer[0] = register_address
+ self.weekday_shared = weekday_shared
+ self.weekday_start = weekday_start
+
+ def __get__(
+ self,
+ obj: Optional[I2CDeviceDriver],
+ objtype: Optional[Type[I2CDeviceDriver]] = None,
+ ) -> Tuple[time.struct_time, FREQUENCY_T]:
+ # Read the alarm register.
+ with obj.i2c_device as i2c:
+ i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
+
+ frequency = None
+ i = 1
+ seconds = 0
+ if self.has_seconds:
+ if (self.buffer[1] & 0x80) != 0:
+ frequency = "secondly"
+ else:
+ frequency = "minutely"
+ seconds = _bcd2bin(self.buffer[1] & 0x7F)
+ i = 2
+ else:
+ frequency = "minutely"
+ seconds = _bcd2bin(self.buffer[i] & 0x7F)
+ minute = 0
+ if (self.buffer[i] & 0x80) == 0:
+ frequency = "hourly"
+ minute = _bcd2bin(self.buffer[i] & 0x7F)
+
+ hour = 0
+ if (self.buffer[i + 1] & 0x80) == 0:
+ frequency = "daily"
+ hour = _bcd2bin(self.buffer[i + 1] & 0x7F)
+
+ mday = None
+ wday = None
+ if (self.buffer[i + 2] & 0x80) == 0:
+ # day of the month
+ if not self.weekday_shared or (self.buffer[i + 2] & 0x40) == 0:
+ frequency = "monthly"
+ mday = _bcd2bin(self.buffer[i + 2] & 0x3F)
+ else: # weekday
+ frequency = "weekly"
+ wday = _bcd2bin(self.buffer[i + 2] & 0x3F) - self.weekday_start
+
+ # weekday
+ if not self.weekday_shared and (self.buffer[i + 3] & 0x80) == 0:
+ frequency = "monthly"
+ mday = _bcd2bin(self.buffer[i + 3] & 0x7F)
+
+ if mday is not None:
+ wday = (mday - 2) % 7
+ elif wday is not None:
+ mday = wday + 2
+ else:
+ # Jan 1, 2017 was a Sunday (6)
+ wday = 6
+ mday = 1
+
+ return (
+ time.struct_time((2017, 1, mday, hour, minute, seconds, wday, mday, -1)),
+ frequency,
+ )
+
+ def __set__(
+ self, obj: I2CDeviceDriver, value: Tuple[time.struct_time, FREQUENCY_T]
+ ) -> None:
+ if len(value) != 2:
+ raise ValueError("Value must be sequence of length two")
+ # Turn all components off by default.
+ for i in range(len(self.buffer) - 1):
+ self.buffer[i + 1] = ALARM_COMPONENT_DISABLED
+ frequency_name = value[1]
+ error_message = "%s is not a supported frequency" % frequency_name
+ if frequency_name not in FREQUENCY:
+ raise ValueError(error_message)
+
+ frequency = FREQUENCY.index(frequency_name)
+ if frequency < 1 and not self.has_seconds:
+ raise ValueError(error_message)
+
+ # i is the index of the minute byte
+ i = 2 if self.has_seconds else 1
+
+ if frequency > 0 and self.has_seconds: # minutely at least
+ self.buffer[1] = _bin2bcd(value[0].tm_sec)
+
+ if frequency > 1: # hourly at least
+ self.buffer[i] = _bin2bcd(value[0].tm_min)
+
+ if frequency > 2: # daily at least
+ self.buffer[i + 1] = _bin2bcd(value[0].tm_hour)
+
+ if value[1] == "weekly":
+ if self.weekday_shared:
+ self.buffer[i + 2] = (
+ _bin2bcd(value[0].tm_wday + self.weekday_start) | 0x40
+ )
+ else:
+ self.buffer[i + 3] = _bin2bcd(value[0].tm_wday + self.weekday_start)
+ elif value[1] == "monthly":
+ self.buffer[i + 2] = _bin2bcd(value[0].tm_mday)
+
+ with obj.i2c_device:
+ obj.i2c_device.write(self.buffer)
diff --git a/src/pico/adafruit_register/i2c_bcd_datetime.py b/src/pico/adafruit_register/i2c_bcd_datetime.py
new file mode 100755
index 0000000..2bb458a
--- /dev/null
+++ b/src/pico/adafruit_register/i2c_bcd_datetime.py
@@ -0,0 +1,114 @@
+# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+# pylint: disable=too-few-public-methods
+
+"""
+`adafruit_register.i2c_bcd_datetime`
+====================================================
+
+Binary Coded Decimal date and time register
+
+* Author(s): Scott Shawcroft
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
+
+import time
+
+try:
+ from typing import Optional, Type
+ from typing_extensions import Literal
+ from circuitpython_typing.device_drivers import I2CDeviceDriver
+except ImportError:
+ pass
+
+
+def _bcd2bin(value: int) -> int:
+ """Convert binary coded decimal to Binary
+
+ :param value: the BCD value to convert to binary (required, no default)
+ """
+ return value - 6 * (value >> 4)
+
+
+def _bin2bcd(value: int) -> int:
+ """Convert a binary value to binary coded decimal.
+
+ :param value: the binary value to convert to BCD. (required, no default)
+ """
+ return value + 6 * (value // 10)
+
+
+class BCDDateTimeRegister:
+ """
+ Date and time register using binary coded decimal structure.
+
+ The byte order of the register must* be: second, minute, hour, weekday, day (1-31), month, year
+ (in years after 2000).
+
+ * Setting weekday_first=False will flip the weekday/day order so that day comes first.
+
+ Values are `time.struct_time`
+
+ :param int register_address: The register address to start the read
+ :param bool weekday_first: True if weekday is in a lower register than the day of the month
+ (1-31)
+ :param int weekday_start: 0 or 1 depending on the RTC's representation of the first day of the
+ week
+ """
+
+ def __init__(
+ self,
+ register_address: int,
+ weekday_first: bool = True,
+ weekday_start: Literal[0, 1] = 1,
+ ) -> None:
+ self.buffer = bytearray(8)
+ self.buffer[0] = register_address
+ if weekday_first:
+ self.weekday_offset = 0
+ else:
+ self.weekday_offset = 1
+ self.weekday_start = weekday_start
+ # Masking value list n/a sec min hr day wkday mon year
+ self.mask_datetime = b"\xFF\x7F\x7F\x3F\x3F\x07\x1F\xFF"
+
+ def __get__(
+ self,
+ obj: Optional[I2CDeviceDriver],
+ objtype: Optional[Type[I2CDeviceDriver]] = None,
+ ) -> time.struct_time:
+ # Read and return the date and time.
+ with obj.i2c_device as i2c:
+ i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
+ return time.struct_time(
+ (
+ _bcd2bin(self.buffer[7] & self.mask_datetime[7]) + 2000,
+ _bcd2bin(self.buffer[6] & self.mask_datetime[6]),
+ _bcd2bin(self.buffer[5 - self.weekday_offset] & self.mask_datetime[4]),
+ _bcd2bin(self.buffer[3] & self.mask_datetime[3]),
+ _bcd2bin(self.buffer[2] & self.mask_datetime[2]),
+ _bcd2bin(self.buffer[1] & self.mask_datetime[1]),
+ _bcd2bin(
+ (self.buffer[4 + self.weekday_offset] & self.mask_datetime[5])
+ - self.weekday_start
+ ),
+ -1,
+ -1,
+ )
+ )
+
+ def __set__(self, obj: I2CDeviceDriver, value: time.struct_time) -> None:
+ self.buffer[1] = _bin2bcd(value.tm_sec) & 0x7F # format conversions
+ self.buffer[2] = _bin2bcd(value.tm_min)
+ self.buffer[3] = _bin2bcd(value.tm_hour)
+ self.buffer[4 + self.weekday_offset] = _bin2bcd(
+ value.tm_wday + self.weekday_start
+ )
+ self.buffer[5 - self.weekday_offset] = _bin2bcd(value.tm_mday)
+ self.buffer[6] = _bin2bcd(value.tm_mon)
+ self.buffer[7] = _bin2bcd(value.tm_year - 2000)
+ with obj.i2c_device:
+ obj.i2c_device.write(self.buffer)
diff --git a/src/pico/adafruit_register/i2c_bit.py b/src/pico/adafruit_register/i2c_bit.py
new file mode 100755
index 0000000..bd32ce7
--- /dev/null
+++ b/src/pico/adafruit_register/i2c_bit.py
@@ -0,0 +1,84 @@
+# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+# pylint: disable=too-few-public-methods
+
+"""
+`adafruit_register.i2c_bit`
+====================================================
+
+Single bit registers
+
+* Author(s): Scott Shawcroft
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
+
+try:
+ from typing import Optional, Type, NoReturn
+ from circuitpython_typing.device_drivers import I2CDeviceDriver
+except ImportError:
+ pass
+
+
+class RWBit:
+ """
+ Single bit register that is readable and writeable.
+
+ Values are `bool`
+
+ :param int register_address: The register address to read the bit from
+ :param int bit: The bit index within the byte at ``register_address``
+ :param int register_width: The number of bytes in the register. Defaults to 1.
+ :param bool lsb_first: Is the first byte we read from I2C the LSB? Defaults to true
+
+ """
+
+ def __init__(
+ self,
+ register_address: int,
+ bit: int,
+ register_width: int = 1,
+ lsb_first: bool = True,
+ ) -> None:
+ self.bit_mask = 1 << (bit % 8) # the bitmask *within* the byte!
+ self.buffer = bytearray(1 + register_width)
+ self.buffer[0] = register_address
+ if lsb_first:
+ self.byte = bit // 8 + 1 # the byte number within the buffer
+ else:
+ self.byte = register_width - (bit // 8) # the byte number within the buffer
+
+ def __get__(
+ self,
+ obj: Optional[I2CDeviceDriver],
+ objtype: Optional[Type[I2CDeviceDriver]] = None,
+ ) -> bool:
+ with obj.i2c_device as i2c:
+ i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
+ return bool(self.buffer[self.byte] & self.bit_mask)
+
+ def __set__(self, obj: I2CDeviceDriver, value: bool) -> None:
+ with obj.i2c_device as i2c:
+ i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
+ if value:
+ self.buffer[self.byte] |= self.bit_mask
+ else:
+ self.buffer[self.byte] &= ~self.bit_mask
+ i2c.write(self.buffer)
+
+
+class ROBit(RWBit):
+ """Single bit register that is read only. Subclass of `RWBit`.
+
+ Values are `bool`
+
+ :param int register_address: The register address to read the bit from
+ :param type bit: The bit index within the byte at ``register_address``
+ :param int register_width: The number of bytes in the register. Defaults to 1.
+
+ """
+
+ def __set__(self, obj: I2CDeviceDriver, value: bool) -> NoReturn:
+ raise AttributeError()
diff --git a/src/pico/adafruit_register/i2c_bits.py b/src/pico/adafruit_register/i2c_bits.py
new file mode 100755
index 0000000..9a9f1d2
--- /dev/null
+++ b/src/pico/adafruit_register/i2c_bits.py
@@ -0,0 +1,114 @@
+# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+# pylint: disable=too-few-public-methods
+
+"""
+`adafruit_register.i2c_bits`
+====================================================
+
+Multi bit registers
+
+* Author(s): Scott Shawcroft
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
+
+try:
+ from typing import Optional, Type, NoReturn
+ from circuitpython_typing.device_drivers import I2CDeviceDriver
+except ImportError:
+ pass
+
+
+class RWBits:
+ """
+ Multibit register (less than a full byte) that is readable and writeable.
+ This must be within a byte register.
+
+ Values are `int` between 0 and 2 ** ``num_bits`` - 1.
+
+ :param int num_bits: The number of bits in the field.
+ :param int register_address: The register address to read the bit from
+ :param int lowest_bit: The lowest bits index within the byte at ``register_address``
+ :param int register_width: The number of bytes in the register. Defaults to 1.
+ :param bool lsb_first: Is the first byte we read from I2C the LSB? Defaults to true
+ :param bool signed: If True, the value is a "two's complement" signed value.
+ If False, it is unsigned.
+ """
+
+ def __init__( # pylint: disable=too-many-arguments
+ self,
+ num_bits: int,
+ register_address: int,
+ lowest_bit: int,
+ register_width: int = 1,
+ lsb_first: bool = True,
+ signed: bool = False,
+ ) -> None:
+ self.bit_mask = ((1 << num_bits) - 1) << lowest_bit
+ # print("bitmask: ",hex(self.bit_mask))
+ if self.bit_mask >= 1 << (register_width * 8):
+ raise ValueError("Cannot have more bits than register size")
+ self.lowest_bit = lowest_bit
+ self.buffer = bytearray(1 + register_width)
+ self.buffer[0] = register_address
+ self.lsb_first = lsb_first
+ self.sign_bit = (1 << (num_bits - 1)) if signed else 0
+
+ def __get__(
+ self,
+ obj: Optional[I2CDeviceDriver],
+ objtype: Optional[Type[I2CDeviceDriver]] = None,
+ ) -> int:
+ with obj.i2c_device as i2c:
+ i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
+ # read the number of bytes into a single variable
+ reg = 0
+ order = range(len(self.buffer) - 1, 0, -1)
+ if not self.lsb_first:
+ order = reversed(order)
+ for i in order:
+ reg = (reg << 8) | self.buffer[i]
+ reg = (reg & self.bit_mask) >> self.lowest_bit
+ # If the value is signed and negative, convert it
+ if reg & self.sign_bit:
+ reg -= 2 * self.sign_bit
+ return reg
+
+ def __set__(self, obj: I2CDeviceDriver, value: int) -> None:
+ value <<= self.lowest_bit # shift the value over to the right spot
+ with obj.i2c_device as i2c:
+ i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
+ reg = 0
+ order = range(len(self.buffer) - 1, 0, -1)
+ if not self.lsb_first:
+ order = range(1, len(self.buffer))
+ for i in order:
+ reg = (reg << 8) | self.buffer[i]
+ # print("old reg: ", hex(reg))
+ reg &= ~self.bit_mask # mask off the bits we're about to change
+ reg |= value # then or in our new value
+ # print("new reg: ", hex(reg))
+ for i in reversed(order):
+ self.buffer[i] = reg & 0xFF
+ reg >>= 8
+ i2c.write(self.buffer)
+
+
+class ROBits(RWBits):
+ """
+ Multibit register (less than a full byte) that is read-only. This must be
+ within a byte register.
+
+ Values are `int` between 0 and 2 ** ``num_bits`` - 1.
+
+ :param int num_bits: The number of bits in the field.
+ :param int register_address: The register address to read the bit from
+ :param type lowest_bit: The lowest bits index within the byte at ``register_address``
+ :param int register_width: The number of bytes in the register. Defaults to 1.
+ """
+
+ def __set__(self, obj: I2CDeviceDriver, value: int) -> NoReturn:
+ raise AttributeError()
diff --git a/src/pico/adafruit_register/i2c_struct.py b/src/pico/adafruit_register/i2c_struct.py
new file mode 100755
index 0000000..2f546a8
--- /dev/null
+++ b/src/pico/adafruit_register/i2c_struct.py
@@ -0,0 +1,104 @@
+# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+# pylint: disable=too-few-public-methods
+
+"""
+`adafruit_register.i2c_struct`
+====================================================
+
+Generic structured registers based on `struct`
+
+* Author(s): Scott Shawcroft
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
+
+import struct
+
+try:
+ from typing import Optional, Type, Tuple, Any, NoReturn
+ from circuitpython_typing.device_drivers import I2CDeviceDriver
+except ImportError:
+ pass
+
+
+class Struct:
+ """
+ Arbitrary structure register that is readable and writeable.
+
+ Values are tuples that map to the values in the defined struct. See struct
+ module documentation for struct format string and its possible value types.
+
+ :param int register_address: The register address to read the bit from
+ :param str struct_format: The struct format string for this register.
+ """
+
+ def __init__(self, register_address: int, struct_format: str) -> None:
+ self.format = struct_format
+ self.buffer = bytearray(1 + struct.calcsize(self.format))
+ self.buffer[0] = register_address
+
+ def __get__(
+ self,
+ obj: Optional[I2CDeviceDriver],
+ objtype: Optional[Type[I2CDeviceDriver]] = None,
+ ) -> Tuple:
+ with obj.i2c_device as i2c:
+ i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1)
+ return struct.unpack_from(self.format, memoryview(self.buffer)[1:])
+
+ def __set__(self, obj: I2CDeviceDriver, value: Tuple) -> None:
+ struct.pack_into(self.format, self.buffer, 1, *value)
+ with obj.i2c_device as i2c:
+ i2c.write(self.buffer)
+
+
+class UnaryStruct:
+ """
+ Arbitrary single value structure register that is readable and writeable.
+
+ Values map to the first value in the defined struct. See struct
+ module documentation for struct format string and its possible value types.
+
+ :param int register_address: The register address to read the bit from
+ :param str struct_format: The struct format string for this register.
+ """
+
+ def __init__(self, register_address: int, struct_format: str) -> None:
+ self.format = struct_format
+ self.address = register_address
+
+ def __get__(
+ self,
+ obj: Optional[I2CDeviceDriver],
+ objtype: Optional[Type[I2CDeviceDriver]] = None,
+ ) -> Any:
+ buf = bytearray(1 + struct.calcsize(self.format))
+ buf[0] = self.address
+ with obj.i2c_device as i2c:
+ i2c.write_then_readinto(buf, buf, out_end=1, in_start=1)
+ return struct.unpack_from(self.format, buf, 1)[0]
+
+ def __set__(self, obj: I2CDeviceDriver, value: Any) -> None:
+ buf = bytearray(1 + struct.calcsize(self.format))
+ buf[0] = self.address
+ struct.pack_into(self.format, buf, 1, value)
+ with obj.i2c_device as i2c:
+ i2c.write(buf)
+
+
+class ROUnaryStruct(UnaryStruct):
+ """
+ Arbitrary single value structure register that is read-only.
+
+ Values map to the first value in the defined struct. See struct
+ module documentation for struct format string and its possible value types.
+
+ :param int register_address: The register address to read the bit from
+ :param type struct_format: The struct format string for this register.
+ """
+
+ def __set__(self, obj: I2CDeviceDriver, value: Any) -> NoReturn:
+ raise AttributeError()
diff --git a/src/pico/adafruit_register/i2c_struct_array.py b/src/pico/adafruit_register/i2c_struct_array.py
new file mode 100755
index 0000000..02d373c
--- /dev/null
+++ b/src/pico/adafruit_register/i2c_struct_array.py
@@ -0,0 +1,114 @@
+# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+# pylint: disable=too-few-public-methods
+
+"""
+`adafruit_register.i2c_struct_array`
+====================================================
+
+Array of structured registers based on `struct`
+
+* Author(s): Scott Shawcroft
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Register.git"
+
+import struct
+
+try:
+ from typing import Tuple, Optional, Type
+ from circuitpython_typing.device_drivers import I2CDeviceDriver
+except ImportError:
+ pass
+
+
+class _BoundStructArray:
+ """
+ Array object that `StructArray` constructs on demand.
+
+ :param object obj: The device object to bind to. It must have a `i2c_device` attribute
+ :param int register_address: The register address to read the bit from
+ :param str struct_format: The struct format string for each register element
+ :param int count: Number of elements in the array
+ """
+
+ def __init__(
+ self,
+ obj: I2CDeviceDriver,
+ register_address: int,
+ struct_format: str,
+ count: int,
+ ) -> None:
+ self.format = struct_format
+ self.first_register = register_address
+ self.obj = obj
+ self.count = count
+
+ def _get_buffer(self, index: int) -> bytearray:
+ """Shared bounds checking and buffer creation."""
+ if not 0 <= index < self.count:
+ raise IndexError()
+ size = struct.calcsize(self.format)
+ # We create the buffer every time instead of keeping the buffer (which is 32 bytes at least)
+ # around forever.
+ buf = bytearray(size + 1)
+ buf[0] = self.first_register + size * index
+ return buf
+
+ def __getitem__(self, index: int) -> Tuple:
+ buf = self._get_buffer(index)
+ with self.obj.i2c_device as i2c:
+ i2c.write_then_readinto(buf, buf, out_end=1, in_start=1)
+ return struct.unpack_from(self.format, buf, 1) # offset=1
+
+ def __setitem__(self, index: int, value: Tuple) -> None:
+ buf = self._get_buffer(index)
+ struct.pack_into(self.format, buf, 1, *value)
+ with self.obj.i2c_device as i2c:
+ i2c.write(buf)
+
+ def __len__(self) -> int:
+ return self.count
+
+
+class StructArray:
+ """
+ Repeated array of structured registers that are readable and writeable.
+
+ Based on the index, values are offset by the size of the structure.
+
+ Values are tuples that map to the values in the defined struct. See struct
+ module documentation for struct format string and its possible value types.
+
+ .. note:: This assumes the device addresses correspond to 8-bit bytes. This is not suitable for
+ devices with registers of other widths such as 16-bit.
+
+ :param int register_address: The register address to begin reading the array from
+ :param str struct_format: The struct format string for this register.
+ :param int count: Number of elements in the array
+ """
+
+ def __init__(self, register_address: int, struct_format: str, count: int) -> None:
+ self.format = struct_format
+ self.address = register_address
+ self.count = count
+ self.array_id = "_structarray{}".format(register_address)
+
+ def __get__(
+ self,
+ obj: Optional[I2CDeviceDriver],
+ objtype: Optional[Type[I2CDeviceDriver]] = None,
+ ) -> _BoundStructArray:
+ # We actually can't handle the indexing ourself due to data descriptor limits. So, we return
+ # an object that can instead. This object is bound to the object passed in here by its
+ # initializer and then cached on the object itself. That way its lifetime is tied to the
+ # lifetime of the object itself.
+ if not hasattr(obj, self.array_id):
+ setattr(
+ obj,
+ self.array_id,
+ _BoundStructArray(obj, self.address, self.format, self.count),
+ )
+ return getattr(obj, self.array_id)
diff --git a/src/pico/boot.py b/src/pico/boot.py
new file mode 100755
index 0000000..bc37857
--- /dev/null
+++ b/src/pico/boot.py
@@ -0,0 +1,42 @@
+import usb_hid
+
+GAMEPAD_REPORT_DESCRIPTOR = bytes([
+ 0x05, 0x01, # Usage Page (Generic Desktop)
+ 0x09, 0x05, # Usage (Game Pad)
+ 0xA1, 0x01, # Collection (Application)
+ 0x85, 0x01, # Report ID (1)
+ 0x05, 0x09, # Usage Page (Button)
+ 0x19, 0x01, # Usage Minimum (Button 1)
+ 0x29, 0x10, # Usage Maximum (Button 16)
+ 0x15, 0x00, # Logical Minimum (0)
+ 0x25, 0x01, # Logical Maximum (1)
+ 0x95, 0x10, # Report Count (16)
+ 0x75, 0x01, # Report Size (1)
+ 0x81, 0x02, # Input (Data, Variable, Absolute)
+ 0x05, 0x01, # Usage Page (Generic Desktop)
+ 0x09, 0x30, # Usage (X)
+ 0x09, 0x31, # Usage (Y)
+ 0x09, 0x32, # Usage (Z)
+ 0x09, 0x33, # Usage (Rx)
+ 0x09, 0x34, # Usage (Ry)
+ 0x09, 0x35, # Usage (Rz)
+ 0x15, 0x81, # Logical Minimum (-127)
+ 0x25, 0x7F, # Logical Maximum (127)
+ 0x75, 0x08, # Report Size (8)
+ 0x95, 0x06, # Report Count (6)
+ 0x81, 0x02, # Input (Data, Variable, Absolute)
+ 0xC0 # End Collection
+])
+
+gamepad_device = usb_hid.Device(
+ report_descriptor=GAMEPAD_REPORT_DESCRIPTOR,
+ usage_page=0x01,
+ usage=0x05,
+ report_ids=(1,),
+ in_report_lengths=(8,), # Total report length is now 8 bytes
+ out_report_lengths=(0,),
+)
+
+usb_hid.enable((gamepad_device,))
+
+print("Custom gamepad HID enabled.") \ No newline at end of file
diff --git a/src/pico/boot_out.txt b/src/pico/boot_out.txt
new file mode 100755
index 0000000..49d91b1
--- /dev/null
+++ b/src/pico/boot_out.txt
@@ -0,0 +1,5 @@
+Adafruit CircuitPython 9.2.4 on 2025-01-29; Raspberry Pi Pico with rp2040
+Board ID:raspberry_pi_pico
+UID:E6609103C3474329
+boot.py output:
+Custom gamepad HID enabled.
diff --git a/src/pico/code.py b/src/pico/code.py
new file mode 100755
index 0000000..eb90c9e
--- /dev/null
+++ b/src/pico/code.py
@@ -0,0 +1,160 @@
+import time
+import board
+import busio
+import usb_hid
+import struct
+import digitalio
+import analogio
+import adafruit_mpu6050
+import math
+
+# https://forums.adafruit.com/viewtopic.php?t=194490
+if True: # optionally wiggle SCL to clear I2C slave devices
+ print("***** Clear I2C bus *****")
+ scl = digitalio.DigitalInOut(board.GP1)
+ scl.direction = digitalio.Direction.OUTPUT # want open drain scl.drive_mode = digitalio.DriveMode.OPEN_DRAIN
+ for n in range(1,20):
+ scl.value = 0
+ time.sleep(0.0001)
+ scl.value = 1
+ time.sleep(0.0001)
+ scl.direction = digitalio.Direction.INPUT
+ scl.deinit()
+
+# Initialize I2C and MPU6050
+i2c = busio.I2C(board.GP1, board.GP0)
+mpu = adafruit_mpu6050.MPU6050(i2c)
+
+# Configure MPU6050
+mpu.gyro_range = adafruit_mpu6050.GyroRange.RANGE_2000_DPS
+mpu.filter_bandwidth = adafruit_mpu6050.Bandwidth.BAND_44_HZ
+mpu.sample_rate_divisor = 9 # Set sample rate to 100Hz
+
+# Create a custom HID device
+gamepad = usb_hid.devices[0] # Assuming the first device is the gamepad
+
+# Button setup (digital inputs)
+buttons = {
+ 1: digitalio.DigitalInOut(board.GP2),
+ 2: digitalio.DigitalInOut(board.GP3),
+ 3: digitalio.DigitalInOut(board.GP4),
+ 4: digitalio.DigitalInOut(board.GP5),
+ 5: digitalio.DigitalInOut(board.GP6),
+ 6: digitalio.DigitalInOut(board.GP7),
+ 7: digitalio.DigitalInOut(board.GP8),
+}
+
+for button in buttons.values():
+ button.direction = digitalio.Direction.INPUT
+ button.pull = digitalio.Pull.UP
+
+# Joystick setup (analog inputs)
+joy_x_axis = analogio.AnalogIn(board.GP26)
+joy_y_axis = analogio.AnalogIn(board.GP27)
+
+# Calibration (replace with actual calibration values)
+gyro_x_offset = 0.0
+gyro_y_offset = 0.0
+gyro_z_offset = 0.0
+
+# Complementary filter parameters
+ALPHA = 0.98 # Weight for gyroscope data (0 < ALPHA < 1)
+DT = 0.005 # Sampling time (200Hz)
+
+# Initialize angles
+pitch = 0.0
+roll = 0.0
+yaw = 0.0
+
+# Function to calculate angles from accelerometer data
+def calculate_angles(accel_x, accel_y, accel_z):
+ pitch = math.atan2(accel_y, math.sqrt(accel_x**2 + accel_z**2)) * (180.0 / math.pi)
+ roll = math.atan2(-accel_x, accel_z) * (180.0 / math.pi)
+ return pitch, roll
+
+# Function to apply complementary filter
+def complementary_filter(gyro_data, accel_data, prev_angles, alpha, dt):
+ # Calculate angles from accelerometer
+ accel_pitch, accel_roll = calculate_angles(*accel_data)
+
+ # Integrate gyroscope data to get angles
+ gyro_pitch = prev_angles[0] + gyro_data[0] * dt
+ gyro_roll = prev_angles[1] + gyro_data[1] * dt
+ gyro_yaw = prev_angles[2] + gyro_data[2] * dt # Integrate Z-axis for yaw
+
+ # Combine gyroscope and accelerometer angles using complementary filter
+ pitch = alpha * gyro_pitch + (1 - alpha) * accel_pitch
+ roll = alpha * gyro_roll + (1 - alpha) * accel_roll
+ yaw = gyro_yaw # Yaw is purely from gyroscope
+
+ return pitch, roll, yaw
+
+def read_buttons():
+ """Reads button states and updates the bitmask for HID report."""
+ buttons_state = 0
+ for button_num, button in buttons.items():
+ if not button.value: # Button pressed (active low)
+ buttons_state |= 1 << (button_num - 1)
+ return buttons_state
+
+def read_joysticks():
+ """Reads analog joystick values and scales them to -127 to 127."""
+ joy_x = max(-127, min(127, ((joy_x_axis.value - 32768) * 127) // 32768))
+ joy_y = max(-127, min(127, ((joy_y_axis.value - 32768) * 127) // 32768))
+ return joy_x, joy_y
+
+print("MPU6050 Game Controller Running!")
+
+while True:
+ while not i2c.try_lock():
+ pass
+
+ print("I2C addresses found:", [hex(x) for x in i2c.scan()])
+ i2c.unlock()
+
+ try:
+ gyro_x, gyro_y, gyro_z = mpu.gyro
+ accel_x, accel_y, accel_z = mpu.acceleration
+ except OSError as e:
+ print(f"Sensor read failed: {e}")
+ time.sleep(0.1)
+
+ continue
+
+ # Read and calibrate gyro data
+ # gyro_x, gyro_y, gyro_z = (0, 0, 0)
+ # gyro_x, gyro_y, gyro_z = mpu.gyro
+ gyro_x -= gyro_x_offset
+ gyro_y -= gyro_y_offset
+ gyro_z -= gyro_z_offset
+ # accel_x, accel_y, accel_z = (0, 0, 0)
+ # accel_x, accel_y, accel_z = mpu.acceleration
+
+ # Apply complementary filter to calculate pitch, roll, and yaw
+ pitch, roll, yaw = complementary_filter(
+ (gyro_x, gyro_y, gyro_z), # Gyroscope data (angular velocity in rad/s)
+ (accel_x, accel_y, accel_z), # Accelerometer data
+ (pitch, roll, yaw), # Previous angles
+ ALPHA, # Filter parameter
+ DT # Sampling time
+ )
+
+ # Scale angles to -127 to 127
+ pitch_scaled = max(-127, min(127, int(pitch)))
+ roll_scaled = max(-127, min(127, int(roll)))
+ yaw_scaled = max(-127, min(127, int(yaw * 100)))
+
+ # Read button and joystick states
+ buttons_state = read_buttons()
+ joy_x, joy_y = read_joysticks()
+
+ # Send HID report
+ report = bytearray(8)
+ struct.pack_into("<H", report, 0, buttons_state) # Buttons (2 bytes)
+ struct.pack_into("<bb", report, 2, joy_x, joy_y) # Joystick X, Y (X and Y axes)
+ struct.pack_into("<bbb", report, 4, pitch_scaled, roll_scaled, yaw_scaled) # Pitch, Roll, Yaw (scaled to -127 to 127)
+
+ gamepad.send_report(report)
+
+ time.sleep(DT)
+
diff --git a/src/pico/gamepad.py b/src/pico/gamepad.py
new file mode 100755
index 0000000..c6d217e
--- /dev/null
+++ b/src/pico/gamepad.py
@@ -0,0 +1,96 @@
+import struct
+import time
+import board
+import digitalio
+import analogio
+from adafruit_hid import find_device
+
+class Gamepad:
+ def __init__(self, devices):
+ self._gamepad_device = find_device(devices, usage_page=0x1, usage=0x05)
+
+ self._buttons = {
+ 1: digitalio.DigitalInOut(board.GP0),
+ 2: digitalio.DigitalInOut(board.GP1),
+ 3: digitalio.DigitalInOut(board.GP2),
+ 4: digitalio.DigitalInOut(board.GP3),
+ 5: digitalio.DigitalInOut(board.GP4),
+ 6: digitalio.DigitalInOut(board.GP5),
+ 7: digitalio.DigitalInOut(board.GP6),
+ }
+
+ for button in self._buttons.values():
+ button.direction = digitalio.Direction.INPUT
+ button.pull = digitalio.Pull.UP
+
+ self._joy_x_axis = analogio.AnalogIn(board.GP26)
+ self._joy_y_axis = analogio.AnalogIn(board.GP27)
+
+ self._report = bytearray(6)
+ self._last_report = bytearray(6)
+
+ self._buttons_state = 0
+ self._joy_x = 0
+ self._joy_y = 0
+ self._joy_z = 0
+ self._joy_r_z = 0
+
+ try:
+ self.reset_all()
+ except OSError:
+ time.sleep(1)
+ self.reset_all()
+
+ def read_buttons(self):
+ self._buttons_state = 0
+ for button_num, button in self._buttons.items():
+ if not button.value:
+ self._buttons_state |= 1 << (button_num - 1)
+
+ def read_joysticks(self):
+ self._joy_x = self._scale_joystick_value(self._joy_x_axis.value)
+ self._joy_y = self._scale_joystick_value(self._joy_y_axis.value)
+
+ def _scale_joystick_value(self, value):
+ return (value - 32768) // 258
+
+ def update(self):
+ self.read_buttons()
+ self.read_joysticks()
+ self._send()
+
+ def _send(self, always=False):
+ struct.pack_into(
+ "<Hbbbb",
+ self._report,
+ 0,
+ self._buttons_state,
+ self._joy_x,
+ self._joy_y,
+ self._joy_z,
+ self._joy_r_z,
+ )
+
+ if always or self._last_report != self._report:
+ self._gamepad_device.send_report(self._report)
+ self._last_report[:] = self._report
+
+ def reset_all(self):
+ self._buttons_state = 0
+ self._joy_x = 0
+ self._joy_y = 0
+ self._joy_z = 0
+ self._joy_r_z = 0
+ self._send(always=True)
+
+ @staticmethod
+ def _validate_button_number(button):
+ if not 1 <= button <= 16:
+ raise ValueError("Button number must in range 1 to 16")
+ return button
+
+ @staticmethod
+ def _validate_joystick_value(value):
+ if not -127 <= value <= 127:
+ raise ValueError("Joystick value must be in range -127 to 127")
+ return value \ No newline at end of file
diff --git a/src/pico/lib/adafruit_display_shapes/__init__.py b/src/pico/lib/adafruit_display_shapes/__init__.py
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/src/pico/lib/adafruit_display_shapes/__init__.py
diff --git a/src/pico/lib/adafruit_display_shapes/arc.mpy b/src/pico/lib/adafruit_display_shapes/arc.mpy
new file mode 100755
index 0000000..7ceedab
--- /dev/null
+++ b/src/pico/lib/adafruit_display_shapes/arc.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_shapes/circle.mpy b/src/pico/lib/adafruit_display_shapes/circle.mpy
new file mode 100755
index 0000000..20db7f0
--- /dev/null
+++ b/src/pico/lib/adafruit_display_shapes/circle.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_shapes/filled_polygon.mpy b/src/pico/lib/adafruit_display_shapes/filled_polygon.mpy
new file mode 100755
index 0000000..0283d7c
--- /dev/null
+++ b/src/pico/lib/adafruit_display_shapes/filled_polygon.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_shapes/line.mpy b/src/pico/lib/adafruit_display_shapes/line.mpy
new file mode 100755
index 0000000..ea434a1
--- /dev/null
+++ b/src/pico/lib/adafruit_display_shapes/line.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_shapes/multisparkline.mpy b/src/pico/lib/adafruit_display_shapes/multisparkline.mpy
new file mode 100755
index 0000000..6d69dcc
--- /dev/null
+++ b/src/pico/lib/adafruit_display_shapes/multisparkline.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_shapes/polygon.mpy b/src/pico/lib/adafruit_display_shapes/polygon.mpy
new file mode 100755
index 0000000..02e36ec
--- /dev/null
+++ b/src/pico/lib/adafruit_display_shapes/polygon.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_shapes/rect.mpy b/src/pico/lib/adafruit_display_shapes/rect.mpy
new file mode 100755
index 0000000..5130c3a
--- /dev/null
+++ b/src/pico/lib/adafruit_display_shapes/rect.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_shapes/roundrect.mpy b/src/pico/lib/adafruit_display_shapes/roundrect.mpy
new file mode 100755
index 0000000..101f68e
--- /dev/null
+++ b/src/pico/lib/adafruit_display_shapes/roundrect.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_shapes/sparkline.mpy/placeholder.txt b/src/pico/lib/adafruit_display_shapes/sparkline.mpy/placeholder.txt
new file mode 100755
index 0000000..6e9754c
--- /dev/null
+++ b/src/pico/lib/adafruit_display_shapes/sparkline.mpy/placeholder.txt
@@ -0,0 +1 @@
+SD cards mounted at /sd will hide this file from Python. SD cards are not visible via USB CIRCUITPY.
diff --git a/src/pico/lib/adafruit_display_shapes/triangle.mpy b/src/pico/lib/adafruit_display_shapes/triangle.mpy
new file mode 100755
index 0000000..a7a4693
--- /dev/null
+++ b/src/pico/lib/adafruit_display_shapes/triangle.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_text/__init__.mpy b/src/pico/lib/adafruit_display_text/__init__.mpy
new file mode 100755
index 0000000..498e254
--- /dev/null
+++ b/src/pico/lib/adafruit_display_text/__init__.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_text/bitmap_label.mpy b/src/pico/lib/adafruit_display_text/bitmap_label.mpy
new file mode 100755
index 0000000..6cf3519
--- /dev/null
+++ b/src/pico/lib/adafruit_display_text/bitmap_label.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_text/label.mpy b/src/pico/lib/adafruit_display_text/label.mpy
new file mode 100755
index 0000000..1adb3c3
--- /dev/null
+++ b/src/pico/lib/adafruit_display_text/label.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_text/outlined_label.mpy b/src/pico/lib/adafruit_display_text/outlined_label.mpy
new file mode 100755
index 0000000..90c76f0
--- /dev/null
+++ b/src/pico/lib/adafruit_display_text/outlined_label.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_text/scrolling_label.mpy b/src/pico/lib/adafruit_display_text/scrolling_label.mpy
new file mode 100755
index 0000000..da3ad48
--- /dev/null
+++ b/src/pico/lib/adafruit_display_text/scrolling_label.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_display_text/text_box.mpy b/src/pico/lib/adafruit_display_text/text_box.mpy
new file mode 100755
index 0000000..4354525
--- /dev/null
+++ b/src/pico/lib/adafruit_display_text/text_box.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_hid/__init__.mpy b/src/pico/lib/adafruit_hid/__init__.mpy
new file mode 100755
index 0000000..19ca96e
--- /dev/null
+++ b/src/pico/lib/adafruit_hid/__init__.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_hid/consumer_control.mpy b/src/pico/lib/adafruit_hid/consumer_control.mpy
new file mode 100755
index 0000000..6c863ad
--- /dev/null
+++ b/src/pico/lib/adafruit_hid/consumer_control.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_hid/consumer_control_code.mpy b/src/pico/lib/adafruit_hid/consumer_control_code.mpy
new file mode 100755
index 0000000..4706fd1
--- /dev/null
+++ b/src/pico/lib/adafruit_hid/consumer_control_code.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_hid/keyboard.mpy b/src/pico/lib/adafruit_hid/keyboard.mpy
new file mode 100755
index 0000000..6db90f4
--- /dev/null
+++ b/src/pico/lib/adafruit_hid/keyboard.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_hid/keyboard_layout_base.mpy b/src/pico/lib/adafruit_hid/keyboard_layout_base.mpy
new file mode 100755
index 0000000..d5dcc5b
--- /dev/null
+++ b/src/pico/lib/adafruit_hid/keyboard_layout_base.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_hid/keyboard_layout_us.mpy b/src/pico/lib/adafruit_hid/keyboard_layout_us.mpy
new file mode 100755
index 0000000..90951e9
--- /dev/null
+++ b/src/pico/lib/adafruit_hid/keyboard_layout_us.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_hid/keycode.mpy b/src/pico/lib/adafruit_hid/keycode.mpy
new file mode 100755
index 0000000..4987997
--- /dev/null
+++ b/src/pico/lib/adafruit_hid/keycode.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_hid/mouse.mpy b/src/pico/lib/adafruit_hid/mouse.mpy
new file mode 100755
index 0000000..18ecdc7
--- /dev/null
+++ b/src/pico/lib/adafruit_hid/mouse.mpy
Binary files differ
diff --git a/src/pico/lib/adafruit_ili9341.mpy b/src/pico/lib/adafruit_ili9341.mpy
new file mode 100755
index 0000000..40f1472
--- /dev/null
+++ b/src/pico/lib/adafruit_ili9341.mpy
Binary files differ
diff --git a/src/pico/sd/placeholder.txt b/src/pico/sd/placeholder.txt
new file mode 100755
index 0000000..6e9754c
--- /dev/null
+++ b/src/pico/sd/placeholder.txt
@@ -0,0 +1 @@
+SD cards mounted at /sd will hide this file from Python. SD cards are not visible via USB CIRCUITPY.
diff --git a/src/pico/settings.toml b/src/pico/settings.toml
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/src/pico/settings.toml