# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`bmi270`
================================================================================
MicroPython Driver for the Bosch BMI270 Accelerometer/Gyro Sensor
* Author(s): Jose D. Montoya
"""
import time
from micropython import const
from micropython_bmi270.i2c_helpers import CBits, RegisterStruct
try:
from typing import Tuple
except ImportError:
pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/jposada202020/MicroPython_BMI270.git"
_REG_WHOAMI = const(0x00)
_ERROR_CODE = const(0x02)
_COMMAND = const(0x7E)
_ACC_RANGE = const(0x41)
_PWR_CTRL = const(0x7D)
_GYRO_RANGE = const(0x43)
_STANDARD_GRAVITY = 9.80665
# Acceleration Data
ACC_X_LSB = const(0x0C)
ACC_Y_LSB = const(0x0E)
ACC_Z_LSB = const(0x10)
# Gyro Data
GYRO_X_LSB = const(0x12)
GYRO_Y_LSB = const(0x14)
GYRO_Z_LSB = const(0x16)
# Acceleration Range
ACCEL_RANGE_2G = const(0b00)
ACCEL_RANGE_4G = const(0b01)
ACCEL_RANGE_8G = const(0b10)
ACCEL_RANGE_16G = const(0b11)
acceleration_range_values = (
ACCEL_RANGE_2G,
ACCEL_RANGE_4G,
ACCEL_RANGE_8G,
ACCEL_RANGE_16G,
)
ACCELERATOR_DISABLED = const(0b0)
ACCELERATOR_ENABLED = const(0b1)
acceleration_operation_mode_values = (ACCELERATOR_DISABLED, ACCELERATOR_ENABLED)
GYRO_RANGE_2000 = const(0b000)
GYRO_RANGE_1000 = const(0b001)
GYRO_RANGE_500 = const(0b010)
GYRO_RANGE_250 = const(0b011)
GYRO_RANGE_125 = const(0b100)
gyro_range_values = (
GYRO_RANGE_2000,
GYRO_RANGE_1000,
GYRO_RANGE_500,
GYRO_RANGE_250,
GYRO_RANGE_125,
)
# RESET Command
RESET_COMMAND = const(0xB6)
_PWR_CONF = const(0x7C)
_INIT_CTRL = const(0x59)
_INIT_ADDR_0 = const(0x5B)
_INIT_ADDR_1 = const(0x5C)
_INIT_DATA = const(0x5E)
[docs]class BMI270:
"""Driver for the BMI270 Sensor connected over I2C.
:param ~machine.I2C i2c: The I2C bus the BMI270 is connected to.
:param int address: The I2C device address. Defaults to :const:`0x68`
:raises RuntimeError: if the sensor is not found
**Quickstart: Importing and using the device**
Here is an example of using the :class:`BMI270` class.
First you will need to import the libraries to use the sensor
.. code-block:: python
from machine import Pin, I2C
from micropython_bmi270 import bmi270
Once this is done you can define your `machine.I2C` object and define your sensor object
.. code-block:: python
i2c = I2C(1, sda=Pin(2), scl=Pin(3))
bmi270 = bmi270.BMI270(i2c)
Now you have access to the attributes
.. code-block:: python
"""
_device_id = RegisterStruct(_REG_WHOAMI, "B")
_error_code = RegisterStruct(_ERROR_CODE, "B")
_soft_reset = RegisterStruct(_COMMAND, "B")
_read = RegisterStruct(_COMMAND, "B")
power_control = RegisterStruct(_PWR_CTRL, "B")
power_config = RegisterStruct(0x7C, "B")
_acc_data_x = RegisterStruct(ACC_X_LSB, "<h")
_acc_data_y = RegisterStruct(ACC_Y_LSB, "<h")
_acc_data_z = RegisterStruct(ACC_Z_LSB, "<h")
# Gyro Data
_gyro_data_x = RegisterStruct(GYRO_X_LSB, "<h")
_gyro_data_y = RegisterStruct(GYRO_Y_LSB, "<h")
_gyro_data_z = RegisterStruct(GYRO_Z_LSB, "<h")
# GYRO_RANGE Register (0x43)
_gyro_range = CBits(3, _GYRO_RANGE, 0)
gyro_scale = (16.4, 32.8, 65.6, 131.2, 262.4)
# ACC_RANGE Register (0x41)
# The register allows the selection of the accelerometer g-range
_acceleration_range = CBits(2, _ACC_RANGE, 0)
acceleration_scale = (16384, 8192, 4096, 2048)
_acceleration_operation_mode = CBits(1, _PWR_CTRL, 2)
_power_configuration = RegisterStruct(_PWR_CONF, "B")
internal_status = RegisterStruct(0x21, "B")
_init_control = RegisterStruct(_INIT_CTRL, "B")
_init_address_0 = RegisterStruct(_INIT_ADDR_0, "B")
_init_address_1 = RegisterStruct(_INIT_ADDR_1, "B")
_init_data = RegisterStruct(_INIT_DATA, ">HHHHHHHHHHHHHHHH")
def __init__(self, i2c, address: int = 0x68) -> None:
self._i2c = i2c
self._address = address
if self._device_id != 0x24:
raise RuntimeError("Failed to find BMI270")
# self.soft_reset()
self.load_config_file()
self.power_control = 0x0E
time.sleep(0.1)
self.power_config = 0x00
time.sleep(0.1)
self.acceleration_range = ACCEL_RANGE_2G
self.gyro_range = GYRO_RANGE_250
[docs] def error_code(self) -> None:
"""
The register is meant for debug purposes, not for regular verification
if an operation completed successfully.
Fatal Error: Error during bootup. This flag will not be cleared after
reading the register.The only way to clear the flag is a POR.
"""
errors = self._error_code
i2c_err = (errors & 0x80) >> 7
fifo_err = (errors & 0x40) >> 6
internal_error = (errors & 0x1E) >> 1
fatal_error = errors & 0x01
if i2c_err:
print("Error in I2C-Master detected. This flag will be reset when read.")
if fifo_err:
print(
"Error when a frame is read in streaming mode (so skipping is not possible) \n"
+ "and fifo is overfilled (with virtual and/or regular frames). This flag will\n"
+ "be reset when read."
)
if internal_error != 0:
print("Internal Sensor Error")
if fatal_error:
print("Fatal Error. This flag will be reset when read")
[docs] def soft_reset(self) -> None:
"""
Performs a Soft Reset
:return: None
"""
self._soft_reset = RESET_COMMAND
time.sleep(0.015)
@property
def acceleration(self) -> Tuple[float, float, float]:
"""
Sensor Acceleration
"""
x = self._acc_data_x / self._acceleration_factor_cached * _STANDARD_GRAVITY
y = self._acc_data_y / self._acceleration_factor_cached * _STANDARD_GRAVITY
z = self._acc_data_z / self._acceleration_factor_cached * _STANDARD_GRAVITY
return x, y, z
@property
def acceleration_range(self) -> str:
"""
Sensor acceleration_range
+------------------------------------+------------------+
| Mode | Value |
+====================================+==================+
| :py:const:`bmi270.ACCEL_RANGE_2G` | :py:const:`0b00` |
+------------------------------------+------------------+
| :py:const:`bmi270.ACCEL_RANGE_4G` | :py:const:`0b01` |
+------------------------------------+------------------+
| :py:const:`bmi270.ACCEL_RANGE_8G` | :py:const:`0b10` |
+------------------------------------+------------------+
| :py:const:`bmi270.ACCEL_RANGE_16G` | :py:const:`0b11` |
+------------------------------------+------------------+
"""
values = (
"ACCEL_RANGE_2G",
"ACCEL_RANGE_4G",
"ACCEL_RANGE_8G",
"ACCEL_RANGE_16G",
)
return values[self._acceleration_range]
@acceleration_range.setter
def acceleration_range(self, value: int) -> None:
if value not in acceleration_range_values:
raise ValueError("Value must be a valid acceleration_range setting")
self._acceleration_range = value
self._acceleration_factor_cached = self.acceleration_scale[value]
@property
def acceleration_operation_mode(self) -> str:
"""
Sensor acceleration_operation_mode
+-----------------------------------------+-----------------+
| Mode | Value |
+=========================================+=================+
| :py:const:`bmi270.ACCELERATOR_DISABLED` | :py:const:`0b0` |
+-----------------------------------------+-----------------+
| :py:const:`bmi270.ACCELERATOR_ENABLED` | :py:const:`0b1` |
+-----------------------------------------+-----------------+
"""
values = ("ACCELERATOR_DISABLED", "ACCELERATOR_ENABLED")
return values[self._acceleration_operation_mode]
@acceleration_operation_mode.setter
def acceleration_operation_mode(self, value: int) -> None:
if value not in acceleration_operation_mode_values:
raise ValueError(
"Value must be a valid acceleration_operation_mode setting"
)
self._acceleration_operation_mode = value
[docs] def load_config_file(self) -> None:
"""
Load configuration file. This is necessary to use the sensor.
Script adapted to use with MicroPython from:
https://github.com/CoRoLab-Berlin/bmi270_python
(c) 2023 MIT License Kevin Sommler
"""
if self.internal_status == 0x01:
print(hex(self._address), " --> Initialization already done")
else:
from micropython_bmi270.config_file import bmi270_config_file
print(hex(self._address), " --> Initializing...")
self._power_configuration = 0x00
time.sleep(0.00045)
self._init_control = 0x00
for i in range(256):
self._init_address_0 = 0x00
self._init_address_1 = i
time.sleep(0.03)
self._i2c.writeto_mem(
self._address,
0x5E,
bytes(bmi270_config_file[i * 32 : (i + 1) * 32]),
)
time.sleep(0.000020)
self._init_control = 0x01
time.sleep(0.02)
print(
hex(self._address),
" --> Initialization status: "
+ "{:08b}".format(self.internal_status)
+ "\t(00000001 --> OK)",
)
@property
def gyro_range(self) -> str:
"""
Sensor gyro_range
+------------------------------------+-------------------+
| Mode | Value |
+====================================+===================+
| :py:const:`bmi270.GYRO_RANGE_2000` | :py:const:`0b000` |
+------------------------------------+-------------------+
| :py:const:`bmi270.GYRO_RANGE_1000` | :py:const:`0b001` |
+------------------------------------+-------------------+
| :py:const:`bmi270.GYRO_RANGE_500` | :py:const:`0b010` |
+------------------------------------+-------------------+
| :py:const:`bmi270.GYRO_RANGE_250` | :py:const:`0b011` |
+------------------------------------+-------------------+
| :py:const:`bmi270.GYRO_RANGE_125` | :py:const:`0b100` |
+------------------------------------+-------------------+
"""
values = (
"GYRO_RANGE_2000",
"GYRO_RANGE_1000",
"GYRO_RANGE_500",
"GYRO_RANGE_250",
"GYRO_RANGE_125",
)
return values[self._gyro_range]
@gyro_range.setter
def gyro_range(self, value: int) -> None:
if value not in gyro_range_values:
raise ValueError("Value must be a valid gyro_range setting")
self._gyro_range = value
self._gyro_factor_cached = self.gyro_scale[value]
@property
def gyro(self) -> Tuple[float, float, float]:
"""
Gyro values
"""
x = self._gyro_data_x / self._gyro_factor_cached
y = self._gyro_data_y / self._gyro_factor_cached
z = self._gyro_data_z / self._gyro_factor_cached
return x, y, z