Is longjmp incompatible with serial on the ATmega32U4?

Go To Last Post
20 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm writing a simple language interpreter in C for Arduino boards, using serial to communicate with the program. Here's a test program to illustrate what I'm doing:

#include <setjmp.h>

jmp_buf exception;

void do_something (char chr) {
  if ((chr>='0' && chr<='9') || chr==13) {
    Serial.println(chr);
    return;
  } else {
    Serial.println("Error - not a digit");
    longjmp(exception, 1);
  }
}

void setup() {
  Serial.begin(9600);
  while (!Serial);  // wait for Serial to initialize
  setjmp(exception);
}

void loop() {
  while (Serial.available() == 0);
  char temp = Serial.read();
  do_something(temp);
}

The demo should echo a digit, but give an error for a non-digit.

 

I've tested it on several boards with the Arduino IDE Serial Monitor, or with a serial terminal.

 

  • It works fine on an ATmega328-based board, such as the Arduino Uno.

 

  • It hangs up on an ATmega32U4-based board, such as the LilyPad USB or Arduino Micro, when you trigger the error.

 

Any suggestions?

 

Last Edited: Thu. May 5, 2016 - 04:11 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
  while (!Serial);  // wait for Serial to initialize

Curious syntax. Can you really do that on a class?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

  while (!Serial);  // wait for Serial to initialize

Curious syntax. Can you really do that on a class?

 

If Serial is the instance of the class (and not a static reference to the class name), one could use the bool() operator:

class Serial
{
    operator bool() const
    {
        return valueIfValid;
    }
};

Note: I'm on my work machine and this works with C++ in Visual Studio. Not tested to see if this would compile for AVR.

My digital portfolio: www.jamisonjerving.com

My game company: www.polygonbyte.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I got while(!Serial) from here:

 

https://www.arduino.cc/en/Serial...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Looks like my comment may be irrelevant than if Serial is the class and not an instance. I don't know the specifics of the Arduino framework, so I was unsure.

 

Sorry I can't be of further assistance with the actual problem.

 

[Edit] Changed may to may be

My digital portfolio: www.jamisonjerving.com

My game company: www.polygonbyte.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You call setjmp from setup and then return from setup. This invalidates the stack frame where setjmp was called from, so you cannot longjmp back to it. You can longjmp only to a setjmp that is in a stack frame that is an ancestor of the longjmp call.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Every time I look at C++ code, I find myself agreeing with Linus.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

What were you hoping to achieve here anyway? Setjump/longjump almost never is the answer.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

Setjump/longjump almost never is the answer.

What kind of programmer are you?  Don't you know you are supposed to leave letters out of words?   Doesn't setjmp and longjmp look much better?   wink

 

The only time I used an RTOS with pre-emptible tasks,  it used setjmp and longjmp to save and restore task contexts.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
What were you hoping to achieve here anyway?

 

I want to provide error handling in a Lisp interpreter I'm trying to write. The errors can occur deep within nested subroutines, so longjmp seemed to be the only answer.

Last Edited: Fri. May 6, 2016 - 09:53 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

johnsondavies wrote:
The errors can occur deep within nested subroutines,

But that's surely the point here? Your example was not deeply nested. The call chain you had was:

main -> setup - > setjmp
     <-
     -> loop -> do_something -> long_jmp

You could almost have done with having the setjmp at the "main" level - though that's very difficult to achieve in the case of Arduino. You almost want a loop() that never actually returns and then you setjmp at that level - call down a chain and when it longjmp's it gets back to that loop() level.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks - I'll try moving the setjmp to inside loop().

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah but in the way Arduino normally works that may not work for you as you are going to be doing a setjmp multiple times, each time it enters loop(). I suppose you could keep a flag to say whether you have done it already or not and just do it the first time loop() is called. OTOH maybe it doesn't matter if it's called every time?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I assume setjmp just saves the stack pointer and program counter, so it shouldn't matter doing it each time round the loop.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The header says it stores this:

/*
   jmp_buf:
        offset  size    description
         0      16      call-saved registers (r2-r17)
        16       2      frame pointer (r29:r28)
        18       2      stack pointer (SPH:SPL)
        20       1      status register (SREG)
        21       2/3    return address (PC) (2 bytes used for <=128Kw flash)
        23/24 = total size
 */

so it's storing a complete "context" including the call saved registers so I think you might want to spend a little time considering the implications of this.

 

PS this is real sledgehammer to crack a nut stuff! I cannot help thinking there's got to be a better way to handle this.

Last Edited: Fri. May 6, 2016 - 10:27 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This works on both platforms:

void loop() {
  setjmp(exception);
  for (;;) {
    while (Serial.available() == 0);
    char temp = Serial.read();
    do_something(temp);
  }
}

Thanks for the help. I was confused by the fact that my original test program seemed to work fine on the ATmega328.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Don't see how it could. The main() for Arduino on 328 is just the same - setup() is a different nesting compared to loop()

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I agree. If I have any insights I'll report back.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That should work fine (your code in post #16). But you also want to look at the return value of setjmp if you want to know if an exception happened.

Last Edited: Fri. May 6, 2016 - 02:19 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In the Arduino framework, main() looks like this:

int main(void)
{
	init();

	initVariant();

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

	setup();

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

	return 0;
}

 

Never exiting loop() would break 'pure' USB boards like the Leonardo, would it not?

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Fri. May 6, 2016 - 03:02 PM