# -*- coding: utf-8 -*-
"""Driver implementation for the BMA456 3-axis digital acceleromter.
More information on the functionality of the chip can be found at
the Bosch-Sensortec site:
https://www.bosch-sensortec.com/products/motion-sensors/accelerometers/bma456/#documents
"""
__author__ = "Oliver Maye"
__version__ = "0.1"
__all__ = ["BMA456"]
import time
from ._bma456_feature import _BMA456_Feature
from .accelerometer import Accelerometer, Activity, AxesSign, Configuration, EventSource, Orientation, SamplingMode, StatusID, Tap
from .bma456_reg import BMA456_Reg
from .dictionary import dictionary
from .gpio import GPIO
from .imath import *
from .interruptable import Event, EventContextControl, Interruptable
from .sensor import CalibrationType, ConfigItem, Info, SelfTest
from .serialbus import SerialBusDevice
from .simBMA456 import SimDevBMA456
from .systypes import ErrorCode, RunLevel
[docs]
class BMA456( BMA456_Reg, _BMA456_Feature, SerialBusDevice, Accelerometer, Interruptable ):
"""BMA456 driver implementation.
"""
ADRESSES_ALLOWED = [0x18, 0x19]
"""Default address is 0x18 assuming that SDO is set/tied to GND.
Alternatively, the address can be 0x19 by pulling SDO high (VDDIO).
"""
BMA456_CHUNK_SIZE = 8
"""No. of bytes that can be written at once."""
BMA456_FEATUREBUF_HEADER_IDX = 0
BMA456_FEATUREBUF_HEADER_SIZE = 1
BMA456_FEATUREBUF_CONTENT_IDX = BMA456_FEATUREBUF_HEADER_SIZE
BMA456_FEATUREBUF_TOTAL_SIZE = (BMA456_FEATUREBUF_HEADER_SIZE + BMA456_Reg.BMA456_FEATURE_MAX_SIZE)
dictRange = dictionary(
myMap = {
BMA456_Reg.BMA456_CNT_ACC_RANGE_2G : 2000,
BMA456_Reg.BMA456_CNT_ACC_RANGE_4G : 4000,
BMA456_Reg.BMA456_CNT_ACC_RANGE_8G : 8000,
BMA456_Reg.BMA456_CNT_ACC_RANGE_16G : 16000
},
mode = dictionary.DICT_STDMODE_UP )
"""The dictionary to map range setting bits into the corresponding\
range value, meant in milli-g.
"""
dictRate = dictionary(
myMap = {
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_0P78 : 1,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_1P5 : 2,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_3P1 : 3,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_6P25 : 6,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_12P5 : 12,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_25 : 25,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_50 : 50,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_100 : 100,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_200 : 200,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_400 : 400,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_800 : 800,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_1K6 : 1600,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_3K2 : 3200,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_6K4 : 6400,
BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_12K8 : 12800
},
mode = dictionary.DICT_STDMODE_UP )
"""Dictionary to map data rate setting bits into the corresponding\
data rates, meant in Hz.
"""
dictAverage = dictionary(
myMap = {
BMA456_Reg.BMA456_CNT_ACC_CONF_MODE_AVG1 : 1,
BMA456_Reg.BMA456_CNT_ACC_CONF_MODE_AVG2 : 2,
BMA456_Reg.BMA456_CNT_ACC_CONF_MODE_AVG4 : 4,
BMA456_Reg.BMA456_CNT_ACC_CONF_MODE_AVG8 : 8,
BMA456_Reg.BMA456_CNT_ACC_CONF_MODE_AVG16 : 16,
BMA456_Reg.BMA456_CNT_ACC_CONF_MODE_AVG32 : 32,
BMA456_Reg.BMA456_CNT_ACC_CONF_MODE_AVG64 : 64,
BMA456_Reg.BMA456_CNT_ACC_CONF_MODE_AVG128 : 128
},
mode = dictionary.DICT_STDMODE_NORMAL )
"""Dictionary to map config mode settings into averaging window size,\
i.e. the number of samples to average.
"""
dictFeatureSetLength = dictionary(
myMap = {
BMA456_Reg.BMA456_FEATURE_SET_WEARABLE : BMA456_Reg.BMA456_FSWBL_TOTAL_SIZE,
BMA456_Reg.BMA456_FEATURE_SET_HEARABLE : BMA456_Reg.BMA456_FSHBL_TOTAL_SIZE,
BMA456_Reg.BMA456_FEATURE_SET_MM : BMA456_Reg.BMA456_FSMM_TOTAL_SIZE,
BMA456_Reg.BMA456_FEATURE_SET_AN : BMA456_Reg.BMA456_FSAN_TOTAL_SIZE,
},
mode = dictionary.DICT_STDMODE_STRICT )
dictConfigLength = dictionary(
myMap = {
BMA456_Reg.BMA456_FEATURE_SET_WEARABLE : _BMA456_Feature.bma456_wbl_configFileSize,
BMA456_Reg.BMA456_FEATURE_SET_HEARABLE : _BMA456_Feature.bma456_hbl_configFileSize,
BMA456_Reg.BMA456_FEATURE_SET_MM : _BMA456_Feature.bma456_mm_configFileSize,
BMA456_Reg.BMA456_FEATURE_SET_AN : _BMA456_Feature.bma456_an_configFileSize,
},
mode = dictionary.DICT_STDMODE_STRICT )
dictConfigData = dictionary(
myMap = {
BMA456_Reg.BMA456_FEATURE_SET_WEARABLE : _BMA456_Feature.bma456_wbl_configFile,
BMA456_Reg.BMA456_FEATURE_SET_HEARABLE : _BMA456_Feature.bma456_hbl_configFile,
BMA456_Reg.BMA456_FEATURE_SET_MM : _BMA456_Feature.bma456_mm_configFile,
BMA456_Reg.BMA456_FEATURE_SET_AN : _BMA456_Feature.bma456_an_configFile,
},
mode = dictionary.DICT_STDMODE_STRICT )
def __init__(self):
# Create instance attributes
self.featureSet = BMA456.BMA456_DEFAULT_FEATURE_SET
self.featureBuf = []
self.pinInt1 = None
self.pinInt2 = None
self.regInt1IOctrl = 0
self.regInt2IOctrl = 0
self.regInt1Map = 0
self.regInt2Map = 0
self.regIntMapData = 0
self.sim = SimDevBMA456()
SerialBusDevice.__init__(self)
Accelerometer.__init__(self)
#
# Sensor-specific helper functions
#
def _getFeatureByteAt(self, idx):
try:
data = self.featureBuf[idx]
err = ErrorCode.errOk
except IndexError:
data = 0
err = ErrorCode.errInvalidParameter
return data, err
def _getFeatureWordAt(self, idx):
high, err = self._getFeatureByteAt(idx)
if (err == ErrorCode.errOk):
low, err = self._getFeatureByteAt(idx+1)
else:
low = 0
return (high << 8) | low, err
def _putFeatureByteAt(self, idx, data):
err = ErrorCode.errOk
try:
self.featureBuf[idx] = (data & 0xFF)
except IndexError:
err = ErrorCode.errInvalidParameter
return err
def _putFeatureWordAt(self, idx, data):
err = self._putFeatureByteAt(idx, data >> 8)
if (err == ErrorCode.errOk):
err = self._putFeatureByteAt(idx+1, data & 0xFF)
return err
def _transfer( self, reading ):
"""Transfer function.
Translate digital measurement reading into physical dimension
value.
:return: Acceleration in milli-g
:rtype: 16 bit signed int
"""
ret = reading
if reading > 0x7FFF:
ret = reading - 0x10000
ret = ret * self.dataRange - 0x4000
else:
ret = ret * self.dataRange + 0x4000
ret = ret >> 15
return ret
def _readFeatures( self ):
length, ret = BMA456.dictFeatureSetLength.getValue( self.featureSet )
if (ret != ErrorCode.errOk):
ret = ErrorCode.errCorruptData
else:
self.featureBuf, ret = self.readBufferRegister( BMA456.BMA456_REG_FEATURES, length )
return ret
def _writeFeatures( self ):
ret = self.writeBufferRegister( BMA456.BMA456_REG_FEATURES, self.featureBuf )
return ret
def _initialize( self ):
"""Start-up the chip and fill/restore configuration registers.
"""
result = ErrorCode.errOk
# Retrieve config file and file size
if (result == ErrorCode.errOk):
fileSize, result = BMA456.dictConfigLength.getValue( self.featureSet )
if (result == ErrorCode.errOk):
cfgFile, result = BMA456.dictConfigData.getValue( self.featureSet )
# Test address
if (result == ErrorCode.errOk):
val, result = self.readByteRegister( BMA456.BMA456_REG_CHIP_ID )
if (result == ErrorCode.errOk):
if (val != BMA456.BMA456_CNT_CHIP_ID ):
result = ErrorCode.errMalfunction
if (result == ErrorCode.errOk):
# Initialization sequence for interrupt feature engine
# Disable advanced power save mode: PWR_CONF.adv_power_save = 0.
result = self.writeByteRegister( BMA456.BMA456_REG_PWR_CONF, BMA456.BMA456_CNT_PWR_CONF_ADV_PWR_SAVE_DISABLE )
if (result == ErrorCode.errOk):
# Wait for 450 us.
time.sleep( 500 / 1000000 )
# Write INIT_CTRL.init_ctrl=0x00
self.writeByteRegister( BMA456.BMA456_REG_INIT_CTRL, BMA456.BMA456_CNT_INIT_CTRL_LOAD_CONFIG_FILE )
# Load configuration file
for idx in range(0, fileSize, BMA456.BMA456_CHUNK_SIZE):
chunk = cfgFile[idx : idx+BMA456.BMA456_CHUNK_SIZE]
widx = int(idx/2)
self.writeByteRegister( BMA456.BMA456_REG_DMA_LOW, widx & 0x0F )
self.writeByteRegister( BMA456.BMA456_REG_DMA_HI, widx >> 4 )
self.writeBufferRegister( BMA456.BMA456_REG_FEATURES, chunk )
# Enable sensor features: write 0x01 into register INIT_CTRL.init_ctrl.
# This operation must not be performed more than once after POR or softreset.
result = self.writeByteRegister( BMA456.BMA456_REG_INIT_CTRL, BMA456.BMA456_CNT_INIT_CTRL_START_INIT )
if (result == ErrorCode.errOk):
# Check status of the interrupt feature engine
# Wait until Register INTERNAL_STATUS.message contains the value 1. This will happen after at most 140-150 msec.
time.sleep( 150 / 1000 )
val, result = self.readByteRegister( BMA456.BMA456_REG_INTERNAL_STATUS )
if ( val & BMA456.BMA456_CNT_INTERNAL_STATUS_MSG) != BMA456.BMA456_CNT_INTERNAL_STATUS_MSG_INIT_OK:
result = ErrorCode.errFailure
if (result == ErrorCode.errOk):
# After initialization sequence has been completed, the device is in
# configuration mode (power mode). Now it is possible to switch to the
# required power mode and all features are ready to use.
val, result = self.dictRange.getValue( BMA456.BMA456_CNT_ACC_RANGE_DEFAULT )
if (result == ErrorCode.errOk):
self.dataRange = val
# Clear por_detect bit: read EVENT register and ignore the result.
val, result = self.readByteRegister( BMA456.BMA456_REG_EVENT )
if (result == ErrorCode.errOk):
# Read feature parameters
result = self._readFeatures()
if (result == ErrorCode.errOk):
# Configure power mode:
result = self.setRunLevel( RunLevel.active )
if (result == ErrorCode.errOk):
# Configure interrupt maps:
self.writeByteRegister( BMA456.BMA456_REG_INT1_MAP, self.regInt1Map );
self.writeByteRegister( BMA456.BMA456_REG_INT2_MAP, self.regInt2Map );
self.writeByteRegister( BMA456.BMA456_REG_INT_MAP_DATA, self.regIntMapData );
# And latch interrupts
self.writeByteRegister( BMA456.BMA456_REG_INT_LATCH, BMA456.BMA456_CNT_INT_LATCH_PERM );
return result
def _bmaInt2accelEvtSrc( self, intID ):
"""Convert a single interrupt as indicated in the INT_STATUS0+1\
word to an :class:`EventSource`.
The exact mapping is documented at :meth:`getStatus`.
:param int intID: Interrupt indicator as read from INT_STATUS0+1 register.
:returns: Event source flag
:rtype: EventSource
"""
ret = EventSource.none
# INT_STATUS_1, high-byte
if( intID & BMA456.BMA456_CNT_INT_STATUS_ACC_DRDY ):
ret |= EventSource.dataReady;
if( intID & BMA456.BMA456_CNT_INT_STATUS_AUX_DRDY ):
ret |= EventSource.none
if( intID & BMA456.BMA456_CNT_INT_STATUS_FIFO_WM ):
ret |= EventSource.fifoWatermark
if( intID & BMA456.BMA456_CNT_INT_STATUS_FIFO_FULL ):
ret |= EventSource.fifoFull
# INT_STATUS_0, low-byte
if( intID & BMA456.BMA456_CNT_INT_STATUS_ERROR ):
ret |= EventSource.error
# Interpretation of the rest of the low-byte INT_STATUS_0 depends
# on the feature set.
if (self.featureSet == BMA456.BMA456_FEATURE_SET_WEARABLE):
if( intID & BMA456.BMA456_FSWBL_CNT_INT_STATUS_NO_MOTION ):
ret |= EventSource.lowSlopeTime
if( intID & BMA456.BMA456_FSWBL_CNT_INT_STATUS_ANY_MOTION ):
ret |= EventSource.highSlopeTime
if( intID & BMA456.BMA456_FSWBL_CNT_INT_STATUS_DBL_TAP ):
ret |= EventSource.tap
if( intID & BMA456.BMA456_FSWBL_CNT_INT_STATUS_WRIST_WKUP ):
ret |= EventSource.gesture
if( intID & BMA456.BMA456_FSWBL_CNT_INT_STATUS_ACTIVITY ):
ret |= EventSource.activity
if( intID & BMA456.BMA456_FSWBL_CNT_INT_STATUS_STEP_COUNT ):
ret |= EventSource.step
if( intID & BMA456.BMA456_FSWBL_CNT_INT_STATUS_TAP_DETECT ):
ret |= EventSource.tap
elif (self.featureSet == BMA456.BMA456_FEATURE_SET_HEARABLE):
if( intID & BMA456.BMA456_FSHBL_CNT_INT_STATUS_NO_MOTION ):
ret |= EventSource.lowSlopeTime
if( intID & BMA456.BMA456_FSHBL_CNT_INT_STATUS_ANY_MOTION ):
ret |= EventSource.highSlopeTime
if( intID & BMA456.BMA456_FSHBL_CNT_INT_STATUS_ACTIVITY ):
ret |= EventSource.activity
if( intID & BMA456.BMA456_FSHBL_CNT_INT_STATUS_STEP_COUNT ):
ret |= EventSource.step
if( intID & BMA456.BMA456_FSHBL_CNT_INT_STATUS_TAP_DETECT ):
ret |= EventSource.tap
elif (self.featureSet == BMA456.BMA456_FEATURE_SET_MM):
if( intID & BMA456.BMA456_FSMM_CNT_INT_STATUS_NO_MOTION ):
ret |= EventSource.lowSlopeTime
if( intID & BMA456.BMA456_FSMM_CNT_INT_STATUS_ANY_MOTION ):
ret |= EventSource.highSlopeTime
if( intID & BMA456.BMA456_FSMM_CNT_INT_STATUS_SIG_MOTION ):
ret |= EventSource.significantMotion
if( intID & BMA456.BMA456_FSMM_CNT_INT_STATUS_HIGH_G ):
ret |= EventSource.highGTime
if( intID & BMA456.BMA456_FSMM_CNT_INT_STATUS_LOW_G ):
ret |= EventSource.lowGTime
if( intID & BMA456.BMA456_FSMM_CNT_INT_STATUS_ORIENT ):
ret |= EventSource.orientation
if( intID & BMA456.BMA456_FSMM_CNT_INT_STATUS_TAP_DETECT ):
ret |= EventSource.tap
elif (self.featureSet == BMA456.BMA456_FEATURE_SET_AN):
if( intID & BMA456.BMA456_FSAN_CNT_INT_STATUS_NO_MOTION ):
ret |= EventSource.lowSlopeTime
if( intID & BMA456.BMA456_FSAN_CNT_INT_STATUS_ANY_MOTION ):
ret |= EventSource.highSlopeTime
return ret;
def _fillEventContext( self, singleIntID, context ):
"""Given a single interrupt identifier, fill the event context\
structure, i.e. the source and detail attributes, appropriately.
The mapping is detailed at :meth:`getEventContext`.
"""
ret = ErrorCode.errOk
# Map BMA interrupt source to API event source
context.source = self._bmaInt2accelEvtSrc( singleIntID )
# Now, depending on the event source, get additional information.
if (context.source == EventSource.dataReady):
context.data, ret = self.getLatestData()
elif (context.source == EventSource.fifoWatermark) or (context.source == EventSource.fifoFull):
context.status, ret = self.getStatus( StatusID.fifo )
elif (context.source == EventSource.activity):
context.status, ret = self.getStatus( StatusID.activity )
elif (context.source == EventSource.step):
context.status, ret = self.getStatus( StatusID.stepCount )
elif (context.source == EventSource.highGTime):
context.status, ret = self.getStatus( StatusID.highG )
elif (context.source == EventSource.orientation):
context.status, ret = self.getStatus( StatusID.orientation )
elif (context.source == EventSource.tap):
if( self.featureSet == BMA456.BMA456_FEATURE_SET_WEARABLE ):
if( singleIntID == BMA456.BMA456_FSWBL_CNT_INT_STATUS_TAP_DETECT ):
context.status = Tap.single
elif( singleIntID == BMA456.BMA456_FSWBL_CNT_INT_STATUS_DBL_TAP ):
context.status = Tap.double
else:
context.status = Tap.none
else:
# Multi-tap concept.
context.status, ret = self.getStatus( StatusID.tap )
return ret
def _accelEvtSrc2bmaMap( self, evtSrc ):
"""For a given event source, get the corresponding interrupt map.
Convert an :class:`EventSource` to a BMA interrupt-map bit mask
compatible to the INTx_MAP and INT_MAP_DATA register content.
The translation is described in detail at :meth:`configure`
when arming events.
"""
remainder = evtSrc
# Set INT_MAP_DATA, first
dataMap = BMA456.BMA456_CNT_INTX_MAP_NONE
if( evtSrc & EventSource.dataReady ):
dataMap |= (BMA456.BMA456_CNT_INT_MAP_DATA_INT1_DRDY | BMA456.BMA456_CNT_INT_MAP_DATA_INT2_DRDY)
remainder &= ~EventSource.dataReady
if( evtSrc & EventSource.fifoWatermark ):
dataMap |= (BMA456.BMA456_CNT_INT_MAP_DATA_INT1_FIFO_WM | BMA456.BMA456_CNT_INT_MAP_DATA_INT2_FIFO_WM)
remainder &= ~EventSource.fifoWatermark
if( evtSrc & EventSource.fifoFull ):
dataMap |= (BMA456.BMA456_CNT_INT_MAP_DATA_INT1_FIFO_FULL | BMA456.BMA456_CNT_INT_MAP_DATA_INT2_FIFO_FULL)
remainder &= ~EventSource.fifoFull
# Now, set INT1_MAP
featMap = BMA456.BMA456_CNT_INTX_MAP_NONE
if( evtSrc & EventSource.error ):
featMap |= BMA456.BMA456_CNT_INTX_MAP_ERROR
remainder &= ~EventSource.error
# Interpretation of INTx_MAP depends on the feature set.
if (self.featureSet == BMA456.BMA456_FEATURE_SET_WEARABLE):
if( evtSrc & EventSource.lowSlopeTime ):
featMap |= BMA456.BMA456_FSWBL_CNT_INTX_MAP_NO_MOTION
remainder &= ~EventSource.lowSlopeTime
if( evtSrc & EventSource.highSlopeTime ):
featMap |= BMA456.BMA456_FSWBL_CNT_INTX_MAP_ANY_MOTION
remainder &= ~EventSource.highSlopeTime
# Double tap must be treated by the caller
if( evtSrc & EventSource.gesture):
featMap |= BMA456.BMA456_FSWBL_CNT_INTX_MAP_WRIST_WKUP
remainder &= ~EventSource.gesture
if( evtSrc & EventSource.activity):
featMap |= BMA456.BMA456_FSWBL_CNT_INTX_MAP_ACTIVITY
remainder &= ~EventSource.activity
if( evtSrc & EventSource.step ):
featMap |= BMA456.BMA456_FSWBL_CNT_INTX_MAP_STEP_CNT
remainder &= ~EventSource.step
if( evtSrc & EventSource.tap):
featMap |= BMA456.BMA456_FSWBL_CNT_INTX_MAP_STAP
remainder &= ~EventSource.tap
elif (self.featureSet == BMA456.BMA456_FEATURE_SET_HEARABLE):
if( evtSrc & EventSource.lowSlopeTime):
featMap |= BMA456.BMA456_FSHBL_CNT_INTX_MAP_NO_MOTION
remainder &= ~EventSource.lowSlopeTime
if( evtSrc & EventSource.highSlopeTime):
featMap |= BMA456.BMA456_FSHBL_CNT_INTX_MAP_ANY_MOTION
remainder &= ~EventSource.highSlopeTime
if( evtSrc & EventSource.activity):
featMap |= BMA456.BMA456_FSHBL_CNT_INTX_MAP_ACTIVITY
remainder &= ~EventSource.activity
if( evtSrc & EventSource.step):
featMap |= BMA456.BMA456_FSHBL_CNT_INTX_MAP_STEP_CNT
remainder &= ~EventSource.step
if( evtSrc & EventSource.tap):
featMap |= BMA456.BMA456_FSHBL_CNT_INTX_MAP_TAP
remainder &= ~EventSource.tap
elif (self.featureSet == BMA456.BMA456_FEATURE_SET_MM):
if( evtSrc & EventSource.lowSlopeTime):
featMap |= BMA456.BMA456_FSMM_CNT_INTX_MAP_NO_MOTION
remainder &= ~EventSource.lowSlopeTime
if( evtSrc & EventSource.highSlopeTime):
featMap |= BMA456.BMA456_FSMM_CNT_INTX_MAP_ANY_MOTION
remainder &= ~EventSource.highSlopeTime
if( evtSrc & EventSource.significantMotion):
featMap |= BMA456.BMA456_FSMM_CNT_INTX_MAP_SIG_MOTION
remainder &= ~EventSource.significantMotion
if( evtSrc & EventSource.highGTime):
featMap |= BMA456.BMA456_FSMM_CNT_INTX_MAP_HIGH_G
remainder &= ~EventSource.highGTime
if( evtSrc & EventSource.lowGTime):
featMap |= BMA456.BMA456_FSMM_CNT_INTX_MAP_LOW_G
remainder &= ~EventSource.lowGTime
if( evtSrc & EventSource.orientation):
featMap |= BMA456.BMA456_FSMM_CNT_INTX_MAP_ORIENT
remainder &= ~EventSource.orientation
if( evtSrc & EventSource.tap):
featMap |= BMA456.BMA456_FSMM_CNT_INTX_MAP_TAP
remainder &= ~EventSource.tap
elif (self.featureSet == BMA456.BMA456_FEATURE_SET_AN):
if( evtSrc & EventSource.lowSlopeTime):
featMap |= BMA456.BMA456_FSAN_CNT_INTX_MAP_NO_MOTION
remainder &= ~EventSource.lowSlopeTime
if( evtSrc & EventSource.highSlopeTime ):
featMap |= BMA456.BMA456_FSAN_CNT_INTX_MAP_ANY_MOTION
remainder &= ~EventSource.highSlopeTime
return remainder, dataMap, featMap
#
# Module API
#
[docs]
@classmethod
def Params_init(cls, paramDict):
"""Initializes configuration parameters with defaults.
The following settings are supported:
============================= ==========================================================================================================
Key name Value type, meaning and default
============================= ==========================================================================================================
SerialBusDevice.address ``int`` I2C serial device address, one of :attr:`ADRESSES_ALLOWED`; default is :attr:`ADRESSES_ALLOWED` ``[0]``.
Sensor.dataRange ``int`` Measurement range in milli-g; default corresponds to :attr:`.BMA456_CNT_ACC_RANGE_DEFAULT`.
Sensor.dataRate ``int`` Data rate in Hz; default corresponds to :attr:`.BMA456_CNT_ACC_CONF_ODR_DEFAULT`.
BMA456.INT1_IO_CTRL ``int`` Content of the INT1_IO_CTRL register; default is :attr:`.BMA456_CNT_INT1_IO_CTRL_DEFAULT`.
BMA456.INT2_IO_CTRL ``int`` Content of the INT2_IO_CTRL register; default is :attr:`.BMA456_CNT_INT2_IO_CTRL_DEFAULT`.
BMA456.INT1_MAP ``int`` Content of the INT1_MAP register; default is :attr:`.BMA456_CNT_INTX_MAP_DEFAULT`.
BMA456.INT2_MAP ``int`` Content of the INT2_MAP register; default is :attr:`.BMA456_CNT_INTX_MAP_DEFAULT`.
BMA456.INT_MAP_DATA ``int`` Content of the INT_MAP_DATA register; default is :attr:`.BMA456_CNT_INT_MAP_DATA_DEFAULT`.
BMA456.int1.gpio.direction see :meth:`.GPIO.Params_init`; default is :attr:`.GPIO.DIRECTION_IN`.
BMA456.int2.gpio.direction see :meth:`.GPIO.Params_init`; default is :attr:`.GPIO.DIRECTION_IN`.
BMA456.int1.gpio.trigger see :meth:`.GPIO.Params_init`; default is :attr:`.GPIO.TRIGGER_EDGE_FALLING`.
BMA456.int2.gpio.trigger see :meth:`.GPIO.Params_init`; default is :attr:`.GPIO.TRIGGER_EDGE_FALLING`.
BMA456.int1.gpio.bounce see :meth:`.GPIO.Params_init`; default is :attr:`.GPIO.BOUNCE_NONE`.
BMA456.int2.gpio.bounce see :meth:`.GPIO.Params_init`; default is :attr:`.GPIO.BOUNCE_NONE`.
All other BMA456.int1.gpio.* and BMA456.int1.gpio.* settings as documented at :meth:`.GPIO.Params_init`.
===========================================================================================================================================
For the ``SerialBusDevice.address`` value, also 0 or 1
can be specified alternatively to the absolute addresses to reflect
the level of the ``SDO`` pin. In this case, 0 will be mapped to
0x18, while 1 maps to 0x19.
Also see: :meth:`.Sensor.Params_init`, :meth:`.SerialBusDevice.Params_init`, :meth:`.GPIO.Params_init`.
"""
# Set defaults, where necessary
if not ("SerialBusDevice.address" in paramDict):
paramDict["SerialBusDevice.address"] = BMA456.ADRESSES_ALLOWED[0]
else:
da = paramDict["SerialBusDevice.address"]
if not (da in BMA456.ADRESSES_ALLOWED):
da = BMA456.ADRESSES_ALLOWED[da!=0]
paramDict["SerialBusDevice.address"] = da
if not ("Sensor.dataRange" in paramDict):
paramDict["Sensor.dataRange"], _ = BMA456.dictRange.getValue( BMA456_Reg.BMA456_CNT_ACC_RANGE_DEFAULT )
if not ("Sensor.dataRate" in paramDict):
paramDict["Sensor.dataRate"], _ = BMA456.dictRate.getValue( BMA456_Reg.BMA456_CNT_ACC_CONF_ODR_DEFAULT )
Accelerometer.Params_init(paramDict)
SerialBusDevice.Params_init(paramDict)
# Specific configuration options
if not ("BMA456.INT1_IO_CTRL" in paramDict):
paramDict["BMA456.INT1_IO_CTRL"] = BMA456.BMA456_CNT_INT1_IO_CTRL_DEFAULT
if not ("BMA456.INT2_IO_CTRL" in paramDict):
paramDict["BMA456.INT2_IO_CTRL"] = BMA456.BMA456_CNT_INT2_IO_CTRL_DEFAULT
if not ("BMA456.INT1_MAP" in paramDict):
paramDict["BMA456.INT1_MAP"] = BMA456.BMA456_CNT_INTX_MAP_DEFAULT
if not ("BMA456.INT2_MAP" in paramDict):
paramDict["BMA456.INT2_MAP"] = BMA456.BMA456_CNT_INTX_MAP_DEFAULT
if not ("BMA456.INT_MAP_DATA" in paramDict):
paramDict["BMA456.INT_MAP_DATA"] = BMA456.BMA456_CNT_INT_MAP_DATA_DEFAULT
# Add interrupt pin /gpio specifics
paramDict["BMA456.int1.gpio.direction"] = GPIO.DIRECTION_IN
paramDict["BMA456.int2.gpio.direction"] = GPIO.DIRECTION_IN
if not ("BMA456.int1.gpio.trigger" in paramDict):
paramDict["BMA456.int1.gpio.trigger"] = GPIO.TRIGGER_EDGE_FALLING
if not ("BMA456.int2.gpio.trigger" in paramDict):
paramDict["BMA456.int2.gpio.trigger"] = GPIO.TRIGGER_EDGE_FALLING
if not ("BMA456.int1.gpio.bounce" in paramDict):
paramDict["BMA456.int1.gpio.bounce"] = GPIO.BOUNCE_NONE
if not ("BMA456.int2.gpio.bounce" in paramDict):
paramDict["BMA456.int2.gpio.bounce"] = GPIO.BOUNCE_NONE
gpioParams = {}
GPIO.Params_init( gpioParams )
gp1 = dict( [("BMA456.int1."+k,v) for k,v in gpioParams.items()] )
gp2 = dict( [("BMA456.int2."+k,v) for k,v in gpioParams.items()] )
gp1.update(gp2)
for key, value in gp1.items():
if not( key in paramDict):
paramDict[key] = value
return None
[docs]
def open(self, paramDict):
"""Set up serial communication and initialize the chip.
Must be called once, before the device can be used.
Carry out the following steps:
* establish serial communication, attach device to bus, if necessary.
* execute the device initialization procedure, see chapter 4.2 of the data sheet for more information.
* adjust data rate and measurement range
* set up interrupt GPIO pins - direction, trigger etc.
* set up interrupt behavior - registers IOCTRL, INT1_MAP etc.
* enable interrupts
Additionally to the defaults generated by :meth:`.Params_init`,
the following configuration parameters are supported - as documented
at :meth:`.GPIO.open`:
* BMA456.int1.gpio.pinDesignator
* BMA456.int2.gpio.pinDesignator
Also see :meth:`.Sensor.open`, :meth:`.close`.
"""
result = ErrorCode.errOk
if (self.isAttached() == ErrorCode.errOk):
result = ErrorCode.errResourceConflict
else:
if (result == ErrorCode.errOk):
paramDict["SerialBusDevice.address"] = paramDict.get("SerialBusDevice.address", BMA456.ADRESSES_ALLOWED[0])
result = SerialBusDevice.open(self, paramDict)
if (result == ErrorCode.errOk):
# Ramp-up the chip
result = self._initialize()
if (result == ErrorCode.errOk):
# Set data rate and range
result = Accelerometer.open(self, paramDict)
if (result == ErrorCode.errOk):
# Setup interrupt related stuff.
if ("BMA456.int1.gpio.pinDesignator" in paramDict):
paramDict["BMA456.int1.gpio.direction"] = GPIO.DIRECTION_IN
gpioParams = dict( [(k.replace("BMA456.int1.", ""),v) for k,v in paramDict.items() if k.startswith("BMA456.int1.")] )
self.pinInt1 = GPIO()
result = self.pinInt1.open(gpioParams)
self.regInt1IOctrl = paramDict.get ("BMA456.INT1_IO_CTRL", BMA456.BMA456_CNT_INT1_IO_CTRL_DEFAULT)
self.regInt1IOctrl &= ~BMA456.BMA456_CNT_INT1_IO_CTRL_OUTPUT
self.regInt1IOctrl |= BMA456.BMA456_CNT_INT1_IO_CTRL_OUTPUT_DISABLE
self.writeByteRegister( BMA456.BMA456_REG_INT1_IO_CTRL, self.regInt1IOctrl )
self.regInt1Map = paramDict.get ("BMA456.INT1_MAP", BMA456.BMA456_CNT_INTX_MAP_DEFAULT)
self.writeByteRegister( BMA456.BMA456_REG_INT1_MAP, self.regInt1Map )
if ("BMA456.int2.gpio.pinDesignator" in paramDict):
paramDict["BMA456.int2.gpio.direction"] = GPIO.DIRECTION_IN
gpioParams = dict( [(k.replace("BMA456.int2.", ""),v) for k,v in paramDict.items() if k.startswith("BMA456.int2.")] )
self.pinInt2 = GPIO()
result = self.pinInt2.open(gpioParams)
self.regInt2IOctrl = paramDict.get ("BMA456.INT2_IO_CTRL", BMA456.BMA456_CNT_INT2_IO_CTRL_DEFAULT)
self.regInt2IOctrl &= ~BMA456.BMA456_CNT_INT2_IO_CTRL_OUTPUT
self.regInt2IOctrl |= BMA456.BMA456_CNT_INT1_IO_CTRL_OUTPUT_DISABLE
self.writeByteRegister( BMA456.BMA456_REG_INT2_IO_CTRL, self.regInt2IOctrl )
self.regInt2Map = paramDict.get ("BMA456.INT2_MAP", BMA456.BMA456_CNT_INTX_MAP_DEFAULT)
self.writeByteRegister( BMA456.BMA456_REG_INT2_MAP, self.regInt2Map )
self.regIntMapData = paramDict.get ("BMA456.INT_MAP_DATA", BMA456.BMA456_CNT_INT_MAP_DATA_DEFAULT)
self.writeByteRegister( BMA456.BMA456_REG_INT_MAP_DATA, self.regIntMapData )
self.enableInterrupt()
return result
[docs]
def close(self):
"""Shut down the device after usage.
This method should be called when the device is not used, anymore,
e.g. as part of the application exit procedure.
The following steps are executed:
* shut down the device by switching to :attr:`.RunLevel.shutdown`
* close serial communication, detach from bus.
* close GPIO pins for int1 and int2
After return, the device can still be re-used, by calling
:meth:`.open` again.
Also see: :meth:`.SerialBusDevice.close`, :meth:`.GPIO.close`,
:meth:`.Module.close`.
"""
result = ErrorCode.errOk
err = self.isAttached()
if (err == ErrorCode.errOk):
err = self.setRunLevel( RunLevel.shutdown )
if (result == ErrorCode.errOk):
result = err
err = SerialBusDevice.close(self)
if (result == ErrorCode.errOk):
result = err
else:
if (result == ErrorCode.errOk):
result = err
if not (self.pinInt1 is None):
err = self.pinInt1.close()
self.pinInt1 = None
if (result == ErrorCode.errOk):
result = err
if not (self.pinInt2 is None):
err = self.pinInt2.close()
self.pinInt2 = None
if (result == ErrorCode.errOk):
result = err
return result
[docs]
def setRunLevel(self, level):
"""Switches the device to the desired power-save mode.
The given run level affects the hardware registers
:attr:`.BMA456_REG_PWR_CTRL`,
:attr:`.BMA456_REG_PWR_CONF` and
:attr:`.BMA456_REG_ACC_CONF`
and, thus, chip behavior as follows:
============== ======= ======= ================ ============== =============
RunLevel ACC_EN AUX_EN FIFO_SELF_WAKEUP ADV_POWER_SAVE ACC_PERF_MODE
============== ======= ======= ================ ============== =============
active ENABLE DISABLE DISABLE DISABLE CONT
idle ENABLE DISABLE DISABLE DISABLE AVG
relax ENABLE DISABLE ENABLE ENABLE AVG
snooze ENABLE DISABLE DISABLE ENABLE AVG
nap and below DISABLE DISABLE DISABLE ENABLE AVG
============== ======= ======= ================ ============== =============
For detailed information on the power modes of the underlying
hardware, see the data shhet, chapter 4.3.
Also see :meth:`.Module.setRunLevel`.
:param RunLevel level: The level to switch to.
:return: An error code indicating either success or the reason of failure.
:rtype: ErrorCode
"""
ret = ErrorCode.errOk
pwrCtrl = 0
pwrConf = 0
accConf = 0
if( self.isAttached() == ErrorCode.errOk):
# Map run levels to register configurations
if (level == RunLevel.active): # High performance operating mode
pwrCtrl = BMA456.BMA456_CNT_PWR_CTRL_ACC_ENABLE | BMA456.BMA456_CNT_PWR_CTRL_AUX_DISABLE
pwrConf = BMA456.BMA456_CNT_PWR_CONF_ADV_PWR_SAVE_DISABLE
accConf = BMA456.BMA456_CNT_ACC_CONF_PERF_MODE_CONT
ret = ErrorCode.errOk
elif (level == RunLevel.idle): # Averaging operating mode
pwrCtrl = BMA456.BMA456_CNT_PWR_CTRL_ACC_ENABLE | BMA456.BMA456_CNT_PWR_CTRL_AUX_DISABLE
pwrConf = BMA456.BMA456_CNT_PWR_CONF_ADV_PWR_SAVE_DISABLE
accConf = BMA456.BMA456_CNT_ACC_CONF_PERF_MODE_AVG
ret = ErrorCode.errOk
elif (level == RunLevel.relax): # Low power mode, still operating, automatic FIFO wakeup
pwrCtrl = BMA456.BMA456_CNT_PWR_CTRL_ACC_ENABLE | BMA456.BMA456_CNT_PWR_CTRL_AUX_DISABLE
pwrConf = BMA456.BMA456_CNT_PWR_CONF_ADV_PWR_SAVE_ENABLE | BMA456.BMA456_CNT_PWR_CONF_FIFO_WKUP_ENABLE
accConf = BMA456.BMA456_CNT_ACC_CONF_PERF_MODE_AVG
ret = ErrorCode.errOk
elif (level == RunLevel.snooze): # Lowest power mode, still operating, no FIFO wakeup
pwrCtrl = BMA456.BMA456_CNT_PWR_CTRL_ACC_ENABLE | BMA456.BMA456_CNT_PWR_CTRL_AUX_DISABLE
pwrConf = BMA456.BMA456_CNT_PWR_CONF_ADV_PWR_SAVE_ENABLE | BMA456.BMA456_CNT_PWR_CONF_FIFO_WKUP_DISABLE
accConf = BMA456.BMA456_CNT_ACC_CONF_PERF_MODE_AVG
ret = ErrorCode.errOk
# Suspend mode, no operation.
elif (level == RunLevel.nap) or (level == RunLevel.sleep) or (level == RunLevel.deepSleep) or (level == RunLevel.shutdown):
pwrCtrl = BMA456.BMA456_CNT_PWR_CTRL_ACC_DISABLE | BMA456.BMA456_CNT_PWR_CTRL_AUX_DISABLE
pwrConf = BMA456.BMA456_CNT_PWR_CONF_ADV_PWR_SAVE_ENABLE | BMA456.BMA456_CNT_PWR_CONF_FIFO_WKUP_DISABLE
accConf = BMA456.BMA456_CNT_ACC_CONF_PERF_MODE_AVG
ret = ErrorCode.errOk
else:
ret = ErrorCode.errNotSupported
# Apply new register settings
if( ret == ErrorCode.errOk ):
ret = self.writeByteRegister( BMA456.BMA456_REG_PWR_CTRL, pwrCtrl )
time.sleep( 450 / 1000000 )
ret = self.writeByteRegister( BMA456.BMA456_REG_PWR_CONF, pwrConf )
time.sleep( 450 / 1000000 )
# For ACC_CONF, only copy the PERF_MODE bit:
temp, ret = self.readByteRegister( BMA456.BMA456_REG_ACC_CONF )
if( (ret == ErrorCode.errOk) and ((temp & BMA456.BMA456_CNT_ACC_CONF_PERF_MODE) != accConf) ):
temp &= ~BMA456.BMA456_CNT_ACC_CONF_PERF_MODE
accConf |= temp;
ret = self.writeByteRegister( BMA456.BMA456_REG_ACC_CONF, accConf )
else:
ret = ErrorCode.errResourceConflict
return ret
#
# Interruptable API
#
[docs]
def registerInterruptHandler(self, onEvent=None, callerFeedBack=None, handler=None ):
ret = ErrorCode.errOk
fAny = False
if ((onEvent == Event.evtInt1) or (onEvent == Event.evtAny)) and not (self.pinInt1 is None):
fAny = True
self.pinInt1.registerInterruptHandler( GPIO.EVENT_DEFAULT, callerFeedBack, handler )
if ((onEvent == Event.evtInt2) or (onEvent == Event.evtAny)) and not (self.pinInt2 is None):
fAny = True
self.pinInt2.registerInterruptHandler( GPIO.EVENT_DEFAULT, callerFeedBack, handler )
if (fAny):
ret = self.enableInterrupt()
else:
ret = ErrorCode.errExhausted
return ret
[docs]
def enableInterrupt(self):
"""Enable the interrupt mechanism/engine.
Clear the interrupts that possibly occurred so far. Then, enable
interrupt signaling at the corresponding GPIO pin. Finally, set
the OUTPUT_ENABLE bit at the involved interrupt IO_CTRL
registers.
Note that this method is just for switching the interrupt capability
on.
For switching it off, see :meth:`.disableInterrupt`.
For configuring interrupts, see :meth:`.configure` and
:meth:`.registerInterruptHandler`.
Also see :meth:`.Interruptable.enableInterrupt`.
:return: An error code indicating either success or the reason\
of failure.
:rtype: ErrorCode
"""
ret = ErrorCode.errOk
if (self.isAttached() != ErrorCode.errOk):
ret = ErrorCode.errResourceConflict
else:
ret = ErrorCode.errOk
# Clear interrupt
_, ret = self.readWordRegister( BMA456.BMA456_REG_INT_STATUS )
# Enable from upper layer down to hardware
if not (self.pinInt1 is None):
ret = self.pinInt1.enableInterrupt()
data = self.regInt1IOctrl & ~BMA456.BMA456_CNT_INT1_IO_CTRL_OUTPUT
data |= BMA456.BMA456_CNT_INT1_IO_CTRL_OUTPUT_ENABLE
ret = self.writeByteRegister( BMA456.BMA456_REG_INT1_IO_CTRL, data )
if not(self.pinInt2 is None):
ret = self.pinInt2.enableInterrupt()
data = self.regInt2IOctrl & ~BMA456.BMA456_CNT_INT2_IO_CTRL_OUTPUT
data |= BMA456.BMA456_CNT_INT2_IO_CTRL_OUTPUT_ENABLE
err = self.writeByteRegister( BMA456.BMA456_REG_INT2_IO_CTRL, data )
if (ret == ErrorCode.errOk):
ret = err
return ret;
[docs]
def disableInterrupt(self):
"""Disable interrupts.
Switch off the interrupt functionality both, on the GPIO and the
chip level.
Note that this method is just for switching the interrupt capability
off.
For switching it on, see :meth:`.enableInterrupt`.
Also see :meth:`.Interruptable.disableInterrupt`.
:return: An error code indicating either success or the reason\
of failure.
:rtype: ErrorCode
"""
ret = ErrorCode.errOk
if (self.isAttached() != ErrorCode.errOk):
ret = ErrorCode.errResourceConflict
else:
ret = ErrorCode.errOk
if not(self.pinInt1 is None):
data = self.regInt1IOctrl & ~BMA456.BMA456_CNT_INT1_IO_CTRL_OUTPUT
data |= BMA456.BMA456_CNT_INT1_IO_CTRL_OUTPUT_DISABLE
ret = self.writeByteRegister( BMA456.BMA456_REG_INT1_IO_CTRL, data )
ret = self.pinInt1.disableInterrupt()
if not(self.pinInt2 is None):
data = self.regInt2IOctrl & ~BMA456.BMA456_CNT_INT2_IO_CTRL_OUTPUT
data |= BMA456.BMA456_CNT_INT2_IO_CTRL_OUTPUT_DISABLE
err = self.writeByteRegister( BMA456.BMA456_REG_INT2_IO_CTRL, data )
if (ret == ErrorCode.errOk):
ret = err
ret = self.pinInt2.disableInterrupt()
return ret;
[docs]
def getEventContext(self, event, context):
"""Retrieve more detailed information on an event.
Typically, an original event notification just carries the
information, that an event/interrupt occurred. This method is
to reveal more details on the cause of the interrupt.
The ``event`` parameter is an input to tell, which of the two
interrupt lined actually fired.
On return, the ``context`` parameter carries the resulting
information. It must be an instance of :class:`.accelerometer.EventContext`,
which is semantically multiplexed by its :attr:`.accelerometer.EventContext.source`
attribute. Depending on that event source indicator, the rest of
the structure is filled as follows:
======================= ==========================================================================================
Event source (flag) Context attribute and data
======================= ==========================================================================================
dataReady ``data`` latest measurement as retrieved by :meth:`getLatestData`
fifoWatermark, fifoFull ``status`` fifo status retrieved from :meth:`getStatus` and :attr:`StatusID.fifo`
activity ``status`` activity status retrieved from :meth:`getStatus` and :attr:`StatusID.activity`
step ``status`` step count retrieved from :meth:`getStatus` and :attr:`StatusID.stepCount`
highGTime ``status`` high-G status retrieved from :meth:`getStatus` and :attr:`StatusID.highG`
orientation ``status`` orientation retrieved from :meth:`getStatus` and :attr:`StatusID.orientation`
tap ``status`` :class:`Tap` instance depending on feature set and interrupt
======================= ==========================================================================================
A single interrupt may have several reasons, simultaneously.
That's why, it may be meaningful/necessary to call this method
repeatedly, until all reasons were reported. Upon its first
call after an event, the context's :attr:`.interruptable.EventContext.control`
attribute must be set to :attr:`.interruptable.EventContextControl.evtCtxtCtrl_getFirst`.
Upon subsequent calls, this attribute should not be changed by
the caller, anymore. In generally, event context information is
retrieved in the order according to the priority of the
corresponding event sources.
The return value indicates, whether or not more information is
available as follows:
============================== ======================================================
Return value Meaning
============================== ======================================================
:attr:`.ErrorCode.errOk` Success. Last context info. No more data to retrieve.
:attr:`.ErrorCode.errMoreData` Success. Context is valid. More data to be retrieved.
:attr:`.ErrorCode.errFewData` No data to retrieve. Context is invalid.
any other ErrorCode.* Error. Context data is invalid.
============================== ======================================================
Also see: :meth:`.Interruptable.getEventContext`.
:param int event: The original event occurred, as received by the\
handling routine. This must be one of the event mnemonics defined\
by :class:``.interruptable.Event``.
:param .accelerometer.EventContext context: Context information.
:return: An error code indicating either success or the reason of failure.
:rtype: ErrorCode
"""
ret = ErrorCode.errOk
if (context is None):
ret = ErrorCode.errInvalidParameter
elif( self.isAttached() != ErrorCode.errOk ):
ret = ErrorCode.errResourceConflict
elif( event == Event.evtNone ):
ret = ErrorCode.errFewData
elif( (event == Event.evtInt1) or (event == Event.evtInt2) ):
ret = ErrorCode.errOk
# Retrieving the interrupt status resets all bits in these registers!
if( context.control == EventContextControl.evtCtxtCtrl_clearAll ):
_, ret = self.readWordRegister( BMA456.BMA456_REG_INT_STATUS )
context.remainInt = 0;
context.source = EventSource.none
else:
if (context.control == EventContextControl.evtCtxtCtrl_getFirst):
data, ret = self.readWordRegister( BMA456.BMA456_REG_INT_STATUS )
context.remainInt = data
context.control = EventContextControl.evtCtxtCtrl_getNext
elif (context.control == EventContextControl.evtCtxtCtrl_getLast):
data, ret = self.readWordRegister( BMA456.BMA456_REG_INT_STATUS )
context.remainInt = data
context.control = EventContextControl.evtCtxtCtrl_getPrevious
if (ret == ErrorCode.errOk):
if (context.remainInt == 0):
ret = ErrorCode.errFewData
else:
data16 = context.remainInt
if (context.control == EventContextControl.evtCtxtCtrl_getNext):
# Find value of highest bit:
data16 = imath.iprevpowtwo( data16 )
else:
# Find (value of) least bit set:
data16 = imath.vlbs(data16)
ret = self._fillEventContext( data16, context )
context.remainInt &= ~data16
if ((ret == ErrorCode.errOk) and (context.remainInt != 0) ):
ret = ErrorCode.errMoreData
else:
ret = ErrorCode.errInvalidParameter
return ret;
#
# Sensor API, as derived from Sensor
#
[docs]
def selfTest(self, tests):
"""Execute one or more sensor self tests.
The test(s) to execute is given as a bit mask. This sensor
supports the following tests:
:attr:`.SelfTest.CONNECTION`:
An attempt is made to read the sensor's chip ID. If reading is
successful, the result is compared to :attr:`.BMA456_CNT_CHIP_ID`.
On a match, the method returns successfully. Otherwise, a failure
is indicated.
:attr:`.SelfTest.FUNCTIONAL`:
A functional sensor self test is executed as described in the
data sheet, chapter 4.9.
Also see: :meth:`.Sensor.selfTest`.
:param int tests: A bit mask to select the tests to be executed.
:return: An error code indicating either success or the reason of failure.
:rtype: ErrorCode
"""
ret = ErrorCode.errOk
if ((tests & SelfTest.CONNECTION) and (ret == ErrorCode.errOk) ):
info, ret = self.getInfo()
if (ret == ErrorCode.errOk):
if (info.chipID == BMA456.BMA456_CNT_CHIP_ID):
ret = ErrorCode.errOk
else:
ret = ErrorCode.errFailure
if ((tests & SelfTest.FUNCTIONAL) and (ret == ErrorCode.errOk)):
# Set +-8g range
oldRange = self.dataRange
config = Configuration()
config.type = ConfigItem.range
config.value = BMA456.BMA456_SELFTEST_RANGE
ret = self.configure( config )
# Set self-test amplitude to low
ret = self.writeByteRegister( BMA456.BMA456_REG_SELF_TST,
BMA456.BMA456_CNT_SELF_TST_AMP_LOW | BMA456.BMA456_CNT_SELF_TST_DISABLE )
# ODR=1600Hz, BWP=norm_avg4, performance mode (continuous)
oldRate, ret = self.readByteRegister( BMA456.BMA456_REG_ACC_CONF )
ret = self.writeByteRegister( BMA456.BMA456_REG_ACC_CONF,
BMA456.BMA456_CNT_ACC_CONF_ODR_1K6 | BMA456.BMA456_CNT_ACC_CONF_MODE_NORM )
# Wait for min. 2ms
time.sleep( BMA456.BMA456_SELFTEST_DELAY_CONFIG / 1000000 )
# Enable self-test and positive test polarity
ret = self.writeByteRegister( BMA456.BMA456_REG_SELF_TST,
BMA456.BMA456_CNT_SELF_TST_AMP_LOW | BMA456.BMA456_CNT_SELF_TST_SIGN_POS | BMA456.BMA456_CNT_SELF_TST_ENABLE )
# Wait for min. 50ms
time.sleep( BMA456.BMA456_SELFTEST_DELAY_MEASURE / 1000000 )
# Read positive acceleration value
posData, ret = self.getLatestData()
# Enable self-test and negative test polarity
ret = self.writeByteRegister( BMA456.BMA456_REG_SELF_TST,
BMA456.BMA456_CNT_SELF_TST_AMP_LOW | BMA456.BMA456_CNT_SELF_TST_SIGN_NEG | BMA456.BMA456_CNT_SELF_TST_ENABLE )
# Wait for min. 50ms
time.sleep( BMA456.BMA456_SELFTEST_DELAY_MEASURE / 1000000 )
# Read negative acceleration value
negData, ret = self.getLatestData()
# Calculate difference and compare against threshold
if ((ret == ErrorCode.errOk) and (
((posData.x - negData.x) < BMA456.BMA456_SELFTEST_THRESHOLD) or
((posData.y - negData.y) < BMA456.BMA456_SELFTEST_THRESHOLD) or
((posData.z - negData.z) < BMA456.BMA456_SELFTEST_THRESHOLD) )):
ret = ErrorCode.errFailure
# Disable self-test
self.writeByteRegister( BMA456.BMA456_REG_SELF_TST,
BMA456.BMA456_CNT_SELF_TST_AMP_LOW | BMA456.BMA456_CNT_SELF_TST_DISABLE )
# Restore old configuration
self.writeByteRegister( BMA456.BMA456_REG_ACC_CONF, oldRate )
config.type = ConfigItem.range
config.value = oldRange
self.configure( config )
return ret
[docs]
def reset(self):
"""Reset the sensor to its default state.
After executing the reset command with the chip, it is re-configured
using the start-up settings. Also, interrupt configuration is
restored and interrupts are enabled.
Also see: :meth:`.Sensor.reset`.
:return: An error code indicating either success or the reason of failure.
:rtype: ErrorCode
"""
# Known issue with BMA456: It does not ACK the soft reset command, when
# the sensor is not in suspend mode. (Search for "BMA456 soft reset error"!)
# Instead of suspending, we catch and ignore the exception thrown.
ret = ErrorCode.errOk
# Initiate the soft reset
try:
ret = self.writeByteRegister( BMA456.BMA456_REG_CMD, BMA456.BMA456_CNT_CMD_SOFTRESET )
except OSError:
pass
# Wait for some time
time.sleep( 5 / 1000 )
# Restore configuration
if (ret == ErrorCode.errOk):
ret = self._initialize()
data = self.regInt1IOctrl & ~BMA456.BMA456_CNT_INT1_IO_CTRL_OUTPUT
data |= BMA456.BMA456_CNT_INT1_IO_CTRL_OUTPUT_DISABLE
self.writeByteRegister( BMA456.BMA456_REG_INT1_IO_CTRL, data )
self.writeByteRegister( BMA456.BMA456_REG_INT1_MAP, self.regInt1Map )
data = self.regInt2IOctrl & ~BMA456.BMA456_CNT_INT2_IO_CTRL_OUTPUT
data |= BMA456.BMA456_CNT_INT2_IO_CTRL_OUTPUT_DISABLE
self.writeByteRegister( BMA456.BMA456_REG_INT2_IO_CTRL, data )
self.writeByteRegister( BMA456.BMA456_REG_INT2_MAP, self.regInt2Map )
self.writeByteRegister( BMA456.BMA456_REG_INT_MAP_DATA, self.regIntMapData )
self.enableInterrupt()
return ret
[docs]
def calibrate(self, calib):
"""Execute a calibration.
The details for the calibration are given by the ``calib``
parameter.
Also see: :meth:`.Sensor.calibrate`, :class:`.Calibration`.
:param Calibration calib: The calibration data.
:return: An error code indicating either success or the reason of failure.
:rtype: ErrorCode
"""
ret = ErrorCode.errOk
if (calib.type == CalibrationType.default):
ret = ErrorCode.errNotImplemented
elif (calib.type == CalibrationType.trueValue):
ret = ErrorCode.errNotImplemented
else:
ret = ErrorCode.errNotSupported
return ret
[docs]
def getInfo(self):
"""Retrieve static information from the sensor chip.
Supported information is
:attr:`.Info.validModelID` and :attr:`.Info.validChipID`.
Also see: :meth:`.Sensor.getInfo`.
:return: The information object and an error code indicating either success or the reason of failure.
:rtype: Info, ErrorCode
"""
info = Info()
ret = self.isAttached()
if (ret==ErrorCode.errOk):
# Model ID
data, ret = self._getFeatureWordAt( BMA456.BMA456_FSWBL_IDX_GENERAL_CONFIG_ID )
if (ret == ErrorCode.errOk):
info.modelID = data
info.validity |= Info.validModelID
# Chip ID
if (ret == ErrorCode.errOk):
data, ret = self.readByteRegister( BMA456.BMA456_REG_CHIP_ID )
if (ret == ErrorCode.errOk):
info.chipID = data
info.validity |= Info.validChipID
else:
info.validity = Info.validNothing
ret = ErrorCode.errInadequate
return info, ret
[docs]
def getStatus(self, statID):
"""Retrieve dynamic meta information from the sensor chip.
The information requested is indicated by the parameter ``statID``.
The resulting status information is passed back as a part of the
return value. The following information is available:
:attr:`.accelerometer.StatusID.dieTemp`:
The chip temperature as read from the register :attr:`.BMA456_REG_TEMPERATURE`
is returned as an Q8.8 integer given in degree Celsius.
:attr:`.accelerometer.StatusID.dataReady`:
Returns a boolean to flag whether or not new data is available.
For that, evaluates the ``DRDY_ACC`` bit in :attr:`.BMA456_REG_STATUS`
:attr:`.accelerometer.StatusID.interrupt`:
Give the pending interrupts by reading :attr:`.BMA456_REG_INT_STATUS`.
This information is cleared until interrupt conditions are
fulfilled anew.
The result is given as an :class:`.acceleroometer.EventSource`
bit mask. The mapping of hardware interrupt bits to event source
flags is as follows:
=============== ==============
Interrupt (Bit) EventSource
=============== ==============
ACC_DRDY dataReady
ACTIVITY activity
ANY_MOTION highSlopeTime
AUX_DRDY none
DBL_TAP tap
ERROR error
FIFO_FULL fifoFull
FIFO_WM fifoWatermark
HIGH_G highGTime
LOW_G lowGTime
NO_MOTION lowSlopeTime
ORIENT orientation
SIG_MOTION significantMotion
STEP_COUNT step
TAP_DETECT tap
WRIST_WKUP gesture
=============== ==============
:attr:`.accelerometer.StatusID.fifo`:
Retrieve the FIFO length by reading :attr:`.BMA456_REG_FIFO_LENGTH`.
The result is returned as an integer value.
:attr:`.accelerometer.StatusID.error`:
Gives the sensor health code as a 32-bit integer value, obtained
by just concatenating the content of the registers
:attr:`.BMA456_REG_INTERNAL_ERR`:
:attr:`.BMA456_REG_INTERNAL_STATUS`:
:attr:`.BMA456_REG_EVENT`:
:attr:`.BMA456_REG_ERROR` in that order.
:attr:`.accelerometer.StatusID.activity`:
For the wearable and hearable feature set, read the current activity
from the :attr:`.BMA456_FSWBL_REG_ACTIVITY_TYPE` register and
return the result as an instance of :class:`.Activity`. Indicate
a failure if another feature set is active.
:attr:`.accelerometer.StatusID.stepCount`:
For the wearable and hearable feature set, return the current
step count as read from the register
:attr:`.BMA456_FSWBL_REG_STEP_COUNTER`. The result is given as
a 32 bit integer number.
:attr:`.accelerometer.StatusID.highG`:
For the MM feature set, retrieve the axis information for a
high-G event from the :attr:`.BMA456_FSMM_REG_HIGH_G_OUTPUT`
register. The result is passed back as an :class:`.AxesSign` bit
mask.
:attr:`.accelerometer.StatusID.orientation`:
For the MM feature set, retrieve the current orientation of the
device as read from the :attr:`.BMA456_FSMM_REG_ORIENT_OUTPUT`
register. The result is given as an :class:`.Orientation` bit
mask.
:attr:`.accelerometer.StatusID.tap`:
For the hearable and MM feature set, give the type of the recently
detected tap (single, double etc.) as a :class:`.Tap` bit mask
after reading that information from the
:attr:`.BMA456_FSHBL_REG_FEAT_OUT` and
:attr:`.BMA456_FSMM_REG_MULTITAP_OUTPUT` registers, respectively.
For the wearable feature set, there are dedicated interrupts for
this information, which get cleared upon reading. That's why,
:meth:`.getEventContext` should be intentionally used by the
caller to retrieve this information.
:attr:`.accelerometer.StatusID.sensorTime`:
Retrieve the current sensor time in milli-seconds [ms]. The
result is given as a Q24.8 integer value.
Also see: :meth:`.Sensor.getStatus`.
:param accelerometer.StatusID statID: Identifies the status information to be retrieved.
:return: The status object and an error code indicating either success or the reason of failure.
:rtype: Object, ErrorCode
"""
ret = ErrorCode.errOk
status = 0
if (statID == StatusID.dieTemp):
# Temperature in degree Celsius as Q8.8
data, ret = self.readByteRegister( BMA456.BMA456_REG_TEMPERATURE )
if (ret == ErrorCode.errOk):
# sign-extend data
if (data > 0x7F):
data = data - 0x100
status = (data + BMA456.BMA456_TEMPERATURE_SHIFT) << 8
elif (statID == StatusID.dataReady):
# Just 0 or 1 to indicate if new data is ready
data, ret = self.readByteRegister( BMA456.BMA456_REG_STATUS )
if (ret == ErrorCode.errOk):
status = ((data & BMA456.BMA456_CNT_STATUS_DRDY_ACC) != 0)
elif (statID == StatusID.interrupt):
# EventSource mask
data, ret = self.readWordRegister( BMA456.BMA456_REG_INT_STATUS )
if (ret == ErrorCode.errOk):
status = self._bmaInt2accelEvtSrc( data )
elif (statID == StatusID.fifo):
# Number of elements in FIFO
data, ret = self.readWordRegister( BMA456.BMA456_REG_FIFO_LENGTH )
if (ret == ErrorCode.errOk):
status = data
elif (statID == StatusID.error):
# Implementation-specific error/health code
status = 0
# Copy INTERNAL_ERROR 0x5F (int_err_2, int_err_1)
if (ret == ErrorCode.errOk):
data, ret = self.readByteRegister( BMA456.BMA456_REG_INTERNAL_ERR )
status = (status << 8) | data
# Copy INTERNAL_STATUS 0x2A (odr_high_error, odr_50hz_error, axes_remap_error, message)
if (ret == ErrorCode.errOk):
data, ret = self.readByteRegister( BMA456.BMA456_REG_INTERNAL_STATUS )
status = (status << 8) | data
# Copy EVENT 0x1B (por_detected)
if (ret == ErrorCode.errOk):
data, ret = self.readByteRegister( BMA456.BMA456_REG_EVENT )
status = (status << 8) | data
# Copy ERR_REG 0x02 (aux_err, fifo_err, error_code, cmd_err, fatal_err)
if (ret == ErrorCode.errOk):
data, ret = self.readByteRegister( BMA456.BMA456_REG_ERROR )
status = (status << 8) | data
elif (statID == StatusID.activity):
# Activity
if (self.featureSet in [BMA456.BMA456_FEATURE_SET_WEARABLE, BMA456.BMA456_FEATURE_SET_HEARABLE]):
data, ret = self.readByteRegister( BMA456.BMA456_FSWBL_REG_ACTIVITY_TYPE )
if (ret == ErrorCode.errOk):
if ((data & BMA456.BMA456_FSWBL_CNT_ACTIVITY_TYPE) == BMA456.BMA456_FSWBL_CNT_ACTIVITY_TYPE_UNKNOWN):
status = Activity.unknown
elif ((data & BMA456.BMA456_FSWBL_CNT_ACTIVITY_TYPE) == BMA456.BMA456_FSWBL_CNT_ACTIVITY_TYPE_STILL):
status = Activity.still
elif ((data & BMA456.BMA456_FSWBL_CNT_ACTIVITY_TYPE) == BMA456.BMA456_FSWBL_CNT_ACTIVITY_TYPE_WALK):
status = Activity.walking
elif ((data & BMA456.BMA456_FSWBL_CNT_ACTIVITY_TYPE) == BMA456.BMA456_FSWBL_CNT_ACTIVITY_TYPE_RUN):
status = Activity.running
else:
ret = ErrorCode.errCorruptData
else:
ret = ErrorCode.errNotSupported
elif (statID == StatusID.stepCount):
# Step count
if (self.featureSet in [BMA456.BMA456_FEATURE_SET_WEARABLE, BMA456.BMA456_FEATURE_SET_HEARABLE]):
status, ret = self.readDWordRegister( BMA456.BMA456_FSWBL_REG_STEP_COUNTER )
else:
ret = ErrorCode.errNotSupported
elif (statID == StatusID.highG):
# AxesSign
if (self.featureSet == BMA456.BMA456_FEATURE_SET_MM):
data, ret = self.readByteRegister( BMA456.BMA456_FSMM_REG_HIGH_G_OUTPUT )
if (ret == ErrorCode.errOk):
status = AxesSign.none
if (data & BMA456.BMA456_FSMM_CNT_HIGH_G_OUTPUT_AXES_X ):
status |= AxesSign.x
if (data & BMA456.BMA456_FSMM_CNT_HIGH_G_OUTPUT_AXES_Y ):
status |= AxesSign.y
if (data & BMA456.BMA456_FSMM_CNT_HIGH_G_OUTPUT_AXES_Z ):
status |= AxesSign.z
if ((data & BMA456.BMA456_FSMM_CNT_HIGH_G_OUTPUT_SIGN) == BMA456.BMA456_FSMM_CNT_HIGH_G_OUTPUT_SIGN_POS ):
status |= AxesSign.signPos
else:
status |= AxesSign.signNeg
else:
ret = ErrorCode.errNotSupported
elif (statID == StatusID.orientation):
# accel_Orientation_t mask
if (self.featureSet == BMA456.BMA456_FEATURE_SET_MM):
data, ret = self.readByteRegister( BMA456.BMA456_FSMM_REG_ORIENT_OUTPUT )
if (ret == ErrorCode.errOk):
if ((data & BMA456.BMA456_FSMM_CNT_ORIENT_OUTPUT_STAND) == BMA456.BMA456_FSMM_CNT_ORIENT_OUTPUT_STAND_PORT_UP):
status = Orientation.portraitUp
elif ((data & BMA456.BMA456_FSMM_CNT_ORIENT_OUTPUT_STAND) == BMA456.BMA456_FSMM_CNT_ORIENT_OUTPUT_STAND_PORT_DOWNUP):
status = Orientation.portraitDown
elif ((data & BMA456.BMA456_FSMM_CNT_ORIENT_OUTPUT_STAND) == BMA456.BMA456_FSMM_CNT_ORIENT_OUTPUT_STAND_LAND_LEFT):
status = Orientation.landscapeLeft
elif ((data & BMA456.BMA456_FSMM_CNT_ORIENT_OUTPUT_STAND) == BMA456.BMA456_FSMM_CNT_ORIENT_OUTPUT_STAND_LAND_RIGHT):
status = Orientation.landscapeRight
else:
# Should never reach here.
status = Orientation.portraitUp
# face up/down info
if ((data & BMA456.BMA456_FSMM_CNT_ORIENT_OUTPUT_FACE) == BMA456.BMA456_FSMM_CNT_ORIENT_OUTPUT_FACE_UP):
status |= Orientation.faceUp
else:
status |= Orientation.faceDown
status |= Orientation.invalidTilt
else:
ret = ErrorCode.errNotSupported
elif (statID == StatusID.tap):
# Number of taps detected as an Tap type
if (self.featureSet == BMA456.BMA456_FEATURE_SET_WEARABLE):
#
# Dedicated interrupts for single and double tap, here.
# So, there is no other chance to find out about single vs.
# double tap, than to read out INT_STATUS_0, again.
# As this would clear the pending interrupts, we abstain
# from this and cannot report more than NONE, at this
# point.
#
status = Tap.none
elif (self.featureSet == BMA456.BMA456_FEATURE_SET_HEARABLE):
data, ret = self.readByteRegister( BMA456.BMA456_FSHBL_REG_FEAT_OUT )
if (ret == ErrorCode.errOk):
status = Tap.none
if (data & BMA456.BMA456_FSHBL_CNT_FEAT_OUT_STAP):
status |= Tap.single
if (data & BMA456.BMA456_FSHBL_CNT_FEAT_OUT_DTAP):
status |= Tap.double
if (data & BMA456.BMA456_FSHBL_CNT_FEAT_OUT_TTAP):
status |= Tap.triple
elif (self.featureSet == BMA456.BMA456_FEATURE_SET_MM):
data, ret = self.readByteRegister( BMA456.BMA456_FSMM_REG_MULTITAP_OUTPUT )
if (ret == ErrorCode.errOk):
status = Tap.none
if (data & BMA456.BMA456_FSMM_CNT_MULTITAP_OUTPUT_STAP):
status |= Tap.single
if (data & BMA456.BMA456_FSMM_CNT_MULTITAP_OUTPUT_DTAP):
status |= Tap.double
if (data & BMA456.BMA456_FSMM_CNT_MULTITAP_OUTPUT_TTAP):
status |= Tap.triple
else:
ret = ErrorCode.errNotSupported
elif (statID == StatusID.sensorTime):
# Sensor time in ms as an unsigned Q24.8
status, ret = self.readDWordRegister( BMA456.BMA456_REG_SENSOR_TIME )
if (ret == ErrorCode.errOk):
# Result's LSB is 625/16 = 39.0625 us (microseconds).
# So we look for result * 625/16 * 256/1000 = result * 10.
status = status * 10
else:
ret = ErrorCode.errNotSupported
return status, ret
[docs]
def getLatestData(self):
"""Retrieve the most-recent available measurement data.
This method is guaranteed to be non-blocking. Therefore, the
data retrieved might be /old/ or /outdated/ to some extend.
The result is given as a 3x1 list of signed integers,
representing the acceleration in milli-G in the x, y and z
direction, respectively.
Also see: :meth:`.Sensor.getLatestData`.
:return: The measurement data list and an error code indicating\
either success or the reason of failure.
:rtype: List(int), ErrorCode
"""
buf, ret = self.readBufferRegister( BMA456.BMA456_REG_ACC_X, 6 )
if (ret == ErrorCode.errOk):
x = buf[0] | (buf[1] << 8)
x = self._transfer( x )
y = buf[2] | (buf[3] << 8)
y = self._transfer( y )
z = buf[4] | (buf[5] << 8)
z = self._transfer( z )
data = [x, y, z]
else:
data = None
return data, ret
[docs]
def getNextData(self):
"""Get the next-available measurement data.
This method is guaranteed to produce up-to-date measurement
data. This may come at the price of a blocking delay.
As with :meth:`getLatestData`, the result is given as a list
of integers representing the acceleration in x, y and z direction
in milli-G.
Also see: :meth:`.Sensor.getNextData`.
:return: The measurement data list and an error code indicating\
either success or the reason of failure.
:rtype: List(int), ErrorCode
"""
done = False
while( not done ):
stat, err = self.getStatus( StatusID.dataReady )
done = (stat != 0) or (err != ErrorCode.errOk)
if (err == ErrorCode.errOk):
data, err = self.getLatestData()
else:
data = None
return data, err