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 /src/pico/adafruit_mpu6050.py | |
| parent | fix(Input): Controller button indexes (diff) | |
| download | splitscreen-duo-513235a9953648026aba927d96b93b0e80b84083.tar.xz splitscreen-duo-513235a9953648026aba927d96b93b0e80b84083.zip | |
feat: Add Pico-specific modules
Diffstat (limited to 'src/pico/adafruit_mpu6050.py')
| -rwxr-xr-x | src/pico/adafruit_mpu6050.py | 401 |
1 files changed, 401 insertions, 0 deletions
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 |