Wire.write problems

Hi everybody,
I'm trying to solve a problem in using wire.write function in a I2C connection between 2 arduino M0 Pro in a master slave fashion.
The slave , on master request, prepare this String:
String msg ="T"+str(String(tTot/samples,2),6)+"U"+str(String(URTot/samples,2),6)+"P"+str(String(PVTot/samples,1),6);
were str is a function that add 0 to right to have a fixed number of digit in every number ( 6 characters).
I'm non able to send msg String with wire function wire.write to the master, I receive everytime ????? characters

String msg ="T"+str(String(tTot/samples,2),6)+"U"+str(String(URTot/samples,2),6)+"P"+str(String(PVTot/samples,1),6);
char tab2[msg.length()];
strncpy(tab2, msg.c_str(),msg.length());
Serial.print("tab2:");Serial.print(tab2);
Wire.write(tab2,msg.length());

Could someone please help me! Thanks!
Mauro

The onRequest handler should be short and fast. I mean really short and really fast.
You have a large amount of heap allocations with the String objects and conversions. That does not belong in a onRequest handler.
Do not use Serial functions in a onRequest handler. It is shown in many examples, but everyone on this forum can tell you that it is a bad idea. Transmitting Serial data is interrupt driven, and the onRequest is also called from a interrupt routine.

The length of a string is without the zero-terminator. When creating an array, there should be enough space for the extra zero-terminator.
The strncpy() was not made for a zero-terminated string. The strlcpy() might be a better choice.
Using strlcpy() instead of strncpy() is easy to understand. Things get hairy with strncat() and strlcat().

We do not use the I2C bus with readable ASCII data. We use binary data, like integers or an array of floats or a struct.
If something is wrong, why don't you start with a byte and make that 100, after that an array of bytes and so on.

Can you show the full Master and the full Slave sketch ?
Can you show a photo ? so we can see what kind of wires and pullup resistors you use.

Hi Koepler,
thanks a loto for your reply
I tried to move the long msg=.... instruction out of the onRequest function and now it goes !
The function now is

void requestEvent() {
richiesta=true;
byte stglen=msg.length();
// Length (with one extra character for the null terminator)
char tab2[stglen+1];
strncpy(tab2, msg.c_str(),stglen);
Wire.write(tab2,stglen);
}

Thanks!!!

Still using a String object :cry: But it is used in a passive way, there are no heap allocations.
Can you try to send binary data ? For example the numbers as an array of 'float' or 'int' or whatever those numbers are. You are the only one that uses readable ASCII data for the I2C bus (along with a few beginners).
Still using the strncpy() function :cry: There are long discussions about strncpy() and strlcpy(), but the strncpy() functions was created for a special purpose, and not for zero-terminated strings.
Why do you use the "tab2" array :confused: You can use the Wire.write() with msg.c_str(). The Wire.write() writes the data to an internal buffer in the Wire library.

These are all minor issues, I'm glad it is working :smiley:

@OP

You can still make the requestEvent() interrupt context shorter (if you want) by putting all those codes in the loop() function.

void loop()
{
    if( richiesta==true)
    {
       byte stglen=msg.length();
       // Length (with one extra character for the null terminator)
       char tab2[stglen+1];
       strncpy(tab2, msg.c_str(),stglen);  
       Wire.write(tab2,stglen);
       richiesta=false;
    }
}

void requestEvent() 
{
    richiesta=true;
}

Koepel:
You are the only one that uses readable ASCII data for the I2C bus (along with a few beginners).

Is there any good reason for your part to discourage the use of the Wire.write(string) function when the said function is well supported by the Arduino?

GolamMostafa:
Is there any good reason for your part to discourage the use of the Wire.write(string) function

Yes, the word "discourage" is an understatement :wink:

You probably know all of this, but I will explain it for others:

The Serial, Wire, Ethernet, SD libraries are part of the Arduino Stream class.

So the Wire library has access to all the string functions, just like the Serial library.
However the Wire library is the odd duck in the pond (a saying in my country), because it uses packets and not a stream.
The Wire.beginTransmission() and Wire.requestFrom() clear the buffers to be ready for the next packet of data. That is not the behaviour of a "stream".

The Serial communication has always been mostly with readable ASCII, and it can be handy for debugging.
The I2C communication has always been mostly with binary data. Do you know a I2C sensor that returns readable ASCII data ? As far as I know, they all return binary data. It is much more straightforward.

mgalvan wants to send binary data 'tTot/samples', 'URTot/samples' and 'PVTot/samples', so all he has to do is sending those variables (probably three 'float' variables). But instead there is a whole bunch of code to convert that data into text and in the Master all that text has to be converted into variables. All that needless code is hard to check for bugs and hard to maintain for no reason. I noticed an array size problem, but I did not even check carefully if everything is okay because all those problems vanish with binary data.

Thanks to everybody 'cause you gave me a lot of usefull info!Thanks!
I have a question about the use of wire.write(...,...) : why if I use msg="T34.340" everythings goes but if I use msg="T"+str(Ttot/samples,s),6) ??
It is due to the heap allocation space problem?

What do you expect to be returned from str(Ttot/samples,s),6) ?

Hi, the str function (self made) append “0” number to the right just to give a string always of 6 character, so str(String(Ttot/samples,2),6) (i made a mistake in the previuos post about the formula) gives 24.000 ( for example) that is an avarage value of samples ( temperatures)
I would like to have a Arduino master that collects data from sensor that are connected to 3 arduino boards .
In the master I connect a wifi to trasmit data to server via TCP/IP and a SD card to save in a file info.
So if I use msg=“24.000” wire.write goes , but if I use msg=str(String(Ttot/samples,2),6) wire.write fails.
Why?

Just a hint:

Slave

// Assuming that 'Ttot' is a float and 'samples' is a unsigned long.
float myData[3];
myData[0] = Ttot / float( samples);
myData[1] = URTot / float( samples);
myData[2] = PVTot / float( samples);
Wire.write( myData, sizeof( myData));

Master

float myData[3];
Wire.requestFrom( slaveAddress, sizeof( myData));
if( Wire.available() == sizeof( myData))
{
  Wire.readBytes( (char *) myData, sizeof( myData));
  Serial.println( myData[0]);
  Serial.println( myData[1]);
  Serial.println( myData[2]);
}

Without full code can’t really help...

see snippets-r-us.com

Koepel:
Just a hint:

Master

float myData[3];

Wire.requestFrom( slaveAddress, sizeof( myData));
if( Wire.available() == sizeof( myData))
{
 Wire.readBytes( (char *) myData, sizeof( myData));
 Serial.println( myData[0]);
 Serial.println( myData[1]);
 Serial.println( myData[2]);
}

A question for you please -
Shouldn’t the Wire.availble() check be >= not ==?

ScrewLoose:
A question for you please -
Shouldn’t the Wire.availble() check be >= not ==?

In the strict sense, it is even not required to execute the ‘Wire.available()’ code after the execution of ‘Wire.requestFrom()’ code as the later is a looping instruction which terminates after bringing in into its ‘local FIFO Buffer’ the requested amount of bytes from the slave/bus. The following codes are good enough:

Wire.requestFrom(slaveAddress, n);  //data are in the unseen FIFO Buffer of Master
for(int i=0; i<n; i++)
{
  myData[i] = Wire.read();  //data comes from unseen FIFO Buffer into user defined 'byte type' myData[] array 
}

@GolamMostafa, nope. Wire.requestFrom() is a non blocking function. All it does is set up the TxBuffer to clock in the specific amount. All the transmission is done in ISR. So you DO need Wire.available().

septillion:
@GolamMostafa, nope. Wire.requestFrom() is a non blocking function. All it does is set up the TxBuffer to clock in the specific amount. All the transmission is done in ISR. So you DO need Wire.available().

This is my understanding:
In response to 'Wire.requestFrom()' command, the Slave(for example: another Arduino) enters into 'void sendEvent(){}' routine which is an 'interrupt context' and then performs necessary 'Wire.write()' instructions to queue the 'to be released data bytes' into local buffer. After that, the Slave sends ACK signal to the Master (the Master is still looping within Wire.requestFrom() to sense ACK) and then the Master keeps issuing SCL pulses to bring in the data bytes from the Slave's buffer (of course on ACK basis for every byte of data) until all the requested data bytes arrive or NACK is received.

GolamMostafa:
(the Master is still looping within Wire.requestFrom() to sense ACK)

This is where your understanding goes wrong :wink: The receiving part of the master is also done in ISR. Because the transmission of the bytes (and response to the ACK etc) is done in hardware :slight_smile:

septillion:
@GolamMostafa, nope. Wire.requestFrom() is a non blocking function. All it does is set up the TxBuffer to clock in the specific amount. All the transmission is done in ISR. So you DO need Wire.available().

hum.. curious about that statement...

How do you understand these lines of code then in Wire.requestFrom() ?

  // perform blocking read into buffer
  uint8_t read = twi_readFrom(address, rxBuffer, quantity, sendStop);

(code for twi_readFrom() is here)

Also how do you explain that the documentation states:

Returns
byte : the number of bytes returned from the slave device

how would the function know the number of bytes actually returned by the slave if it was non blocking?

septillion:
This is where your understanding goes wrong :wink: The receiving part of the master is also done in ISR. Because the transmission of the bytes (and response to the ACK etc) is done in hardware :slight_smile:

But, the following codes are working without the need of Wire.available() code at the Master side:

Master Codes:

#include <Wire.h>

void setup()
{
  Serial.begin(9600);
  Wire.begin();
  
  Wire.requestFrom(0x08, 2);  //(looping code) requested 2-byte known data (0x12, 0x34) from Slave
  byte x = Wire.read();
  byte y = Wire.read();
  
  Serial.println(x, HEX);
  Serial.println(y, HEX);

}

void loop()
{
 
}

Slave Codes:

#include <Wire.h>

void setup()
{
  Serial.begin(9600);
  Wire.begin(0x08);
  Wire.onRequest(sendEvent);
}

void loop()   //waiting for I2C interrupt.
{

}

void sendEvent(int howMany)  //interrupt context
{
  Wire.write(0x12);  //queuing in buffer 
  Wire.write(0x34);
}

Screenshot:
sm152.png

sm152.png

the best practice is probably to read how many bytes were actually returned by Wire.requestFrom() and not trust blindly that you can read exactly how many you requested.

comparing the number of bytes returned versus the number of bytes expected should be a good hint of things "looking good" if they are equal. but your use case can be different, ask of a string and you don't know the length, so you provide a large enough buffer for the ask and then really handle what is coming back.