Notifications
Clear all

Xiao to Xiao I2C communication demo

21 Posts
4 Users
2 Likes
3,125 Views
(@jfabernathy)
Member
Joined: 3 years ago
Posts: 141
Topic starter  

Some of us have noticed that the Arduino to Arduino Master/Slave demo that Bill used in the Video does not compile clean for use on the Xiao. During my looking at that I wanted to learn more about I2C communication and the Wire library on Arduino so I added some logic and Serial.print statements to see what's really happening. The puzzling part of this is that I seem to have observed that the OnReceive function can be called when there is no data yet. This means that the howmany available variable is 0 and the Wire.available() function returns 0.

I had to put in some logic to look for this to avoid going thru the code with no data.  I'd be interested in others letting me know if this is my bug or just the way it operates.  My code is below.

This is the master code

/*
Modified from the demo that Dronebotworkshop created.
I2C Master Demo
i2c-master-demo.ino
Demonstrate use of I2C bus
Master sends character and gets reply from Slave
DroneBot Workshop 2019
https://dronebotworkshop.com
*/

// Include Arduino Wire library for I2C
#include <Wire.h>

// Define Slave I2C Address
#define SLAVE_ADDR 9

// Define Slave answer size
#define ANSWERSIZE 5

void setup() {

// Initialize I2C communications as Master
Wire.begin();

// Setup serial monitor
SerialUSB.begin(9600);
SerialUSB.println("I2C Master Demonstration");
}

byte i = 0;

void loop() {
delay(1000);
SerialUSB.println("\nWrite data to slave");

// Write a character to the Slave
Wire.beginTransmission(SLAVE_ADDR);
Wire.write(i);
Wire.endTransmission();
i += 1;

SerialUSB.print("One byte sent to slave = ");
SerialUSB.println(i, HEX);

// Read response from Slave
// Read back 5 characters
int number_of_bytes_returned = Wire.requestFrom(SLAVE_ADDR,ANSWERSIZE);

// Add characters to string
String response = "";
while (Wire.available()) {
char b = Wire.read();
response += b;
}

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

This is the slave code:

/*
Modified from the demo that Dronebotworkshop created.
I2C Slave Demo
i2c-slave-demo.ino
Demonstrate use of I2C bus
Slave receives character from Master and responds
DroneBot Workshop 2019
https://dronebotworkshop.com
*/

// Include Arduino Wire library for I2C
#include <Wire.h>

// Define Slave I2C Address
#define SLAVE_ADDR 9

// Define Slave answer size
#define ANSWERSIZE 5

// Define string with response to Master
String answer = "Hello";

void setup() {

// Initialize I2C communications as Slave
Wire.begin(SLAVE_ADDR);

// Function to run when data requested from master
Wire.onRequest(*requestEvent);

// Function to run when data received from master
Wire.onReceive(*receiveEvent);

// Setup Serial Monitor
SerialUSB.begin(9600);
SerialUSB.println("I2C Slave Demonstration");
}

void receiveEvent(int howmany) {
byte x = 0;
if (howmany == 0) {
return;
}

// Read while data received
while (Wire.available()) {
x = Wire.read();
}

// Print to Serial Monitor
SerialUSB.print("\nReceive event - data receviced = ");
SerialUSB.println(x,HEX);
}

void requestEvent() {

// Setup byte variable in the correct size
byte response[ANSWERSIZE];

// Format answer as array
for (byte i=0;i<ANSWERSIZE;i++) {
response[i] = (byte)answer.charAt(i);
}

// Send response back to Master
Wire.write(response,sizeof(response));

// Print to Serial Monitor
SerialUSB.println("Request event - data sent");
}

void loop() {

// Time delay in loop
delay(50);
}

 

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


   
Quote
(@dronebot-workshop)
Workshop Guru Admin
Joined: 5 years ago
Posts: 1085
 

@jfabernathy  Well, I think we've just found our resident "XIAO Guru"!  I'll have to work to catch up with you.

🤩

Thanks so much for taking the time to work this out and share it with everyone!

😎

Bill

"Never trust a computer you can’t throw out a window." — Steve Wozniak


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

@jfabernathy

I'm not sure exactly what you're trying to demonstrate?

// Function to run when data requested from master
Wire.onRequest(*requestEvent);

// Function to run when data received from master
Wire.onReceive(*receiveEvent);

The function declarations declared above do not even match the wire library function signatures directly?

void onReceive( void (*)(int) );
void onRequest( void (*)(void) );

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

@jfabernathy

I'm not sure exactly what you're trying to demonstrate?

// Function to run when data requested from master
Wire.onRequest(*requestEvent);

// Function to run when data received from master
Wire.onReceive(*receiveEvent);

The function declarations declared above do not even match the wire library function signatures directly?

void onReceive( void (*)(int) );
void onRequest( void (*)(void) );

I guess I don't understand your question. 

The original problem was that in the Arduino to Arduino demo that Bill did in the Video a while back, the code would not compile for those of us who tried it with the latest Ardunio IDE setup for Xiao. All I did to fix that was turn the Wire.onRequest argument into a pointer by putting in the *. I guess that the original code for Arduino assume the argument was a pointer without the *.

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  

I went back and looked at that section of code that gives the Wire library the callback functions. I tried putting in more legal definitions but they all failed to compile . My coding days are behind me so I'm not smart enough to get it right all the time. If it works, I leave it alone 😀 .

Her'e what worked

// Function to run when data requested from master
    Wire.onRequest(*requestEvent);

// Function to run when data received from master
    Wire.onReceive(*receiveEvent);

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  

To better understand I2C communication on the Xiao I created a Master in Circuitpython that can communicate with the original Arduino slave code I posted above.

import time
import board
import busio

i2c = busio.I2C(board.SCL, board.SDA)
cmd = ['A', 'B', 'C', 'D']
i = 0

while True:
    time.sleep(1)
    while not i2c.try_lock():
        pass
        i2c.writeto(0x09, cmd[i])
        print("byte written = ", cmd[i])
        i = i + 1
        if (i > 3): i=0
        result = bytearray(5)
        i2c.readfrom_into(0x09, result)
        print("results sent back = ", result)
        i2c.unlock()

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


   
ReplyQuote
byron
(@byron)
No Title
Joined: 5 years ago
Posts: 1122
 

@jfabernathy

I recognised your happy fizzog on the circuitpython forum 😀 and saw that maybe you are going to make a custom build for your xiao to get and i2c slave to work ?   If you do get to try this then perhaps you could give a short write up on this process as that would interest me at least.  

I have found that using the xiao with the Arduino IDE on my mac did not always work so well meaning I had to twiddle a wire to the small pads to re-boot it on several occasions.  Everything went smoothly when using it with circuitpython so that may be the best way for me to use it.  However, using the xiao with circuitpython and using one of their libraries for making temperature readings (that also meant I had to include some other libraries too) the whole shebang was just too large for the board and I was getting out of memory errors.   So one way around this may be to do a custom build that only includes items relevant for the particular project.  So when I saw you may be thinking of doing a custom build I saw an opportunity to ride on your coattails. 😎   (ok I'm a lazy old git)


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

@bryon, so maybe a custom build for the Xiao might work. I had memory errors when I tried to use the bmp280 and lcd libraries for a temperture/pressure display. After deleting every comment and whitespace I got it to work. Then I did the whole thing in Arduino IDE.  I have no issues with Arduino and the Xiao on either Windows 10 or Linux Mint 20. For my I2c test programs I can even run 2 copies of Arduino IDE with separate Comms ports so I can control both Xiao's at the same time. I can't help you with Xiao on Mac as I thru my last one away. 

I've heard around the net that Circuitpython on the Xiao is not easy because of the low memory and the rest method with the jumper.  Once I get Arduino IDE talking to it, no issues for me.

 

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  

FYI, there's a slightly higher level way to do the Master program by using the I2C_Device library. The equivalent for the slave is I2C_Peripheral but that's not in the CP build for the Xiao. The code for the Master is below:

 

import time
import board
import busio
from adafruit_bus_device.i2c_device import I2CDevice
i2c = busio.I2C(board.SCL, board.SDA)
device = I2CDevice(i2c, 0x09)

cmd = ['A', 'B', 'C', 'D']
i = 0

while True:
time.sleep(1)
with device:
device.write(cmd[i])
print("byte written = ", cmd[i])
i = i + 1
if (i > 3):
i = 0
result = bytearray(5)
device.readinto(result)
print("results sent back = ", result)

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: @frogandtoad
Posted by: @frogandtoad

@jfabernathy

I'm not sure exactly what you're trying to demonstrate?

// Function to run when data requested from master
Wire.onRequest(*requestEvent);

// Function to run when data received from master
Wire.onReceive(*receiveEvent);

The function declarations declared above do not even match the wire library function signatures directly?

void onReceive( void (*)(int) );
void onRequest( void (*)(void) );

I guess I don't understand your question. 

The original problem was that in the Arduino to Arduino demo that Bill did in the Video a while back, the code would not compile for those of us who tried it with the latest Ardunio IDE setup for Xiao. All I did to fix that was turn the Wire.onRequest argument into a pointer by putting in the *. I guess that the original code for Arduino assume the argument was a pointer without the *.

Sorry, I just couldn't understand what you're trying to demonstrate (specifically the puzzling part), on top of what was originally demonstrated?

I only mentioned the function pointer signature, because normally you just pass the function name, which provides the address of the function, and although applying a pointer still yields the address of the function, it's quite unusual to do it that way.  Normally, we just use the address of operator '&' preceding the function name as part of the argument, or just provide the function name as the argument alone.

If you were having a problem with the Xiao, then I suggest the problem wasn't how you passed the function as an argument, but rather something else... just passing the name should not make a difference.

Cheers.


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

So my puzzling part got mixed up with all the talk about pointers. Now that the code compiles, I'll do a better job of explaining that part. I've been chasing this issues over on the Arduino forum and a gentleman pointed out that I stupidly included SerialUSB.print statements in an ISR which the OnReceive function really is.  So I cleaned all that up and can attach the Arduino IDE Slave program again with the fixes.

Now to the puzzling part.  The Xiao, like some other Arduino parts in the past, like Arduino Due, have been seeing the OnRecieve callback function triggered with the argument "howmany" equal to zero. This is not suppose to happen. It was explained to me that if the sending program sent 5 bytes, then when the callback function was triggered howmany would be 5 and Wire.available() would also return 5 on the first call to Wire.read() and then 4 on the second call, etc. When it got to zero you were done.

I was getting some triggers where howmany was zero and Wire.available() returned 0 on the first trigger of the OnReceive callback function. I  have a workaround to test for these conditions. It appears that the OnReceive callback function gets triggered for each message once with howmany = 0 and then again with the right number. I'm going to do further testing and send different number of bytes in the initial message to the slave.

So this is a bug that has been seen before. I'll see if the Seeedunio folks can do something about it.

/*
Modified from the demo that Dronebotworkshop created.
I2C Slave Demo
i2c-slave-demo.ino
Demonstrate use of I2C bus
Slave receives character from Master and responds
DroneBot Workshop 2019
https://dronebotworkshop.com
*/

// Include Arduino Wire library for I2C
#include <Wire.h>

// Define Slave I2C Address
#define SLAVE_ADDR 9

// Define Slave answer size
#define ANSWERSIZE 5

// Define string with response to Master
String answer = "Hello";

int num_howmany_zero = 0;
int num_request_received = 0;

void setup() {

// Initialize I2C communications as Slave
Wire.begin(SLAVE_ADDR);

// Function to run when data requested from master
Wire.onRequest(*requestEvent);

// Function to run when data received from master
Wire.onReceive(*receiveEvent);
}

void receiveEvent(int howmany) {
byte x = 0;
if (howmany == 0) {
num_howmany_zero += 1;
return;
}

// Read while data received
while (Wire.available()) {
x = Wire.read();
}


}

void requestEvent() {

// Setup byte variable in the correct size
byte response[ANSWERSIZE];

// Format answer as array
for (byte i=0;i<ANSWERSIZE;i++) {
response[i] = (byte)answer.charAt(i);
}

// Send response back to Master
Wire.write(response,sizeof(response));
num_request_received += 1;
}

void loop() {

// Time delay in loop
delay(50);
// SerialUSB.println("\n----------");
// SerialUSB.print("num_request_received = ");
// SerialUSB.println(num_request_received);
// SerialUSB.print("num_howmany_zero = ");
// SerialUSB.println(num_howmany_zero);

}
 

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  
Posted by: @frogandtoad

I only mentioned the function pointer signature, because normally you just pass the function name, which provides the address of the function, and although applying a pointer still yields the address of the function, it's quite unusual to do it that way.  Normally, we just use the address of operator '&' preceding the function name as part of the argument, or just provide the function name as the argument alone.

 

Cheers.

You are of course right about the & instead of what I used '*'. I went back and changed that and it still works as before. As I explained before the compile issue was not my problem. It was the bug in the OnReceive function triggering.

Thanks again for you help.

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

ow to the puzzling part.  The Xiao, like some other Arduino parts in the past, like Arduino Due, have been seeing the OnRecieve callback function triggered with the argument "howmany" equal to zero. This is not suppose to happen.

 Hmn, I see... I had not heard of that ever happening.

Posted by: @jfabernathy

It was explained to me that if the sending program sent 5 bytes, then when the callback function was triggered howmany would be 5 and Wire.available() would also return 5 on the first call to Wire.read() and then 4 on the second call, etc. When it got to zero you were done.

That is not only my understanding, but also my experience to date, so I would expect it to do just that.

Posted by: @jfabernathy

I was getting some triggers where howmany was zero and Wire.available() returned 0 on the first trigger of the OnReceive callback function. I  have a workaround to test for these conditions. It appears that the OnReceive callback function gets triggered for each message once with howmany = 0 and then again with the right number. I'm going to do further testing and send different number of bytes in the initial message to the slave.

So this is a bug that has been seen before. I'll see if the Seeedunio folks can do something about it.

No worries... I don't have one of those boards, but I'll have a look and see what I can find out.


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

Normally, we just use the address of operator '&' preceding the function name as part of the argument, or just provide the function name as the argument alone.

Cheers.

You are of course right about the & instead of what I used '*'. I went back and changed that and it still works as before. As I explained before the compile issue was not my problem. It was the bug in the OnReceive function triggering.

Thanks again for you help.

No worries... I'm so used to it now, I personally leave out the ampersand (address of operator) altogether.

Cheers.


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

Oh!, I did get chastised for having variable in my callback functions that were not declared volatile, so I'm going to clean it up, but with my check for false triggering installed, I should be okay using the Xiao.

 

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


   
ReplyQuote
Page 1 / 2