Wire.requestFrom question

I am working on a project where I need to communicate between an Arduino uno (the slave) and a Wemos D1 Mini (the master).

Using the code in this example works just fine. In the example sketch the slave sends a set number of characters which is requested by the master with: Wire.requestFrom(8, 16);

Here’s my problem, the slave will be sending the master a string from 1 to 20 characters.

If I do a Wire.requestFrom(8, 20);, I get the data from the slave but with non-Ascii characters to finish the request of 20-characters if the slave sends less than 20-characters.

So, here’s my question - How can I determine when the sent text is finished? For example, if the slave is sending “12345” to the master’s request, I get “12345⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮”.

I considered preceding the text with the length, so in my above example, “12345” would be sent as “0512345”, then when I build a String (I know you hate Strings), I will know the size of the string to extract. I am hoping there’s a more straightforward way to accomplish this.

Here’s the code I am testing with:

/*
   MASTER
   Wemos D1 Mini
   From: https://www.electronicwings.com/nodemcu/nodemcu-i2c-with-arduino-ide
   
*/

#include <Wire.h>

int i = 0;
String s;

void setup() {
  Serial.begin(115200);          //begin serial for debug
  Wire.begin(D1, D2);            //join i2c bus with SDA=D1 and SCL=D2 of NodeMCU
}

void loop() {
  i += 1;
  s = "Wemos (Master) sent: " + String(i);
  Serial.print(s);
  Serial.println();

  Wire.beginTransmission(8);     //begin with device address 8
  Wire.write((char*) s.c_str());      /* sends string */
  Wire.endTransmission();        //stop transmitting

  Wire.requestFrom(8, 20);       //request & read data of size 16 chars, "Hello from Slave"
  while (Wire.available()) {
    char c = Wire.read();
    Serial.print(c);
  }
  Serial.println();
  delay(1000);
}
/*
 * SLAVE
 * Arduino Uno
 * 
 * From: https://www.electronicwings.com/nodemcu/nodemcu-i2c-with-arduino-ide
 * 
 */
 
#include <Wire.h>

void setup() {
 Wire.begin(8);                /* join i2c bus with address 8 */
 Wire.onReceive(receiveEvent); /* register receive event */
 Wire.onRequest(requestEvent); /* register request event */
 Serial.begin(115200);         /* start serial for debug */
}

void loop() {
 delay(100);
}

// function that executes whenever data is received from master
void receiveEvent(int howMany) {
 while (0 <Wire.available()) {
    char c = Wire.read();      /* receive byte as a character */
    Serial.print(c);           /* print the character */
  }
 Serial.println();             /* to newline */
}

// function that executes whenever data is requested from master
void requestEvent() {
 Wire.write("Hello from Slave");  /*send string on request */
}

How can I determine when the sent text is finished?

Adding a termination character to the text that is sent would seem to be the obvious way

This lineWire.write((char*) s.c_str());does not send the trailing NULL char of your c-String so when you receive the data, the buffer you build is not a well formed c-String (missing trailing NULL) unless you have initialized all the bytes to 0 (NULL char) before for example (or add the NULL)

Wire.requestFrom() will return the number of bytes sent by the slave device. If you send back a c-String ensure to send the trailing NULL char and upon receiving if you ask for 20 bytes and get 10, just check if the last one is NULL, then you can « assume » that the data before that is your correct c-String. Alternatively add yourself a null char at the end of the buffer once you have received the data

That being said sending an extra meaningless few bytes (ie sending a full buffer with known size) probably won’t be visible in terms of timing at end user level unless your application needs to be really super optimized for efficiency

UKHeliBob:
Adding a termination character to the text that is sent would seem to be the obvious way

Indeed, interesting! Here is the implementation.

Master - ESP8266 Codes:

#include <Wire.h>

char myArray[20] = "";
int i = 0;
char x;

void setup()
{
  Serial.begin(115200);   //begin serial for debug
  Wire.begin(D1, D2);    //join i2c bus with SDA=D1 and SCL=D2 of NodeMCU
}

void loop()
{
  Wire.requestFrom(8, 20);  //request & read "Hello from Slave"
  do
  {
    x = Wire.read();
    myArray[i] = x;
    i++;
  }
  while (x != '\n');
  myArray[i - 1] = 0x00;   //insert null-byte at location where \n has entered
  Serial.println(myArray);
  delay(1000);
}

Slave - UNO Codes:

#include <Wire.h>

void setup() 
{
  Wire.begin(8);                /* join i2c bus with address 8 */
  Wire.onRequest(requestEvent); /* register request event */
  Serial.begin(115200);         /* start serial for debug */
}

void loop() 
{

}

void requestEvent(int howMany) 
{
  Wire.write("Hello from Slave\n");  /*send string with terminator */
}

Screenshot:
sm1.png

sm1.png

Sure but why mess around adding an \n to remove it on the other side when the compiler already added the NULL char for you?

Just use the other form of write where you pass a buffer and number of bytes (strlen() + 1) to send your data

Or just trust wire to work and manually add the trailing NULL upon reception.

Almost no one sends a text over the I2C bus. We send floats, integers, structs, and other binary variables.
Can you skip this text trouble, and use binary data ?

The answers above are correct, the Slave has to send a character to tell that it is the end of the text. It can be a '\0' or '\n' or something else. Since the Wire.write() does not send the '\0' at the end of string, you can add that yourself or use '\n'.

There are many ways of doing the same thing -- why are there so many ways? It is because, different people have different tastes. We propose alternate ways; but, we are miserably lazy (in some cases) to amplify the concepts with example codes -- attitudes that bring obstacles in the learning process.

GolamMostafa:
There are many ways of doing the same thing -- why are there so many ways? It is because, different people have different tastes. We propose alternate ways; but, we are miserably lazy (in some cases) to amplify the concepts with example codes -- attitudes that bring obstacles in the learning process.

The point is exactly about the "learning process".

  • As documented above, the OP now learnt he needs an end marker in the transmission

I'm offering a new learning: A c-Strings has already a built-in end marker which is the NULL char '\0'

So the idea was to use this existing end marker (rather than creating a magic number dependency, cluttering memory by adding a new meaningless one on top of the one that is already there in the sender, and simplifying code in the receiver as you need to get rid of that marker to replace it manually by standard end marker...)

it's not about obstacles in the learning process, it's not about being lazy (always sending a full buffer would be lazy I agree), it's about elegance in coding.

But as anything related to personal taste, what I find elegant you might find ugly and vice-versa.

it's OK

J-M-L:
But as anything related to personal taste, what I find elegant you might find ugly and vice-versa.

An Elegant Expression always radiates luminescence lum.png that can even be felt by a blind.

lum.png

I have a project that needs an Arduino Uno (slave) to communicate with a Wemos D1 Mini (master) over the I2C bus.

In my experiments I determined that there needs to be a brief delay in the master between sending then receiving reply data from the slave, or else I occasionally get garbage data.

I try to avoid putting a delay() statement in my loop code as this project has a few asynchronous moving parts that a blocking delay() might cause problems. (The one-second delay at the end of the loop will not be in the final code - this is just a test).

So, is my sample code (below) missing something between the Wire.beginTransmission(); and Wire.requestFrom();, or is the delay(1) a necessary evil?

/*
   MASTER
   Wemos D1 Mini
   From: https://www.electronicwings.com/nodemcu/nodemcu-i2c-with-arduino-ide
   Version 1
     Original Sampls, modified for use with Wemos D1 Mini.


*/

#include <Wire.h>

int i = 0;
String s;

void setup() {
  Serial.begin(115200);               //begin serial for debug
  Wire.begin(D1, D2);                 //join i2c bus with SDA=D1 and SCL=D2 of NodeMCU
}

void loop() {
  i += 1;
  s = String(i);
  Serial.print(F("Wemos (Master) sent: "));
  Serial.print(s);
  Serial.println();

  Wire.beginTransmission(8);          //begin with device address 8
  Wire.write((char*) s.c_str());      //sends string
  Wire.endTransmission();             //stop transmitting
  
  delay(1);                           //Why is this needed?
  
  Wire.requestFrom(8, 14);            //request & read data of size 14 chars, "Hello from Slave"
  while (Wire.available()) {
    char c = Wire.read();
    Serial.print(c);
  }
  Serial.println();
  delay(1000);
}

Me:
The problem is in the code you didn’t post.

If this is the same slave code as your other thread then that is printing inside the interrupt. Not a good idea. That may be the source of the slowdown.

delay(1) is not evil. Unless you have some really tight timing requirements, I would leave this alone and go solve other problems.

SteveMann:
is the delay(1) a necessary evil?

Yes. Probably. I had similar problems in the past with an Arduino as I2C Slave.

In the past the Wire library for AVR microcontrollers had a bug. When two Arduino Uno boards were used, then that delay was needed. That bug is fixed.
Then the Wire library for the Arduino Due had problems with it. I don't know if that is fixed.
The ESP8266 Wire library is in software. Perhaps it does not handle this situation very well.

Can you use delayMicroseconds() ? Perhaps the problem will disappear with 10 or 100 µs.

You didn't show the code of the Slave. Don't use Serial functions in the onRequest or onReceive functions. Perhaps the Arduino as a Slave needs extra time because the onRequest or onReceive functions are too long.

not your question but...

"Hello from Slave" does not have 14 bytes

  Wire.requestFrom(8, 14); //request & read data of size 14 chars, "Hello from Slave"

This code

  i += 1;
  s = String(i);
  Serial.print(F("Wemos (Master) sent: "));
  Serial.print(s);
  Serial.println();

can be written like this (no need for the String class)

  i++;
  Serial.print(F("Wemos (Master) sent: "));
  Serial.println(i);

Thanks, everyone.
J-M-L
The “Hello from Slave” is in an incorrect comment. I am actually sending “Hello from UNO”.

When you say" no need for the String class", I do later when I actually send the string. Yes, I realize that I am sending a char array, but not using String class is still a challenge for me.

Koepel-
“Can you use delayMicroseconds()” I am down to 500us and it still works, I will try shorter, but a 500us block won’t affect any of my other things happening in the loop.

MorganS-
“printing inside the interrupt. Not a good idea. That may be the source of the slowdown.” I was unaware that print was a blocking event? In the final code, I won’t be doing any printing. Are you thinking that the print inside the receiveEvent() in the slave code may be causing the problem? And that the problem isn’t really in my Master code, but delaying the requestFrom() is allowing the Slave to be ready to send the data?

Again, thanks for the prompt replies.

OK c-String are not that difficult, there are C libraries with some good functions you can use (stdlib.h and string.h )

I was unaware that print was a blocking event?

It is when the outgoing buffer is full, but that is not the point here, the challenge is that print requires interrupts to work and they are disabled if you execute your code within the context of an interrupt

Thanks for the tip. I was unaware that I could use functions from string.h in the Arduino IDE.

SteveMann:
Thanks for the tip. I was unaware that I could use functions from string.h in the Arduino IDE.

The Arduino is programmed in C++ and with very few exceptions all of the normal C/C++ functions are available

What I understand is this:

The IDE supplied by 'Arduino Development Team' does support creating 'program source codes' using most of the 'normal C/C++ library functions'.

On AVR processor you get AVR lib as well and also what GCC brings