earlephilhower / arduino-pico

Raspberry Pi Pico Arduino core, for all RP2040 and RP2350 boards
GNU Lesser General Public License v2.1
2.09k stars 434 forks source link

Problems using RP2040 as target ("slave") - incoming bytes always read as 255 #486

Closed heliophagus closed 2 years ago

heliophagus commented 2 years ago

Greetings. Here's a problem that I've been battling for a couple of days now. Any help would be appreciated!

I am setting up an RP2040 as a target i2c device. The controller is an ESP32. When doing a minimal test of this setup, any bytes sent from the controller ("master") are correctly sent, as validated by a logic analyzer (Kingst LA1010, which works nicely). However, on the RP2040, they are invariably received as 255. I have tried pullups ranging from 10k down to 1k with no effect. The i2c lines are short (about 20 cm).

The sent bytes reliably trigger the receive event on the RP2040, but because they are always "read" as 255, they trigger the return of an error float (-255) rather than return of the test data. I started off with the Example code for "TalkingToMyself", with the target and controller on the same chip, which usually worked as expected but also read received bytes in the range 0 ... 5 as 255, though only about 10% of the time as opposed to 100% of the time. Unfortunately there is a mission-critical need for code running on an ESP32 to read sensor data via i2c from an RP2040. So, using a single chip is not an option.

Here is the code for the controller, running on an ESP32, with the correct i2c outputs verified using a logic analyzer:

// Controller code running on ESP32
// NOTE: output byte (0 ... 5) validated
// using a logic analyzer

#include <Wire.h>

union floatToBytes {
  char buffer[4];
  float singleFloat;
} converter;
uint8_t sCommand = 0;

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

void loop() {
  Wire.beginTransmission(0x30);
  Wire.write(sCommand);
  uint8_t wut = Wire.endTransmission();
  Serial.println("Sent target command " + String(sCommand));
  Serial.println("EndTransmission = " + String(wut));
  sCommand++; // wrap around above 5, limiting range to 0 ... 5
  if (sCommand > 5) sCommand = 0;
  delay(1000);
  uint8_t n = Wire.requestFrom(0x30, (uint8_t)4);
  Serial.println("Bytes received = " + String(n));
  n = 0;
  while (Wire.available()) {
    if (n < 4) converter.buffer[n] = Wire.read();
    n++;
  }
  Serial.println("n = " + String(n));
  Serial.println("Target replied with float " + String(converter.singleFloat));
  converter.singleFloat = 0.0;
  Serial.println("----");
  delay(50);
}

And here is code running on the target (RP2040):

// Target code running on RPi2040 Pico
// NOTE: incoming byte (0 ... 5) validated
// using a logic analyzer, BUT always 
// is "received" as 255. Pull-up resistors 
// of 10k & 1k tried (makes no difference)
#include <Wire.h>

union floatToBytes {
  char buffer[4];
  float singleFloat;
} converter;

volatile boolean rec_i2c = false;
// make up some sample data
float response[6] = {99, 204, 345, 420, 698, 720};
volatile uint8_t rCommand = 0;

void setup() {
  Serial.begin(115200);
  delay(5000);
  Wire1.setSDA(6);
  Wire1.setSCL(7);
  Wire1.begin(0x30);

  Wire1.onReceive(recv);
  Wire1.onRequest(req);
}

void loop() {
  if (rec_i2c) {
    Serial.println("i2c command received: " + String(rCommand));
    Serial.println("Response sent was " + String(converter.singleFloat) + "\r\n");
    rec_i2c = false;
  }
}

// Called when the I2C slave gets written to
void recv(int len) {
  converter.singleFloat = 0.0;
  rCommand = Wire.read();
  // rCommand should always be in the range 0 ... 5
  if (rCommand > 5) {
    converter.singleFloat = -1 * rCommand;
  } else {
    converter.singleFloat = response[rCommand] + random(100) / 100.0;
  }
  rec_i2c = true;
}

// Called when the I2C slave is read from
void req() {
  Wire1.write(converter.buffer, 4);
}

Maybe I'm just being dense! But, I cannot get this to work, and would greatly appreciate any help I can get.

earlephilhower commented 2 years ago

Can't say that I've seen this in the example code, but since you seem to be able to repro it I'd recommend you start looking at the lines https://github.com/earlephilhower/arduino-pico/blob/d689165a390401bd0ac3356d57bfd32cffe26fc2/libraries/Wire/src/Wire.cpp#L136-L139

These set the GPIO pins to I2C with weak onchip pullups (100K or so?) and under control of the I2C HW itself.

One possibility would be to

gpio_set_dir(_sda, false);
gpio_put(_sda, 0);
gpio_set_dir(_scl, false);
gpio_put(_scl, 0);

on the 2 pins, but I believe the I2C HW actually controls the OEs anyway.

The other possibility is the HW FIFO read is going wacky in https://github.com/earlephilhower/arduino-pico/blob/d689165a390401bd0ac3356d57bfd32cffe26fc2/libraries/Wire/src/Wire.cpp#L171-L178

heliophagus commented 2 years ago

Thanks for the reply. I must confess to being exceedingly dense (neutron star grade). The solution was staring me in the face & I just didn't see it. In the target code, where I was expecting i2c input on channel 1, not channel 0 (Wire1, not Wire), I used the line

rCommand = Wire.read();

when it should have been

rCommand = Wire1.read();

Of course, because nothing was being received on channel 0, reading from it returned -1 as an int which got truncated to 0xFF as a byte.

Thanks for answering, though. I learned something from the code you referenced.

And now, I'll see myself out.