I2C Slave Problems (and Repeated Start

Status
Not open for further replies.

Jp3141

Well-known member
I am trying to get 2 Teensy 3.0s to a talk to each other via I2C. I have that working, but what I really want to do is have the Slave behave similarly to standard I2C slaves with addressable registers and values stored in them. At this stage I don't know if (a) my Master is wrong; b) my Slave is wrong, or c) the I2C Wire library doesn't support what I am trying to do. If anyone has example code, I'd appreciate seeing it.

Basically it's some type of issue with a Repeated Start symbol in the I2C link.

This is my Master Code:
Code:
#define LEDPIN 13
#define TRIGGER 0
#define I2CAddress 4

#include <Wire.h>

void setup() {
  pinMode(TRIGGER, OUTPUT);
  pinMode(LEDPIN, OUTPUT);
  Wire.begin(); // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
  Serial.println("Starting Master");
}

byte x = 0;

void loop() {
  digitalWrite(LEDPIN, x % 2); //toggle LED
  x++;
  delay(500);

 Wire.beginTransmission(I2CAddress); // transmit to device 
 digitalWrite(TRIGGER, HIGH);delayMicroseconds(1);digitalWrite(TRIGGER, LOW);

  Wire.write(x);        // sends register number
  Serial.print("Asking for ");Serial.println(x);

// false here creates a Repeated Start symbol
  Wire.endTransmission(false);    // stop transmitting
 digitalWrite(TRIGGER, HIGH);delayMicroseconds(1);digitalWrite(TRIGGER, LOW);
  Wire.requestFrom(I2CAddress, 1);    // request 1 byte
  while (!Wire.available()); // wait...
    char c = Wire.read();         // print the character
    Serial.print(c);         // print the character
 
 digitalWrite(TRIGGER, HIGH);delayMicroseconds(1);digitalWrite(TRIGGER, LOW);
 
[B][I]// This line makes a difference, but is it correct ?
[/I][/B]Wire.endTransmission();    // stop transmitting
  delay(300);

}

and this is my Slave code:
Code:
#define LEDPIN 13
#define I2CAddress 4

int i=0;
byte I2CRequest;
#include <Wire.h>

void setup() {
  Wire.begin(I2CAddress);       // join i2c bus with address
  Wire.onReceive(receiveEvent); // register event
  Wire.onRequest(requestEvent); // register event to send
  Serial.begin(9600);           // start serial for output
  Serial.println("Starting Slave");
}

void loop() {  delay(100);}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
  Serial.print("receive Event:");Serial.print(howMany);Serial.print(" ");
  while(1 < Wire.available())  // loop through all but the last
    Serial.print((char) Wire.read()); // receive byte as a character
  
  Serial.print("!");
  I2CRequest=Wire.read();         // print the integer
  Serial.println((int) I2CRequest);         // print the integer
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
   Serial.print("request"); Serial.println(i++);
  Wire.write((byte) 'A'); // respond with 1 byte
                      
}

Without the second Wire.endTransmission() in the Master, there are 4 groups of I2C 8-bit transmissions (as expected), and the Master does get the 'A' transmitted by the slave. But the Slave never gets the message (x) from the Master -- i.e. its receiveEvent is never triggered. I am using x to represent the register that I am asking the slave to respond with.

Conversely, when I enable that line, I do get a receiveEvent triggered, but there are an additional 2 8-bit I2C transmissions on the bus. I want the Slave to accurately reflect a real peripheral (e.g. like MMA8452Q), so want to get this fixed.

Does anyone have working code that is close to what I want ?
 
Without the second Wire.endTransmission() in the Master, there are 4 groups of I2C 8-bit transmissions (as expected), and the Master does get the 'A' transmitted by the slave. But the Slave never gets the message (x) from the Master -- i.e. its receiveEvent is never triggered. I am using x to represent the register that I am asking the slave to respond with.

The problem is that in the default Wire library it is incapable of detecting RepSTART on a Slave device. This is a bug in the I2C peripheral itself, in which there is no mechanism for Slaves to detect STOP or RepSTART (which Freescale's lame design team has seen fit not to fix.. yes just keep RTM'ing new parts with this crap).

Paul did a good job patching this with the SDA-rising-ISR routine, but it can only detect STOP, not RepSTART. In my reworked I2C library I built upon this and added "inferred" RepSTART detection which seems to work well. You can try using that if you want, get it here. FYI - the Master and Slave examples use RepSTARTs.
 
You can if you want to. I forget the structure of the original code, but in my code I keep track of the "mode" of the I2C (Idle, Tx, Rx, etc.). If IAAS gets set while it is already in a Slave mode, then it infers a RepSTART occurred (which triggers the callback on the previous receive). Look for this bit:

Code:
        if(status & I2C_S_IAAS)
        {
            // If in Slave Rx already, then RepSTART occured, run callback
            if(i2c->currentStatus == I2C_SLAVE_RX && i2c->user_onReceive != NULL)
            {
                I2C_DEBUG_STR("RSTART");
                i2c->rxBufferIndex = 0;
                i2c->user_onReceive(i2c->rxBufferLength);
            }

            ... continue as if addressed as a Slave for the first time ...
 
Status
Not open for further replies.
Back
Top