serialEvent function, Interrupt driven or not?

Hello,

I am trying to figure out if serialEvent is interrupt driven. It doesn't say in the reference if it is or not. There is no initializing in setup. I am not sure how to use it. I would like to interrupt when serial data is available rather than sitting in a loop waiting with Serial.Available. I have tried it and it doesn't seem to do anything.

StanK

In main.cpp in the arduino core:

int main(void)
{
    init();

#if defined(USBCON)
    USBDevice.attach();
#endif

    setup();

    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
    }

    return 0;
}

As you can see from that, any serial events are processed at the termination of each iteration of the loop() function. It's not interrupt driven.

You can only reliably use the serial events if your loop() function doesn't run for too long (and certainly not if loop() never returns), so never ever use delay() unless you have modified your system to give the serial system a huuuuuge buffer.

Thank You,

StanK

majenko:
In main.cpp in the arduino core:

int main(void)

{
    init();

#if defined(USBCON)
    USBDevice.attach();
#endif

setup();

for (;:wink: {
        loop();
        if (serialEventRun) serialEventRun();
    }

return 0;
}



As you can see from that, any serial events are processed at the termination of each iteration of the loop() function. It's not interrupt driven.

You can only reliably use the serial events if your loop() function doesn't run for too long (and certainly not if loop() never returns), so never ever use delay() unless you have modified your system to give the serial system a huuuuuge buffer.

Interesting. The documentation I read somewhere just says it gets called after loop(). (!!) I have been using it to check for and process serial data, and did not realise it only gets called if there is serial data available.

Interesting.

Thanks for the question, OP.

and did not realise it only gets called if there is serial data available.

It doesn't - it is always called after "loop"

serialEventRun() is called after every loop, but it then checks the availability of serial data and calls the serialEvent() function only if needed:

void serialEventRun(void)
{
#ifdef serialEvent_implemented
  if (Serial.available()) serialEvent();
#endif
#ifdef serialEvent1_implemented
  if (Serial1.available()) serialEvent1();
#endif
#ifdef serialEvent2_implemented
  if (Serial2.available()) serialEvent2();
#endif
#ifdef serialEvent3_implemented
  if (Serial3.available()) serialEvent3();
#endif
}

It doesn't - it is always called after "loop"

void serialEventRun(void)
{
#ifdef serialEvent_implemented
  if (Serial.available()) serialEvent();
#endif
#ifdef serialEvent1_implemented
  if (Serial1.available()) serialEvent1();
#endif
#ifdef serialEvent2_implemented
  if (Serial2.available()) serialEvent2();
#endif
#ifdef serialEvent3_implemented
  if (Serial3.available()) serialEvent3();
#endif
}

The serialEventRun() method is always called. The user-supplied serialEvent() method is only called if there is some serial; data to process.

Is there an echo in here?

echo in here?

AWOL:

and did not realise it only gets called if there is serial data available.

It doesn't - it is always called after "loop"

I'm talking about SerialEvent - the topic of the thread. It isn't always called after "loop".

It would be interesting to learn more how this feature works. My ambitions with the AVR micros are to create a slave terminal device on linux that purely runs in command mode. And the ArduinoUno with the VirtualSerialPort interface is a perfect fit. I wrote an assembler program a few years ago using the Atmel stk500 listed here; http://www.sytekcom.com/eng/eAVR_Command_Interpreter.asm.html
I can understand why standalone robots want parallel tasking, and scheduling, but old fashioned linear command/return script programming is solid.
I saw the headers and main.cpp, and really can't figure out what the uploaded assembler code might be doing.
Glad others are confused too.

The /Tutorial/SerialEvent documentation makes explicit calls to serialEvent from loop. Looks like that call is not needed.

It's not interrupt driven.

The Serial port itself continues to be interrupt driven, but SerialEvent() is not called from interrupt level.
(which is a really good thing.)

The current Tutorial example is really stupid, IMO.

SerialEvent gives you "baby multitasking." You have the "loop" task, and you have the "serialEvent" task(s) that only gets called when there has been Serial data received. If you are reading sensors and controlling stuff in loop(), you could put a command/response function in SerialEvent so that you could "check" on things in the event code without interfering with the logic of loop() As long as "commands" are infrequent and short (fit in the serial ISR buffer: usually 64 bytes), you can even do significant delay() calls in your loop.

Here's a slighly less silly example. "Blink with Serial Event." A typical "Blink" program, with a serialEvent handler added to modify or report on the state of blinking...

/*
  Blink with SerialEvent
  by WestfW: released to the public domain
  This demonstrates the use of serialEvent to implement a command "add-on"
  to a simple "Blink" sketch.
*/

byte ledstate = 0;
static const byte LED = 13;

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
}

/*
 * loop() blinks the LED by complementing the current state and writing the result, and then
 * delaying for a second.  A trivial process, and not at all encumbered by having to deal with
 * the serial port.
 */
void loop() {
  ledstate = ! ledstate;  // Complement the LED state
  digitalWrite(LED, ledstate);  // write the new value.
  delay(1000);
}

/*
 * seralEvent will be called whenever there is traffic on the serial port.  We read what is 
 * expected to be a command, and can report on and/or modify the program state used in loop
 */
void serialEvent()
{
  switch (Serial.read()) {  // read a character
    case '0':  //  0 means turn the LED off
      Serial.println("turning off LED");
      ledstate = 1;  // gets complemented in loop()
      break;
    case '1':  // 1 means turn it on
      Serial.println("Turning on LED");
      ledstate = 0;
      break;
    case 's':  // S means report the current/next status
    case 'S':
      Serial.print("Current LED state: "),
                   Serial.print(ledstate);
      if (ledstate)
        Serial.println(".  LED will be OFF next");
      else
        Serial.println(".  LED will be ON next");
      break;
    default:  // all other commands are ignored.
      Serial.println("Bad Command");
  }
  while (Serial.read() >= 0)
     ; // flush any extra characters
}

serialEvent() is the exact equivalent of this

void loop() {

    // other code

    if (Serial.available() > 0) {
         mySerialFunction();
    }
}

You may be interested in Serial Input Basics

...R

You may be interested in Serial Input Basics

...or the truly interrupt-driven NeoHWSerial, NeoICSerial and NeoSWSerial. They're just slightly-modified versions of our favorites, HardwareSerial, AltSoftSerial and jboyton's efficient version of SoftwareSerial.

Cheers,
/dev

/dev:
...or the truly interrupt-driven

Are they alternatives to the code in Serial Input Basics?

Or are they alternatives to SoftwareSerial and could/should be used in conjunction with my code?

...R

Robin2:
Are they alternatives to the code in Serial Input Basics?

Or are they alternatives to SoftwareSerial and could/should be used in conjunction with my code?

LOL, yes. There are several tangled things here...

The first thing is that 99.999% of serial handling is recommended and performed in a non-interrupt way. I call this "polling", because the sketch is constantly checking for new characters. Contrast that with Pin Change Interrupts: they are part of the core, and there are plenty of examples for attachInterrupt, as well as warnings about its pitfalls.

However, I am a little surprised that the same approach is not available (or recommended) for character handling. Why is there no attachInterrupt for serial data? I got tired of the issues with polling and just added it to our favorite serial classes. They are an alternative to polling the characters.

The Serial Input Basics sketch could easily be interrupt-driven instead of polling:

const byte numChars = 32;
char receivedChars[numChars];	// an array to store the received data

volatile boolean newData = false;

void setup() {
	Serial.attachInterrupt( recvWithEndMarker );
	Serial.begin(9600);
	Serial.println("<Arduino is ready>");
}

void loop() {
	showNewData();
}

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) {
		Serial.print("This just in ... ");
		Serial.println(receivedChars);
		newData = false;
	}
}

The second thing is that SoftwareSerial is the absolute last choice for serial bit-banging. AltSoftSerial and NeoSWSerial (née gSoftSerial) are much more reliable and efficient. The Neo__Serial trio allows two independent choices:

  • interrupt vs. polling, and
  • HW UART vs. Input Capture pin vs. Pin Change bit-banging.
    I have decided not to implement an interrupt version of SoftwareSerial, although I'm examining the ASCII vs. binary limitation (i.e., msb=1) of NeoSWSerial.

Cheers,
/dev

/dev:
There are several tangled things here...

The first thing is that 99.999% of serial handling is recommended and performed in a non-interrupt way. ..... Contrast that with Pin Change Interrupts: they are part of the core .....

However, I am a little surprised that the same approach is not available (or recommended) for character handling. ......

The Serial Input Basics sketch could easily be interrupt-driven instead of polling:

Tangled certainly.
The characters go from the USART to the Serial input buffer using interrupts

Interrupts actually take more CPU resources than polling because of the need to save and restore the system state.

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.

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()

However my inclination is to keep the reading of the input buffer under my control so that I can, for example, wait until there are enough characters before I waste resources reading them.

I have one application where my PC sends a command to control my lathe and I am happy to allow the data to arrive in the background and stay in the input buffer while the Arduino concentrates on moving my stepper motors. When the move is finished all the data for the next move is waiting in the buffer for almost instant action.

...R

The "model" where interrupts are used to move characters from the USART to an intermediate buffer, and some sort of polling is used to read the buffer from the user program, is very common. Most Command Line Interfaces, for instance.
It would be nice if there were some slightly higher level functions for Arduino; reading a line of text with echoing, editing, etc. Here's a start: GitHub - WestfW/parser: Simple serial command line parser for Arduino/etc

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? :slight_smile: 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. :slight_smile:

Cheers,
/dev

/dev:
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:

I misunderstood. That looks interesting. I can see how it bypasses a chunk of the action. I will bookmark this for future study.

I presume if you don't attachInterrupt() the data just gets lost.

...R