Thursday, October 15, 2009

ADXL345 at full speed (3.2kHz) on an arduino

The ADXL345 is a wonderful accelerometer chip. Available bare from digikey, or easily attached from SparkFun:
http://www.sparkfun.com/commerce/product_info.php?products_id=9156 NOTE IT IS 3.3V ONLY.

If you don't use long wires, the pullups on the arduino suffice. I use INT1 to indicate data available.

It can mount easily on a pro-mini since the scl and sda pins align (though if you use headers the plastic can be a problem - but it also aligns the pins). You need to run 3.3v and ground to the board:



I've been trying to get the full speed, full resolution, on the Arduino. And I've succeeded, but I had to use polling instead of interrupts. I need INT1 since it takes a while to get the FIFO status (it would work if I knew I needed to get N sets of samples, but the extra wire is not a problem in most cases - but that is what the readreg routine is for - to read the FIFO status).

I'm also trying to output base64, but it is just slightly too slow at the moment.

Code follows (uses master makefile):



/*
GPL V3. Copyright 2009, tz.
Max speed (3.2khz) ADXL345 accelerometer
INT1 pin to D2, sda and scl to proper pins on a 3.3v arduino, 8mhz.
1000000 baud UART, A5A5 are sync bytes.
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

#define CMDSTART ((1<<TWINT)|(1<<TWSTA)|(1<<TWEN))
#define CMDXFER ((1<<TWINT)|(1<<TWEN))
#define CMDXFERACK ((1<<TWINT)|(1<<TWEN)|(1<<TWEA))
#define CMDSTOP ((1<<TWINT)|(1<<TWEN)|(1<<TWSTO))

#define STASTART 0x08
#define STAREPSTART 0x10
#define STASLAWACK 0x18
#define STASLAWNAK 0x20
#define STADATWACK 0x28
#define STADATWNAK 0x30
#define LOSTARB 0x38
#define STASLARACK 0x40
#define STASLARNAK 0x48
#define STADATRACK 0x50
#define STADATRNAK 0x58

unsigned char readreg(unsigned char reg)
{
do {
TWCR = CMDSTART;
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xf8) != STASTART)
break;
TWDR = 0x3a; //0x3a;//a6; // write
TWCR = CMDXFER;
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xf8) != STASLAWACK)
break;
TWDR = reg; // register
TWCR = CMDXFER;
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xf8) != STADATWACK)
break;
TWCR = CMDSTART;
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xf8) != STAREPSTART)
break;
TWDR = 0x3b; //0x3b;//a7; // read
TWCR = CMDXFER;
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xf8) != STASLARACK)
break;
TWCR = CMDXFER;
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xf8) != STADATRNAK)
break;
} while (0);
unsigned char r = TWDR;
TWCR = CMDSTOP;
return r;
}

void writereg(unsigned char reg, unsigned char val)
{
do {
TWCR = CMDSTART;
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xf8) != STASTART)
break;
TWDR = 0x3a; //0x3a;//a6; // write
TWCR = CMDXFER;
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xf8) != STASLAWACK)
break;
TWDR = reg; // register
TWCR = CMDXFER;
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xf8) != STADATWACK)
break;
TWDR = val;
TWCR = CMDXFER;
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xf8) != STADATWACK)
break;
} while (0);
TWCR = CMDSTOP;
}

unsigned char read6(unsigned char *buf)
{
unsigned char j = 0;
UDR0 = 0xa5;

TWCR = CMDSTART;
while (!(TWCR & (1 << TWINT)));
TWDR = 0x3a; //0x3a;//a6; // write
TWCR = CMDXFER;
while (!(TWCR & (1 << TWINT)));
TWDR = 0x32; // register
TWCR = CMDXFER;
UDR0 = 0xa5;
while (!(TWCR & (1 << TWINT)));
TWCR = CMDSTART;
while (!(TWCR & (1 << TWINT)));
TWDR = 0x3b; //0x3b;//a7; // read
TWCR = CMDXFER;
while (!(TWCR & (1 << TWINT)));
for (j = 0; j < 6; j++) {
TWCR = j == 5 ? CMDXFER : CMDXFERACK;
while (!(TWCR & (1 << TWINT)));
UDR0 = TWDR;
}
TWCR = CMDSTOP;
return j;
}

int main(void)
{
DDRB = 0x20;
PORTB &= ~0x20; // LED

// Crystal based: xtal/16 or xtal/8 for clk / baud - 1
#define BAUD (57600)
UBRR0 = 0; //1000000 / BAUD - 1;

UCSR0C = 0x06; //8N1 (should be this from reset)
UCSR0A = 0xE2; // clear Interrupts, UART at 2x (xtal/8)
UCSR0B = 0x18; // oring in 0x80 would enable rx interrupt

PORTC |= 0x30;
TWCR = CMDSTOP;
TWAMR = TWDR = TWAR = 0;
DDRD = 0; // input
PORTD = 4; //pull up

TWSR = 0;
TWBR = 2; //2

sei();

unsigned char i;

i = readreg(0);

writereg(0x2d, 8);
writereg(0x2c, 0x0f);
writereg(0x31, 0xb);
writereg(0x38, 0x9f);
writereg(0x2e, 0x80); // data available on int 1

unsigned char buf[6];
for (;;) {
PORTB |= 0x20; // LED
if (!(PIND & 4)) // no more data, wait for edge to interrupt
continue;
PORTB &= ~0x20; // LED
i = read6(buf);
}
sleep_mode();
}

17 comments:

  1. Hi there,
    Thanks for the code and the photo.
    Would you please also explain the connections or put a schematic up?
    I want to make sure what is connected to what. Actually I want to test on a full arduino then switch to a mini or nano board.
    Thanks again,
    Ali

    ReplyDelete
  2. I will try for a diagram shortly (I'm in the middle of a big project), but basically the accelerometer is setup to the default address in I2C mode. This requires some of the pins to be pulled to vcc which I did on the board itself. I would need to check the datasheet, but it says which pins set the mode and address.

    4 connections are required - SDA, SCL (from PORTD on the 168 and 328, bits 4 and 5 - I forgot which one is SDA and SCL but that is in the datasheet), and 3.3v which I get off vcc on the arduino, and ground.

    ReplyDelete
  3. I'll see if I can add a pic, but to describe it:

    On the ADXL345 breakout, you have

    GND VCC CS INT1 INT2 SDO SDA SCL

    You need to pull up CS and SDA - you can tie them to VCC. The interrupt outputs still work, they just need to go to an input pin. The SDA controls the address - grounding it makes it the alternate address from the datasheet.

    On a 3.3v Arduino Pro, I connect a ground and the VCC pin to the first two. I connect the two inside pins (I put a right-angle connector) to SDA (to PC4 - AD4) and SCL (to PC5 - AD5).

    ReplyDelete
  4. Thanks, I've been busy, this weekend I'll give it another go.

    ReplyDelete
  5. Hello TZ, I've been using the ADXL345 with an Arduino Uno using I2C as well but I'm unable to achieve such speeds. I use Wire library (the TWI functions I wrote don't seem to work) and it takes approximately 1ms for each read!

    I also use the polling method, and I also have an ITG3200 on the bus and a HMC5843 in the future (having problems talking to that particular sensor).

    I need to cut the read times by as much as possible so I can run the main loop at a higher frequency. Could you give me some tips on how this can be done?

    The details (including code I use) can be found on the Arduino forum. Here's the link: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1291567011

    Thank you!

    ReplyDelete
  6. You cannot use the wire library to go at the fastest rate. It does a bit-bang which slows it.

    I have some sample code at http://github.com/tz1 in my sparkfun section, https://github.com/tz1/sparkfun/tree/master/i2cperiph which has C examples that send to the serial port for many parts.

    Note you might need a really high baud rate on the FTDI.

    ReplyDelete
  7. This is great info, and I apologize for my noobish questions.

    What exactly is using bit bang? the wire lib or the adxl345? I read ( on a forum ) that as long as analog 4 and 5 are used the wire lib will not bit bang.

    I'm not sure if its the lack of type definitions or if the interrupt lib defines it ( I've never used it ) but how does PIND work? I assume thats the interrupt.

    I will be trying this out soon..

    ReplyDelete
  8. They may have updated the wire library from when I wrote this. Originally it did I2C by setting and clearing bits in the port registers in the I2C pattern (setting port pins is bit banging) - it would do this even when you specified the TWI pins and I don't think it was interrupt driven itself (it might use the TWI but would poll instead of returning).

    Each port has 3 registers. DDRx controls the direction, input or output. PORTx is the image of the output register or controls pullups if the port is set to input. PINx is the image of the state of the pins. POrt POutput, PIN PInput.

    Data will cause the pin to change, and I'm using an edge detect interrupt, so I have to repeat until the pin indicating data is available returns to the inactive state when I've emptied the buffers since new or more data might be available since the original interrupt.

    ReplyDelete
  9. Jason Wylie ( mite51@hotmail.com )January 21, 2011 at 10:58 PM

    Finally got your code to work after converting to Arduino IDE. It probably would have run straight away but with a combination of me having the wrong address and the connections being loose between the breadboard and the breakout it took some debugging. In any case I now know a lot more about I2C that a week ago.

    I have a question. In read6 you set TWDR = 0x3b; why is that, I know its the (device_address << 1) + 1 but why?

    So the good news, I made some timing comparisions between your code and WireLib ( both in I2C fastmode:
    -read6 - 224 micros
    -writereg - 80 micros
    -(Wire)writereg(start, addr, reg, value, stop) 120 micros
    -(Wire)read6 - 376 micros

    The posted I2C code is faster, but still not fast enough on my duemilanove, the INT1 is high constantly, thats with doing nothing but reading ( no serial out ). I think to do any better I need a faster chip using SPI, the datasheet seems to suggest a 5Mhz operation as being optimal.

    I also found this (http://pythonicle.net/svn/projects/teensy/accel/i2c.c) code. Which looks suspiciously similar to the code here ;)


    anyway, here's my slightly altered code, if anyone cares to see it. Strangly I couldn't get the attachInterrupt callback to work, but I think because INT1 is always HIGH and the attachInterrupt doesnt support HIGH as a trigger state.

    #include

    #include

    #define DEVICE (0x53) //ADXL345 device address //A6
    #define DEVICE2 (0x1D) //ADXL345 device address //3A

    #define FAST_I2C_SPEED 400000L

    #define TO_READ (6) //num of bytes we are going to read each time (two bytes for each axis)
    byte buf[TO_READ] ; //6 bytes buffer for saving data read from the device
    char str[99]; //string buffer to transform data before sending it to the serial port
    float X, Y, Z;
    int zeroX, zeroY, zeroZ;
    int rawX, rawY, rawZ;
    unsigned long timeSinceLastRead;


    #define CMDSTART ((1< 0 )
    {
    int incomingByte = Serial.read();

    switch ( incomingByte )
    {
    case 'p':
    //print state
    //we send the x y z values as a string to the serial port
    //snprintf(str, 99, "%.3f %.3f %.3f\n", X, Y, Z); //%f doesnt work in arduino land
    Serial.print(X);
    Serial.print(" ");
    Serial.print(Y);
    Serial.print(" ");
    Serial.print(Z);
    Serial.print("\n");
    break;
    case 'o':
    //print raw values
    //we send the x y z values as a string to the serial port
    snprintf(str, 99, "%d %d %d\n", rawX, rawY, rawZ);
    Serial.print(str);
    break;
    case 'c':
    //calibrate
    zeroX = rawX;
    zeroY = rawY;
    zeroZ = rawZ;
    break;
    case 'r':
    //reset
    X = 0.0f;
    Y = 0.0f;
    Z = 0.0f;
    break;
    }
    bytesAvailable--;
    }

    }

    ReplyDelete
  10. Jason Wylie ( mite51@hotmail.com )January 21, 2011 at 10:59 PM

    Looks like my code got cut up :(

    ReplyDelete
  11. How fast is your serial port? I needed 2 megabaud (the fdti will go this fast) in order to get data from ADXL as text at full speed.

    ReplyDelete
  12. 115200, I didn't realize it could go faster. In any case I do the math integrations on the arduino so I don't need to send nearly as much data. similar ( I think ) to how the new IMU3000 works

    ReplyDelete
  13. It can't (without a crystal change) get close to higher multiples like 912600, but it can do 500k 1M and 2M - at 8Mhz it can do 1M (an FTDI can do 3M).

    115k is only about 11 characters per millisecond, Count the digits and spaces you are sending.

    ReplyDelete
  14. Would you please help with a code that will read the accelerometer ADXL345 as fast as possible and display on the computer.

    ReplyDelete
  15. It can be read at maximum speed using I2C and that is what the code does. I haven't done any SPI code for the ADXL345, but you could use the SPI on the microcontroller.

    ReplyDelete