synchronous master-slave communication

This is just to report my progress with getting a master Arduino to send query information to a slave Arduino, which then synchronously returns the specified data.

I'm using a master-slave Arduino pair (along with a real time clock, two 24LC1025 I2C EEPROMs, and a SRF08 range finder as part of a solar/boiler controller in my basement. All of this is networked via RS-232 (not USB, since it doesn't have the range) to my laboratory computer.

A thought occurred to me about the master and slave Atmegas in the solar controller and why I was having such a difficult time making a system allowing the master to synchronously request data from the slave. The documentation of "Wire," the library responsible for I2C communication in Atmegas, is spotty and example-free, and I've been able to find no examples online of anyone sending requests from a master for the return of specific data from a slave. Part of the problem I'd been having was my assumptions about how the Wire.onRequest (Wire - Arduino Reference) handler works. I'd assumed that this handler could be used to retrieve data sent by the master (since other I2C devices seemed to work this way). But it turns out that this is not the case. To send data to a slave as part of a synchronous request followed by a returned result, the slave can only retrieve the request data using the onReceive (Wire - Arduino Reference) method. On the slave side, this means that the onReceive handler has to take the data passed to it, process it however it needs to be processed, and put the results into global variables that can then be queried in the onRequest handler.

I stayed up late working on this problem but wasn't having much luck solving it. This morning I got up early and continued to work on it, trying a few things that occurred to me after I'd awaken (but before I'd gotten out bed). But no matter what I did, I couldn't make synchronous communication completely error-free, but I managed to come close by sending three copies of all the data being requested and testing to see if they all matched. Those as frustrated by this problem as I have been are welcome to download my code (http://asecular.com/ran/1103/release02.zip), which includes this master-slave synchronous communication (along with many other things).

All of this is networked via RS-232

"Wire," the library responsible for I2C communication

How do these two work together?


Rob

Graynomad:

All of this is networked via RS-232

"Wire," the library responsible for I2C communication

How do these two work together?


Rob

That is also not real clear to me.

As far as master/slave I2C, what kind of distance do you have between them? I2C was designed for on board chip to chip communictations. It can be module to module if the distance is kept short say 1-2 feet max.

Lefty

to be clear, the i2c stuff is entirely separate from the serial communications to the computer i use to program the arduinos. basically, i have an old-school arduino setup using a max232 chip instead of the USB chip an arduino normally has (i'm not actually using an arduino board -- just the bootloader and the IDE). it's all in the basement 100 feet from my computer, so i had to use rs-232 for communication since usb range isn't very good.

down in the basement are two atmega328s connected via their i2c ports (pins 28 and 27). the master can read and write the analog and digital pins on the slave by specifying which ones it wants. it can also read and write the slave's eeprom. it cannot reflash the slave's firmware. to do that i have to throw a switch that decides which atmega's serial port (pins 2 and 3) is connected to the rs-232 cable. if you look at the source code in the link, you can see the details of the i2c communication. as far as i know, this is the only example online of reasonably-successful synchronous query-response communication between master and slave arduinos.

the reason it is so important to be able to query the slave from the master is that the master's code supports a sort of telnet-style interpreter to which i can send commands. these commands come to the master via rs-232 and can then be forwarded onto the slave.

as for the range of i2c communications, i've kept that bus short because it gets unreliable when it gets longer than a few feet. however, the rangefinder (which reads fuel level in an oil tank) has to be twelve feet away. to read it, i temporarily connect a "long range i2c" cable via quad bilateral switches for just the time necessary to make the reading. this seems to work okay.

I get it, this is very similar to something I just did but I used SPI and the boards are very close.

I had a browse at the code, but at over 3000 lines it's a bit much to absorb just for curiosity's sake.

There has to be a better way than sending stuff 3 times though. i2c slaves are supposed to be able to stretch the clock to slow things down, can you do that? Slow == reliable.

For that matter use SPI, you can run that as slow as you like.


Rob

i don't know how to slow things down on the i2c bus -- there doesn't appear to be support in the Wire library for that. as for the amount of code -- that's the whole system and i didn't have time to break out the parts. the slave code (slaverman.pde) is actually the most interesting, since it allows the slave to behave like any good synchronous query-and-respond i2c slave (such as the Ds1307 real time clock).

I2c does allow for a slave to stretch the clock, but I had a look at the Mega328 data sheet and can see no reference to that so have to assume it's not build in to the AVRs.


Rob

bigfun:
This is just to report my progress with getting a master Arduino to send query information to a slave Arduino, which then synchronously returns the specified data.

Can you explain exactly what you mean by "synchronously" in this context? Do you mean, you ask a question and get an answer?

... this means that the onReceive handler has to take the data passed to it, process it however it needs to be processed, and put the results into global variables that can then be queried in the onRequest handler ...

What is the problem with global variables? The response is done in an interrupt handler, so the thing you are responding to must, therefore, be in a global variable somewhere.

I have done some examples of sending/receiving data via I2C here (scroll down to "Request/response"):

You shouldn't need to send things three times. Why do that?

Can you explain exactly what you mean by "synchronously" in this context? Do you mean, you ask a question and get an answer?

i mean to ask a question of a device and then stop everything while waiting for an answer (as opposed to asynchronously, where i ask a question, go on with the program, and get the answer later, perhaps as an interrupt or in some place i can poll).

What is the problem with global variables? The response is done in an interrupt handle

there's nothing wrong with it -- but initially i'd thought the onRequest handler could also read data being passed in with the request. this does not appear to be the case. i'd found no examples of anyone using a slave to do request-response type data exchanges of the type done by, say, a 24LC512 EEPROM, and i wanted to implement it.

I have done some examples of sending/receiving data via I2C here (scroll down to "Request/response"):

Gammon Forum : Electronics : Microprocessors : I2C - Two-Wire Peripheral Interface - for Arduino

nice! i would have loved to find this example when i was working on the problem.

You shouldn't need to send things three times. Why do that?

i did it because i'd often get zero instead of the byte i'd requested. i have no idea why -- it was being flaky. i need to investigate further. the other i2c devices on the bus all seem to work fine; it's just the slave that occasionally throws out glitch zeroes in response to queries.

I've been able to find no examples online of anyone sending requests from a master for the return of specific data from a slave.

I agree with this as stated, however, just for other's future reference, there are 6 short example sketches in the examples folder under the wire lib. Four of these are the various combinations of master and slave comm - but none for an entire comm loop. I only mention this because it seems that lately many people are not aware of the examples in the lib folders.

bigfun, I understand your situation. I've had similar problems. What I've come to believe is that response from the Slave must be almost instantaneous after the Masters request. (I remember one project I had where the slave would immediately respond to that master with a value of how long it must wait until making the request again.)

I also know about "clock stretching" on the Slave side but haven't tried it. It might be simply a matter of the slave setting the clock pin low with a digitalWrite.

In any case, I'm interested in how this thread develops, and what Nick has done.

bigfun:
i did it because i'd often get zero instead of the byte i'd requested. i have no idea why -- it was being flaky. i need to investigate further. the other i2c devices on the bus all seem to work fine; it's just the slave that occasionally throws out glitch zeroes in response to queries.

Glad my examples helped. As for the zeros, do you check that there is data available? (if (Wire.available)?). If it passes that test, the data should be good. If not, the slave may have taken too long to respond. I haven't found a quoted maximum time for the response but I assume you are supposed to do it almost immediately. Also it might conceivably help if the master did a "repeated start" for the response rather than "end/start" but I don't know that the Wire library supports that right now.

As for the timing, it isn't clear exactly how long your slave takes to respond, but if it has to do something (like an analog measurement) that might take too long. You could take a leaf from the 1-wire temperature sensors, and have two messages.

Eg. Message 1: take a reading. (pause 50ms). Message 2: give me your most recent reading (and then request a response). That way you introduce a delay to let the slave actually find the data. But this is still synchronous because you do these things in sequence at the master end.

My preference with this sort of thing has always been to separate the two functions, ie the IO action and the serial comms action.

IO is done periodically regardless and when asked for a value the most recent one is supplied so there is no waiting. OTOH if the IO is simply a read of a pin (using direct IO, not digitalRead()) it doesn't matter, I would not call any library functions though.


Rob

this is in reply to nick about the use of Wire.available(). interestingly, i'd had code in there to do that but had bypassed it for some reason (i notice it's occasionally not used when communicating with certain other i2c devices when the byte count to be returned is known). re-enabling it seems to be making my communication completely reliable. thanks for the idea!

I saw a routine in The Arduino Cookbook by Margolis. Chapter 13.9

Have you seen that? If so was it helpful?

I only have one Arduino so I cannot test the code.

i don't have the arduino cookbook. things are moving so fast with arduino that a book would probably get out of date pretty fast. i started working on atmega8s and my old sketches resemble my first VIC-20 BASIC programs: short and primitive.

Hi,

can you post code to write and read on 24LC1025?

Thanks!!!!