Interrupts actually take more CPU resources than polling because of the need to save and restore the system state.
The attachInterrupt
technique allows you to trade this sequence of steps:
-
UART interrupt dispatch to ISR vector
-
ISR stores character in ring buffer
-
return from interrupt
-
something eventually calls available
which looks at ring buffer
-
something calls read
, which retrieves byte from ring buffer
-
handle character
-
something uses result of handling character
...for this sequence of steps:
-
UART interrupt dispatch to ISR vector
-
handle character
-
return from interrupt
-
something uses result of handling character
For each character, the latter approach saves copying the character 2 times, manipulating a ring buffer 2 times, and has 2 more procedure calls. The invariants are ISR dispatch and return, handling the character, and using the result of handling the character. The interrupt-driven approach uses less CPU time than polling -- you're not wasting resources, you're actually saving them.
You could also eliminate the RAM for a ring buffer: 68 bytes/UART on most Arduinos, 20 bytes on ATtinys. Most apps don't need to squeeze that out, but it's nice to know you could get it.
Compare it with Pin Change interrupts: there isn't a 2nd interrupt to dispatch the event; the current, original HW interrupt handles the event, instead of saving it for later. And just like Pin Change interrupt, you attach a function to be called by the ISR. For both approaches, the system state is saved and restored just once.
Interrupts are primarily for situations that need to happen very fast or are short-lived or unpredictable. None of these is true once the characters are in the Serial input buffer.
Yes, I agree, mostly. It would be silly to attach a handler if it does a lot of work, or does work that requires interrupts to be enabled. I was just surprised, and a little frustrated, that light-weight handling cannot be attached like you can do for Pin Change interrupts.
Many projects can depend on the input buffer to hold characters until it can get to them, especially when it's keyboard input. Really, how fast can the user type?
However, if you have several modules that need serviced, one module may be able to overflow the input buffer while you are handling the others (i.e. bottlenecks). If handling the sensor is a light-weight procedure, it would be more efficient to handle the character when it is received. This completely avoids buffer overflow and the extra CPU load of polling.
When you figure out a working version of an interrupt-driven Serial Input Basics I will keep an open mind and look at it with interest. At the moment I don't see how its net effect would be different from serialEvent()
Sorry, that code was what I wanted to be able to do. If you want a working example, you'll have to grab the NeoHWSerial library and use this sketch:
//#include <NeoHWSerial.h>
#ifdef NeoHWSerial_h
#define mySerial NeoSerial
#else
#define mySerial Serial
#endif
const byte numChars = 120;
char receivedChars[numChars]; // an array to store the received data
volatile boolean newData = false;
void setup() {
#ifdef NeoHWSerial_h
mySerial.attachInterrupt( recvWithEndMarker );
#endif
mySerial.begin(9600);
mySerial.println("<Arduino is ready>");
}
void loop() {
#ifndef NeoHWSerial_h
if (mySerial.available()) {
uint8_t c = mySerial.read();
recvWithEndMarker( c );
}
#endif
showNewData();
//delay(5);
}
void recvWithEndMarker( uint8_t rc ) {
static byte ndx = 0;
char endMarker = '\n';
if (rc != endMarker) {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
ndx = 0;
newData = true;
}
}
void showNewData() {
if (newData == true) {
mySerial.print("This just in ... ");
mySerial.println(receivedChars);
newData = false;
}
}
With the include commented out, it runs in polling mode. Just uncomment the include to run in interrupt-driven mode. Note that NeoHWSerial is the exact same code as HardwareSerial, with the singular addition of the attachInterrupt
. (You have to use the NeoSerial
instance instead of the typical Serial
instance... #defines
on mySerial
make the switch.)
To simulate the condition I described above, set numChars
> 64, and uncomment that 5ms delay
in loop
. Then copy this long input string
ABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijklmnopqrstuvwxyz012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijklmnopqrstuvwyz
...and paste it into the input field of the Serial Monitor and press ENTER. Well, in polling mode, it loses the '\n', so press ENTER twice. Here's what I get in the polling mode:
<Arduino is ready>
This just in ... ABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijklmnopqrstuvwxyz012345ABCDEFGHIJKLMNOSX2bglpuABCDEFGHIJKLMNOPQRSTUVWXYZ012345
This just in ... ABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijklmnopqrstv05EJNSX2bgABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijmrv05EJNSX2bgk
This just in ... ABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijklmnopqrstv05EJNSX2bgABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefinrw1AFKOTY3cglqvA
This just in ... ABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijklmnopqrstuvwxyz05EJOABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijklmnoprw05EJOS
In interrupt mode, it never misses the '\n'. Here's what I get:
<Arduino is ready>
This just in ... ABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijklmnopqrstuvwxyz012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijklmnopqrstuvw
This just in ... ABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijklmnopqrstuvwxyz012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345abcdefghijklmnopqrstuvw
It's always right, never missing a character. So it's not that you need to process the character immediately; rather, it's that the input buffer overflows while you're doing something else. Yes, you can edit HardwareSerial to increase the input buffer size. That's really the best solution if you have the RAM, and you're logging the raw character data. However, if the characters can be processed quickly, and there are other time-consuming operations in your sketch, then interrupt-driven processing is an efficient technique. Right now, it's not even a choice.
As you probably know, I see this all the time in trying to handle GPS streams. That character data can be processed very quickly into numeric values, which also take up much less space. Interrupt-driven is perfect if you just want lat/lon values. If it takes 90ms to write some values to an SD card, no big deal! But for logging the raw GPS data, it's better to increase the input buffer size.
I would not expect your lathe application to need it. The odds of input buffer overflow are very low. I assume you are sending a speed, not an individual step. If you are sending a step every 10-20ms, then you might be able to eliminate some jitter. Hard to tell. If it's over USB, it could be subject to USB delays as well.
However my inclination is to keep the reading of the input buffer under my control
Yep, that's the great thing about the Arduino environment: you can choose lots of ways to do things. In some applications, especially when you're using libraries that use delay
, there is no alternative without attachInterrupt
. So I added it, and I thought you might find it an interesting alternative. 
Cheers,
/dev