Mega 2560 ISR bug?

Probably someone wrote bad code once and got away with it (probably ran his demo for the 30s youTube video :slight_smile: ) and then many others copied without thinking about it....

The reality is that indeed you need to inform the compiler that some optimisations are not welcome (that's the volatile keyword) and then there is also the concept of atomicity or critical sections. Those are crucial concepts for ensuring proper handling of shared variables and data consistency.

➜ The volatile keyword tells the compiler that a variable's value can change at any time, even outside the current program's flow, such as due to an interrupt. This prevents the compiler from optimising away repeated reads or writes to that variable, ensuring that the most up-to-date value is always accessed.

in @arduinoer_cc's code he was using errorIsr as a shared variable and the loop was accessing it both in reading and writing mode.

In this example, the compiler could decide to embed the loop() function within main() since it's pretty trivial

int main(void) {
	init();
	initVariant();
	setup();   
	for (;;) {
	  if (++loopIng >= 32000000) {
	    Serial.println("Running");  //Run?
	    loopIng = 0;
	  }
	  if (errorIsr == 1) {
	  Serial.println("error");
	    errorIsr = 0;
	  }
	}      
	return 0;
}

and then further optimise since nothing is modifying errorIsr and it's probably 0 to start with and get totally rid of it and compile

int main(void) {
	init();
	initVariant();
	setup();   
	for (;;) {
	  if (++loopIng >= 32000000) {
	    Serial.println("Running");  //Run?
	    loopIng = 0;
	  }
	return 0;
}

using volatile is what tells the compiler that errorIsr can be modified


➜ Atomic operations or critical sections deal with ensuring that shared resources are accessed safely when they can be modified by both the main program and interrupt service routines. An atomic operation is one that completes entirely without being interrupted, guaranteeing consistent state changes. A critical section is a portion of code that ensures mutual exclusion, meaning only one context (either the interrupt or the main program) can execute it at a time. (In embedded C++, critical sections are often implemented using techniques like disabling interrupts temporarily during sensitive operations.)

in the code @arduinoer_cc wrote, there is not really dire consequences if we don't implement a critical section and that the value of errorIsr is modified between the if and when it's reset to 0 (even if this variable is not a bool or byte given it's only 0 and 1).

Thank you for the additional clarification. I have just had a convo with my new friend chatGPT, just to improve my prompting chops.

I come away from that with the idea that for setting a byte flag in the ISR and checking said flag in the interrupted code, no critical section is necessary.

If more than that is done outside the interrupt, some of those more things would need to be in a protected section.

Which may explain why ppl who get volatile are still caught up by failing to use a critical section - if their exposure has always been around that safe case.

During my interactions, Lucy jumped onto my tablet, and chatGPT noticed my half response, I blamed the cat, it understood. At the end

Me:
Nice. Thank you. Now I must play with the cat or pay the price.

ChatGPT:

Haha, you definitely don't want to incur the wrath of a cat! 😸
Enjoy your playtime, and feel free to come back anytime if you have more questions. Take care!


I will never understand how it does that kind of thing, any explanations for the layman just don't add up, and any explanations for, well, whoever, are light years outside my ability to follow.

Magic.

a7

That would usually be fine as long as the flag's type offers an atomic compare for that platform (would work with bool or byte on a UNO indeed) and is declared volatile.

Typical code with such a flag don't just read it, they tend to reset it after reading it. If you do that then you have two different accesses to the variable and you need a critical section or you might miss an interrupt.

that feels like it :slight_smile:

Indeed. For a byte you get away with it since operations on a byte (on the Mega) are atomic. You may not be so lucky with an int since this is 2 bytes and, therefore, need a critical section to lock out any possible asynchronous interference. On an ESP32, for example, even 32 bit operations are atomic so less need of critical sections.

1 Like

I am reminded of a flawed machine instruction "fetch and clear", which was incorrectly implemented in the microcode as "clear and fetch", rendering it useless for its intended purposes.

At the time I had not the knowledge or experience to understand.

a7

I think that the issue of volatile variables and critical sections is only the tip of the iceberg when it comes to writing code which appears to work under most circumstances but can, on occasions, give rise to unpleasant surprises. The richness of C++ gives you plenty of rope to hang yourself with.

Some examples which spring to mind are:

  1. care about what you put in a class constructor function because it may be clobbered by something that occurs later in the initialisation sequence.
  2. Using pointers to objects created in functions which have long since vanished.
  3. The whole issue of writing code which gives rise to multiple threads spread over different cores in such a way that the objects don't interfere with each other.
  4. The famous "C++ Static Initialization Order Fiasco"
    and many more which I probably fall foul of without noticing.

After several days, thank you very much for your suggestion

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.