Sending serial data from an ISR

I truly apologize that this topic has been beat to death here, but I see equally conflicting posts that you CAN versus CANNOT reliably do Serial.write() from within an ISR. Perhaps it is a pre-1.0 versus post-1.0 issue? Also, this is a separate issue from my post of a couple of days ago regarding RECEIVING serial data inside an ISR, which Grumpy_Mike answered very clearly.

I now understand that you can't READ serial data inside an ISR, because the ISR disables lower-priority interrupts while it is running - and serial is a lower priority -- and that there is a two-byte incoming hardware buffer that could hold at most two characters while the ISR is running, otherwise incoming serial data will be lost if it comes in during an ISR (which is one reason to keep the ISR short.)

But what about WRITING serial data from within the ISR? Many posts say it's okay, and many other posts say you can't do so reliably. I don't know who to believe!

WHAT I WANT TO DO: I only need to write three bytes! My Lionel train layout has Emergency Stop buttons spread around it. When someone hits the button, I want my Arduino to see it as pulling a pin low, and immediately send three bytes out of the serial port (FF FF FF.) This serial command stops all trains that are running, immediately. When the pin goes low, an ISR will be executed per attachInterrupt(). I would love the ISR to simply dump the three bytes to the serial port, for the fastest possible response -- i.e. to stop the trains before they collide in a fiery head-on collision.

I know I can set a flag and test for the flag in loop(), but if I can reliably send three bytes out of the serial port from inside the ISR, immediately, that would be ideal, because it would be fastest.

So, can I do it, or not? Thanks in advance for shedding light on this obviously-controversial yet seemingly-simple concept.

Randy

"Can" vs "should" is sort of the question. In general, an ISR should be as fast as possible with no "delay()", serial or other stuff going on inside it. If you want something like sending to happen, put the data where your code can get at it and set a flag in the ISR to indicate "time to send" or whatever. As to "can you send from inside the ISR?", well, yes, but recognize you shouldn't do it and if not now, it will probably cause you problems later as you tweak your code to get things to do what you want. It is sort of like the definition of "real time" - real time requirements for flight control systems in something moving mach 2 is much different than "real time" for controlling the rudder position on a super tanker. It depends on your application whether or not you can get away with sending from inside the ISR. In answer to the question "should you", in general the answer is no. The ISR should deal with whatever caused the interrupt, set a flag or put some data somewhere (or both) as needed then exit again without wasting additional time.

As to "can you send from inside the ISR?", well, yes,

The problem with printing from within an ISR is that the Serial interface uses interrupts and they are disabled when in an ISR so all bets are off as to whether it will work.

HardwareSerial.cpp has provisional code for sending data when interrupts are disabled. At least, that's what I get out of a 3 minute look at it. But....

There is a reasonable limit to how long a wire you should have on an unbuffered Arduino input pin. I think "all around the layout" is pushing your luck. What value is your input pullup resistor? What kind of wire are you using? Is it shielded?

What about debounce? Each time the button bounces, it will begin to send "FF, FF, FF" all over again, because the ISR has been called again.

You do know loop() is fast, as long as there is no blocking code.
We are talking in micro seconds so looking at a flag you set in your ISR will happen quickly.

How fast is this Lionel train?

BTW, if your wife sees your avatar, you won't have to worry about your Lionel train as she will get it!
.

Appreciate the feedback, but I'm still hoping for a clear explanation of what happens when you try to do a Serial.write() from inside of an ISR.

aarg, love the name :wink: I will have Emergency Stop buttons connected to a common ground on my train layout, and there could be one that is up to 30 feet (by wire) from the Arduino that will be monitoring the interrupt pin to see if it goes low. This would just be 24awg (or larger if necessary) unshielded hook up wire. Several Emergency Stop buttons connected in parallel. The common ground for these buttons will come from a separate wall wart, not from the Arduino ground (although the Arduino ground will be connected to the wall-wart ground.)

I planned to use the internal pullup resistor...

What kind of a risk are you suggesting? There is no signal for the Emergency Stop button wire...just grounding a pin. I'm not concerned about bounce in this case - but I could easily run it through a Schmitt trigger/inverter to take care of both bounce and whatever risk you're suggesting by connecting the wire directly. Your input on this is appreciated.

Thanks!

Randy

Simple answer to the Title question is DON'T

For the purpose you are talking about (emergency stop of a model train) there is absolutely no need.

If your ISR sets a flag to show it was triggered and if your main code is properly written it will detect the emergency within a few millisecs and can then send whatever message is required.

If that is not fast enough just use an emergency switch that cuts power to the train and then notifies the Arduino that it has done so.

...R

Thanks Robin, but as I said in the original post, I understand I can set a flag -- although I can't use a button to cut power -- we're talking several different circuits. Different 15-amp circuits. The serial command is broadcast to many different devices and they will all respond to the three-byte emergency stop command.

While I appreciate the replies thus far, they have been pretty vague about my actual original question.

What I'd really like to understand is, if indeed it is NOT reliable to send a 3-byte serial command from within an ISR -- WHY is it not reliable? Because some folks are convinced that it IS reliable, and without any specifics, it's hard to know who to believe.

Randy

An emergency stop button simply turns the power off.
No, absolutely zero, reliance on interrupts or any kind of software.

If you want to understand, you have all the source.

The lockup situation when the write buffer becomes full when interrups are disabled was fixed in v.1.5.6:

Fix lockup when writing to HardwareSerial with interrupts disabled

When interrupts are disabled, writing to HardwareSerial could cause a
lockup. When the tx buffer is full, a busy-wait loop is used to wait for
the interrupt handler to free up a byte in the buffer. However, when
interrupts are disabled, this will of course never happen and the
Arduino will lock up. This often caused lockups when doing (big) debug
printing from an interrupt handler.

Additionally, calling flush() with interrupts disabled while
transmission was in progress would also cause a lockup.

When interrupts are disabled, the code now actively checks the UDRE
(UART Data Register Empty) and calls the interrupt handler to free up
room if the bit is set.

This can lead to delays in interrupt handlers when the serial buffer is
full, but a delay is of course always preferred to a lockup.

Closes: #672
References: #1147

Thanks, guys. Appreciate the insight.

Randy

PS AWOL, I can't use a regular switch to simply cut power as you suggest, which I've already mentioned. I would have to shut off three 15 amp circuits to kill all track power. I think that would be a dangerous and stupid way to stop a toy train, when a three-byte command out the serial port would accomplish the same goal :wink:

randydpeck:
I think that would be a dangerous and stupid way to stop a toy train, when a three-byte command out the serial port would accomplish the same goal :wink:

I can't imagine how it could be dangerous.

It has the benefit of being far more reliable than relying on several computers all to work correctly at a critical moment.

Of course you may wish to have the BIG RED SWITCH as well as the computer controlled system.

I can see how the big red switch would be inconvenient. It just depends on whether a train crash or a train falling to the floor matters.

I used a software emergency switch for my computerized train control system but I am prepared for it not to 100% reliable. I was/am quite content if my trains get the message within 1 second.

...R

randydpeck:
Appreciate the feedback, but I'm still hoping for a clear explanation of what happens when you try to do a Serial.write() from inside of an ISR.

aarg, love the name :wink: I will have Emergency Stop buttons connected to a common ground on my train layout, and there could be one that is up to 30 feet (by wire) from the Arduino that will be monitoring the interrupt pin to see if it goes low. This would just be 24awg (or larger if necessary) unshielded hook up wire. Several Emergency Stop buttons connected in parallel. The common ground for these buttons will come from a separate wall wart, not from the Arduino ground (although the Arduino ground will be connected to the wall-wart ground.)

I planned to use the internal pullup resistor...

What kind of a risk are you suggesting? There is no signal for the Emergency Stop button wire...just grounding a pin. I'm not concerned about bounce in this case - but I could easily run it through a Schmitt trigger/inverter to take care of both bounce and whatever risk you're suggesting by connecting the wire directly. Your input on this is appreciated.

Thanks!

Randy

Why are you not concerned about bounce? As I said, it will wreck the shutdown code transmission. Are you aware that a Schmidt trigger by itself doesn't remove bounce? Also, a 30 foot random wire is an antenna. The internal pullup resistor is too weak (too high a resistance) to effectively dampen the noise that can show up on that. The grounding system you mention is also a trap. It could end up putting negative potentials on the Arduino input that could destroy it.

As it stands, there are many things you need to do if you want to use the interrupt. You have to provide a decent input conditioning circuit, and you need to detach the interrupt as soon as it is received, so it doesn't respond multiple times (normally that would be a crappy debounce, but in this case it's okay because you only look at one leading edge).

aarg:
Why are you not concerned about bounce?

I can't see how bounce matters for an emergency switch because you are only going to act on the first trigger. If there are more triggers later, so what?

...R

As Robin2 points out, once the emergency signal is seen I can set a flag to ignore it thereafter. I would restart the system after an emergency stop.

The Schmitt trigger would have the common R-C debounce circuit on the button side, and the trigger output (now inverted) would go directly to an Arduino interrupt input pin such as pin 2 or 3. No internal or external pull up or pull down resistors between the chip and pin. I can post a schematic if you like but it's a very common debounce circuit, although I have not tried it yet.

Would this be sufficient to protect the Arduino (and the Schmitt chip) from the kind of damage you're suggesting?

I can't think of any other example where a digital low or signal would travel more than about ten feet...but there are several similar high/low pin signals between Arduinos that are about ten feet apart, connected directly pin to pin. Also a few serial (TTL level RS232) 2004 LCD displays controlled by Arduinos that are about 10 feet away. And these signal wires will at some points be bundled with larger wires carrying several amps of AC current at 18 volts. If the signals simply don't work, I can use shielded wire. But are there other risks?

There will also be a lot of low voltage AC (12, 14, 18) with common ground and I have been encouraged to connect my common AC and DC grounds, but I don't see how that would be beneficial and seems like an unnecessary risk to my digital components. Agreed?

My control panel has over 150 LEDs controlled by Mace Tech Centipede shift register shields controlled via I2C from my Arduinos. The LEDs have a common +5VDC. My nightmare scenario is something hitting my 5VDC bus that kills all those tediously-wired LEDs in a single event. Which is why I'm motivated to keep my DC totally isolated from my AC.

As you can tell, I'm really pushing the frontiers of my knowledge (which is absolutely the point and the fun of doing this) so I may well be overlooking many important considerations.

Thanks for your thoughts!

Randy

The very minimal input protection.

To make it better add diodes, zener, etc.

Capture.JPG

Robin2:
I can't see how bounce matters for an emergency switch because you are only going to act on the first trigger. If there are more triggers later, so what?

...R

If the switch is connected to an interrupt pin, you have to specifically code the "only going to act on the first trigger". This isn't a demolition switch, where everything is hands off once the process is started. The transmission of "FF, FF, FF" has to be cleanly completed in order for a shutdown to occur.

If the transmission has begun, and many more are immediately started before the first byte has even been sent, that could be a very bad thing. The actual result can only be known with a careful study, but I'm sure you can see the danger here.

aarg:
If the transmission has begun, and many more are immediately started before the first byte has even been sent, that could be a very bad thing.

I guess you are assuming the bytes would be sent from within the ISR. I was/am not - I hope I have talked the OP out of that :slight_smile:

...R

Robin2:
I guess you are assuming the bytes would be sent from within the ISR. I was/am not - I hope I have talked the OP out of that :slight_smile:

...R

I don't think you did. Just to add - I certainly wouldn't. I would just kill the power to everything.

I will just set a flag in the ISR but the whole point of my post was that I wanted to understand what happened if you tried to send serial from an ISR. The answer as best as I can tell is you would populate the buffer up to its max 64 byte size, any further data being lost, and data would go out when the ISR returned.

aarg, your comments about my digital ground having unintended consequences were a bit alarming. Would you kindly have a look at my reply and let me know if my RC+Schmitt debounce circuit would solve both the issue of debounce and also protect my Arduino inputs? I also mentioned several other small wires carrying ground and serial data roughly 10 feet between Arduinos and would like to know if I need to consider similar hazards there.

Thanks.