An interesting arduino point to consider

299 views
Skip to first unread message

Ted Meyers

unread,
Sep 6, 2016, 11:42:22 AM9/6/16
to diyrovers
I always assumed that the main() function in arduino looked something like this (leaving out a few details):
void main() {
   setup(); 
   while (true) loop();
}
 
--- Actually more ike this (because embedded coders are a bit odd -- I mean why use the more cryptic for loop, and why return an int from a function that never returns???):
int main() {
   setup(); 
    for (;;;) loop();
   return 0; 
}
But, anyway, I found out this weekend that is not quite right, it actually looks like this:
int main() {
   setup(); 
    for (;;;) {

       loop();
       if (serialEventRun) serialEventRun(); 

   } 

   return 0; 
}


 What this means in practice is that if you don't return from loop() or setup(), your serialEvent() function(s) won't be called.  I suppose that you could call them yourself, or call serialEventRun().  But, it is a bit surprising; I always thought that serial events were attached to an interrupt, not so though.  I learned this the hard way this weekend, when I tried to add a serial interface for debugging.

Also, curiously, on a teensy 3.x, adding a delay() call causes serialEventRun() to be called.  I haven't tested this with an Uno yet, but it would make sense to have that bit of code in delay().

Ted

Jon Watte

unread,
Sep 6, 2016, 1:52:34 PM9/6/16
to diyrovers
IIRC, Teensy has another call named yield() that also lets the support library do some work.

Returning from main() is because the compiler will complain if you don't -- main is defined to return int, so a void declaration of main is an error, and not returning a value is an error-generating warning in itself.

The idea of "serialEventRun()" is similar to the idea of DPC in kernel programming. There are many things you can't do at interrupt time (such as anything that requires interlock, calling malloc(), etc) so a mechanism is available to queue actions to run at non-interrupt time. Your interrupt handler is then "do the minimum possible to correctly wiggle the hardware based on the specific interrupt, then queue a callback for anything else."

Unfortunately, Arduino is aimed at non-computer-scientists, and is partially even developed by non-computer-scientists, so these very fundamental principles are not well documented or explained in the Arduino docs. You have to read the code to figure out what it does, and then reason backwards as for why it does it.

Sincerely,

jw






Sincerely,

Jon Watte


--
"I find that the harder I work, the more luck I seem to have." -- Thomas Jefferson

--
You received this message because you are subscribed to the Google Groups "diyrovers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to diyrovers+unsubscribe@googlegroups.com.
To post to this group, send email to diyr...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/diyrovers/84efaf6a-ce46-444a-abe6-9fe13dd7ae13%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Ted Meyers

unread,
Sep 6, 2016, 2:49:52 PM9/6/16
to diyrovers

IIRC, Teensy has another call named yield() that also lets the support library do some work.

Yeah, teensy calls yield() from main() and from delay().  Actually, both arduino and teensy call yield(), but in arduino yield() is defined as a NOP.  In his case, I think that teensy does it right.


Returning from main() is because the compiler will complain if you don't -- main is defined to return int, so a void declaration of main is an error, and not returning a value is an error-generating warning in itself.

Well, yes, but still why not define main() in a more sensible manner?  I suppose that it is a limitation of the C language, but still, I would rather write clear and concise code and take the warnings than write obfuscated code.  Again, looking at the teensy main(), it does not return 0, and also uses a while(1) loop.  I much prefer the Teensy coding style (Paul Stoffregen's).
 

The idea of "serialEventRun()" is similar to the idea of DPC in kernel programming. There are many things you can't do at interrupt time (such as anything that requires interlock, calling malloc(), etc) so a mechanism is available to queue actions to run at non-interrupt time. Your interrupt handler is then "do the minimum possible to correctly wiggle the hardware based on the specific interrupt, then queue a callback for anything else."

Thanks for the great explanation, Jon!  That makes a lot of sense.  I suppose that the point of doing this with the serial event handler is to keep arduino newbs out of getting themselves in trouble by doing something stupid in the handler.  But, it really should be documented better than this and made easier to call with a yield() or something as you might not want to wait until the main loop is done executing.   Sure, you could call serialEventRun(), but to do that you have to find the correct header file, and know that it exists, and blah blah blah.
 

jw


--
"I find that the harder I work, the more luck I seem to have." -- Thomas Jefferson
On Tue, Sep 6, 2016 at 8:42 AM, Ted Meyers <ted.m...@gmail.com> wrote:
I always assumed that the main() function in arduino looked something like this (leaving out a few details):
void main() {
   setup(); 
   while (true) loop();
}
 
--- Actually more ike this (because embedded coders are a bit odd -- I mean why use the more cryptic for loop, and why return an int from a function that never returns???):
int main() {
   setup(); 
    for (;;;) loop();
   return 0; 
}
But, anyway, I found out this weekend that is not quite right, it actually looks like this:
int main() {
   setup(); 
    for (;;;) {

       loop();
       if (serialEventRun) serialEventRun(); 

   } 

   return 0; 
}


 What this means in practice is that if you don't return from loop() or setup(), your serialEvent() function(s) won't be called.  I suppose that you could call them yourself, or call serialEventRun().  But, it is a bit surprising; I always thought that serial events were attached to an interrupt, not so though.  I learned this the hard way this weekend, when I tried to add a serial interface for debugging.

Also, curiously, on a teensy 3.x, adding a delay() call causes serialEventRun() to be called.  I haven't tested this with an Uno yet, but it would make sense to have that bit of code in delay().

Ted

--
You received this message because you are subscribed to the Google Groups "diyrovers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to diyrovers+...@googlegroups.com.

Jon Watte

unread,
Sep 6, 2016, 3:45:42 PM9/6/16
to diyrovers
I think we share the same lamentations about the documentation of and underpinnings of the Arduino libraries :-)

Sincerely,

jw





Sincerely,

Jon Watte


--
"I find that the harder I work, the more luck I seem to have." -- Thomas Jefferson
To unsubscribe from this group and stop receiving emails from it, send an email to diyrovers+unsubscribe@googlegroups.com.

To post to this group, send email to diyr...@googlegroups.com.

Thomas Roell

unread,
Sep 7, 2016, 12:46:06 PM9/7/16
to diyrovers
Having implemented that one it's a PITA. The "if (serialEventRun)" means really "if you have linked in something the requires serialEventRun()". The code for that again can look a tad convoluted:

void serialEventDispatch(void)
{
  if (serialEvent && SerialUSB_empty && !SerialUSB_empty()) { serialEvent();  }

  if (serialEvent1 && Serial1_empty && !Serial1_empty()) { serialEvent1(); }
#if SERIAL_INTERFACES_COUNT > 1
  if (serialEvent2 && Serial2_empty && !Serial2_empty()) { serialEvent2(); }
#endif
#if SERIAL_INTERFACES_COUNT > 2
  if (serialEvent3 && Serial3_empty && !Serial3_empty()) { serialEvent3(); }
#endif
#if SERIAL_INTERFACES_COUNT > 3
  if (serialEvent4 && Serial4_empty && !Serial4_empty()) { serialEvent4(); }
#endif
#if SERIAL_INTERFACES_COUNT > 4
  if (serialEvent5 && Serial5_empty && !Serial5_empty()) { serialEvent5(); }
#endif
#if SERIAL_INTERFACES_COUNT > 5
  if (serialEvent6 && Serial6_empty && !Serial6_empty()) { serialEvent6(); }
#endif
}

In any case the usual PITA is that you need to check the empty flag throu the class interface and such. So if you have 6 serial ports and one USA port, then the overhead of this routine is kind of bad. 

Tryed a simple scheme with a bitmask (atomic set from ISR, atomic clear in the dispatcher), but that turned out to be not that much faster ...


At the end of the day it seems to be a bad idea to use this mechanism.

- Thomas
Reply all
Reply to author
Forward
0 new messages