I2C slave mode (Wire library) on T4.x vs repeated STARTs

jmarsh

Well-known member
Are there known issues with Wire in slave mode not properly recognizing repeated STARTS correctly, when used on T4.x? I have a pretty good idea what the problem is but I'm a bit puzzled how people are using slave mode for it to have gone unnoticed.
 
Wire slave mode isn't commonly used, so it's quite possible a bug could have gone unnoticed.

I'm curious to hear what you've found. Any chance to share?
 
Consider the case where you want the Teensy to act like an I2C EEPROM, here's a transaction sequence to perform a read:
Screenshot 2025-10-17 130050.png


This should be pretty trivial to implement: register a function with onReceive() to catch the address byte and register another function with onRequest() that writes the bytes for the given address. But what happens is the onRequest hook gets called before the onReceive hook, so it sends the data for the wrong address. I tested the exact same code on an arduino nano and it worked correctly on that device.

From looking at the code in WireIMXRT.cpp, the problem is only the slave STOP interrupt is being handled (LPI2C_SSR_SDF), which doesn't get triggered when a repeated START happens - there's a separate status/interrupt bit for that, LPI2C_SSR_RSF. If that interrupt is enabled and the ISR checks for both (LPI2C_SSR_SDF | LPI2C_SSR_RSF), the hook functions get called in the correct order.
 
FWIW I'm also seeing another issue (not using slave mode, although it may be playing a role) where the transmit FIFO seems to be getting out of sync, the values being returned by endTransmission()/requestFrom() end up being related to the previous transfer rather than the one given for the current call... I will continue investigating, but there's definitely an issue with larger transfers (8+ bytes).
 
If I'm trying to write code to be portable between MCUs it has to be the Wire library.
Similarly Teensy's Wire library should behave the same as the one that regular Arduinos use.
 
Just want to confirm I'm looking into this problem. I am able to reproduce it.

Also looks like the onRequest handler doesn't have any way to detect when the master size replies NAK to end the transfer.

As usual, no solution yet. Just documenting the problem and I'm reproducing it.

1766284771809.png


Code:
#include <Wire.h>

void setup() {
  Wire.begin(0x50);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);
}

void receiveEvent(int howMany)
{
  int n = Wire.available();
  Serial.print("receiveEvent: ");
  Serial.print(n);
  Serial.print(" bytes: ");
  while (Wire.available() > 0) { // loop through all but the last
    int x = Wire.read();        // receive byte as a character
    Serial.print(x);             // print the character
    Serial.print(",");
  }
  Serial.println();
}

void requestEvent()
{
  Serial.print("requestEvent: ");
  int n = Wire.write("hello ");     // respond with message of 6 bytes
  Serial.print("  wrote ");
  Serial.print(n);
  Serial.println(" bytes");
}

void loop() {
  // put your main code here, to run repeatedly:

}


Code:
#include <Wire.h>

void setup() {
  Wire.begin();
}

int loopcount = 0;
unsigned char b[4] = {0, 0, 0, 0};

void loop() {
  Wire.beginTransmission(0x50);
  Wire.write(0x00);
  Wire.endTransmission(false);
  Wire.requestFrom(0x50, 4);
  int count = Wire.available();
  for (int i = 0; i < count; i++) {
    b[i] = Wire.read();
    Serial.print(b[i], HEX);
    if (i >= 3) break;
    Serial.print(',');
  }
  Serial.println();
  delay(2000);
  if (++loopcount > 5) {
    Serial.println("Write increment");
    uint32_t n = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
    n = n + 1;
    b[0] = n >> 24;
    b[1] = n >> 16;
    b[2] = n >> 8;
    b[3] = n;
    Wire.beginTransmission(0x50);
    Wire.write(0x00);
    for (int i = 0; i < 4; i++) {
      Wire.write(b[i]);
    }
    Wire.endTransmission();
    delay(2000);
    loopcount = 0;
  }
}

file.png
 
Just want to confirm I'm looking into this problem. I am able to reproduce it.

Also looks like the onRequest handler doesn't have any way to detect when the master size replies NAK to end the transfer.
Yes, the API for slave mode is rather lacking; you have to take a punt on how many bytes to give to onRequest and there's no way to know how many get consumed. But that's a design problem with the Wire library.

For the issue with repeat starts I found it good enough to enable the repeated start interrupt (LPI2C_SIER_RSIE) and change:
if (status & LPI2C_SSR_SDF) { // Stop
to
if (status & (LPI2C_SSR_SDF|LPI2C_SSR_RSF)) { // Stop or Repeat Start
 
I've committed the fix on github.


Here's an updated copy of my test program that tries to emulate a 24C02 EEPROM chip with Wire and EEPROM libraries.

Code:
#include <Wire.h>
#include <EEPROM.h>

int address = 0;

void setup() {
  Wire.begin(0x50);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);
}

void receiveEvent(int howMany)
{
  int n = Wire.available();
  Serial.print("receiveEvent: ");
  Serial.print(n);
  Serial.print(" bytes: ");
  for (int i=0; i < n; i++) {
    int x = Wire.read();
    Serial.print(x);
    Serial.print(",");
    if (i == 0) {
      address = x;
    } else {
      EEPROM.write(address, x);
      address = address + 1;
    }
  }
  Serial.println();
}

void requestEvent()
{
  Serial.println("requestEvent: ");
  // no way to know how much data is wants
  for (int i=0; i < 16; i++) {
    int x = EEPROM.read(address + i);
    Wire.write(x);
  }
  // we don't get info about how many bytes
  // actually sent, so no way to increment
  // address by number actually read, as a
  // real EEPROM chip does
}

void loop() {
}
 
Looks like slave mode handling of repeated start might also be broken on Teensy 3.2 and Teensy LC (the two I tested just now) and perhaps other older boards. Not planning to dive into those right now, just leaving this message to document this bug probably also exists on those old boards.
 
Back
Top