how to use SoftWire library to retrieve data from ADXL345 IMU with an ATTiny85

Hello!

I'm developing an IMU/Accelerometer breakout board using an ADXL345 3-axis accelerometer and an ATTiny85, which will hopefully allow me to put many modules on a single bus. I am using the TinyWireS library to communicate between the ATTiny85 and an arduino Uno, which is working fine. I am attempting to use the SoftWire library to retrieve the sensor values from the ADXL345. Unfortunately, the documentation on the SoftWire Library is not very good, and I'm not sure how to implement it. When I do the read and output the individual bytes, they are all reading 255, so something is overwriting the default values for my data. I adapted the script that cycles through all the addresses and finds the device, which gives me 0x53, which is the correct address for the accelerometer, so I'm pretty sure it's not a wiring problem or broken component. Here's the basic code, please let me know what I'm doing wrong.

#include "TinyWireS.h"
#include <SoftWire.h>
#include <AsyncDelay.h>

#define SLAVE_ADDR 0x27
#define IMU_ADDR 0x53
SoftWire I2C_IMU(PB4, PB3);                             //SoftWire sw(SDA, SCL);

unsigned char data[6] = {1, 2, 3, 4, 5, 6};

void setup()
{
  TinyWireS.begin(SLAVE_ADDR);
  TinyWireS.onRequest(requestEvent);

  IMU_INIT();
}

void loop()
{
  updateVals();
}

void requestEvent()
{
  for (int i = 0; i < sizeof(data); i++)
    TinyWireS.write(data[i]);                     // send it back to Uno
}

void updateVals()
{
  I2C_IMU.startWait(IMU_ADDR, SoftWire::writeMode);
  I2C_IMU.write(0x32);                                                        // X0 byte start of multi-byte read
  I2C_IMU.repeatedStart(IMU_ADDR, SoftWire::readMode);
  for (int i = 0; i < 6; i++) data[i] = I2C_IMU.read();             // read 6 bytes
  I2C_IMU.stop();
  delayMicroseconds(10);
}

void IMU_INIT()
{
  I2C_IMU.setTimeout_ms(40);
  I2C_IMU.beginTransmission(IMU_ADDR);
  I2C_IMU.write(0x2D);                                // power control register
  I2C_IMU.write(B00001000);                       // turn on measure bit
  I2C_IMU.endTransmission();
}

Thanks for any help you can offer!!

In the updateVals() function, I also tried various configurations of beginTransmission(), write(), requestFrom() etc, such as this:

void updateVals()
{
  I2C_IMU.beginTransmission(IMU_ADDR);
  I2C_IMU.write(0x32);       // X0 byte
  I2C_IMU.endTransmission();
  
  I2C_IMU.requestFrom(IMU_ADDR, 6, 0);
  for (int i = 0; i < 6; i++) data[i] = I2C_IMU.read(); // read 6 bytes
  I2C_IMU.stop();
  delayMicroseconds(10);
}

with the same results...

I'm developing an IMU/Accelerometer breakout board using an ADXL345 3-axis accelerometer and an ATTiny85, which will hopefully allow me to put many modules on a single bus. I am using the TinyWireS library to communicate between the ATTiny85 and an arduino Uno, which is working fine. I am attempting to use the SoftWire library to retrieve the sensor values from the ADXL345.

Please explain why you think you need the ATtiny in this setup. The UNO can read the ADXL345 itself.

If you must use this hardware setup, the code in the second post is much better, the one in the first post is wrong. But even in the second version you don't check the return values, so you don't see errors.

Did you activate the I2C interface of the chip? Post a wiring diagram. If you use a breakout board, post a link to it (better to the schematics).

Hi pylon,

thanks for the reply. I need to have 8-12 sensors on a single bus, and they need to be as minimal as possible. The sensor itself only has two possible addresses, so it gets very messy very quickly if I dont use the ATTiny to enable custom i2c addressing.

The schematic and board diagrams are attached. I am essentially making my own breakout board, because I couldn't find any with more than 2 possible addresses.

According to the datasheet, all that should be required for I2C is tying the chip select pin to high, which I have done. I will change the code to include the return values of the SoftWire functions. Maybe I can identify where the problem is coming from. I will post the code tomorrow whether I figure it out or not :smiley:

Best

well it looks like for some reason my board diagram didn't want to upload... here is a jpg version

Hello again!

So i did some minor modifications to the code found on the MIT: How to Make Almost Anything website, and I got it working. It isn't even a library, just the raw functions for a Bit-Bang approach. I'm guessing it doesn't support bit stretching and may lack some other functionality, but hey better than nothing.

here is my code:

#include "TinyWireS.h"
//
// hello.ADXL343.c
//
// ADXL343 accelerometer hello-world
//    9600 baud FTDI interface
//
// Neil Gershenfeld 11/8/15
// (c) Massachusetts Institute of Technology 2015
//
// This work may be reproduced, modified, distributed,
// performed, and displayed for any purpose. Copyright is
// retained and must be preserved. The work is provided
// as is; no warranty is provided, and users accept all
// liability.
//

#include <avr/io.h>
#include <util/delay.h>

#define output(directions,pin) (directions |= pin) // set port direction for output
#define input(directions,pin) (directions &= (~pin)) // set port direction for input
#define set(port,pin) (port |= pin) // set port pin
#define clear(port,pin) (port &= (~pin)) // clear port pin
#define pin_test(pins,pin) (pins & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define bit_delay_time 102 // bit delay for 9600 with overhead
#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay

#define I2C_slave_address 0x53 // ADXL345 alt address
#define I2C_delay() _delay_us(5)
#define SCL_pin (1 << PB3)
#define SCL_pins PINB
#define SCL_port PORTB
#define SCL_direction DDRB
#define SDA_pin (1 << PB4)
#define SDA_pins PINB
#define SDA_port PORTB
#define SDA_direction DDRB


#define SLAVE_ADDR 0x27
unsigned char data[6] = {0, 1, 2, 3, 4, 5};
unsigned char ret;

void requestEvent()
{
  for (int i = 0; i < sizeof(data); i++)
    TinyWireS.write(data[i]);           // send it back to master
}

void setup() {
  TinyWireS.begin(SLAVE_ADDR);
  TinyWireS.onRequest(requestEvent);
  //
  // set clock divider to /1
  //
  CLKPR = (1 << CLKPCE);
  CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
  //
  // main loop
  //
  I2C_init();
  data[0] = 0x2D; // POWER_CTL register
  data[1] = 8; // turn on measure bit
  ret = I2C_master_write(data, 2, I2C_slave_address);
}
void loop() {
  //
  // read data
  //
  data[0] = 0x32; // X0 register
  ret = I2C_master_write(data, 1, I2C_slave_address);
  ret = I2C_master_read(data, 6, I2C_slave_address);
}


void SCL_write(char bit) {
  //
  // write SCL bit
  //
  if (bit == 0) {
    output(SCL_direction, SCL_pin);
    clear(SCL_port, SCL_pin);
  }
  else {
    input(SCL_direction, SCL_pin);
    while (pin_test(SCL_pins, SCL_pin) == 0); // check for clock stretching
  }
}

void SDA_write(char bit) {
  //
  // write SDA bit
  //
  if (bit == 0) {
    output(SDA_direction, SDA_pin);
    clear(SDA_port, SDA_pin);
  }
  else
    input(SDA_direction, SDA_pin);
}

void I2C_init() {
  //
  // initialize I2C lines
  //
  SDA_write(1);
  SCL_write(1);
}

char I2C_master_write_byte(unsigned char byte) {
  //
  // master write I2C byte
  //
  unsigned char bit;
  //
  // loop over bits
  //
  for (bit = 0; bit < 8; ++bit) {
    if ((byte & 0x80) == 0)
      SDA_write(0);
    else
      SDA_write(1);
    SCL_write(1);
    I2C_delay();
    SCL_write(0);
    I2C_delay();
    byte <<= 1;
  }
  //
  // check for ACK
  //
  SDA_write(1);
  SCL_write(1);
  I2C_delay();
  if (pin_test(SDA_pins, SDA_pin) != 0) {
    //
    // no ACK, return 1
    //
    return 1;
  }
  //
  // yes ACK, return 0
  //
  SCL_write(0);
  I2C_delay();
  return 0;
}

char I2C_master_write(unsigned char* data, unsigned char nbytes, unsigned char slave_address) {
  //
  // I2C master write
  //
  unsigned char index, ret, slave_address_write;
  //
  // send start
  //
  SDA_write(0);
  I2C_delay();
  SCL_write(0);
  I2C_delay();
  //
  // send slave address
  //
  slave_address_write = slave_address << 1;
  if (I2C_master_write_byte(slave_address_write) != 0)
    //
    // no ACK, return 1
    //
    return 1;
  //
  // loop over bytes
  //
  for (index = 0; index < nbytes; ++index) {
    ret = I2C_master_write_byte(data[index]);
    if (ret != 0)
      //
      // no ACK, return 1
      //
      break;
    //
    // yes ACK, continue
    //
  }
  //
  // send stop
  //
  SCL_write(1);
  I2C_delay();
  SDA_write(1);
  I2C_delay();
  return ret;
}

void I2C_master_read_byte(unsigned char* data, unsigned char index, unsigned char nbytes) {
  //
  // master read I2C byte
  //
  unsigned char byte, bit;
  SDA_write(1);
  byte = 0;
  //
  // loop over bits
  //
  for (bit = 0; bit < 8; ++bit)  {
    SCL_write(1);
    I2C_delay();
    if (pin_test(SDA_pins, SDA_pin) != 0)
      byte |= (1 << (7 - bit));
    SCL_write(0);
    I2C_delay();
  }
  data[index] = byte;
  if (index < (nbytes - 1)) {
    //
    // not done, send ACK
    //
    SDA_write(0);
    SCL_write(1);
    I2C_delay();
    SCL_write(0);
    SDA_write(1);
    I2C_delay();
  }
  else {
    //
    // done, send NACK
    //
    SDA_write(1);
    SCL_write(1);
    I2C_delay();
    SCL_write(0);
    I2C_delay();
  }
}

char I2C_master_read(unsigned char* data, unsigned char nbytes, unsigned char slave_address) {
  //
  // I2C master read
  //
  unsigned char index, slave_address_read;
  //
  // send start
  //
  SDA_write(0);
  I2C_delay();
  SCL_write(0);
  I2C_delay();
  //
  // send slave address
  //
  slave_address_read = (slave_address << 1) + 1;
  if (I2C_master_write_byte(slave_address_read) == 1)
    //
    // no ACK, return 1
    //
    return 1;
  //
  // loop over bytes
  //
  for (index = 0; index < nbytes; ++index)
    I2C_master_read_byte(data, index, nbytes);
  //
  // send stop
  //
  SCL_write(1);
  I2C_delay();
  SDA_write(1);
  I2C_delay();
  return 0;
}

I would still be interested in implementing a library that might be a bit more robust, so if you have any ideas, please let me know.

The sensor itself only has two possible addresses, so it gets very messy very quickly if I dont use the ATTiny to enable custom i2c addressing.

Do you know I2C multiplexers? PCA9548A is one example.

I need to have 8-12 sensors on a single bus, and they need to be as minimal as possible.

You do know that the maximum bus length of an I2C bus is about 0.5m, don't you? The multiplexer mentioned above splits the your bus into several buses but each of them still has this limitation. Bus extenders may be used but sooner or later such concepts get to complicated which usually results in poor reliability.