Top

rstem.accel module

This module provides interfaces to the Accelerometer RaspberrySTEM Cell. An Accelerometer allows a program to detemine which way the RaspberrySTEM is oriented and/or moving.

#
# Copyright (c) 2014, Scott Silver Labs, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
'''
This module provides interfaces to the Accelerometer RaspberrySTEM Cell.  An
Accelerometer allows a program to detemine which way the RaspberrySTEM is
oriented and/or moving.
'''

import os
import time
from struct import pack, unpack
from fcntl import ioctl
from ctypes import create_string_buffer, sizeof, string_at
from ctypes import c_int, c_uint16, c_ushort, c_short, c_char, POINTER, Structure

#
# Required defs from 
#
class i2c_msg(Structure):
    _fields_ = [
        ('addr', c_uint16),
        ('flags', c_ushort),
        ('len', c_short),
        ('buf', POINTER(c_char))
        ]

class i2c_rdwr_ioctl_data(Structure):
    _fields_ = [
        ('msgs', POINTER(i2c_msg)),
        ('nmsgs', c_int)
        ]

I2C_SLAVE   = 0x0703
I2C_RDWR    = 0x0707
I2C_M_RD    = 0x0001

#
# MMA8653 Register Map
#
STATUS =        0x00
OUT_X_MSB =     0x01
OUT_X_LSB =     0x02
OUT_Y_MSB =     0x03
OUT_Y_LSB =     0x04
OUT_Z_MSB =     0x05
OUT_Z_LSB =     0x06
SYSMOD =        0x0B
INT_SOURCE =    0x0C
WHO_AM_I =      0x0D
XYZ_DATA_CFG =  0x0E
PL_STATUS =     0x10
PL_CFG =        0x11
PL_COUNT =      0x12
PL_BF_ZCOMP =   0x13
PL_THS_REG =    0x14
FF_MT_CFG =     0x15
FF_MT_SRC =     0x16
FF_MT_THS =     0x17
FF_MT_COUNT =   0x18
ASLP_Count =    0x29
CTRL_REG1 =     0x2A
CTRL_REG2 =     0x2B
CTRL_REG3 =     0x2C
CTRL_REG4 =     0x2D
CTRL_REG5 =     0x2E
OFF_X =         0x2F
OFF_Y =         0x30
OFF_Z =         0x31

MMA8653_DEVICE_ADDR = 0x1D
I2C_BUS = 1

# Allowed full scale settings: 2, 4, 8 (in 'g')
FULL_SCALE = 4

class Accel(object):
    def __init__(self):
        # i2c_bcm2708 driver only support repeated-start transactions (which
        # the MMA8653 requires) if the 'combined' parameter is set.
        with  open('/sys/module/i2c_bcm2708/parameters/combined', 'w') as f:
            f.write('1\n')

        # Open I2C bus device, and set Accelerometer device address.
        self.fd = os.open('/dev/i2c-{}'.format(I2C_BUS), os.O_RDWR)
        ioctl(self.fd, I2C_SLAVE, MMA8653_DEVICE_ADDR)

        # Configure all device registers while in standby mode (see app note AN4083)
        os.write(self.fd, pack('bb', CTRL_REG1, 0x00))

        full_scale_setting = { 2 : 0x00, 4 : 0x01, 8 : 0x02 }[FULL_SCALE]
        os.write(self.fd, pack('bb', XYZ_DATA_CFG, full_scale_setting))
        self.resolution = { 2 : 256, 4 : 128, 8 : 64 }[FULL_SCALE]

        # All other regs use defaults.  Sleep mode is a don't care, as max
        # power consumption is minimal (< 1mw).

        # Set ACTIVE.  All other bits default (Data rate 800Hz, normal mode)
        os.write(self.fd, pack('bb', CTRL_REG1, 0x01))

        # Verify device is who we think it is
        # NOTE: Must retry, as self._read() is not guaranteed.
        TRIES = 10
        for i in range(TRIES):
            id = self._read(WHO_AM_I)[0]
            if id == 0x5A:
                break
            time.sleep(0.001)
        if i + 1 == TRIES:
            raise IOError('MMA8653 Accelerometer is not returning correct ID ({})'.format(id))

    def _read(self, register, bytes_to_read=1):
        '''Read bytes from Accelerometer, starting at `register`.

        Reads from an arbitrary register require writing the register number, a
        repeated-start, and the reading the register value.

        Unfortunately, the MMA8653 appears to be a bit flaky, and although a
        repeated-start transaction works most of the time, it sometimes gets
        the MMA8653 in a funky state (specifically, it sometimes returns
        nonsense values, even though it always correctly responds with ACKs on
        the bus).

        As a workaround, we do two things:
            - If the register is low, we do not use a repeated-start
              transaction, and instead use os.read() to read from regsiter 0 up
              through the requested registers using the parts auto-increment
              feature.  This works always, but only works for low registers
              (STATUS and OUT_?_?SB)
            - We add a read of one byte after the repeated-start transaction.
              This appears to leave the device in a state where it is
              responding correctly MOST of the time, in testing.  But beware -
              this means you can't trust reads from high registers, and may
              need to retry them.
        '''
        if bytes_to_read > 8:
            raise ValueError('MMA8653 Accel. does not (seem) to allow consectutive reads > 8')

        # HW Workaround for low registers to avoid repeated-start.  See docstring.
        if register + bytes_to_read <= OUT_Z_LSB + 1:
            data = os.read(self.fd, register + bytes_to_read)
            return data[register:]

        # Create C-based I2C write message struct (1 byte register)
        buf = create_string_buffer(bytes((register,)), 1)
        write_msg = i2c_msg(
            addr=MMA8653_DEVICE_ADDR,
            flags=0,
            len=sizeof(buf),
            buf=buf
            )

        # Create C-based I2C read message struct (bytes_to_read bytes)
        buf = create_string_buffer(bytes_to_read)
        read_msg = i2c_msg(
            addr=MMA8653_DEVICE_ADDR,
            flags=I2C_M_RD,
            len=sizeof(buf),
            buf=buf
            )

        # Create C-based I2C struct to pass as ioctl() arg
        nmsgs = 2
        msgs = (i2c_msg * 2)(write_msg, read_msg)
        ioctl_arg = i2c_rdwr_ioctl_data(
            msgs=msgs,
            nmsgs=nmsgs
            )

        # ioctl() to do the I2C READ/WRITE
        ret = ioctl(self.fd, I2C_RDWR, ioctl_arg)
        if ret != 2:
            raise IOError('ioctl() failed to send to I2C messages ({})'.format(ret))

        # HW workaround.  See docstring.
        os.read(self.fd, 1)

        return string_at(read_msg.buf, read_msg.len)

    def forces(self):
        '''Returns a tuple of X, Y and Z forces.

        Forces are floats in units of `g` (for g-force), where the g-force is
        the gravitational force of the Earth.  An object in free-fall will
        measure 0 `g` in the direction that it is falling.  An object resting
        on a table will measure 1 `g` in the downward direction.  Thus, if the
        RaspberrySTEM is sitting flat and upright on a table, the return value
        should be close to (0.0, 0.0, 1.0).
        '''
        # Verify data is ready
        TRIES = 5
        for i in range(TRIES):
            if self._read(STATUS, 1)[0] & 0x0F == 0xF:
                break
            time.sleep(0.001)
        if i + 1 == TRIES:
            raise IOError('Accelerometer data is not available')

        data = self._read(OUT_X_MSB, 6)
        raw_forces = unpack('>hhh', data)

        # Convert raw forces (10-bit left aligned 2's complement data in an
        # unsigned short) to floats in units of 'g'.
        g_forces = [(raw_force>>6)/self.resolution for raw_force in raw_forces]

        # Part is on underside of board, this inverts the Y and Z axes.
        x, y, z = g_forces
        g_forces = x, -y, -z

        return g_forces

    def __del__(self):
        os.close(self.fd)

__all__ = ['Accel']

Classes

class Accel

class Accel(object):
    def __init__(self):
        # i2c_bcm2708 driver only support repeated-start transactions (which
        # the MMA8653 requires) if the 'combined' parameter is set.
        with  open('/sys/module/i2c_bcm2708/parameters/combined', 'w') as f:
            f.write('1\n')

        # Open I2C bus device, and set Accelerometer device address.
        self.fd = os.open('/dev/i2c-{}'.format(I2C_BUS), os.O_RDWR)
        ioctl(self.fd, I2C_SLAVE, MMA8653_DEVICE_ADDR)

        # Configure all device registers while in standby mode (see app note AN4083)
        os.write(self.fd, pack('bb', CTRL_REG1, 0x00))

        full_scale_setting = { 2 : 0x00, 4 : 0x01, 8 : 0x02 }[FULL_SCALE]
        os.write(self.fd, pack('bb', XYZ_DATA_CFG, full_scale_setting))
        self.resolution = { 2 : 256, 4 : 128, 8 : 64 }[FULL_SCALE]

        # All other regs use defaults.  Sleep mode is a don't care, as max
        # power consumption is minimal (< 1mw).

        # Set ACTIVE.  All other bits default (Data rate 800Hz, normal mode)
        os.write(self.fd, pack('bb', CTRL_REG1, 0x01))

        # Verify device is who we think it is
        # NOTE: Must retry, as self._read() is not guaranteed.
        TRIES = 10
        for i in range(TRIES):
            id = self._read(WHO_AM_I)[0]
            if id == 0x5A:
                break
            time.sleep(0.001)
        if i + 1 == TRIES:
            raise IOError('MMA8653 Accelerometer is not returning correct ID ({})'.format(id))

    def _read(self, register, bytes_to_read=1):
        '''Read bytes from Accelerometer, starting at `register`.

        Reads from an arbitrary register require writing the register number, a
        repeated-start, and the reading the register value.

        Unfortunately, the MMA8653 appears to be a bit flaky, and although a
        repeated-start transaction works most of the time, it sometimes gets
        the MMA8653 in a funky state (specifically, it sometimes returns
        nonsense values, even though it always correctly responds with ACKs on
        the bus).

        As a workaround, we do two things:
            - If the register is low, we do not use a repeated-start
              transaction, and instead use os.read() to read from regsiter 0 up
              through the requested registers using the parts auto-increment
              feature.  This works always, but only works for low registers
              (STATUS and OUT_?_?SB)
            - We add a read of one byte after the repeated-start transaction.
              This appears to leave the device in a state where it is
              responding correctly MOST of the time, in testing.  But beware -
              this means you can't trust reads from high registers, and may
              need to retry them.
        '''
        if bytes_to_read > 8:
            raise ValueError('MMA8653 Accel. does not (seem) to allow consectutive reads > 8')

        # HW Workaround for low registers to avoid repeated-start.  See docstring.
        if register + bytes_to_read <= OUT_Z_LSB + 1:
            data = os.read(self.fd, register + bytes_to_read)
            return data[register:]

        # Create C-based I2C write message struct (1 byte register)
        buf = create_string_buffer(bytes((register,)), 1)
        write_msg = i2c_msg(
            addr=MMA8653_DEVICE_ADDR,
            flags=0,
            len=sizeof(buf),
            buf=buf
            )

        # Create C-based I2C read message struct (bytes_to_read bytes)
        buf = create_string_buffer(bytes_to_read)
        read_msg = i2c_msg(
            addr=MMA8653_DEVICE_ADDR,
            flags=I2C_M_RD,
            len=sizeof(buf),
            buf=buf
            )

        # Create C-based I2C struct to pass as ioctl() arg
        nmsgs = 2
        msgs = (i2c_msg * 2)(write_msg, read_msg)
        ioctl_arg = i2c_rdwr_ioctl_data(
            msgs=msgs,
            nmsgs=nmsgs
            )

        # ioctl() to do the I2C READ/WRITE
        ret = ioctl(self.fd, I2C_RDWR, ioctl_arg)
        if ret != 2:
            raise IOError('ioctl() failed to send to I2C messages ({})'.format(ret))

        # HW workaround.  See docstring.
        os.read(self.fd, 1)

        return string_at(read_msg.buf, read_msg.len)

    def forces(self):
        '''Returns a tuple of X, Y and Z forces.

        Forces are floats in units of `g` (for g-force), where the g-force is
        the gravitational force of the Earth.  An object in free-fall will
        measure 0 `g` in the direction that it is falling.  An object resting
        on a table will measure 1 `g` in the downward direction.  Thus, if the
        RaspberrySTEM is sitting flat and upright on a table, the return value
        should be close to (0.0, 0.0, 1.0).
        '''
        # Verify data is ready
        TRIES = 5
        for i in range(TRIES):
            if self._read(STATUS, 1)[0] & 0x0F == 0xF:
                break
            time.sleep(0.001)
        if i + 1 == TRIES:
            raise IOError('Accelerometer data is not available')

        data = self._read(OUT_X_MSB, 6)
        raw_forces = unpack('>hhh', data)

        # Convert raw forces (10-bit left aligned 2's complement data in an
        # unsigned short) to floats in units of 'g'.
        g_forces = [(raw_force>>6)/self.resolution for raw_force in raw_forces]

        # Part is on underside of board, this inverts the Y and Z axes.
        x, y, z = g_forces
        g_forces = x, -y, -z

        return g_forces

    def __del__(self):
        os.close(self.fd)

Ancestors (in MRO)

Static methods

def __init__(

self)

def __init__(self):
    # i2c_bcm2708 driver only support repeated-start transactions (which
    # the MMA8653 requires) if the 'combined' parameter is set.
    with  open('/sys/module/i2c_bcm2708/parameters/combined', 'w') as f:
        f.write('1\n')
    # Open I2C bus device, and set Accelerometer device address.
    self.fd = os.open('/dev/i2c-{}'.format(I2C_BUS), os.O_RDWR)
    ioctl(self.fd, I2C_SLAVE, MMA8653_DEVICE_ADDR)
    # Configure all device registers while in standby mode (see app note AN4083)
    os.write(self.fd, pack('bb', CTRL_REG1, 0x00))
    full_scale_setting = { 2 : 0x00, 4 : 0x01, 8 : 0x02 }[FULL_SCALE]
    os.write(self.fd, pack('bb', XYZ_DATA_CFG, full_scale_setting))
    self.resolution = { 2 : 256, 4 : 128, 8 : 64 }[FULL_SCALE]
    # All other regs use defaults.  Sleep mode is a don't care, as max
    # power consumption is minimal (< 1mw).
    # Set ACTIVE.  All other bits default (Data rate 800Hz, normal mode)
    os.write(self.fd, pack('bb', CTRL_REG1, 0x01))
    # Verify device is who we think it is
    # NOTE: Must retry, as self._read() is not guaranteed.
    TRIES = 10
    for i in range(TRIES):
        id = self._read(WHO_AM_I)[0]
        if id == 0x5A:
            break
        time.sleep(0.001)
    if i + 1 == TRIES:
        raise IOError('MMA8653 Accelerometer is not returning correct ID ({})'.format(id))

def forces(

self)

Returns a tuple of X, Y and Z forces.

Forces are floats in units of g (for g-force), where the g-force is the gravitational force of the Earth. An object in free-fall will measure 0 g in the direction that it is falling. An object resting on a table will measure 1 g in the downward direction. Thus, if the RaspberrySTEM is sitting flat and upright on a table, the return value should be close to (0.0, 0.0, 1.0).

def forces(self):
    '''Returns a tuple of X, Y and Z forces.
    Forces are floats in units of `g` (for g-force), where the g-force is
    the gravitational force of the Earth.  An object in free-fall will
    measure 0 `g` in the direction that it is falling.  An object resting
    on a table will measure 1 `g` in the downward direction.  Thus, if the
    RaspberrySTEM is sitting flat and upright on a table, the return value
    should be close to (0.0, 0.0, 1.0).
    '''
    # Verify data is ready
    TRIES = 5
    for i in range(TRIES):
        if self._read(STATUS, 1)[0] & 0x0F == 0xF:
            break
        time.sleep(0.001)
    if i + 1 == TRIES:
        raise IOError('Accelerometer data is not available')
    data = self._read(OUT_X_MSB, 6)
    raw_forces = unpack('>hhh', data)
    # Convert raw forces (10-bit left aligned 2's complement data in an
    # unsigned short) to floats in units of 'g'.
    g_forces = [(raw_force>>6)/self.resolution for raw_force in raw_forces]
    # Part is on underside of board, this inverts the Y and Z axes.
    x, y, z = g_forces
    g_forces = x, -y, -z
    return g_forces

Instance variables

var fd

var resolution