diff options
| author | Zoltan Szabatin <[email protected]> | 2025-03-05 20:07:54 -0800 |
|---|---|---|
| committer | Zoltan Szabatin <[email protected]> | 2025-03-05 20:07:54 -0800 |
| commit | 513235a9953648026aba927d96b93b0e80b84083 (patch) | |
| tree | 3f37eaf9ed4fc8257f148def2f928de93f4d046f | |
| parent | fix(Input): Controller button indexes (diff) | |
| download | splitscreen-duo-513235a9953648026aba927d96b93b0e80b84083.tar.xz splitscreen-duo-513235a9953648026aba927d96b93b0e80b84083.zip | |
feat: Add Pico-specific modules
45 files changed, 1746 insertions, 0 deletions
diff --git a/src/pico/System Volume Information/IndexerVolumeGuid b/src/pico/System Volume Information/IndexerVolumeGuid Binary files differnew file mode 100755 index 0000000..de78ebf --- /dev/null +++ b/src/pico/System Volume Information/IndexerVolumeGuid diff --git a/src/pico/System Volume Information/WPSettings.dat b/src/pico/System Volume Information/WPSettings.dat Binary files differnew file mode 100755 index 0000000..e96e427 --- /dev/null +++ b/src/pico/System Volume Information/WPSettings.dat 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 Binary files differnew file mode 100755 index 0000000..7ceedab --- /dev/null +++ b/src/pico/lib/adafruit_display_shapes/arc.mpy diff --git a/src/pico/lib/adafruit_display_shapes/circle.mpy b/src/pico/lib/adafruit_display_shapes/circle.mpy Binary files differnew file mode 100755 index 0000000..20db7f0 --- /dev/null +++ b/src/pico/lib/adafruit_display_shapes/circle.mpy diff --git a/src/pico/lib/adafruit_display_shapes/filled_polygon.mpy b/src/pico/lib/adafruit_display_shapes/filled_polygon.mpy Binary files differnew file mode 100755 index 0000000..0283d7c --- /dev/null +++ b/src/pico/lib/adafruit_display_shapes/filled_polygon.mpy diff --git a/src/pico/lib/adafruit_display_shapes/line.mpy b/src/pico/lib/adafruit_display_shapes/line.mpy Binary files differnew file mode 100755 index 0000000..ea434a1 --- /dev/null +++ b/src/pico/lib/adafruit_display_shapes/line.mpy diff --git a/src/pico/lib/adafruit_display_shapes/multisparkline.mpy b/src/pico/lib/adafruit_display_shapes/multisparkline.mpy Binary files differnew file mode 100755 index 0000000..6d69dcc --- /dev/null +++ b/src/pico/lib/adafruit_display_shapes/multisparkline.mpy diff --git a/src/pico/lib/adafruit_display_shapes/polygon.mpy b/src/pico/lib/adafruit_display_shapes/polygon.mpy Binary files differnew file mode 100755 index 0000000..02e36ec --- /dev/null +++ b/src/pico/lib/adafruit_display_shapes/polygon.mpy diff --git a/src/pico/lib/adafruit_display_shapes/rect.mpy b/src/pico/lib/adafruit_display_shapes/rect.mpy Binary files differnew file mode 100755 index 0000000..5130c3a --- /dev/null +++ b/src/pico/lib/adafruit_display_shapes/rect.mpy diff --git a/src/pico/lib/adafruit_display_shapes/roundrect.mpy b/src/pico/lib/adafruit_display_shapes/roundrect.mpy Binary files differnew file mode 100755 index 0000000..101f68e --- /dev/null +++ b/src/pico/lib/adafruit_display_shapes/roundrect.mpy 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 Binary files differnew file mode 100755 index 0000000..a7a4693 --- /dev/null +++ b/src/pico/lib/adafruit_display_shapes/triangle.mpy diff --git a/src/pico/lib/adafruit_display_text/__init__.mpy b/src/pico/lib/adafruit_display_text/__init__.mpy Binary files differnew file mode 100755 index 0000000..498e254 --- /dev/null +++ b/src/pico/lib/adafruit_display_text/__init__.mpy diff --git a/src/pico/lib/adafruit_display_text/bitmap_label.mpy b/src/pico/lib/adafruit_display_text/bitmap_label.mpy Binary files differnew file mode 100755 index 0000000..6cf3519 --- /dev/null +++ b/src/pico/lib/adafruit_display_text/bitmap_label.mpy diff --git a/src/pico/lib/adafruit_display_text/label.mpy b/src/pico/lib/adafruit_display_text/label.mpy Binary files differnew file mode 100755 index 0000000..1adb3c3 --- /dev/null +++ b/src/pico/lib/adafruit_display_text/label.mpy diff --git a/src/pico/lib/adafruit_display_text/outlined_label.mpy b/src/pico/lib/adafruit_display_text/outlined_label.mpy Binary files differnew file mode 100755 index 0000000..90c76f0 --- /dev/null +++ b/src/pico/lib/adafruit_display_text/outlined_label.mpy diff --git a/src/pico/lib/adafruit_display_text/scrolling_label.mpy b/src/pico/lib/adafruit_display_text/scrolling_label.mpy Binary files differnew file mode 100755 index 0000000..da3ad48 --- /dev/null +++ b/src/pico/lib/adafruit_display_text/scrolling_label.mpy diff --git a/src/pico/lib/adafruit_display_text/text_box.mpy b/src/pico/lib/adafruit_display_text/text_box.mpy Binary files differnew file mode 100755 index 0000000..4354525 --- /dev/null +++ b/src/pico/lib/adafruit_display_text/text_box.mpy diff --git a/src/pico/lib/adafruit_hid/__init__.mpy b/src/pico/lib/adafruit_hid/__init__.mpy Binary files differnew file mode 100755 index 0000000..19ca96e --- /dev/null +++ b/src/pico/lib/adafruit_hid/__init__.mpy diff --git a/src/pico/lib/adafruit_hid/consumer_control.mpy b/src/pico/lib/adafruit_hid/consumer_control.mpy Binary files differnew file mode 100755 index 0000000..6c863ad --- /dev/null +++ b/src/pico/lib/adafruit_hid/consumer_control.mpy diff --git a/src/pico/lib/adafruit_hid/consumer_control_code.mpy b/src/pico/lib/adafruit_hid/consumer_control_code.mpy Binary files differnew file mode 100755 index 0000000..4706fd1 --- /dev/null +++ b/src/pico/lib/adafruit_hid/consumer_control_code.mpy diff --git a/src/pico/lib/adafruit_hid/keyboard.mpy b/src/pico/lib/adafruit_hid/keyboard.mpy Binary files differnew file mode 100755 index 0000000..6db90f4 --- /dev/null +++ b/src/pico/lib/adafruit_hid/keyboard.mpy diff --git a/src/pico/lib/adafruit_hid/keyboard_layout_base.mpy b/src/pico/lib/adafruit_hid/keyboard_layout_base.mpy Binary files differnew file mode 100755 index 0000000..d5dcc5b --- /dev/null +++ b/src/pico/lib/adafruit_hid/keyboard_layout_base.mpy diff --git a/src/pico/lib/adafruit_hid/keyboard_layout_us.mpy b/src/pico/lib/adafruit_hid/keyboard_layout_us.mpy Binary files differnew file mode 100755 index 0000000..90951e9 --- /dev/null +++ b/src/pico/lib/adafruit_hid/keyboard_layout_us.mpy diff --git a/src/pico/lib/adafruit_hid/keycode.mpy b/src/pico/lib/adafruit_hid/keycode.mpy Binary files differnew file mode 100755 index 0000000..4987997 --- /dev/null +++ b/src/pico/lib/adafruit_hid/keycode.mpy diff --git a/src/pico/lib/adafruit_hid/mouse.mpy b/src/pico/lib/adafruit_hid/mouse.mpy Binary files differnew file mode 100755 index 0000000..18ecdc7 --- /dev/null +++ b/src/pico/lib/adafruit_hid/mouse.mpy diff --git a/src/pico/lib/adafruit_ili9341.mpy b/src/pico/lib/adafruit_ili9341.mpy Binary files differnew file mode 100755 index 0000000..40f1472 --- /dev/null +++ b/src/pico/lib/adafruit_ili9341.mpy 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 |