Attiny85 Peculiar I2C Problem

Hello All!
I am using an Attiny85 as an I2C driver for an MPU9150. In an effort to minimize code size I am trying to write the code in plain C.
I am using the USI_TWI_Master.c library. I have also incorporated the TinyWireM functions into my code to avoid adding the library to the makefile...

For the time being, I only have an LED to debug the attiny, but as best I can tell, any time I attempt to read from a specific register, the return value returned is the same as the register address. In this code, I am attempting to read to the WH0_AM_I register off the MPU9150 returns the register address (0x75) instead of the device address (0x68).

I have never used the USI before, and this is actually my first experience writing real C for an AVR. I have been trying to build the .hex file on my mint distro.

Here is the file containing my main function (sorry, the main function is at the bottom because I didn't want to make function prototypes). The code is configured to turn on the IO port to flash an LED if the I2C read returns the read address:

#include "USI_TWI_Master.h"
#include <avr/interrupt.h>
#define F_CPU 1000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <inttypes.h>

//Device I2C Address
#define MPU6050 0x68
#define AK8975  0x0C

//MPU6050 Configuration Registers
#define CONFIG       0x1A
#define GYRO_CONFIG  0x1B
#define ACCEL_CONFIG 0x1C
#define INT_PIN_CFG  0x37
#define USER_CTRL    0x6A
#define PWR_MGMT_1   0x6B
#define PWR_MGMT_2   0x6C

//MPU6050 Data Registers
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H   0x41
#define TEMP_OUT_L   0x42
#define GYRO_XOUT_H  0x43
#define GYRO_XOUT_L  0x44
#define GYRO_YOUT_H  0x45
#define GYRO_YOUT_L  0x46
#define GYRO_ZOUT_H  0x47
#define GYRO_ZOUT_L  0x48
#define WHO_AM_I     0x75

//AK8975 Configuration Registers
#define MAG_ST1     0x02
#define MAG_ST2     0x09
#define MAG_CNTL    0x0A

//AK8975 Data Registers
#define MAG_XOUT_L   0x03
#define MAG_XOUT_H   0x04
#define MAG_YOUT_L   0x05
#define MAG_YOUT_H   0x06
#define MAG_ZOUT_L   0x07
#define MAG_ZOUT_H   0x08

#define USI_SEND         0              // indicates sending to TWI
#define USI_RCVE         1              // indicates receiving from TWI
#define USI_BUF_SIZE    16              // bytes in message buffer



// Initialize Class Variables //////////////////////////////////////////////////
  uint8_t USI_Buf[USI_BUF_SIZE];             // holds I2C send and receive data
  uint8_t USI_BufIdx = 0;                    // current number of bytes in the send buff
  uint8_t USI_LastRead = 0;                  // number of bytes read so far
  uint8_t USI_BytesAvail = 0;                // number of bytes requested but not read

// Public Methods //////////////////////////////////////////////////////////////

void begin(void){ // initialize I2C lib
  USI_TWI_Master_Initialise();          
}

void beginTransmission(uint8_t slaveAddr){ // setup address & write bit
  USI_BufIdx = 0; 
  USI_Buf[USI_BufIdx] = (slaveAddr<<TWI_ADR_BITS) | USI_SEND; 
}

void send(uint8_t data){ // buffers up data to send
  if (USI_BufIdx >= USI_BUF_SIZE) return;         // dont blow out the buffer
  USI_BufIdx++;                                   // inc for next byte in buffer
  USI_Buf[USI_BufIdx] = data;
}

uint8_t endTransmission(void){ // actually sends the buffer
  uint8_t xferOK = FALSE;
  uint8_t errorCode = 0;
  xferOK = USI_TWI_Start_Read_Write(USI_Buf,USI_BufIdx+1); // core func that does the work
  USI_BufIdx = 0;
  if (xferOK) return 0;
  else {                                  // there was an error
    errorCode = USI_TWI_Get_State_Info(); // this function returns the error number
    return errorCode;
  }
}

uint8_t requestFrom(uint8_t slaveAddr, uint8_t numBytes){ // setup for receiving from slave
  uint8_t xferOK = FALSE;
  uint8_t errorCode = 0;
  USI_LastRead = 0;
  USI_BytesAvail = numBytes; // save this off in a global
  numBytes++;                // add extra byte to transmit header
  USI_Buf[0] = (slaveAddr<<TWI_ADR_BITS) | USI_RCVE;   // setup address & Rcve bit
  xferOK = USI_TWI_Start_Read_Write(USI_Buf,numBytes); // core func that does the work
  // USI_Buf now holds the data read
  if (xferOK) return 0;
  else {                                  // there was an error
    errorCode = USI_TWI_Get_State_Info(); // this function returns the error number
    return errorCode;
  }
}

uint8_t receive(void){ // returns the bytes received one at a time
  USI_LastRead++;     // inc first since first uint8_t read is in USI_Buf[1]
  return USI_Buf[USI_LastRead];
}

uint8_t available(void){ // the bytes available that haven't been read yet
  return USI_BytesAvail - (USI_LastRead); 
}


//Writes a single register on the I2C bus
uint8_t writeReg(uint8_t devAddr, uint8_t regAddr, uint8_t value){
  beginTransmission(devAddr);
  send(regAddr);
  send(value);
  return endTransmission();
}


//Reads a single register on the I2C bus
uint8_t readReg(uint8_t devAddr, uint8_t regAddr){
  uint8_t value;

  beginTransmission(devAddr);
  send(regAddr);
  endTransmission();
  beginTransmission(devAddr);
  requestFrom(devAddr, 1);
  value = receive();
  endTransmission();

  return value;
}

/*
Perfoms two's complement on an array of unsigned bytes.
Returns signed 2 byte value.
Can be used for Big & Little Endian encoded values.
*/
void twosComplement(uint8_t * in, uint8_t length, int16_t * out, uint8_t invert){
  uint8_t i;
  for ( i = 0; i < length; i++){
    if (invert == 0)
      out[i] = in[2 * i] << 8 | in[(2 * i) + 1];
    else
      out[i] = in[(2 * i) + 1] << 8 | in[2 * i];
  }
}

int main(void){
  begin();
  if(readReg(MPU6050, WHO_AM_I)==WHO_AM_I){
   DDRB = 0xFF;
	 PORTB = 0xFF;
  }
  return 0;
}

As far as I can tell, the attiny is running at an internal 1Mhz, although eventually I would like to use the internal 8Mhz...
Any ideas on this one??!!

Thanks gang!

makefile (11.2 KB)

USI_TWI_Master.c (13.3 KB)

tinyAHRS.c (4.66 KB)

You forgot the link to the library you're using.

I don't know the library but usually this routine:

uint8_t readReg(uint8_t devAddr, uint8_t regAddr){
  uint8_t value;

  beginTransmission(devAddr);
  send(regAddr);
  endTransmission();
  beginTransmission(devAddr);
  requestFrom(devAddr, 1);
  value = receive();
  endTransmission();

  return value;
}

should look like this:

uint8_t readReg(uint8_t devAddr, uint8_t regAddr){
  uint8_t value;

  beginTransmission(devAddr);
  send(regAddr);
  endTransmission();
  requestFrom(devAddr, 1);
  value = receive();

  return value;
}

although there still is no error checking so it might do the wrong thing.

It's also not a good idea set all outputs high in case you got that value, one single pin should be enough (remember that you have a device connected to some of these pins).

hmmmm....
I changed the "readReg" function

uint8_t readReg(uint8_t devAddr, uint8_t regAddr){
  uint8_t value;

  beginTransmission(devAddr);
  send(regAddr);
  endTransmission();
  requestFrom(devAddr, 1);
  value = receive();

  return value;

And changed my main function to only set the IO with the LED on it:

int main(void){
  begin();
  if(readReg(MPU6050, WHO_AM_I)==WHO_AM_I){
   DDRB |= 1 << 0x03;
   PORTB |= 1 << 0x03;
  }
  return 0;
}

However, the LED still turns on, indicating that the returned value from the I2C bus is equal to register address instead of the value stored to the registers address.

Also, if I unplug power from the sensor, or unplug the SDA and SCL bus, the LED still lights.
Why would there even BE a return value if there is no activity on the bus???

As for linking the library to the my "tinyAHRS.c" file, I thought the makefile was supposed to handle this?

I was struggling to understand the avr-ar function to link the .o object files. If someone had a good example that would be great.
Regardless, if the library was not properly linked, wouldn't I get compiler errors for calling functions that are not declared inside of my .c file?

I did write a couple bash scripts to generate .hex and .o files and to upload the .hex to the attiny85 using an arduino as an ISP.

avr_compiler.sh (374 Bytes)

avr_object.sh (371 Bytes)

attiny85_upload.sh (160 Bytes)

There is still no link to the library you used! I don't google for it just to find another version than you're using.

I attached the library to my first post. But here is the site hosting the USI_TWI library:
http://code.ohloh.net/project?pid=PJb5Asr8mwA&did=LedBlink&cid=hhNGq80HSMg&fp=52952&mp=&projSelected=true

I have changed the makefile and added the softuart library so I can do a little serial-debug. Although this has only confirmed my original suspicion: the readReg() function is returning the address of the register instead of the contents of the register. Even if it the sensor is not plugged in...

#include "USI_TWI_Master.h"
#include <avr/interrupt.h>
#define F_CPU 1000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <inttypes.h>
#include "softuart.h"

//Device I2C Address
#define MPU6050 0x68
#define AK8975  0x0C

//MPU6050 Configuration Registers
#define CONFIG       0x1A
#define GYRO_CONFIG  0x1B
#define ACCEL_CONFIG 0x1C
#define INT_PIN_CFG  0x37
#define USER_CTRL    0x6A
#define PWR_MGMT_1   0x6B
#define PWR_MGMT_2   0x6C

//MPU6050 Data Registers
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H   0x41
#define TEMP_OUT_L   0x42
#define GYRO_XOUT_H  0x43
#define GYRO_XOUT_L  0x44
#define GYRO_YOUT_H  0x45
#define GYRO_YOUT_L  0x46
#define GYRO_ZOUT_H  0x47
#define GYRO_ZOUT_L  0x48
#define WHO_AM_I     0x75

//AK8975 Configuration Registers
#define MAG_ST1     0x02
#define MAG_ST2     0x09
#define MAG_CNTL    0x0A

//AK8975 Data Registers
#define MAG_XOUT_L   0x03
#define MAG_XOUT_H   0x04
#define MAG_YOUT_L   0x05
#define MAG_YOUT_H   0x06
#define MAG_ZOUT_L   0x07
#define MAG_ZOUT_H   0x08

#define USI_SEND         0              // indicates sending to TWI
#define USI_RCVE         1              // indicates receiving from TWI
#define USI_BUF_SIZE    16              // bytes in message buffer



// Initialize Class Variables //////////////////////////////////////////////////
  uint8_t USI_Buf[USI_BUF_SIZE];             // holds I2C send and receive data
  uint8_t USI_BufIdx = 0;                    // current number of bytes in the send buff
  uint8_t USI_LastRead = 0;                  // number of bytes read so far
  uint8_t USI_BytesAvail = 0;                // number of bytes requested but not read

// Public Methods //////////////////////////////////////////////////////////////

void begin(void){ // initialize I2C lib
  USI_TWI_Master_Initialise();          
}

void beginTransmission(uint8_t slaveAddr){ // setup address & write bit
  USI_BufIdx = 0; 
  USI_Buf[USI_BufIdx] = (slaveAddr<<TWI_ADR_BITS) | USI_SEND; 
}

void send(uint8_t data){ // buffers up data to send
  if (USI_BufIdx >= USI_BUF_SIZE) return;         // dont blow out the buffer
  USI_BufIdx++;                                   // inc for next byte in buffer
  USI_Buf[USI_BufIdx] = data;
}

uint8_t endTransmission(void){ // actually sends the buffer
  uint8_t xferOK = FALSE;
  uint8_t errorCode = 0;
  xferOK = USI_TWI_Start_Read_Write(USI_Buf,USI_BufIdx+1); // core func that does the work
  USI_BufIdx = 0;
  if (xferOK) return 0;
  else {                                  // there was an error
    errorCode = USI_TWI_Get_State_Info(); // this function returns the error number
    return errorCode;
  }
}

uint8_t requestFrom(uint8_t slaveAddr, uint8_t numBytes){ // setup for receiving from slave
  uint8_t xferOK = FALSE;
  uint8_t errorCode = 0;
  USI_LastRead = 0;
  USI_BytesAvail = numBytes; // save this off in a global
  numBytes++;                // add extra byte to transmit header
  USI_Buf[0] = (slaveAddr<<TWI_ADR_BITS) | USI_RCVE;   // setup address & Rcve bit
  xferOK = USI_TWI_Start_Read_Write(USI_Buf,numBytes); // core func that does the work
  // USI_Buf now holds the data read
  if (xferOK) return 0;
  else {                                  // there was an error
    errorCode = USI_TWI_Get_State_Info(); // this function returns the error number
    return errorCode;
  }
}

uint8_t receive(void){ // returns the bytes received one at a time
  USI_LastRead++;     // inc first since first uint8_t read is in USI_Buf[1]
  return USI_Buf[USI_LastRead];
}

uint8_t available(void){ // the bytes available that haven't been read yet
  return USI_BytesAvail - (USI_LastRead); 
}


//Writes a single register on the I2C bus
uint8_t writeReg(uint8_t devAddr, uint8_t regAddr, uint8_t value){
  beginTransmission(devAddr);
  send(regAddr);
  send(value);
  return endTransmission();
}


//Reads a single register on the I2C bus
uint8_t readReg(uint8_t devAddr, uint8_t regAddr){
  uint8_t value;

  beginTransmission(devAddr);
  send(regAddr);
  endTransmission();
  requestFrom(devAddr, 1);
  value = receive();

  return value;
}

/*
Perfoms two's complement on an array of unsigned bytes.
Returns signed 2 byte value.
Can be used for Big & Little Endian encoded values.
*/
void twosComplement(uint8_t * in, uint8_t length, int16_t * out, uint8_t invert){
  uint8_t i;
  for ( i = 0; i < length; i++){
    if (invert == 0)
      out[i] = in[2 * i] << 8 | in[(2 * i) + 1];
    else
      out[i] = in[(2 * i) + 1] << 8 | in[2 * i];
  }
}

#define PRINT_NL softuart_putchar( '\n' )

// Define function to write an integer to serial out
#define PRINT_INT_BUFSIZE 16
void printInt(int32_t i) {
  char buf[PRINT_INT_BUFSIZE];
  int8_t sign = 1;
  int8_t len = 0;

  if (i < 0) { // look for the sign
    sign = -1;
    i = -i;
  }

  // fill buffer with digits (in reverse order)
  do {
    buf[len++] = 48 + i % 10; // ASCII digits start at 48
  } while ((i /= 10) > 0 && len < PRINT_INT_BUFSIZE);

  if (sign < 0) { // don't forget to add the sign
    buf[len] = '-';
  } else {
    len--;
  }

  // reverse output of the buffer
  do {
    softuart_putchar(buf[len--]);
  } while (len >= 0);
}

int main(void){
  softuart_init();
  softuart_turn_rx_off();   
  sei(); 
  
  while(1){
    begin(); 
    int i = readReg(MPU6050, WHO_AM_I);
    printInt(i);
    PRINT_NL;
  }
  
  return 0;   /* never reached */
}

USI_TWI_Master.h (4.96 KB)

USI_TWI_Master.c (10.7 KB)

tinyAHRS.c (5.36 KB)

Makefile (16.6 KB)

Although this has only confirmed my original suspicion: the readReg() function is returning the address of the register instead of the contents of the register. Even if it the sensor is not plugged in...

That's because you don't do error handling. If you change the code for readReg() as below, you can handle the errors (but you have to insert it into the routine where I placed the comments).

uint8_t readReg(uint8_t devAddr, uint8_t regAddr){
  uint8_t value = 0;
  uint8_t error = 0;

  beginTransmission(devAddr);
  send(regAddr);
  if (error = endTransmission()) {
    // handle the error
  }
  if (! (error = requestFrom(devAddr, 1))) {
    value = receive();
  } else {
    // error condition, act accordingly
  }

  return value;
}

Im also trying to get an attiny to read from the mpu6050; there is example code on the playground you use with the larger arduinos and it uses Wire.h, does anyone know how to transcribe the read write and error functions to match the TinyWire Libraries?

This is a link to my other question with the sample code. thanks

http://forum.arduino.cc/index.php?topic=224702