Teensy 3.1 reads wrong data through I2C

Status
Not open for further replies.
I am trying to read the data off of an IMU3000 Gyro and KXTF9 accelerometer chip through I2C using teensy 3.1. Teensy and the IMU do communicate, however the data read is wrong. The exact same code runs fine on arduino Uno and Leonardo. I have tried both the wire library for Teensy and the i2c_t3 library provided by nox771 and they both have the same issue. I have looked at the SDA/SCL signals on the scope on both arduino and Teensy and one difference that I noticed was that, with the arduino, the scl line is pulled high for two to three clocks after the 7 bit address is sent. After the delay, the 8bit data byte shows up on the SDA line. However for the teensy that 2 or 3 clocks delay does not exits and the data byte comes up on the SDA line in the next clock cycle after the ACK bit of the 7bit address. I have tried both 400kHz and 100kHz. I also tried 24Mhz and 48MHz CPU clocks and nothing made a difference. Below is my code. The code is setup to read the sensors at 80Hz. I am really out of troubleshooting ideas for this.:confused::confused:


Code:
#define GYRO 0x68         // gyro I2C address

#define IMU3000_SLAVE_ACC_ADR 0x14 // IMU-3000 Register address for the Acc sensor addrees
#define IMU3000_SAMPLE_RATE 0x15 // IMU-3000 Register address for the sampling rate divider
#define IMU3000_DATA_RANGE 0x16 // IMU-3000 Register address for analog sample rate and data range
#define IMU3000_ACC_DATA_START_ADR 0x18 // IMU-3000 Register address for acc semsor data start address
#define IMU3000_AUX_DMP_CONTROL 0x3D // IMU-3000 Register address for controling digital motion processor and the aux device

#define IMU3000_X_DATA 0x1D   // IMU-3000 Register address for GYRO_XOUT_H


#define ACCEL 0x0F        // Accel I2c Address

#define KXTF9_FILT_REG 0x21   // Data control reg addr
#define KXTF9_HPFHZ_50 0x00 // HPF roll off at 100 Hz
#define KXTF9_LPFHZ_400 0x06  // LPF roll off at 25 hz

#define KXTF9_POWER_CTL 0x1B  // Power control reg addr
#define KXTF9_PC0 0x80        // Operating mode bit
#define KXTF9_RES 0x40        // Resolution bit
#define KXTF9_GS2G 0x00       // bits for 2G mode
#define KXTF9_GS4G 0x08       // bits for 4G mode
#define KXTF9_GS8G 0x10       // bits for 8G mode

IntervalTimer syncTimer;

byte buffer[12];   // Array to store ADC values 
int gyro_x;
int gyro_y;
int gyro_z;
int accel_x;
int accel_y;
int accel_z;

boolean stopRun = false; 
boolean valveState = false;
const int stopSwitch = 7;
const int valvePin = 11;
const int scopeDebug = 8;

unsigned int controlTime = 0;
unsigned int controlTime2 = 0;
volatile boolean Tik = false;
boolean preTik = false;

int i;
int machine = 0;
char str[64];
#include <i2c_t3.h>

void setup()
{
    Serial.begin(115200); 
    
    Serial.write('S');

    while(Serial.available() == 0)
    {
    }

     
    pinMode(valvePin, OUTPUT);
    pinMode(stopSwitch, INPUT);
    pinMode(scopeDebug,OUTPUT);
    
    
    // initialize timer1 on arduino 
//    noInterrupts();           // disable all interrupts
//    TCCR1A = 0;
//    TCCR1B = 0;
//    TCNT1  = 0;
//    //78
//    OCR1A = 75;            // compare match register 16MHz/256/2Hz
//    TCCR1B |= (1 << WGM12);   // CTC mode
//    TCCR1B |= (1 << CS12);    // 256 prescaler 
//    TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
//    interrupts();             // enable all interrupts
//    
    
    
    // initialize timer1 on Teensy 3.1 

    syncTimer.begin(timerCallback1,12500); 
    
    
    
    Wire.begin();
    // Set Gyro settings
    

    
    writeTo(GYRO, IMU3000_SLAVE_ACC_ADR, ACCEL); // set accel i2c slave address 
    writeTo(GYRO, IMU3000_SAMPLE_RATE, 0x09); // set gyro sampling rate to 800Hz
    writeTo(GYRO, IMU3000_DATA_RANGE, 0x18);  // Analog Sample Rate 8kHz, Gyro Range 2000 d/s 
    writeTo(GYRO, IMU3000_ACC_DATA_START_ADR , 0x06);  // set accel register data address
     
    writeTo(GYRO, IMU3000_AUX_DMP_CONTROL, 0x08);  // Set passthrough mode to Accel so we can turn it on
    
    writeTo(ACCEL, KXTF9_POWER_CTL, KXTF9_PC0); // set KXTF9 to opertaing mode, set to measure +/- 8G   
    writeTo(ACCEL, KXTF9_POWER_CTL, KXTF9_PC0 | KXTF9_RES| KXTF9_GS8G); // set LPF to 25 hz and HPF to 100 
    writeTo(ACCEL, KXTF9_FILT_REG, KXTF9_HPFHZ_50 | KXTF9_LPFHZ_400);
    
    writeTo(GYRO, IMU3000_AUX_DMP_CONTROL, 0xEE);  //cancel pass through to accel, gyro will now read accel for us 
    
}


//ISR(TIMER1_COMPA_vect){
//    Tik = !Tik;
//}

void timerCallback1() {
  Tik = !Tik;
}

void loop()
{
  if(digitalRead(stopSwitch))
  {
      stopRun = true;
      Serial.write("FFFFFFFFFFFF");
  }
  else if(!stopRun)
  {
    if (Tik != preTik)
    {      
        digitalWrite(scopeDebug,LOW);
        
        // First set the register start address for X on Gyro  
        Wire.beginTransmission(GYRO);
        Wire.write(IMU3000_X_DATA); //Register Address GYRO_XOUT_H
        Wire.endTransmission();
    
        // New read the 12 data bytes
        Wire.beginTransmission(GYRO);
        Wire.requestFrom(GYRO,12); // Read 12 bytes
        i = 0;
        while(Wire.available())
        {
            buffer[i] = Wire.read();
//            Serial.write(buffer[i]);
            i++;
        }

        
        Wire.endTransmission();
        Serial.write(buffer,12);
      
        controlTime ++;
        preTik = Tik;
        digitalWrite(scopeDebug,HIGH);

    }
    if (controlTime>80)
    {
      if (!valveState)
      {
        valveState = true;
        digitalWrite(valvePin,HIGH);
      }
      else
      {        
        valveState = false;
        digitalWrite(valvePin,LOW);
      }
      controlTime = 0;
    }
  }
}




// Write a value to address register on device
void writeTo(int device, byte address, byte val) {
  Wire.beginTransmission(device); // start transmission to device 
  Wire.write(address);            // send register address
  Wire.write(val);                // send value to write
  Wire.endTransmission();         // end transmission
}
 
Try using a Repeated-START between the commands. IMU3000 datasheet indicates it requires RepSTART. Failing that, a scope shot would be useful.

To do RepSTART, do this:

Code:
        // First set the register start address for X on Gyro  
        Wire.beginTransmission(GYRO);
        Wire.write(IMU3000_X_DATA); //Register Address GYRO_XOUT_H
        Wire.endTransmission([COLOR="#FF0000"]I2C_NOSTOP[/COLOR]);
    
        // New read the 12 data bytes
        [COLOR="#FF0000"]//Wire.beginTransmission(GYRO);  <-- comment out, this is only for Write, not Read[/COLOR]
        Wire.requestFrom(GYRO,12); // Read 12 bytes
        i = 0;
        while(Wire.available())
        {
            buffer[i] = Wire.read();
//            Serial.write(buffer[i]);
            i++;
        }
 
Thanks for the quick response. Omitting the Wire.beginTransmission before a read makes total sense. I was wondering why the address is being one on beginTransmission and once on requestFrom. However, that and the repStart by usin I2C_NOSTOP did not dolve the issue. I have written the following code to read just one byte from the IMU3000 chip. The address of the byte is 0x68 and it is the MSB of the 16-bit x axis rotational speed of the sensor. Because the values are in two's complement, the value of the byte should stay at 255 when the sensor is not rotating. It also has to stay at 255 for small rotational speeds. With Teensy however, the byte stays at 255 when stationary but after a gentle rotation on the sensor, the value of the byte changes to meaningless values. I have captured the SDA/SCL signals during one of these wrong readings and the picture are attached.

This is my code for capturing one byte on a key press. The setup and variable initialization are the same as the previous code.

Code:
void loop()
{
  delay(1);
  keyPressed = digitalRead(stopSwitch);
  if (keyPressed)
  {
    if (doRead && keyPressed)
    {    
          doRead = false;
          digitalWrite(scopeDebug,LOW);

          // First sequence
          Wire.beginTransmission(GYRO);
          Wire.write(IMU3000_X_DATA); //Register Address GYRO_XOUT_H
          Wire.endTransmission(I2C_NOSTOP);

      
          // Second sequence
          //Wire.beginTransmission(GYRO);
          Wire.requestFrom(GYRO,1); // Read 12 bytes
          while(Wire.available())
          { 
             buffer = Wire.read();
          }
          //Wire.endTransmission();
         
          Serial.println(buffer);
      
          digitalWrite(scopeDebug,HIGH);
         
    }
  }
  else
  {
    doRead = true;
  }
}

This first signal sequence represents writing 0x1D (0011101b) to device address 0x68 (01101000b) in
FirstSeq.jpg

The second signal sequence is reading the one byte data from device 0x68 and here is what the issue exist. The sequence is showing the data byte has a value of 2. But the ACK bit is 1. What does this mean? does it mean that this data should be disregarded or just no more data can be transmitted? Also at position of the stared bar in arduino there is a 2-3 cycle gap where both SDA and SCL pull high.

SecSeq.jpg


This really is tarting to seems crazy I mean why would it read wrong data from the same address that arduino reads write data from?

Oh and the peak to peak voltage of the signals are around 2.8V that is fine right?
 
The datasheet says that the I2C address for the gyro is 110100 which is 0x34. The read/write bit is not included when you specify the address to the I2C library.

Pete
 
Naturally it's never the first suggested fix...

The datasheet I saw said the address was 110100x (where the last bit is pin defined), so in this case if the pin makes the last bit a zero, it would be 0x68 (and I can tell 0x68 is indeed working because the Slave ACKs).

Now, looking at your plots, I interpret it as:
START 0x68+W ACK 0x1D ACK RepSTART 0x68+R ACK 0x02 NAK STOP

The reason it NAKs on the last byte is that is how Master receive works. It will NAK the last byte of the receive to tell the Slave that it is ending. The reason it NAKs after only one byte is because you explicitly requested only 1 byte:
Code:
Wire.requestFrom(GYRO,[COLOR="#FF0000"]1[/COLOR]); // Read 12 bytes

If you were to change that to say, 3, you would see it ACKs the first two bytes and NAKs the third. This behavior is part of the I2C protocol, but since many Slaves will reset on detecting a START or STOP, it often doesn't matter if they get a NAK or not.

From a communication perspective I don't see any I2C problem here, this all looks correct. As for why the return value is 0x02, I can't say.

As far as the Arduino discrepancy, the gap you are referring to could be something in the Arduino routine causing one of the clocks to stretch, or perhaps it is part of the direction turnaround (at the point you indicate the Master device changes from sending data to receiving data).

Oh and the peak to peak voltage of the signals are around 2.8V that is fine right?
Logic levels are ok I think.

You might try finding a Read/Write register and see if you can set it and read back the value. That would verify the communication. You should at the very least be able to read register 0 (the "who am I" register) and get 0x68.
 
This behavior is part of the I2C protocol, but since many Slaves will reset on detecting a START or STOP, it often doesn't matter if they get a NAK or not.

I recall now why this is the case. It is required for the Master to NAK because when the Slave is sending data, after falling-edge of the ACK bit (9th bit), it will immediately output the 1st data bit on the next byte. If the data bit happens to be a zero, the Master cannot send a START/STOP sequence because the SDA line is held low. The NAK tells the Slave to release the SDA line so the Master can send a START/STOP.
 
Thanks a lot for your help nox771. Your verification on the I2C communication with the sensor made me look elsewhere for the cause of the issue. It turned out that since I was sending the data byte by byte using serial.write, for some reason the bytes where being swapped making the data meaningless on the computer side. I don't know for sure; but one of my guesses is that the teesny gateway software writes "teensy_gateway" on the port causing the sensor data to shift . Anyhow I switched to serial.print. It takes a bit longer but I can send a terminator for each sample making the communication more robust.
 
Status
Not open for further replies.
Back
Top