Notifications
Clear all

Xiao command response communication demo

11 Posts
2 Users
0 Likes
753 Views
(@jfabernathy)
Member
Joined: 3 years ago
Posts: 141
Topic starter  

In an effort to educate myself on how I could have one Xiao act as master and tell a slave Xiao what to do, I created a demo where the master sends over to the slave 2 bytes, the first is which data set to return and the second byte is which color LED to turn on. To keep it simple the master alternates the data set byte command between 0 and 1.  And it alternates the LED command between 0, 1, and 2.

The slave looks at the 2nd byte command and turns on the right LED. It used the 1st byte command to select which message to send back. I thought about an analog value, but settled on 2 different text messages to return.

  I'll attach the Arduino files.

I left in some SerialUSB.print statements that have been commented out on the slave side. Those are what helped me figure out what was going own. I have a workaround that is to get around a bug in the Xiao Wire.OnReceive callback function triggering with zero data prior to triggering correctly.

If your code won't compile, have another glass of bourbon. Eventual the problem will be solved.


   
Quote
(@jfabernathy)
Member
Joined: 3 years ago
Posts: 141
Topic starter  

FYI, I have a Raspberry PI Python3 programs that does the same as the Master Xiao program above.  I'll need that capability for an other project.

If anyone is interested I can share.

If your code won't compile, have another glass of bourbon. Eventual the problem will be solved.


   
ReplyQuote
frogandtoad
(@frogandtoad)
Member
Joined: 5 years ago
Posts: 1458
 

@jfabernathy

I've been having a quick look at your code, and there is one particular piece of it that I will highlight for improvement, as it looks like you're making it hard on yourself 🙂

Well, in IMHO anyway, for whatever that's worth.

Here, you receive the number of bytes returned, but you never use the value obtained:

  int number_of_bytes_returned = Wire.requestFrom(SLAVE_ADDR,return_size);

It appears the following was to just make space for (number_of_bytes_returned)?
If so, I'd advise against doing that, as there are far better ways to approach this:

  // Add characters to string
String response = " ";

Again, you set the number of bytes returned to the variable back to zero, throwing away the returned value without using it.  It looks like you might have done that just to set your index to zero for the response array, but since you're only printing it to the serial monitor, there is a much better way to do this, as I show below:

  number_of_bytes_returned = 0;
  while (Wire.available()) {
  response[number_of_bytes_returned] = Wire.read();
  number_of_bytes_returned += 1;
 }

Here are two examples, which are no better or worse than your original code, in terms of safety, but IMHO, much cleaner.

  // Example 1 - We already know the size we requested
for(byte index(0); index < return_size; index++) {
response[index] = Wire.read();
}

// Example 2 - We don't really need to know the size 
while (Wire.available()) {
response.concat(Wire.read());
}

However, the following example, is IMHO, both much cleaner and safer, and the one I would personally go with in my final version of code:

  // Example 3 - Uses the number of bytes we got back to compare with what we requested
String response;
if(number_of_bytes_returned == return_size) {
while (Wire.available()) {
response.concat(Wire.read());
}
}

As we can see, there is nothing to track and increment, etc...

Cheers.


   
ReplyQuote
(@jfabernathy)
Member
Joined: 3 years ago
Posts: 141
Topic starter  

thanks, I'll play with these suggestions.  Some are there for debug and I didn't completely remove them. One that bugged me was the initializing of the string to 10 blanks in quotes.  Before I did that the code wouldn't work.  I know it's been a while since I had to make a living with C++ code, but some of this was bizarre. 

I'd like to blame it on drinking too much bourbon while coding, but there isn't such a thing as too much for a retiree, regardless of what your doctor says. 😀 

 

If your code won't compile, have another glass of bourbon. Eventual the problem will be solved.


   
ReplyQuote
(@jfabernathy)
Member
Joined: 3 years ago
Posts: 141
Topic starter  

ran into a problem trying to implement one of your solutions.

When I see the SerialUSB output now the word that should be Goodbye is 71111111100981211010 

and the word that should be Hello is 72101108108111.

It appears to be the decimal equivalent. H = 72, e = 101, l = 108, l = 108, o = 111 

since it was declared a String shouldn't it print "Hello" or is it missing some termination character to make the print treat it as a string?

If your code won't compile, have another glass of bourbon. Eventual the problem will be solved.


   
ReplyQuote
frogandtoad
(@frogandtoad)
Member
Joined: 5 years ago
Posts: 1458
 

@jfabernathy

Posted by: @jfabernathy

ran into a problem trying to implement one of your solutions.

When I see the SerialUSB output now the word that should be Goodbye is 71111111100981211010 

and the word that should be Hello is 72101108108111.

It appears to be the decimal equivalent. H = 72, e = 101, l = 108, l = 108, o = 111 

since it was declared a String shouldn't it print "Hello" or is it missing some termination character to make the print treat it as a string?

Sorry about that, I had forgotten to add that because it comes back as a byte, it needs to be cast back to a char - Here is how it should look like with the cast:

  String response;
  if(bytesRecieved == bytesRequested) {
    while (Wire.available()) {
      response.concat(char(Wire.read()));
     }
   }

As for the nul termination character '\0', it generally should be added if you intend to send a string of characters, so you have to be careful when using character arrays to send strings... much better to simply build up and send a 'String response' instead, such as:

 Wire.write(response);

Or even simply the following, if you know what you want to send ahead of time:

 Wire.write("Hello");

Cheers.


   
ReplyQuote
(@jfabernathy)
Member
Joined: 3 years ago
Posts: 141
Topic starter  

I finally got something that prints correctly, but it's really not important. The demo was about the communication between 2 Xiao's on I2C. Now that that is working, I'll move on to other projects.

char response[7];
for(byte index(0); index < return_size; index++) {
response[index] = Wire.read();
}
if(number_of_bytes_returned == 5) {
response[5] = ' ';
response[6] = ' ';
}
// Print to Serial Monitor
SerialUSB.print(number_of_bytes_returned);
SerialUSB.print(" bytes returned from slave = ");
SerialUSB.println(response);

If your code won't compile, have another glass of bourbon. Eventual the problem will be solved.


   
ReplyQuote
frogandtoad
(@frogandtoad)
Member
Joined: 5 years ago
Posts: 1458
 

@jfabernathy

Posted by: @jfabernathy

I finally got something that prints correctly, but it's really not important. The demo was about the communication between 2 Xiao's on I2C. Now that that is working, I'll move on to other projects.

Glad it's working for you, and you that learn't more about I2C communications.

Personally, I always use, and advise that newbies use of the 'String' data type over arrays, unless they really need an array for something specific.  I know you're not a newbie, but they are easier to use, and can make your code logically cleaner to read, and less buggy, IMHO.

Cheers.


   
ReplyQuote
(@jfabernathy)
Member
Joined: 3 years ago
Posts: 141
Topic starter  

@frogandtoad I couldn't let it go. 😀 😀  I had to figure out how the String stuff works. They didn't have them in Fortran IV so I've never been comfortable with them. 😋 

So, this worked as well:

int number_of_bytes_returned = Wire.requestFrom(SLAVE_ADDR,return_size,true);

String response;
if(number_of_bytes_returned == return_size) {
    while (Wire.available()) {
        response.concat(char(Wire.read()));
    }
}
// add NULL}
response[return_size] = '\0';

// Print to Serial Monitor
SerialUSB.print(number_of_bytes_returned);
SerialUSB.print(" bytes returned from slave = ");
SerialUSB.println(response);

If your code won't compile, have another glass of bourbon. Eventual the problem will be solved.


   
ReplyQuote
frogandtoad
(@frogandtoad)
Member
Joined: 5 years ago
Posts: 1458
 

@jfabernathy

Posted by: @jfabernathy

@frogandtoad I couldn't let it go. 😀 😀  I had to figure out how the String stuff works. They didn't have them in Fortran IV so I've never been comfortable with them. 😋 

So, this worked as well:

int number_of_bytes_returned = Wire.requestFrom(SLAVE_ADDR,return_size,true);

String response;
if(number_of_bytes_returned == return_size) {
    while (Wire.available()) {
        response.concat(char(Wire.read()));
    }
}
// add NULL}
response[return_size] = '\0';

// Print to Serial Monitor
SerialUSB.print(number_of_bytes_returned);
SerialUSB.print(" bytes returned from slave = ");
SerialUSB.println(response);

Good for you... glad you're looking into these things 🙂

I'll add that you do not need to add the NULL terminator to the end of the 'String' object variable type, as it handles that automatically for you.

Just to prove it - Lets add 1 to the length to see if it's there.
As an experiment, also try embedding a NULL terminator in the string and see what happens: ("Hello \0World")

 String response1 = "Hello World";

for(intindex(0); index < response1.length() 1; index++) {

if(response1[index] == NULL) {
Serial.println("NULL character found at index: " + String(index));
   }
}
As for C-Style character array strings:
char response2[10];

response2[0] = 'H';
response2[1] = 'e';
response2[2] = 'l';
response2[3] = 'l';
response2[4] = 'o';
Serial.println(response2); // No terminator == Not OK == Undefined behavior (may simply produce junk characters)

char response3[10];
response3[0] = 'H';
response3[1] = 'e';
response3[2] = 'l';
response3[3] = 'l';
response3[4] = 'o';
response3[5] = '\0';
Serial.println(response2); // With terminator... OK, defined behavior, and no junk characters

char response4[10] = {"Hello"}; // OK, NULL terminator automatically added to end

char response5[] = {"Hello"}; // OK and better as size automatically set, NULL terminator automatically added to end
In a nutshell, the NULL terminator in strings is what allows functions to know where they end, so they can be displayed and copy, etc....
 
Cheers.

   
ReplyQuote
(@jfabernathy)
Member
Joined: 3 years ago
Posts: 141
Topic starter  

thanks, that all makes sense to me now.

 

If your code won't compile, have another glass of bourbon. Eventual the problem will be solved.


   
ReplyQuote