EasyTransferI2C with 3 Arduinos - trouble w/ Master Tx & Rx

Hello.. many thanks for any help!

I have a brushless motor system being run by three Arduinos. The system has grown from a single Uno as the pulse-controller, adding a 2nd with an LCD and Joystick as a user interface, and finally a Mega w/ ethernet and relay shields. As the system grew, I first used SoftEasyTransfer between the Pulse controller (Slave) and User Interface (Master), and moved over to I2C to integrate the 3rd Arduino (BATManager, 2nd slave)... and now I've hit a wall... being that Tx to the 2nd Slave works perfectly. But I can not get the Master to Tx or Rx from Slave 1 in either direction. My current code has periodic transfers from the Slave, and a push-putton to cause a momentary Tx to the Slave from the Master.

In summary, the system its a brushless motor controller, using A0 to sense a magnet through a sensing coil. When a "threshold" value is reached, the drive coils are charged until the analog input falls below the "threshold". The "threshold" value is the primary variable being passed around, but the EastTransfer data structures have a standard set that may be implemented in the future. Master has pins for an IR LED and IR phototransistor, which make up an optical tachometer, as well as 2 pushbuttons, joystick, and Seeed Studio 16x2 LCD, and 2 LEDs (unused at the moment).

Hardware setup along the I2C bus is 2.2K Ohm resistors to the 5v line, and all Arduinos share a GND. I've read conflicting info about whether the 5v (+) VDD needs to also be shared. Testing with the (+) shared had the same results. The relay shield and LCD seem to overload a common 9v battery, so I plan to power each Arduino with it's own battery. For testing each are powered through the USB cable.

From the research, I've covered the basics... I'm using A4 & A5 on both UNOs and 20/21 on the Mega for SDA and SCL respectively. Shared Ground. Each Arduino has their own I2C ID.

I've added a bunch of Serial.printlns to debug where things are... odd thing is that all three programs run along, no crashes... the BatMan (slave 2) receives fine. Problems are 1) when the Master sends data to the Pulse Controller (slave 1), code makes it as far as receiveEvent() and simply moves on.. the data is not received, no variables updated, keeps on looping. The next time I try to update the Slave 1, it does not make make it to the receiveEvent(). On the Master, the same behavior, in that the receiveEvent() will trigger once, then not again... but the loops keep going. No compile errors.

System seems to run for as long as practical without locking, so I don't think it's a stack-overflow or something like that... I'm curious if I have a type mis-match somewhere/somehow?!? I'm only passing Int's around at this point... seemed simple enough! Interrupt collision maybe? I'm grasping at straws at now.

Master is an UNO R3, Slave 1 is an UNO SMD and the BatMan (slave 2) is a Mega 2560. Not sure what else to add except the code. Questions welcome and thanks again for any suggestions.

Master code: Dropbox - Error - Simplify your life
Slave 1 code: Dropbox - Error - Simplify your life
BatMan (slave 2) code: Dropbox - Error - Simplify your life

void receiveEvent(int numBytes) {  Serial.println(".receive.");
}

This is called in interrupt context, you cannot put something out to the serial interface (the serial interface uses interrupts too which are blocked during the interrupt handler).

    SlaveIn.begin(details(Srxdata), &Wire);    // start ET library, pass data details and serial port.
    delay(100);
    SlaveOut.begin(details(Stxdata), &Wire);
    delay(100);
    
    BatManIn.begin(details(BMrxdata), &Wire);    // start ET library, pass data details and serial port.
    delay(100);
    BatManOut.begin(details(BMtxdata), &Wire);
    delay(100);

The I2C bus is not a full duplex bus as the standard (USART) serial interface is. The communication is always controlled by the bus master. Although theoretically there can be multiple bus masters this feature is not supported by the Wire library and you shouldn't use it anyway under normal circumstances.

The bus master initiates a communication by addressing the slave, tell it if it's a read or write request and then supplying the clock signal for both the read (data line is handled by the slave) and write (data line is handled by the master) transfers. So if you wanna receive something from the slave on the master, the master has to poll the slave periodically. And such transfers usually are following this scheme: the master writes the register address to the slave and initiates a read transfer from it. The slave sends the contents of the addressed register and any following if the master is still reading.

The slave is not able to initiate a transfer, it always has to wait for the master to initiate a transfer in either direction.

Why do you have the delays in here? They are completely unnecessary.

Excellent. Thank you for the reply. I'll convert the flow to onRequest, instead of onReceive, and poll Slave 1 from the Master. Did I get that correct?

Interestingly, the serial printLn's that are part of the receiveEvent on Slave 2 does not seem to mind them.. those Tx & Rx OK. I added them just to debug... but from what you're saying it seems like I'm creating a fail-state. Please help me understand why they work in one instance and not the other.

Also, do calls to different slave devices take any processing cycles away from the non-called slaves? My goal is to keep Slave 1 doing it's work as quickly as possible, and not "distracted" by other communications or processes, which is the whole reason I went from 1 to multiple Arduinos.

The delays were just more grasping at straws.. I'll pull them. :slight_smile: Is there a case when delays are critical when starting up communications? I see them suggested for the LCD and others..They seem like band-aids for many peoples sketches. I'm having a difficult time understanding the distinctions when it's truly needed.

cheers, and thanks again. I look forward to posting again when I've made the mods!!!

I'll convert the flow to onRequest, instead of onReceive, and poll Slave 1 from the Master. Did I get that correct?

Not really. The usual way to use I2C communication is to have a register based device. To control the device you write to some registers or read from them. To set a register you first send the register address and then the value. Reading is similar. You first write the register address. When you start reading the device will start at the last register address you wrote.

To get your communication between the Arduinos up you have to define your set of registers and what they are good for. Then implement the slave that way to act like a chip device, so when the master writes some register it does one thing, when the master writes to the second register it does another thing. The master can also read the value of some register to get resulting or sensed values back from the slave. This is a completely different way of talking to the slaves than you would with a two way serial connection.

Please help me understand why they work in one instance and not the other.

If a write the serial interface succeeds within an interrupt handler depends on several factors. The problem is, it's executed while another part of your program is interrupted. The buffer of the serial interface is filled up with new characters. If the buffer fills up to it's upper limit, the call blocks till the buffer gets emptied. If the call was made within an interrupt, other interrupts are disabled, so the buffer will never be processed.

You have to take care for the variables too. Every variable you use inside an interrupt handler and in your normal code has to be declared "volatile", else the compiler may optimize it away.

Also, do calls to different slave devices take any processing cycles away from the non-called slaves?

No, the hardware will take care for that, the MCU knows the address and will call the interrupt routine only if it's been addressed accordingly.

I'm having a difficult time understanding the distinctions when it's truly needed.

In most situations they are not needed. If the datasheet of a device specifies that the MCU has to wait some time before putting some other line HIGH or LOW, you may have to insert some delays but in almost all cases I know, these delays are not calls to delay() but to delayMicroseconds().
The delay() call is handy if you want or have to wait some time and you know that the MCU doesn't have any other task to do. Many people are lazy and just insert some delay() calls although they don't know why they're doing so. Often they tried and with some delay it worked for them, later some other people use the same sketch with minimally different circumstances and the code will fail. You should always take the correct way (perhaps wait for some pin to become HIGH or for a value to appear in some register, etc.).

Thanks Pylon. But, I'm still stuck... I'm having trouble understanding your feedback, with regard to how to implement this via EasyTransferI2C. I had assumed that some of this is handled by the library. Are the "wire.onRequest" and "wire.onReceive" needed? Do I use the ".sendData()" in stead of the ".write()"?

Further troubling was that this combination would not compile

Wire.onRequest(requestEvent);           // register event
void requestEvent(int numBytes) { }

with the following error:

:136: error: invalid conversion from 'void ()(int)' to 'void ()()':136: error: initializing argument 1 of 'void TwoWire::onRequest(void (*)())'

I've trimmed out the multiple structures and simplified things a bit.

New files are here:Dropbox - Bedini_AutoTuner - Simplify your life

...Not sure exactly where to go from here. Any thoughts?

The library is making things easier but you're trying to use it in a manner it is not intended for. It just tries to give an easier interface to the Wire library but it's not able to give the I2C bus capabilities it does not have.

Have you understood the I2C paradigm of master and slave? Your master is still configured as a slave (Wire.begin() call with an address parameter), this way you cannot send commands to the slaves.

Hi Pylon, thanks for your reply.

Have you understood the I2C paradigm of master and slave? Your master is still configured as a slave (Wire.begin() call with an address parameter), this way you cannot send commands to the slaves.

Hmm.. I guess not! I thought that each I2C "node" had to have an address, in order for messages to be routed to the correct node.. thus I thought each had to be identified w/ a #define.

Looking at the Tx and Rx examples from the EasyTransferI2C library, they send from the Master to the Slave, which I will want sometimes, but I also am wanting the reverse... in order to get information from the slave. How will I identify the Master, when receiving data from the slave?

What is also confusing me, with regard to EasyTransfer vs standard I2C, is the "number of bytes" when doing a request. How do I make a request to the slave, in the EasyTransfer data structure paradigm?

Many thanks for helping me to understand this. You've been very helpful. :smiley:

The most important fact in I2C communication is that a transfer is always initiated by the master. This is either a write operation (master sends to slave) or a read operation (master receives data from slave).
The master doesn't need an address on the bus because it's always initiating every communication and it's also controlling it (by providing the clock signal).
If you wanna send information from the slave to the master, you have to take care that the master is polling the slave for that information often enough. Although there is no rule how the slave organizes the I2C transfers the most often used scheme is the register based approach. That means the slave defines some registers which the master is able to read and write. Each register has it's address, sequential reads and writes are allowed (auto-incrementing register address). Most I2C devices use this way to communicate because it naturally fits the I2C specification.
In your case for slave 1 I would define two registers, on address 0 (and 1 because it's an integer) you have threshold value, on address 2 (and 3 for the second byte) you have the sensor value. If you wanna save some register addresses you can combine the two because probably the threshold value is only written while the sensor value can only be read. How you organize the byte order (little or big endian) is on you, both have advantages and disadvantages.

What is also confusing me, with regard to EasyTransfer vs standard I2C, is the "number of bytes" when doing a request. How do I make a request to the slave, in the EasyTransfer data structure paradigm?

I wouldn't use the EasyTransfer library in your case. It only makes things more complicated and I don't see the need for dynamically sized transfers on the I2C bus.

I wouldn't use the EasyTransfer library in your case. It only makes things more complicated and I don't see the need for dynamically sized transfers on the I2C bus.

Ok.. I suppose it's best I understand the I2C basic methods, especially to work with devices not-as-smart as Arduinos. I was hoping to use EasyTransfer as I assumed I would be able to more easily change data types and sizes without having to bit-bang the changes through... perhaps I'm over thinking!

My confusion is centered around this thing you're call a "register based approach". The theory is clear, but the implementation eludes me. I feel like I'm asking to much for your to hand-hold me through this, so I wonder if you could recommend an online resource for I2C, or other resource? I suppose I need an I2C primer which is more in-depth than the Arduino references.

EasyTransfer (imho) still feels like the right, more dynamic solution for communicating to the web-server slave... do you foresee any problems using them together?

I was hoping to use EasyTransfer as I assumed I would be able to more easily change data types and sizes without having to bit-bang the changes through... perhaps I'm over thinking!

This isn't wrong, EasyTransfer makes this a little bit easier. The question is, does this approach make it easier for you in your project.

I suppose I need an I2C primer which is more in-depth than the Arduino references.

Try the introduction of Nick Gammon (a moderator on this forum), it's the only one I know which is Arduino based.

EasyTransfer (imho) still feels like the right, more dynamic solution for communicating to the web-server slave... do you foresee any problems using them together?

No, other than it might guide you again to think about the I2C as a fast version of an UART communication. It was designed to let a processor talk to some devices (chips) and not to let two processors talk to each other. Although this is possible with I2C, it forces you to select one side to be the master while the other side is the slave. In a processor-processor communication it's not always obvious which one should be the master.

Thank you Pylon (bow). I'll read Gammon's intro and see how it goes. Very much appreciate your feedback with this. cheers!