SPI between two Arduinos, slave acknowledgment

I'm neck deep learning about all of this and am trying to understand at a high level how I will be able to connect multiple Arduinos using SPI, and the timing involved. The examples I've seen with an Arduino as a slave, seem to have the slave code just looping forever until the master sends it a command. And it seems to require a virtually instantaneous response from the slave.

In my case, one of the duties of the slave is to turn on and then immediately turn off a solenoid (via a relay, via a shift register.) It is very important that the slave be able to turn off the solenoid very quickly after turning it on -- only a fraction of a second or the solenoid can burn out (this is a hobby project, automatically controlling model trains.)

I'm not really clear if the SPI interface triggers an actual interrupt in the slave, or if it is the duty of the slave to simply be at the ever-ready awaiting input. Please clarify as I seem to have read it both ways on various posts.

My concern is that if the master sends a command to the slave at the moment that the slave has turned on the solenoid, but before turning it off -- whether an actual interrupt or some looping code that is constantly checking for input from the master -- I have a problem. If SPI triggers an interrupt, then my slave won't get to finish the job of turning off the solenoid. On the other hand, if the slave doesn't check the Slave Select line until it has turned of the solenoid, then it may have missed some of the data that the master has been sending -- and with no built-in acknowledgment or error checking to know otherwise.

I have some ideas on how to deal with this, such as dedicating another I/O line that the slave will control and the master can check, so the master does not try to transmit until it knows the slave is idle...and also to send back the command (actually data) to the master so the master can confirm that it's the same as the data that was sent..but I'm probably just overlooking something fundamental.

Thanks so much for any replies to my questions!

Randy

The underlying principle is that SPI exchanges values between the master and the slave - as the master sends a value to the slave, the slave is sending its value to the master. The master is in control of when this happens.
Therefore, the way to write the slave code is to place the next value you want sent to the master into the SPI buffer and then wait until the master has performed the exchange. This 'wait" can take tot form of a loop or you simply set up an interrupt which is called within the slave when the exchange has completed.
This is why you are seeing that the slave has to respond "instantaneously" to the master - in fact the master is simply receiving the value the slave had sitting there waiting for the master to initiate the exchange.
AS for your problem with how long to activate the solenoid, I gather that the master is sending a command to the slave and you want the slave to activate the solenoid on receipt of the command. In that case, you could use the ISR from the SPI slave to initiate the solenoid power and also start a timer that has a period of the time you want the solenoid to be powered. When the timer completes, then its ISR can turn off the power. That way you separate the time the solenoid is powered from anything other than the timer period. If you could receive another command from the SPI master to activate the solenoid while the solenoid was powered from the previous command, then the SPI ISR could simply test if the timer was counting and reject/ignore the 2nd command.
Susan

Thank you, Susan. So as I understand it, SPI does not automatically trigger an ISR in the slave -- correct? I was actually planning to use the ISR as part of a watchdog circuit, in case for some reason a solenoid was energized for more than i.e. .5 seconds, I would cut power to everything.

In my case, the master Arduino is going to be sending an 8-byte string that will tell the slave Arduino which solenoids to fire. The slave will fire them one at a time, which in certain circumstances could take several seconds to complete. There is no message to be sent back to the Master, except that I thought if I echoed the original command, then the Master could check it just to confirm the data was received accurately.

If all of that seems reasonable, and I'm not overlooking anything, then I think I will dedicate one output pin of the slave and one input pin of the master, so the master will (rarely but occasionally) wait until that pin goes low before initiating an SPI transmission. The slave will hold the pin high while it is triggering solenoids, and when it's all done it can pull the pin low and wait in a loop for the next SPI command to show up from the master.

I hope that keeps things relatively simple and robust -- unless you or anyone can poke any holes in it.

Thanks so much for your time!

Randy

You don't need SPI for that - a simple serial transfer at 115200 baud will be fast enough. The slave can be looping watching for an input message, and perform its spread-over-time command sequence. If the master tries to restart before it completes, the slave can send a "hey, still busy here" message back, or start the sequence when it completes, or send a "sequence done" message, or ...
You don't need to complicate it with SPI.

I have used RS-232 with my Arduino and it is simple, but I thought SPI was equally simple to use :wink: Haven't actually tried it yet.

You are right in that the data I'm sending back and forth between Arduinos is really simple (never more than 256 bits), but in fact my project will have a master Arduino and six slave Arduinos. I could probably reduce the total number, but most of them are either reading or writing as many as 70 I/O pins (reading button presses, reading relay contact closures, turning on LEDs, powering on solenoids, and turning on relays, and interfacing to a serial device that also sends commands to the master) so I'm maxed out with the hardware I've chosen (MCP23017 shift register chips, with a max of 8 chips per Arduino, due to addressing limitations.) The project is just easier to get my head around when I have dedicated "dumb" Arduinos to handle I/O, and do the heavy lifting with the master Arduino which won't have to deal with the nitty gritty details of dozens of shift registers.

Anyway, I could probably do the whole thing (get 7 Arduinos talking to each other) with RS-485...and that's always an option. But I've been under the impression that SPI would be pretty simple, and would not require me to use interrupts in my code (which I don't want to do, because I'm using them for other purposes.) And FYI I'm interfacing to the MCP23017 chips (on Macetech Centipede boards) via an I2C interface, so that's not an option for Arduino-to-Arduino either :wink:

In this one case where I don't want to risk being interrupted before I can de-energize a solenoid, it seems simplest to just dedicate a pin that advises the master when that particular slave is available to listen for an SPI data request...not very sophisticated, but seems like it will keep life relatively simple and comprehensible. I just didn't know for sure if there was some other way for an SPI slave to tell an SPI master "hey, wait a minute until I finish this task, then I can listen to you." Sounds like there is not. And I wasn't clear if SPI required the use of an interrupt, and it sounds like it doesn't (as long as the slave is constantly listening to the SPI bus for a command.) Am I mistaken?

Thanks for your thoughts!

RS232/RS485 (i.e. serial) to all the devices would be fine, just have the master send an address byte for the slaves to monitor. I2C does the same, only with a dedicated clock line for faster tranfers (100KHz or 400 KHz), so you could connect them all up with I2C, each device (up to 128) gets a unique address that it watches for and responds to. No interrupts needed. Master can send, then request status back to see if the task is done. Can do the same with SPI, but each device needs a slave select pin so it knows when it is being addressed.

Okay that's very helpful. I'll explore RS-485 a bit more deeply, might be simpler, just check to see if anything is in the serial buffer after doing critical tasks, and respond if appropriate. Thanks so much for your thoughts!

Randy

randydpeck:
Thank you, Susan. So as I understand it, SPI does not automatically trigger an ISR in the slave -- correct?

It can do (if you want it to).

... in fact my project will have a master Arduino and six slave Arduinos.

Are they adjacent or all over the room? If adjacent I2C might work quite well for you.

I agree with AussieSusan about the way it works. To get a "response" you really have to send two bytes, the first is some sort of request, and during the second send you receive the response to the first. Even then you may have to allow a few microseconds to let the slave work out what its response is (and thus have it ready in the hardware register).

... so I'm maxed out with the hardware I've chosen (MCP23017 shift register chips)

Hmm, so you are already using I2C. Still you can share that with the other Arduinos. The hardware limitation is only for that chip, not the I2C bus, although it conceivably might get a bit busy.

... in fact my project will have a master Arduino and six slave Arduinos ...

I can't offhand think of why you couldn't simultaneously talk to all 6 Arduinos via SPI, providing you weren't expecting a response*. But if they are reading things, then it gets more complex. Still as CrossRoads says, you can have slave select pins for each one, if you have that many spare pins. So you would need:

MOSI - shared
SCK - shared
MISO - shared
Slave Select x 6
SDA - for I2C - shared
SCL - for I2C - shared

It is manageable if you let the slaves do all the heavy work.

  • And if they are adjacent. Not on the other side of the room.

Thanks so much everyone for your thoughts. Nick, I'm thrilled that you have chimed in because I have read so much of what you have written about SPI, I2C, RS232 and RS485 - thank you SO MUCH for providing all of that content - it has been incredibly helpful -- more so than most everything else I have read.

Please let me just give you an idea of what challenge I am thinking about. And yes, I can put all of the Arduinos near each other, so that is not an issue. But I'm having trouble understanding if an Arduino on the receiving end of a message via SPI (or I2C, but I lean towards SPI because I'm using I2C with my shift registers) -- if the receiving Arduino needs to be standing by the moment an SPI message arrives (lest the message be lost), or if it's held in a buffer and the receiving Arduino can get to it after completing some other task. Every example I've seen is a simple loop where the slave just stands by until there is an incoming message, whereby it responds instantly. Perhaps even if it is just a 1-byte buffer, if the master can send a byte saying "I have a message for you" and the slave can come along a second or two later and say "okay, go ahead and send, I'm listening now", that might go a long way to solving my dilemma.

I also have the case that a slave Arduino might have an urgent message to send to the master, as when a certain event happens and the master must respond as quickly as possible (lest a pair of locomotives have a head-on collision.) That was where I was thinking perhaps I would just dedicate a spare digital output pin on the slave for the master to keep an eye on. When the slave pulled that line low, then the master would know to send a message for a status update. Rather than wasting the time of having the master constantly poll the slave to see if it has any urgent messages - which would complicate other logic as well.

The best possible scenario would be if an Arduino could send a message that would just sit in a big (say 128 byte) buffer like I think you get with the regular serial library (via RS232 or RS485.) But even a 1-byte buffer, where the sender could then wait until it got a response "okay to send" from the slave, would work.

Can you please provide some clarification? I'll be happy to provide you with more specific details of my project, but out of respect for your time I'm trying to whittle it down to the basic issues.

Thanks!

Randy

randydpeck:
... if the receiving Arduino needs to be standing by the moment an SPI message arrives (lest the message be lost), or if it's held in a buffer and the receiving Arduino can get to it after completing some other task.

No it would not need to be "standing by". Once you are in Slave mode, and SS pin is driven low (to activate it) then the hardware will receive the next byte without any other intervention. It can also raise an interrupt. My example in reply #1 on my SPI page shows an example of that. From the datasheet:

When the SPI is configured as a Slave, the Slave Select (SS) pin is always input. When SS is
held low, the SPI is activated, and MISO becomes an output if configured so by the user. All
other pins are inputs.

...

When configured as a Slave, the SPI interface will remain sleeping with MISO tri-stated as long
as the /SS pin is driven high. In this state, software may update the contents of the SPI Data
Register, SPDR, but the data will not be shifted out by incoming clock pulses on the SCK pin
until the /SS pin is driven low. As one byte has been completely shifted, the end of Transmission
Flag, SPIF is set. If the SPI Interrupt Enable bit, SPIE, in the SPCR Register is set, an interrupt
is requested. The Slave may continue to place new data to be sent into SPDR before reading
the incoming data. The last incoming byte will be kept in the Buffer Register for later use.

Now it won't buffer a lot of bytes, in fact it buffers two (the current one being received and the previous one).

The system is single buffered in the transmit direction and double buffered in the receive direc-
tion. This means that bytes to be transmitted cannot be written to the SPI Data Register before
the entire shift cycle is completed. When receiving data, however, a received character must be
read from the SPI Data Register before the next character has been completely shifted in. Oth-
erwise, the first byte is lost.

This gives you an entire "byte time" to pull that out of the hardware register in the ISR before it is lost. You may need to slow down SPI a bit to give the slave time to respond. (A response in 1 µS might be pushing it). However the ISR can move the bytes into a holding buffer of some size of your choice, ready for you to "really" react (as my example illustrates).

I should caution you that, as I recall, the I2C read routines go into a tight loop waiting for an interrupt on incoming data (once a byte has started to arrive) so that the I2C interface might conceivably slow down your ability to service incoming SPI. I can't remember whether interrupts are enabled or not at that point. Here (from twi.c):

  // wait for read operation to complete
  while(TWI_MRX == twi_state){
    continue;
  }

A bit more research appears to indicate interrupts are active so that might not be an issue. A bit of testing with your MCP23017s and some busy SPI might confirm that.


I also have the case that a slave Arduino might have an urgent message to send to the master, as when a certain event happens and the master must respond as quickly as possible (lest a pair of locomotives have a head-on collision.)

Hmm, this might require a bit of timing testing. After all, you can send a lot of messages in a millisecond. And these trains don't go that fast. Your idea of the extra "emergency" pins might work, although then you are dedicating another 6 or so pins to that function. If your collision detection was being handled by a single slave that might simplify it a bit.

I had an idea just then, which might save your expensive trains from crashing into each other. Instead of detecting a crash "just in time" to prevent them colliding, use the idea of the Dead Man's Handle.

In this situation, you reverse the logic. Set up a watchdog timer on the main processor (there is hardware for that, or just have it in your main loop, if it executes fast enough). What you now do is:

  • Confirm no trains are going to crash every (say) half second
  • If no such confirmation arrives in time, shut down all movement immediately

That way, a lost or delayed message, or hung slave, rather than being a disaster, simply means the main processor issues an "emergency stop". You can regard the confirmation being like the driver pushing the Dead Man's Handle button. It is an active action that proves he is awake.

Thank you for all your thoughts, Nick. Wow, SPI is a lot more complicated than I realized :wink: I will spend some time re-reading your (and other) material and try to get my head around your ideas.

Your "dead man's handle" idea is a great one, though my typical scenario is not quite like that. Typically what happens is a train is diverted into a siding, and when it trips a sensor at the end of that siding, the train needs to be stopped right away, lest it proceed back into a converging switch and onto the mainline. It will happen all the time. So one of my Arduinos will be responsible for keeping track of all of the occupancy sensors...and whenever a sensor is tripped (such as a train hitting a siding exit sensor) it needs to report that to the master Arduino right away so the master can pass a command to another Arduino that manages the interface between the Lionel Legacy control system, to tell that train it stop immediately. You are right, this happens relatively slowly compared to computer time, but there is so much going on and so much to keep track of...that the messages need to be passed relatively quickly.

May I ask, can you think of a reason why RS485 might not be a less complex solution (i.e. better!) to communicating among the various Arudinos? With a 128 byte buffer (I believe), and serial communication is just so refreshingly simple :wink: I still have situations where I want a "slave" Arduino to advise the master Aruduino "hey, you better ask me for an update" but I think that is easily handled with a dedicated digital output pin (I will be using Megas.)

Most of the examples of RS485 seem to use the SoftwareSerial library, but with 4 hardware serial ports on every Arduino I think I can do this in hardware...and have a nice big buffer...so I can have loops that check the buffer frequently but do not have to rely on interrupts which can cause problems as I have mentioned.

Does that seem reasonable?

Thank you!

Randy

My short answer is that both hardware serial and software serial use interrupts. So you are not magically avoiding them. The hardware serial only has a double byte buffer* (like SPI) so in order to keep up an interrupt has to fire and move the data from the hardware to your 128 byte buffer. There is no hardware 128-byte buffer. The simplicity is just provided by the library. :slight_smile:

  • I think, but at maximum it would be triple.

May I ask, can you think of a reason why RS485 might not be a less complex solution (i.e. better!) to communicating among the various Arudinos?

Er, all solutions have complexity, but with 6 slaves and only 4 hardware serial ports, you are running out of ports. And I would stay away from software serial if I were you in this situation, because that really does turn interrupts off for a while.

Thanks Nick. I will have to start experimenting.

I was under the impression that RS232 or RS485 would "automagically" load up a buffer with incoming data, up to 128 bytes (based on a post I read elsewhere.) When I have used RS232 I have not had to think about interrupts, but I have also used the typical example where the Arudino would just sit and listen for incoming data, which worked fine and was super easy. Sending data over RS232 was similarly very simple for me to accomplish.

Regarding communicating among six Arduinos, I was thinking that I would use RS485 and just use a single hardware serial interface on each Arduino. I'd assign each Arduino an "address" (i.e. slave 1 thru 5) and they would either respond or trash the incoming data depending on if it was addressed to them. So no need for SoftwareSerial.

Thanks again.

Randy

Yes, but how would they respond? I suppose if you use RS485 you can enable and disable each line so the RS232 is only in use (on the master) by one slave at a time, but this has to be managed.

I was under the impression that RS232 or RS485 would "automagically" load up a buffer with incoming data, up to 128 bytes (based on a post I read elsewhere.)

Sometimes people get an idea into their heads and then post incorrect information. It's been known to happen. :wink:

However if by "automagically" you/they mean "by using an interrupt" then yes, that is what happens.

Hi Nick,

Thank you for the thoughts, but I'm sorry I'm still confused. Today I read every entire poste on your website re: SPI, I2C, Serial, RS485, and Serial without blocking. I enjoyed it immensely :wink: And the light is slowly turning on. But I don't understand what you meant in the above statement.

If I decide to communicate among six different Arduinos using hardware serial over RS485, why can't I just write the software so that each device know's its own address, and the master will include the address at the beginning of each message? In which case, what do you mean by "enable and disable each line"?

Thank you for being patient with this novice :wink:

Randy

OK, with a single hardware interface that you mentioned I presume each slave is sharing the same RS485 pair (which in itself is perfectly valid).

However only one device can be writing to that pair at once (fairly logically). So one has to write and the others read.

(My page on RS485, which it sounds like you read: http://www.gammon.com.au/forum/?id=11428 )

So, the master can certainly talk to all the slaves at once, no problem. The master is the only writer and the slaves all read. But for one slave to respond it has to "take over" the bus. So the master has to switch to reading, and the slave to writing.

So one scenario is that the master sends: "Slave 1: anything to report?" and goes into read mode.
Then the master waits (how long?) for a response. Not getting a response would have to be allowed for.
If the slave notices the question, it goes from read to write mode, sends a response, and goes back into read mode ready for next time.
Then the master might address Slave 2, and so on.

This switching from write to read to write is what I meant by "has to be managed".

Thank you, Nick -- that makes perfect sense. I spent the whole day re-reading your posts and many others, and finally feel that I have a theoretical idea of how to make this work. I want to try it with both SPI and RS485 serial.

One key point that I had never realized about SPI, and I have read a ton of material on SPI, is that the only way for a master to receive information from a slave is for the master to transmit something -- even if it is just a bunch of null characters. That part never clicked, but I have re-read all of the posts on this thread as well, and now that makes sense. You all (especially Susan) were telling me that in so many words, but I just never read that plainly stated. Now I think I get it.

Well, at least I'm optimistic :wink: But I do lean towards RS485 because i can structure very specific messages and sizes between the various Arduinos and no problem managing the whole thing from a "master" who basically tells the different slaves when it is okay to transmit, and knows exactly how many bytes to expect etc. And I think I will dedicate a couple of digital pins for a couple of the mission-critical slaves to be able to "raise their hand" so the master can call on them to report their urgent data.

Thanks again everyone for all of your thoughts. If I learn anything unexpected or interesting I will certainly post back.

Randy

Can setup PCINT for each critical device so the master can tell who is requesting attention.
If two come in at once, assign a priority to decide who gets attention first.

CrossRoads:
Can setup PCINT for each critical device so the master can tell who is requesting attention.
If two come in at once, assign a priority to decide who gets attention first.

Yes thanks for the suggestion. I hope that my main master loop is fast and compact enough that I can just check to see if one or the other of my "please query me" pins has been pulled low by one of the slaves. Interrupts still scare me a little :wink:

But I will certainly keep that plan in my back pocket should I find that the system needs to respond a little bit faster.

Thanks.

Randy

P.S. Sorry if I'm not doing the "quote" properly -- still trying to figure that out.