363 lines
12 KiB
Python
363 lines
12 KiB
Python
# bma400.py - BMA400 Accelerometer Library for Python
|
|
|
|
import time
|
|
import struct
|
|
from typing import Tuple, Optional, Union, List
|
|
|
|
try:
|
|
import smbus
|
|
except ImportError:
|
|
pass # Allow for non-Raspberry Pi environments
|
|
|
|
|
|
class BMA400:
|
|
"""
|
|
Python driver for the Bosch BMA400 Accelerometer.
|
|
|
|
This library provides access to the BMA400 accelerometer over I2C,
|
|
allowing reading of acceleration data and configuration of sensor parameters.
|
|
|
|
:param i2c_bus: The I2C bus number (usually 1 on Raspberry Pi 3+)
|
|
:param address: The I2C device address, defaults to 0x14
|
|
"""
|
|
|
|
# Register addresses
|
|
_REG_WHOAMI = 0x00
|
|
_ACC_CONFIG0 = 0x19
|
|
_ACC_CONFIG1 = 0x1A
|
|
_ACC_CONFIG2 = 0x1B
|
|
_ACCEL_DATA = 0x04
|
|
_TEMP_DATA = 0x11
|
|
|
|
# Constants for conversion
|
|
_ACC_CONVERSION = 9.80665 # Convert to m/s²
|
|
|
|
# Power mode values
|
|
SLEEP_MODE = 0x00
|
|
LOW_POWER_MODE = 0x01
|
|
NORMAL_MODE = 0x02
|
|
SWITCH_TO_SLEEP = 0x03
|
|
|
|
# Output data rate values
|
|
ACCEL_12_5HZ = 0x05
|
|
ACCEL_25HZ = 0x06
|
|
ACCEL_50HZ = 0x07
|
|
ACCEL_100HZ = 0x08
|
|
ACCEL_200HZ = 0x09
|
|
ACCEL_400HZ = 0xA4
|
|
ACCEL_800HZ = 0xB8
|
|
|
|
# Filter bandwidth values
|
|
ACC_FILT_BW0 = 0x00 # 0.48 x ODR
|
|
ACC_FILT_BW1 = 0x01 # 0.24 x ODR
|
|
|
|
# Oversampling values
|
|
OVERSAMPLING_0 = 0x00
|
|
OVERSAMPLING_1 = 0x01
|
|
OVERSAMPLING_2 = 0x02
|
|
OVERSAMPLING_3 = 0x03
|
|
|
|
# Acceleration range values
|
|
ACC_RANGE_2 = 0x00 # ±2g
|
|
ACC_RANGE_4 = 0x01 # ±4g
|
|
ACC_RANGE_8 = 0x02 # ±8g
|
|
ACC_RANGE_16 = 0x03 # ±16g
|
|
|
|
# Range factors for converting raw values
|
|
_acc_range_factor = {
|
|
ACC_RANGE_2: 1024,
|
|
ACC_RANGE_4: 512,
|
|
ACC_RANGE_8: 256,
|
|
ACC_RANGE_16: 128
|
|
}
|
|
|
|
# Data source register values
|
|
ACC_FILT1 = 0x00
|
|
ACC_FILT2 = 0x01
|
|
ACC_FILT_LP = 0x02
|
|
|
|
def __init__(self, i2c_bus: int = 1, address: int = 0x14):
|
|
"""
|
|
Initialize the BMA400 sensor.
|
|
|
|
:param i2c_bus: The I2C bus number
|
|
:param address: The I2C device address
|
|
:raises RuntimeError: If the sensor is not found
|
|
"""
|
|
self._bus = smbus.SMBus(i2c_bus)
|
|
self._address = address
|
|
|
|
# Check device ID
|
|
device_id = self._read_byte(BMA400._REG_WHOAMI)
|
|
if device_id != 0x90:
|
|
raise RuntimeError(f"Failed to find BMA400, got device ID: 0x{device_id:02X}")
|
|
|
|
# Initialize with default settings
|
|
self._power_mode = self.NORMAL_MODE
|
|
self._acc_range = self.ACC_RANGE_2
|
|
self._output_data_rate = self.ACCEL_100HZ
|
|
self._oversampling_rate = self.OVERSAMPLING_3
|
|
self._filter_bandwidth = self.ACC_FILT_BW0
|
|
self._source_data_registers = self.ACC_FILT1
|
|
|
|
# Apply default settings
|
|
self._write_register_bits(self._ACC_CONFIG0, 0, 2, self._power_mode)
|
|
self._write_register_bits(self._ACC_CONFIG0, 7, 1, self._filter_bandwidth)
|
|
self._write_register_bits(self._ACC_CONFIG1, 0, 4, self._output_data_rate)
|
|
self._write_register_bits(self._ACC_CONFIG1, 4, 2, self._oversampling_rate)
|
|
self._write_register_bits(self._ACC_CONFIG1, 6, 2, self._acc_range)
|
|
self._write_register_bits(self._ACC_CONFIG2, 2, 2, self._source_data_registers)
|
|
|
|
def _read_byte(self, register: int) -> int:
|
|
"""Read a single byte from the specified register"""
|
|
return self._bus.read_byte_data(self._address, register)
|
|
|
|
def _read_bytes(self, register: int, length: int) -> List[int]:
|
|
"""Read multiple bytes from the specified register"""
|
|
return self._bus.read_i2c_block_data(self._address, register, length)
|
|
|
|
def _write_byte(self, register: int, value: int) -> None:
|
|
"""Write a single byte to the specified register"""
|
|
self._bus.write_byte_data(self._address, register, value)
|
|
|
|
def _write_register_bits(self, register: int, pos: int, length: int, value: int) -> None:
|
|
"""
|
|
Write specific bits in a register
|
|
|
|
:param register: Register address
|
|
:param pos: Position of the LSB (0-indexed)
|
|
:param length: Number of bits to modify
|
|
:param value: Value to write
|
|
"""
|
|
current = self._read_byte(register)
|
|
mask = (1 << length) - 1
|
|
current &= ~(mask << pos) # Clear the bits
|
|
current |= (value & mask) << pos # Set the bits
|
|
self._write_byte(register, current)
|
|
|
|
def _read_register_bits(self, register: int, pos: int, length: int) -> int:
|
|
"""
|
|
Read specific bits from a register
|
|
|
|
:param register: Register address
|
|
:param pos: Position of the LSB (0-indexed)
|
|
:param length: Number of bits to read
|
|
:return: Value of the specified bits
|
|
"""
|
|
value = self._read_byte(register)
|
|
mask = (1 << length) - 1
|
|
return (value >> pos) & mask
|
|
|
|
@property
|
|
def power_mode(self) -> str:
|
|
"""
|
|
Get the current power mode.
|
|
|
|
:return: String describing the current power mode
|
|
"""
|
|
mode = self._read_register_bits(self._ACC_CONFIG0, 0, 2)
|
|
modes = ["SLEEP_MODE", "LOW_POWER_MODE", "NORMAL_MODE", "SWITCH_TO_SLEEP"]
|
|
return modes[mode]
|
|
|
|
@power_mode.setter
|
|
def power_mode(self, value: int) -> None:
|
|
"""
|
|
Set the power mode.
|
|
|
|
:param value: Power mode value (use BMA400.XXX_MODE constants)
|
|
:raises ValueError: If the value is invalid
|
|
"""
|
|
if value not in [self.SLEEP_MODE, self.LOW_POWER_MODE, self.NORMAL_MODE, self.SWITCH_TO_SLEEP]:
|
|
raise ValueError("Value must be a valid power mode setting")
|
|
self._write_register_bits(self._ACC_CONFIG0, 0, 2, value)
|
|
self._power_mode = value
|
|
|
|
@property
|
|
def output_data_rate(self) -> str:
|
|
"""
|
|
Get the current output data rate.
|
|
|
|
:return: String describing the current output data rate
|
|
"""
|
|
odr = self._read_register_bits(self._ACC_CONFIG1, 0, 4)
|
|
rates = {
|
|
0x05: "ACCEL_12_5HZ",
|
|
0x06: "ACCEL_25HZ",
|
|
0x07: "ACCEL_50HZ",
|
|
0x08: "ACCEL_100HZ",
|
|
0x09: "ACCEL_200HZ",
|
|
0xA4: "ACCEL_400HZ",
|
|
0xB8: "ACCEL_800HZ"
|
|
}
|
|
return rates.get(odr, f"UNKNOWN (0x{odr:02X})")
|
|
|
|
@output_data_rate.setter
|
|
def output_data_rate(self, value: int) -> None:
|
|
"""
|
|
Set the output data rate.
|
|
|
|
:param value: Output data rate value (use BMA400.ACCEL_XXX constants)
|
|
:raises ValueError: If the value is invalid
|
|
"""
|
|
valid_values = [self.ACCEL_12_5HZ, self.ACCEL_25HZ, self.ACCEL_50HZ,
|
|
self.ACCEL_100HZ, self.ACCEL_200HZ, self.ACCEL_400HZ,
|
|
self.ACCEL_800HZ]
|
|
if value not in valid_values:
|
|
raise ValueError("Value must be a valid output data rate setting")
|
|
self._write_register_bits(self._ACC_CONFIG1, 0, 4, value)
|
|
self._output_data_rate = value
|
|
|
|
@property
|
|
def oversampling_rate(self) -> str:
|
|
"""
|
|
Get the current oversampling rate.
|
|
|
|
:return: String describing the current oversampling rate
|
|
"""
|
|
osr = self._read_register_bits(self._ACC_CONFIG1, 4, 2)
|
|
rates = ["OVERSAMPLING_0", "OVERSAMPLING_1", "OVERSAMPLING_2", "OVERSAMPLING_3"]
|
|
return rates[osr]
|
|
|
|
@oversampling_rate.setter
|
|
def oversampling_rate(self, value: int) -> None:
|
|
"""
|
|
Set the oversampling rate.
|
|
|
|
:param value: Oversampling rate value (use BMA400.OVERSAMPLING_X constants)
|
|
:raises ValueError: If the value is invalid
|
|
"""
|
|
if value not in [self.OVERSAMPLING_0, self.OVERSAMPLING_1,
|
|
self.OVERSAMPLING_2, self.OVERSAMPLING_3]:
|
|
raise ValueError("Value must be a valid oversampling rate setting")
|
|
self._write_register_bits(self._ACC_CONFIG1, 4, 2, value)
|
|
self._oversampling_rate = value
|
|
|
|
@property
|
|
def acc_range(self) -> str:
|
|
"""
|
|
Get the current acceleration range.
|
|
|
|
:return: String describing the current acceleration range
|
|
"""
|
|
range_val = self._read_register_bits(self._ACC_CONFIG1, 6, 2)
|
|
ranges = ["ACC_RANGE_2", "ACC_RANGE_4", "ACC_RANGE_8", "ACC_RANGE_16"]
|
|
return ranges[range_val]
|
|
|
|
@acc_range.setter
|
|
def acc_range(self, value: int) -> None:
|
|
"""
|
|
Set the acceleration range.
|
|
|
|
:param value: Acceleration range value (use BMA400.ACC_RANGE_X constants)
|
|
:raises ValueError: If the value is invalid
|
|
"""
|
|
if value not in [self.ACC_RANGE_2, self.ACC_RANGE_4,
|
|
self.ACC_RANGE_8, self.ACC_RANGE_16]:
|
|
raise ValueError("Value must be a valid acceleration range setting")
|
|
self._write_register_bits(self._ACC_CONFIG1, 6, 2, value)
|
|
self._acc_range = value
|
|
|
|
@property
|
|
def filter_bandwidth(self) -> str:
|
|
"""
|
|
Get the current filter bandwidth.
|
|
|
|
:return: String describing the current filter bandwidth
|
|
"""
|
|
bw = self._read_register_bits(self._ACC_CONFIG0, 7, 1)
|
|
bandwidths = ["ACC_FILT_BW0", "ACC_FILT_BW1"]
|
|
return bandwidths[bw]
|
|
|
|
@filter_bandwidth.setter
|
|
def filter_bandwidth(self, value: int) -> None:
|
|
"""
|
|
Set the filter bandwidth.
|
|
|
|
:param value: Filter bandwidth value (use BMA400.ACC_FILT_BWx constants)
|
|
:raises ValueError: If the value is invalid
|
|
"""
|
|
if value not in [self.ACC_FILT_BW0, self.ACC_FILT_BW1]:
|
|
raise ValueError("Value must be a valid filter bandwidth setting")
|
|
self._write_register_bits(self._ACC_CONFIG0, 7, 1, value)
|
|
self._filter_bandwidth = value
|
|
|
|
@property
|
|
def source_data_registers(self) -> str:
|
|
"""
|
|
Get the current source data register setting.
|
|
|
|
:return: String describing the current source data register
|
|
"""
|
|
src = self._read_register_bits(self._ACC_CONFIG2, 2, 2)
|
|
sources = ["ACC_FILT1", "ACC_FILT2", "ACC_FILT_LP"]
|
|
return sources[src] if src < len(sources) else f"UNKNOWN (0x{src:02X})"
|
|
|
|
@source_data_registers.setter
|
|
def source_data_registers(self, value: int) -> None:
|
|
"""
|
|
Set the source data register.
|
|
|
|
:param value: Source data register value (use BMA400.ACC_FILT_X constants)
|
|
:raises ValueError: If the value is invalid
|
|
"""
|
|
if value not in [self.ACC_FILT1, self.ACC_FILT2, self.ACC_FILT_LP]:
|
|
raise ValueError("Value must be a valid source data register setting")
|
|
self._write_register_bits(self._ACC_CONFIG2, 2, 2, value)
|
|
self._source_data_registers = value
|
|
|
|
@property
|
|
def acceleration(self) -> Tuple[float, float, float]:
|
|
"""
|
|
Get the current acceleration measurements in m/s².
|
|
|
|
:return: Tuple of (x, y, z) acceleration values
|
|
"""
|
|
# Read 6 bytes (2 bytes per axis, x, y, z)
|
|
data = self._read_bytes(self._ACCEL_DATA, 6)
|
|
|
|
# Convert data to 16-bit signed integers
|
|
rawx = (data[1] << 8) | data[0]
|
|
rawy = (data[3] << 8) | data[2]
|
|
rawz = (data[5] << 8) | data[4]
|
|
|
|
# Apply two's complement if needed
|
|
if rawx > 2047:
|
|
rawx -= 4096
|
|
if rawy > 2047:
|
|
rawy -= 4096
|
|
if rawz > 2047:
|
|
rawz -= 4096
|
|
|
|
# Convert to m/s² based on range setting
|
|
factor = self._acc_range_factor[self._acc_range] * self._ACC_CONVERSION
|
|
|
|
return (rawx / factor, rawy / factor, rawz / factor)
|
|
|
|
@property
|
|
def temperature(self) -> float:
|
|
"""
|
|
Get the current temperature in Celsius.
|
|
The temperature sensor is calibrated with a precision of ±5°C.
|
|
|
|
:return: Temperature in Celsius
|
|
"""
|
|
raw_temp = self._read_byte(self._TEMP_DATA)
|
|
|
|
# Apply two's complement for 8-bit value
|
|
if raw_temp > 127:
|
|
raw_temp -= 256
|
|
|
|
# Convert to Celsius according to datasheet
|
|
return (raw_temp * 0.5) + 23.0
|
|
|
|
def soft_reset(self) -> None:
|
|
"""
|
|
Perform a soft reset of the sensor.
|
|
This resets all registers to their default values.
|
|
"""
|
|
# Command register and soft reset command
|
|
CMD_REG = 0x7E
|
|
SOFT_RESET_CMD = 0xB6
|
|
|
|
self._write_byte(CMD_REG, SOFT_RESET_CMD)
|
|
time.sleep(0.2) # Wait for reset to complete |