Wednesday, March 9, 2016

Linux - Getting the MC3610 accelerometer to work on Raspberry Pi

Spoiler Alert - This doesn't work!

References:
https://github.com/sajingeo/i2crw
https://github.com/mcubemems/Accelerometer_MC3610



No, I haven't got is 'quite' working yet, but right now I'm sick of it, so I'll write about it.

The MC3610 is a brand new accelerometer and the breakout board has just come out.  Now, the 5 year old ADXL345 works perfectly with Python.  That's the thing about Linux, 5 year old hardware works perfectly, the new stuff never works.  Somewhere along the line the Great Linus put up his finger and carved into stone: "Linux will never support I2C repeated starts, not now, not ever!"

So that got me royally screwed, since the bums made this chip 'repeated start'.  It's great when you have multiple masters on one i2c bus, but who does that?  So drop all that kernel support and go directly to Jail, in this case ioctl commands, or bit-banging.  The Horror!

After mucking around trying to do it the easy way, I finally found 12crw, which is C code (I used C++) for bit banging.  Lot's of fun since c is a teensy weensy bit different from c++.  The reason I had to go for horrible c is that the only driver is written in c for the Arduino.  The mc3610 is a complex computer in itself, and has a heck of a lot of commands.  You just can't tell it 'Get me the damn accelerations', no you have to write specific bytes in specific registers.  The driver had all these commands.

The mc3610 c driver worked with Arduino, because of the long and horrible program 'wire.h', which, apparently, nobody can understand.  It accesses i2c at a primitive level, which is why it works.  The raspi is a sophisticated machine running full Linux.  

So I had to convert the driver to c++ and use the i2crw calls.  Here's the darn thing.

The header file.

#ifndef MC3610_h
#define MC3610_h



/*******************************************************************************
 *** CONSTANT / DEFINE
 *******************************************************************************/
#define MC3610_RETCODE_SUCCESS                 (0)
#define MC3610_RETCODE_ERROR_BUS               (-1)
#define MC3610_RETCODE_ERROR_NULL_POINTER      (-2)
#define MC3610_RETCODE_ERROR_STATUS            (-3)
#define MC3610_RETCODE_ERROR_SETUP             (-4)
#define MC3610_RETCODE_ERROR_GET_DATA          (-5)
#define MC3610_RETCODE_ERROR_IDENTIFICATION    (-6)
#define MC3610_RETCODE_ERROR_NO_DATA           (-7)
#define MC3610_RETCODE_ERROR_WRONG_ARGUMENT    (-8)
#define MC3610_FIFO_DEPTH    32
#define MC3610_REG_MAP_SIZE    64



/*******************************************************************************
 *** CONSTANT / DEFINE
 *******************************************************************************/

//=============================================
#define MC3610_INTR_C_IPP_MODE_OPEN_DRAIN    (0x00)
#define MC3610_INTR_C_IPP_MODE_PUSH_PULL     (0x01)

#define MC3610_INTR_C_IAH_ACTIVE_LOW     (0x00)
#define MC3610_INTR_C_IAH_ACTIVE_HIGH    (0x02)


/*******************************************************************************
 *** Register Map
 *******************************************************************************/
//=============================================
#define MC3610_REG_EXT_STAT_1       (0x00)
#define MC3610_REG_EXT_STAT_2       (0x01)
#define MC3610_REG_XOUT_LSB         (0x02)
#define MC3610_REG_XOUT_MSB         (0x03)
#define MC3610_REG_YOUT_LSB         (0x04)
#define MC3610_REG_YOUT_MSB         (0x05)
#define MC3610_REG_ZOUT_LSB         (0x06)
#define MC3610_REG_ZOUT_MSB         (0x07)
#define MC3610_REG_STATUS_1         (0x08)
#define MC3610_REG_STATUS_2         (0x09)
#define MC3610_REG_MODE_C           (0x10)
#define MC3610_REG_WAKE_C           (0x11)
#define MC3610_REG_SNIFF_C          (0x12)
#define MC3610_REG_SNIFFTH_C        (0x13)
#define MC3610_REG_IO_C             (0x14)
#define MC3610_REG_RANGE_C          (0x15)
#define MC3610_REG_FIFO_C           (0x16)
#define MC3610_REG_INTR_C           (0x17)
#define MC3610_REG_PROD             (0x18)
#define MC3610_REG_DMX              (0x20)
#define MC3610_REG_DMY              (0x21)
#define MC3610_REG_DMZ              (0x22)
#define MC3610_REG_XOFFL            (0x2A)
#define MC3610_REG_XOFFH            (0x2B)
#define MC3610_REG_YOFFL            (0x2C)
#define MC3610_REG_YOFFH            (0x2D)
#define MC3610_REG_ZOFFL            (0x2E)
#define MC3610_REG_ZOFFH            (0x2F)
#define MC3610_REG_XGAIN            (0x30)
#define MC3610_REG_YGAIN            (0x31)
#define MC3610_REG_ZGAIN            (0x32)
#define MC3610_REG_OPT              (0x3B)
#define MC3610_REG_LOC_X            (0x3C)
#define MC3610_REG_LOC_Y            (0x3D)
#define MC3610_REG_LOT_dAOFSZ       (0x3E)
#define MC3610_REG_WAF_LOT          (0x3F)

#define MC3610_NULL_ADDR     (0)

struct mc3610_acc_t
{
short XAxis;
    short YAxis;
    short ZAxis;    
    float XAxis_g;
float YAxis_g;
float ZAxis_g;
} ;

typedef enum
{
    MC3610_MODE_SLEEP   = 0b000,
    MC3610_MODE_STANDBY    = 0b001,
    MC3610_MODE_SNIFF      = 0b010,
    MC3610_MODE_CWAKE      = 0b101,
    MC3610_MODE_TRIG       = 0b111,
}   mc3610_mode_t;

typedef enum
{
    MC3610_RANGE_2G   = 0b000,
    MC3610_RANGE_4G   = 0b001,
    MC3610_RANGE_8G   = 0b010,
    MC3610_RANGE_16G  = 0b011,
    MC3610_RANGE_12G  = 0b100,
    MC3610_RANGE_END,
}   mc3610_range_t;

typedef enum
{
    MC3610_RESOLUTION_6BIT    = 0b000,
    MC3610_RESOLUTION_7BIT    = 0b001,
    MC3610_RESOLUTION_8BIT    = 0b010,
    MC3610_RESOLUTION_10BIT   = 0b011,
    MC3610_RESOLUTION_12BIT   = 0b100,
    MC3610_RESOLUTION_14BIT   = 0b101,  //(Do not select if FIFO enabled)
    MC3610_RESOLUTION_END,
}   mc3610_resolution_t;

typedef enum
{
    MC3610_CWAKE_SR_DEFAULT_50Hz = 0b0000,
    MC3610_CWAKE_SR_0p4Hz        = 0b0001,
    MC3610_CWAKE_SR_0p8Hz        = 0b0010,
    MC3610_CWAKE_SR_2Hz          = 0b0011,
    MC3610_CWAKE_SR_6Hz          = 0b0100,
    MC3610_CWAKE_SR_13Hz         = 0b0101,
    MC3610_CWAKE_SR_25Hz         = 0b0110,
    MC3610_CWAKE_SR_50Hz         = 0b0111,
    MC3610_CWAKE_SR_100Hz        = 0b1000,
    MC3610_CWAKE_SR_200Hz        = 0b1001,
    MC3610_CWAKE_SR_END,
}   mc3610_cwake_sr_t;

typedef enum
{
    MC3610_SNIFF_SR_DEFAULT_6Hz = 0b0000,
    MC3610_SNIFF_SR_0p4Hz      = 0b0001,
    MC3610_SNIFF_SR_0p8Hz      = 0b0010,
    MC3610_SNIFF_SR_2Hz        = 0b0011,
    MC3610_SNIFF_SR_6Hz        = 0b0100,
    MC3610_SNIFF_SR_13Hz       = 0b0101,
    MC3610_SNIFF_SR_25Hz       = 0b0110,
    MC3610_SNIFF_SR_50Hz       = 0b0111,
    MC3610_SNIFF_SR_100Hz      = 0b1000,
    MC3610_SNIFF_SR_200Hz      = 0b1001,
    MC3610_SNIFF_SR_400Hz      = 0b1010,// only for OSR = 32
    MC3610_SNIFF_SR_END,
}   mc3610_sniff_sr_t;

typedef enum
{
    MC3610_FIFO_CONTROL_DISABLE = 0,
    MC3610_FIFO_CONTROL_ENABLE,
    MC3610_FIFO_CONTROL_END,
}   mc3610_fifo_control_t;

typedef enum
{
    MC3610_FIFO_MODE_NORMAL = 0,
    MC3610_FIFO_MODE_WATERMARK,
    MC3610_FIFO_MODE_END,
}   mc3610_fifo_mode_t;

typedef struct
{
    unsigned char    bWAKE;              // Sensor wakes from sniff mode.
    unsigned char    bACQ;               // New sample is ready and acquired.
    unsigned char    bFIFO_EMPTY;        // FIFO is empty.
    unsigned char    bFIFO_FULL;         // FIFO is full.
    unsigned char    bFIFO_THRESHOLD;    // FIFO sample count is equal to or greater than the threshold count.
    unsigned char    bRESV;
    unsigned char    baPadding[2];
}   MC3610_InterruptEvent;


class MC3610{
 public:

  /* general accel methods */
  bool start();      // begin measurements
  void stop();       // end measurments
  void SetMode(mc3610_mode_t);
void SetRangeCtrl(mc3610_range_t);
void SetResolutionCtrl(mc3610_resolution_t);
void SetCWakeSampleRate(mc3610_cwake_sr_t);
mc3610_sniff_sr_t GetSniffSampleRate(mc3610_sniff_sr_t);
mc3610_resolution_t GetResolutionCtrl(void);
mc3610_range_t GetRangeCtrl(void);
mc3610_cwake_sr_t GetCWakeSampleRate(void);
mc3610_acc_t readRawAccel(void);

 private:
  short x, y, z;
mc3610_acc_t AccRaw; // Raw Accelerometer data
uint8_t readRegister8(uint8_t reg);
int16_t readRegister16(uint8_t reg);
void readRegisters(uint8_t reg, uint8_t *buffer, uint8_t len);
bool readRegisterBit(uint8_t reg, uint8_t pos);
void writeRegisterBit(uint8_t reg, uint8_t pos, bool state);
void writeRegister16(uint8_t reg, int16_t value);
void writeRegister8(uint8_t reg, uint8_t value);    
};
#endif


Note all the wonderful definitions.

The main cpp program.

#include
#include
#include
#include
#include
#include
#include

#include "MC3610.h"

#define MC3610_CFG_I2C_ADDR     (0x6C)
#define MC3610_CFG_MODE_DEFAULT     MC3610_MODE_STANDBY
#define MC3610_CFG_SAMPLE_RATE_CWAKE_DEFAULT    MC3610_CWAKE_SR_DEFAULT_50Hz
#define MC3610_CFG_SAMPLE_RATE_SNIFF_DEFAULT    MC3610_SNIFF_SR_0p4Hz
#define MC3610_CFG_RANGE_DEFAULT         MC3610_RANGE_8G
#define MC3610_CFG_RESOLUTION_DEFAULT     MC3610_RESOLUTION_14BIT
#define MC3610_CFG_ORIENTATION_MAP_DEFAULT     ORIENTATION_TOP_RIGHT_UP

int i2c_write(int file,
                            unsigned char addr,
                            unsigned char *obuff,
                            unsigned int len);

int i2c_rw(int file,
                            unsigned char addr,
                            unsigned char *obuff,
                            unsigned int olen,
                            unsigned char*ibuff,
                            unsigned int ilen);

void i2c_close(int file);
int i2c_init();
int i2c_file = 0;
extern int i2c_file;


uint8_t CfgRange, CfgResolution;

 // Read register bit
int readRegisterBit(uint8_t reg, uint8_t pos)
{
    uint8_t value;
    int readRegister8(int reg);
    value = readRegister8(reg);
    return ((value >> pos) & 1);
}

// Write register bit
void writeRegisterBit(uint8_t reg, uint8_t pos, bool state)
{
    uint8_t value;
    value = readRegister8(reg);

    if (state)
        value |= (1 << pos);
    else
        value &= ~(1 << pos);

    writeRegister8(reg, value);
}

// Read 8-bit from register
uint8_t readRegister8(uint8_t reg)
{
    uint8_t value;
   
unsigned char ottbuff[]={0x00};

unsigned char ibuffer[248];
i2c_rw(i2c_file,reg,ottbuff,1,ibuffer,1);
        value = ibuffer[0];

    return value;
}          

// Write 8-bit to register
void writeRegister8(uint8_t reg, uint8_t value)
{
    unsigned char outbuf[] = {value};
     
     i2c_write(i2c_file,reg,outbuf,1);

}

// Read 16-bit from register
int16_t readRegister16(uint8_t reg)
{
    int16_t value;
    int i2c_file;
    uint8_t vha[2];
    unsigned char ottbuff[]={0x00};

    i2c_rw(i2c_file,reg,ottbuff,1,vha,2);    

    value = vha[0] << 8 | vha[1];
    return value;
}



// Repeated Read Byte(s) from register
void readRegisters(uint8_t reg, uint8_t *buffer, uint8_t len)
{
        unsigned char ottbuff[]={0x00};

i2c_rw(i2c_file,reg,ottbuff,1,buffer,len);

        

}
  
//Set the operation mode  
void SetMode(mc3610_mode_t mode)
{
    uint8_t value;
    value = readRegister8(MC3610_REG_MODE_C);
    value &= 0b11110000;
    value |= mode;
    writeRegister8(MC3610_REG_MODE_C, value);
}

//Set the range control
void SetRangeCtrl(mc3610_range_t range)
{
    uint8_t value;    
    CfgRange = range;
    SetMode(MC3610_MODE_STANDBY);
    value = readRegister8(MC3610_REG_RANGE_C);
    value &= 0b00000111;
    value |= (range << 4)&0x70 ;
    writeRegister8(MC3610_REG_RANGE_C, value);
}

//Set the resolution control
void SetResolutionCtrl(mc3610_resolution_t resolution)
{
     uint8_t value;
     CfgResolution = resolution;
     SetMode(MC3610_MODE_STANDBY);
     value = readRegister8(MC3610_REG_RANGE_C);
     value &= 0b01110000;
     value |= resolution;
     writeRegister8(MC3610_REG_RANGE_C, value);
}

//Set the sampling rate
void SetCWakeSampleRate(mc3610_cwake_sr_t sample_rate)
{
     uint8_t value;
     SetMode(MC3610_MODE_STANDBY);
     value = readRegister8(MC3610_REG_WAKE_C);
     value &= 0b00000000;
     value |= sample_rate;
     writeRegister8(MC3610_REG_WAKE_C, value);
}

//Get the output sampling rate
mc3610_cwake_sr_t GetCWakeSampleRate(void)
{
/* Read the data format register to preserve bits */
     uint8_t value;
     value = readRegister8(MC3610_REG_WAKE_C);
     value &= 0b00000111;
     return (mc3610_cwake_sr_t) (value);
}

//Get the range control
mc3610_range_t GetRangeCtrl(void)
{
  /* Read the data format register to preserve bits */
     uint8_t value;
     value = readRegister8(MC3610_REG_RANGE_C);
     value &= 0x70;
     return (mc3610_range_t) (value >> 4);
}

//Get the range control
mc3610_resolution_t GetResolutionCtrl(void)
{
  /* Read the data format register to preserve bits */
     uint8_t value;
     value = readRegister8(MC3610_REG_RANGE_C);
     value &= 0x07;
     return (mc3610_resolution_t) (value);
}

//Initialize the MC3610 sensor and set as the default configuration
bool start(void)
{
     
     
     SetMode(MC3610_MODE_STANDBY);
     /* Check I2C connection */
     uint8_t id = readRegister8(MC3610_REG_PROD);
     if (id != 0x70)
     {
     /* No MC3610 detected ... return false */

      return false;
      }
      SetRangeCtrl(MC3610_RANGE_8G); //Range: 8g
      SetResolutionCtrl(MC3610_RESOLUTION_14BIT); //Resolution: 14bit
      SetCWakeSampleRate(MC3610_CWAKE_SR_DEFAULT_50Hz); //Sampling Rate: 50Hz
      SetMode(MC3610_MODE_CWAKE); //Mode: Active
      return true;
}

void stop()
{
      SetMode(MC3610_MODE_SLEEP); //Mode: Sleep
}

//Read the raw counts and SI units mearsurement data
mc3610_acc_t readRawAccel(void)
{
      float faRange[5] = { 19.614f, 39.228f, 78.456f, 156.912f, 117.684f}; //{2g, 4g, 8g, 16g, 12g}
      float faResolution[6] = { 32.0f, 64.0f, 128.0f, 512.0f, 2048.0f, 8192.0f}; //{6bit, 7bit, 8bit, 10bit, 12bit, 14bit}

      uint8_t rawData[6];
      readRegisters(MC3610_REG_XOUT_LSB, rawData, 6);  // Read the six raw data registers into data array
      x = (short)((((unsigned short)rawData[1]) << 8) | rawData[0]);
      y = (short)((((unsigned short)rawData[3]) << 8) | rawData[2]);
      z = (short)((((unsigned short)rawData[5]) << 8) | rawData[4]);

      AccRaw.XAxis = (short) (x);
      AccRaw.YAxis = (short) (y);
      AccRaw.ZAxis = (short) (z);
      AccRaw.XAxis_g = (float) (x) / faResolution[CfgResolution]*faRange[CfgRange];
      AccRaw.YAxis_g = (float) (y) / faResolution[CfgResolution]*faRange[CfgRange];
      AccRaw.ZAxis_g = (float) (z) / faResolution[CfgResolution]*faRange[CfgRange];

      return AccRaw;
}

The modified i2crw header file

#ifndef I2CRW
#define I2CRW

// for intel edison i2c breakout board from spakfun.com
#define I2C_FILE_NAME "/dev/i2c-1"



/**
 *  Perform a simple I2C write
 * @param addr address of the slave device
 * @param *obuff array of bytes to be writen to the slave device
 * @param len length / number of bytes to be writen to the slave device
 *  @return 1 for pass 0 fail, prints error on STDOUT
 */
int i2c_write(int file,
                            unsigned char addr,
                            unsigned char *obuff,
                            unsigned int len);

/**
 * Performs a mixed transaction I2C, START | WRITE | REPEATED START | READ
 * @param addr address of the slave device
 * @param *obuff array of bytes to be writen to the slave device
 * @param olen length / number of bytes to be writen to the slave device
 * @param *ibuff array to store the received bytes
 * @param ilen length / number of bytes to receive
 * @return 1 for pass 0 fail, prints error on STDOUT
 */
int i2c_rw(int file,
                            unsigned char addr,
                            unsigned char *obuff,
                            unsigned int olen,
                            unsigned char*ibuff,
                            unsigned int ilen);

/**
 * Initilize the I2C interface and returns a number for the file system handle
 */

int i2c_init();

/**
 * close / release resource for the I2C bus
 * @param file the file system handle for the initilazed I2C
 */
void i2c_close(int file);




#endif

and finally the main program, still using the mc3610 header.

/**
 * @file   i2crw.c
 * @Author sajin.geo@gmail.com
 * @date   Jan 12th 2015
 * @brief  API for using the repeated start feature of the I2C bus
 *
 * Wraper library to i2c. based on the work of Sean Cross / chumby industries , Copyright (c) 2010, released under BSD licence
 */
#include
#include
#include
#include
#include
#include
#include
#include
#include "i2crw.h"

extern int i2c_file;

int i2c_write(int file,
                            unsigned char addr,
                            unsigned char *obuff,
                            unsigned int len) {
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg messages[1];
    messages[0].addr  = addr;
    messages[0].flags = 0;
    messages[0].len   = len;
    messages[0].buf   = obuff;
    
    packets.msgs  = messages;
    packets.nmsgs = 1;
    if(ioctl(file, I2C_RDWR, &packets) < 0) {
            perror("Unable to send W data");
            return 1;
    }
    return 0;
}

int i2c_rw(int file,
                            unsigned char addr,
                            unsigned char *obuff,
                            unsigned int olen,
                            unsigned char*ibuff,
                            unsigned int ilen) {
struct i2c_rdwr_ioctl_data packets;
   struct i2c_msg messages[2];
   
   // tx message
   messages[0].addr  = addr;
   messages[0].flags = 0;
   messages[0].len   = olen;
   messages[0].buf   = obuff;
   
   // rx message
   messages[1].addr  = addr;
   messages[1].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
   messages[1].len   = ilen;
   messages[1].buf   = ibuff;
   
   packets.msgs      = messages;
   packets.nmsgs     = 2;
   
   if(ioctl(file, I2C_RDWR, &packets) < 0) {
           perror("Unable to send RW data");
           return 1;
       }
    return 0;
}


int i2c_init()
{

if ((i2c_file = open(I2C_FILE_NAME, O_RDWR)) < 0) {
       perror("Unable to open i2c control file");
       exit(1);
   }
return 0;
}


void i2c_close(int file)
{
close(file);
}



//int main(int argc, char **argv) {
// int i2c_file,i=0;
// unsigned char ottbuff[]={0x00};
// unsigned char outbuf[]={0x00,0xB8,0x0D,0x1F,0x74};
// unsigned char ibuffer[248];
// // Open I2C file
// i2c_file = i2c_init();
//
//
//
// if(i2c_write(i2c_file,0x60,outbuf,5))
// {
// printf("unable to write to register");
// }
//
// if (i2c_rw(i2c_file,0x50,ottbuff,1,ibuffer,248))
// {
// printf("unable to read from register");
// }
// for (i=0;i<=248;i++)
// {
// printf("%x ",ibuffer[i]);
// }
//
// i2c_close(i2c_file);
//    return 0;
//}


Note that it has a sample 'main' which I used in mc3610

I've compiled each of these into '.o' object files, and I'm about to mash the thing together.  Maybe I'll do it tomorrow.  I had hoped to work slow enough that somebody would take this on, but nobody seems to have the board yet.  

When is works (yeah!), the mc3610 should be much more sensitive than two adxl345 summed together, I hope.  I did learn a lot about c++, more than I want to know.  :)

ps.  It turns out that driver is total b*llsh*t.  It sends the wrong values.  I got through using manual bit-banging and am now writing that in Python.

3 comments:

CB said...

You have a couple of spots in the pasting with a bunch of empty include statements; ioctl.h is one I think, what else is missing?

Harold Asmis said...

Nothing is missing. ioctl is a system library which does the direct bit transfer.

CB said...

Everything in angle brackets after the include statements doesn't show up in your post, probably because browsers are seeing the brackets as format characters (I've tried Firefox and the default Pi browser). I've got them from the github links though, and hopefully I can give this a try over the weekend.