Source code for micropython_bmi160.bmi160

# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`bmi160`
================================================================================

MicroPython Driver for the Bosch BMI160 Acc/Gyro Sensor


* Author(s): Jose D. Montoya


"""

import time
from micropython import const
from micropython_bmi160.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_BMI160.git"


_I2C_ADDR = const(0x69)
_REG_WHOAMI = const(0x00)
_ERROR_CODE = const(0x02)
_COMMAND = const(0x7E)
_ACCEL_CONFIG = const(0x40)
_ACC_RANGE = const(0x41)
_GYRO_CONFIG = const(0x42)
_GYRO_RANGE = const(0x43)

# RESET Command
RESET_COMMAND = const(0xB6)

# Acceleration Output Rate HZ
BANDWIDTH_25_32 = const(0b0001)  # 25/32 Hz
BANDWIDTH_25_16 = const(0b0010)  # 25/16 Hz
BANDWIDTH_25_8 = const(0b0011)  # 25/8 Hz
BANDWIDTH_25_4 = const(0b0100)  # 25/4 Hz
BANDWIDTH_25_2 = const(0b0101)  # 25/2 Hz
BANDWIDTH_25 = const(0b0110)  # 25 Hz
BANDWIDTH_50 = const(0b0111)  # 50 Hz
BANDWIDTH_100 = const(0b1000)  # 100 Hz
BANDWIDTH_200 = const(0b1001)  # 200 Hz
BANDWIDTH_400 = const(0b1010)  # 400 Hz
BANDWIDTH_800 = const(0b1011)  # 800 Hz
BANDWIDTH_1600 = const(0b1100)  # 1600 Hz
BANDWIDTH_3200 = const(0b1101)  # 3200 Hz
bandwidth_values = (
    BANDWIDTH_25_32,
    BANDWIDTH_25_16,
    BANDWIDTH_25_8,
    BANDWIDTH_25_4,
    BANDWIDTH_25_2,
    BANDWIDTH_25,
    BANDWIDTH_50,
    BANDWIDTH_100,
    BANDWIDTH_200,
    BANDWIDTH_400,
    BANDWIDTH_800,
    BANDWIDTH_1600,
    BANDWIDTH_3200,
)
gyro_bandwidth_values = (
    BANDWIDTH_25,
    BANDWIDTH_50,
    BANDWIDTH_100,
    BANDWIDTH_200,
    BANDWIDTH_400,
    BANDWIDTH_800,
    BANDWIDTH_1600,
    BANDWIDTH_3200,
)

# Acceleration Range
ACCEL_RANGE_2G = const(0b0011)
ACCEL_RANGE_4G = const(0b0101)
ACCEL_RANGE_8G = const(0b1000)
ACCEL_RANGE_16G = const(0b1100)
acc_range_values = (ACCEL_RANGE_2G, ACCEL_RANGE_4G, ACCEL_RANGE_8G, ACCEL_RANGE_16G)

# UNDERSAMPLE
NO_UNDERSAMPLE = const(0)
UNDERSAMPLE = const(1)
acc_sample_values = (NO_UNDERSAMPLE, UNDERSAMPLE)

# Bandwidth Parameter
FILTER = const(0)
AVERAGING = const(1)
acc_bandwidth_values = (FILTER, AVERAGING)

# Acceleration Data
ACC_X_LSB = const(0x12)
ACC_Y_LSB = const(0x14)
ACC_Z_LSB = const(0x16)

# Acc Power Modes
ACC_POWER_SUSPEND = const(0x10)
ACC_POWER_NORMAL = const(0x11)
ACC_POWER_LOWPOWER = const(0x12)
acc_power_mode_values = (ACC_POWER_LOWPOWER, ACC_POWER_NORMAL, ACC_POWER_SUSPEND)

# Temperature
TEMP_LSB = const(0x20)

# Gyro Data
GYRO_X_LSB = const(0x0C)
GYRO_Y_LSB = const(0x0E)
GYRO_Z_LSB = const(0x10)

# Gyro Cutoffs
GYRO_NORMAL = const(0b10)
GYRO_OSR2 = const(0b01)
GYRO_OSR4 = const(0b00)
gyro_cutoffs_values = (GYRO_OSR4, GYRO_OSR2, GYRO_NORMAL)

# Gyro Power Modes
GYRO_POWER_SUSPEND = const(0x14)
GYRO_POWER_NORMAL = const(0x15)
GYRO_POWER_FASTSTARTUP = const(0x17)
gyro_power_modes = (GYRO_POWER_SUSPEND, GYRO_POWER_NORMAL, GYRO_POWER_FASTSTARTUP)

# Gyro Ranges
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_values = (
    GYRO_RANGE_125,
    GYRO_RANGE_250,
    GYRO_RANGE_500,
    GYRO_RANGE_1000,
    GYRO_RANGE_2000,
)

_ACC_CONVERSION = const(9.80665)

# pylint: disable= invalid-name


[docs] class BMI160: """Driver for the BMI160 Sensor connected over I2C. :param ~machine.I2C i2c: The I2C bus the BMI160 is connected to. :param int address: The I2C device address. Defaults to :const:`0x69` :raises RuntimeError: if the sensor is not found **Quickstart: Importing and using the device** Here is an example of using the :class:`BMI160` class. First you will need to import the libraries to use the sensor .. code-block:: python from machine import Pin, I2C from micropython_bmi160 import bmi160 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)) bmi160 = bmi160.BMI160(i2c) Now you have access to the attributes .. code-block:: python accx, accy, accz = bmi.acceleration gyrox, gyroy, gyroz = bmi.gyro """ _device_id = RegisterStruct(_REG_WHOAMI, "B") _soft_reset = RegisterStruct(_COMMAND, "B") _error_code = RegisterStruct(_ERROR_CODE, "B") _acc_config = RegisterStruct(_ACCEL_CONFIG, "B") _power_mode = RegisterStruct(0x03, "B") _gyro_config = RegisterStruct(_GYRO_CONFIG, "B") # Acceleration Data _acc_data_x = RegisterStruct(ACC_X_LSB, "<h") _acc_data_y = RegisterStruct(ACC_Y_LSB, "<h") _acc_data_z = RegisterStruct(ACC_Z_LSB, "<h") _read = RegisterStruct(_COMMAND, "B") # ACC_CONF Register (0x40) # Sets the output data rate, the bandwidth, and the read mode of the acceleration # sensor _acc_us = CBits(1, _ACCEL_CONFIG, 7) _acc_bwp = CBits(1, _ACCEL_CONFIG, 6) _acc_odr = CBits(4, _ACCEL_CONFIG, 0) # ACC_RANGE Register (0x41) # The register allows the selection of the accelerometer g-range _acc_range = CBits(4, _ACC_RANGE, 0) acceleration_scale = { "ACCEL_RANGE_2G": 16384, "ACCEL_RANGE_4G": 8192, "ACCEL_RANGE_8G": 4096, "ACCEL_RANGE_16G": 2048, } gyro_scale = { "GYRO_RANGE_125": 16.4, "GYRO_RANGE_250": 32.8, "GYRO_RANGE_500": 65.6, "GYRO_RANGE_1000": 131.2, "GYRO_RANGE_2000": 262.4, } # Temperature _temp_data = RegisterStruct(TEMP_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_CONF Register (0x41) # Sets the output data rate, the bandwidth, and the read mode of the gyro # sensor _gyro_bwp = CBits(2, _GYRO_CONFIG, 4) _gyro_odr = CBits(4, _GYRO_CONFIG, 0) # GYRO_RANGE Register (0x43) _gyro_range = CBits(3, _GYRO_RANGE, 0) def __init__(self, i2c, address: int = 0x69) -> None: self._i2c = i2c self._address = address if self._device_id != 0xD1: raise RuntimeError("Failed to find BMI160") self.soft_reset() self._read = 0x03 time.sleep(0.1) self._read = ACC_POWER_NORMAL time.sleep(0.1) self._read = GYRO_POWER_NORMAL time.sleep(0.1)
[docs] def soft_reset(self) -> None: """ Performs a Soft Reset :return: None """ self._soft_reset = RESET_COMMAND time.sleep(0.015)
[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. Broken hardware(e.g.NVM error, see ASIC spec for details).This flag will not be cleared after reading the register.The only way to clear the flag is a POR. Error flags (bits 7:4) store error event until they are reset by reading the register. """ code_errors = { 0: "No Error", 1: "Error", 2: "Error", 3: "low-power mode and interrupt uses pre-filtered data", 6: "ODRs of enabled sensors in header-less mode do not match", 7: "pre-filtered data are used in low power mode", } errors = self._error_code drop_cmd_err = (errors & 0x40) >> 6 error_codes = (errors & 0x1E) >> 1 fatal_error = errors & 0x01 if drop_cmd_err: print("Drop Command Error") if code_errors[error_codes] != "No Error": print(code_errors[error_codes]) if fatal_error: print("Fatal Error")
@property def acceleration_undersample(self) -> str: """ The undersampling parameter is typically used in low power mode. When acc_us is set to '0' and the accelerometer is in low-power mode, it will change to normal mode. If the acc_us is set to '0' and a command to enter low-power mode is sent to the Register (0x7E) CMD, this command is ignored. +----------------------------------------+-------------------------+ | Mode | Value | +========================================+=========================+ | :py:const:`bmi160.NO_UNDERSAMPLE` | :py:const:`0` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.UNDERSAMPLE` | :py:const:`1` | +----------------------------------------+-------------------------+ """ sample_values = ("NO_UNDERSAMPLE", "UNDERSAMPLE") return sample_values[self._acc_us] @acceleration_undersample.setter def acceleration_undersample(self, value: int) -> None: if value not in acc_sample_values: raise ValueError("Value must be a valid acceleration undersample value") self._acc_us = value @property def acceleration_bandwidth_parameter(self) -> str: """ Determines filter configuration (acc_us=0) and averaging for undersampling mode (acc_us=1). +----------------------------------------+-------------------------+ | Mode | Value | +========================================+=========================+ | :py:const:`bmi160.FILTER` | :py:const:`0` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.AVERAGING` | :py:const:`1` | +----------------------------------------+-------------------------+ """ values = ("FILTER", "AVERAGING") return values[self._acc_bwp] @acceleration_bandwidth_parameter.setter def acceleration_bandwidth_parameter(self, value: int) -> None: if value not in acc_bandwidth_values: raise ValueError("Value must a be a valid Acceleration bandwidth setting") self._acc_bwp = value @property def acceleration_output_data_rate(self) -> str: """ Define the output data rate in Hz is given by :math:`100/2^(8-accodr)` The output data rate is independent of the power mode setting for the sensor Configurations without a bandwidth number are illegal settings and will result in an error code in the Register (0x02) ERR_REG. At startup this is setup at 100 Hz +----------------------------------------+---------------------------------+ | Mode | Value | +========================================+=================================+ | :py:const:`bmi160.BANDWIDTH_25_32` | :py:const:`0b0001` 25/32 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_25_16` | :py:const:`0b0010` 25/16 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_25_8` | :py:const:`0b0011` 25/8 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_25_4` | :py:const:`0b0100` 25/4 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_25_2` | :py:const:`0b0101` 25/2 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_25` | :py:const:`0b0110` 25 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_50` | :py:const:`0b0111` 50 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_100` | :py:const:`0b1000` 100 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_200` | :py:const:`0b1001` 200 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_400` | :py:const:`0b1010` 400 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_800` | :py:const:`0b1011` 800 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_1600` | :py:const:`0b1100` 1600 Hz | +----------------------------------------+---------------------------------+ """ values = ( "BANDWIDTH_25_32", "BANDWIDTH_25_16", "BANDWIDTH_25_8", "BANDWIDTH_25_4", "BANDWIDTH_25_2", "BANDWIDTH_25", "BANDWIDTH_50", "BANDWIDTH_100", "BANDWIDTH_200", "BANDWIDTH_400", "BANDWIDTH_800", "BANDWIDTH_1600", "BANDWIDTH_3200", ) return values[self._acc_odr] @acceleration_output_data_rate.setter def acceleration_output_data_rate(self, value: int) -> None: if value not in bandwidth_values: raise ValueError("Value must be a valid Acceleration Data Rate setting") self._acc_odr = value @property def acceleration_range(self) -> str: """ The register allows the selection of the accelerometer g-range. Changing the range of the accelerometer does not clear the data ready bit in the Register (0x1B) STATUS. It is recommended to read the Register (0x04-0x17) DATA after the range change to remove a stall data ready bit from before the range change. +----------------------------------------+-------------------------+ | Mode | Value | +========================================+=========================+ | :py:const:`bmi160.ACCEL_RANGE_2G` | :py:const:`0b0011` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.ACCEL_RANGE_4G` | :py:const:`0b0101` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.ACCEL_RANGE_8G` | :py:const:`0b1000` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.ACCEL_RANGE_16G` | :py:const:`0b1100` | +----------------------------------------+-------------------------+ """ values = { 3: "ACCEL_RANGE_2G", 5: "ACCEL_RANGE_4G", 8: "ACCEL_RANGE_8G", 12: "ACCEL_RANGE_16G", } return values[self._acc_range] @acceleration_range.setter def acceleration_range(self, value: int) -> None: if value not in acc_range_values: raise ValueError("Value must be a valid Acceleration Range setting") self._acc_range = value @property def acceleration(self) -> Tuple[int, int, int]: """ Sensor Acceleration """ factor = self.acceleration_scale[self.acceleration_range] / _ACC_CONVERSION x = self._acc_data_x / factor y = self._acc_data_y / factor z = self._acc_data_z / factor return x, y, z
[docs] def power_mode_status(self) -> None: """ Returns Power mode status """ values = self._power_mode acc_pmu_status = (values & 0x18) >> 4 gyr_pmu_status = (values & 0xC) >> 2 mag_pmu_status = values & 0x03 acc_pmu_codes = {0: "Suspend", 1: "Normal", 2: "Low Power"} gyr_pmu_codes = {0: "Suspend", 1: "Normal", 3: "Fast Start - Up"} mag_pmu_codes = {0: "Suspend", 1: "Normal", 2: "Low Power"} print("Acceleration Power Mode: ", acc_pmu_codes[acc_pmu_status]) print("Gyro Power Mode", gyr_pmu_codes[gyr_pmu_status]) print("Mag Power Mode", mag_pmu_codes[mag_pmu_status])
[docs] def acc_power_mode(self, value: int) -> None: """ +----------------------------------------+-------------------------+ | Mode | Value | +========================================+=========================+ | :py:const:`bmi160.ACC_POWER_SUSPEND` | :py:const:`0x10` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.ACC_POWER_NORMAL` | :py:const:`0x11` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.POWER_LOWPOWER` | :py:const:`0x12` | +----------------------------------------+-------------------------+ """ if value not in acc_power_mode_values: raise ValueError("Value must be a valid Acceleration Power Mode Setting") self._read = value time.sleep(0.1)
@property def temperature(self) -> int: """ The temperature is disabled when all sensors are in suspend mode. The output word of the 16-bit temperature sensor is valid if the gyroscope is in normal mode, i.e. gyr_pmu_status=0b01. The resolution is typically :math:`1/2^9` K/LSB. If the gyroscope is in normal mode (see Register (0x03) PMU_STATUS), the temperature is updated every 10 ms (+-12%). If the gyroscope is in suspend mode or fast-power up mode, the temperature is updated every 1.28 s aligned :return: int """ return (self._temp_data * 1 / 2**9) + 23 @property def gyro(self) -> Tuple[int, int, int]: """ Gyro values """ factor = self.gyro_scale[self.gyro_range] x = self._gyro_data_x / factor y = self._gyro_data_y / factor z = self._gyro_data_z / factor return x, y, z @property def gyro_output_data_rate(self) -> str: """ Define the output data rate in Hz is given by :math:`100/2^(8-gyroodr)` The output data rate is independent of the power mode setting for the sensor Configurations without a bandwidth number are illegal settings and will result in an error code in the Register (0x02) ERR_REG. .. warning :: Lower ODR values than 25Hz are not allowed. If they are used they result in an error code in Register (0x02) ERR_REG. At startup this is setup at 100 Hz +----------------------------------------+---------------------------------+ | Mode | Value | +========================================+=================================+ | :py:const:`bmi160.BANDWIDTH_25` | :py:const:`0b0110` 25 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_50` | :py:const:`0b0111` 50 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_100` | :py:const:`0b1000` 100 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_200` | :py:const:`0b1001` 200 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_400` | :py:const:`0b1010` 400 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_800` | :py:const:`0b1011` 800 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_1600` | :py:const:`0b1100` 1600 Hz | +----------------------------------------+---------------------------------+ | :py:const:`bmi160.BANDWIDTH_3200` | :py:const:`0b1101` 3200 Hz | +----------------------------------------+---------------------------------+ """ values = ( "BANDWIDTH_25", "BANDWIDTH_50", "BANDWIDTH_100", "BANDWIDTH_200", "BANDWIDTH_400", "BANDWIDTH_800", "BANDWIDTH_1600", "BANDWIDTH_3200", ) return values[self._gyro_odr] @gyro_output_data_rate.setter def gyro_output_data_rate(self, value: int) -> None: if value not in gyro_bandwidth_values: raise ValueError("Value must be a valid Gyro Data Rate setting") self._gyro_odr = value @property def gyro_bandwidth_parameter(self) -> str: """ The gyroscope bandwidth coefficient defines the 3 dB cutoff frequency of the low pass filter for the sensor data. When the filter mode is set to normal (gyr_bwp=0b10), the gyroscope data is sampled at equidistant points in the time, defined by the gyroscope output data rate parameter (gyr_odr). The output data rate can be configured in one of eight different valid ODR configurations going from 25Hz up to 3200Hz. When the filter mode is set to OSR2 (gyr_bwp=0b01), both stages of the digital filter are used and the data is oversampled with an oversampling rate of 2. That means that for a certain filter configuration, the ODR has to be 2 times higher than in the normal filter mode. Conversely, for a certain filter configuration, the filter bandwidth will be the approximately half of the bandwidth achieved for the same ODR in the normal filter mode. For example, for ODR=50Hz we will have a 3dB cutoff frequency of 10.12Hz. When the filter mode is set to OSR4 (gyr_bwp=0b000), both stages of the digital filter are used and the data is oversampled with an oversampling rate of 4. That means that for a certain filter configuration, the ODR has to be 4 times higher than in the normal filter mode. Conversely, for a certain filter configuration, the filter bandwidth will be approximately 4 times smaller than the bandwidth achieved for the same ODR in the normal filter mode. For example, for ODR=50Hz we will have a 3dB cutoff frequency of 5.06Hz. +----------------------------------------+-------------------------+ | Mode | Value | +========================================+=========================+ | :py:const:`bmi160.GYRO_NORMAL` | :py:const:`0b10` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.GYRO_OSR2` | :py:const:`0b01` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.GYRO_OSR4` | :py:const:`0b00` | +----------------------------------------+-------------------------+ """ values = ("GYRO_OSR4", "GYRO_OSR2", "GYRO_NORMAL") return values[self._gyro_bwp] @gyro_bandwidth_parameter.setter def gyro_bandwidth_parameter(self, value: int) -> None: if value not in gyro_cutoffs_values: raise ValueError("Value must be a valid Gyro Bandwidth setting") self._gyro_bwp = value @property def gyro_power_mode(self) -> str: """ +-------------------------------------------+-------------------------+ | Mode | Value | +===========================================+=========================+ | :py:const:`bmi160.GYRO_POWER_SUSPEND` | :py:const:`0x14` | +-------------------------------------------+-------------------------+ | :py:const:`bmi160.GYRO_POWER_NORMAL` | :py:const:`0x15` | +-------------------------------------------+-------------------------+ | :py:const:`bmi160.GYRO_POWER_FASTSTARTUP` | :py:const:`0x17` | +-------------------------------------------+-------------------------+ """ g_power_modes = { 0x14: "GYRO_POWER_SUSPEND", 0x15: "GYRO_POWER_NORMAL", 0x17: "GYRO_POWER_FASTSTARTUP", } return g_power_modes[self._read] @gyro_power_mode.setter def gyro_power_mode(self, value: int) -> None: if value not in gyro_power_modes: raise ValueError("Value must be a valid Gyro Power Mode") self._read = value time.sleep(0.1) @property def gyro_range(self) -> str: """ The register allows the selection of the gyro g-range. Changing the range of the accelerometer does not clear the data ready bit in the Register (0x1B) STATUS. It is recommended to read the Register (0x04-0x17) DATA after the range change to remove a stall data ready bit from before the range change. +----------------------------------------+-------------------------+ | Mode | Value | +========================================+=========================+ | :py:const:`bmi160.GYRO_RANGE_2000` | :py:const:`0b000` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.GYRO_RANGE_1000` | :py:const:`0b001` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.GYRO_RANGE_500` | :py:const:`0b010` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.GYRO_RANGE_250` | :py:const:`0b011` | +----------------------------------------+-------------------------+ | :py:const:`bmi160.GYRO_RANGE_125` | :py:const:`0b100` | +----------------------------------------+-------------------------+ """ g_values = ( "GYRO_RANGE_125", "GYRO_RANGE_250", "GYRO_RANGE_500", "GYRO_RANGE_1000", "GYRO_RANGE_2000", ) return g_values[self._gyro_range] @gyro_range.setter def gyro_range(self, value: int) -> None: if value not in gyro_values: raise ValueError("Value must be a valid Gyro range") self._gyro_range = value