4
\$\begingroup\$

Problem was virtually undebuggable, the interrupt that was changing a variable would randomly fail to do so, with no apparent pattern; within interrupt code variable would change but outside of it the change is essentially reverted back to previous state. It's a regular variable so it's not double-buffered. Marking it as volatile had no effect (besides making the code run slower). I've already worked around the problem, but phenomena is still there nonetheless. Also, my chip is Atmega644 and language is C.

I have narrower down the problem to a following code: the interrupt was firing during the execution of code that was also writing to that variable. It checked for pin state, and depending on it would either set or clear a bit from that variable. I resolved the issue by putting that code in the interrupt as well, which left interruptable code with not a single place where that variable was written - only read.

The code is substantially long, so I only provide relevant snippets (the rest of the code doesn't even deals with related variables):

#define BIT(x) (1<<(x))
#define BITSET(x,y) ((x)|=(y))
#define BITCLR(x,y) ((x)&=~(y))

//Interrupt snippet:
if ( PINB & BIT(PINB0) )
    BITSET( display, BIT(DSPL_PWR) );
if ( PINB & BIT(PINB1) )
    BITSET( display, BIT(DSPL_ACT) );

//Main code:
if ( PIND & BIT(PIND2) )
    BITSET( display, BIT(DSPL_ACC) );
else
    BITCLR( display, BIT(DSPL_ACC) );

<...>
leds_display = 0x00;
switch ( roll )
{
    case 0:
        leds_display |= display & BIT(DSPL_PWR);
        break;
    //etc.
}
PORTA = leds_display;

However, that is but a crutch-fix. It works that way, but it should've worked the way it was. Changing a variable is atomic operation, so it can not be interrupted mid-action. My guess was that it was due to optimization magic, but then volatile would have changed the behavior. And even then, even without volatile marker, the code may have ignored the change the first time it ran (it's not critical for it to be real-time up to date), but the next iteration would have recognized the change. This leaves me puzzled about nature of the phenomena.

Anyone knows anything about it? Or is it just a compiler bug?

\$\endgroup\$
  • 1
    \$\begingroup\$ Code, please... \$\endgroup\$ – Eugene Sh. May 12 '15 at 18:47
  • 2
    \$\begingroup\$ Changing a variable is most certainly not atomic. \$\endgroup\$ – Jon May 12 '15 at 18:55
  • 2
    \$\begingroup\$ Without code the whole bunch of words up there is useless. It can be anything. \$\endgroup\$ – Eugene Sh. May 12 '15 at 18:55
  • \$\begingroup\$ I honestly can't imagine why anyone would need an actual code to see into the problem, but there you go. \$\endgroup\$ – Raidho Coaxil May 12 '15 at 19:03
  • 2
    \$\begingroup\$ @RaidhoCoaxil Because the problem might be totally different from what you are thinking of. \$\endgroup\$ – Eugene Sh. May 12 '15 at 19:04
5
\$\begingroup\$

It is highly unlikely that you have discovered a compiler problem.

Plain and simple.....many variable modifications will require the CPU to perform a read-modify-write behavior to the memory location. If your interrupt comes in the middle of that process you will have undefined behavior.

The fix for it in the mainline code is to bracket the variable modification with a sequence like...

disable interrupts

modify variable

re-enable interrupts

Note that most embedded C compilers have specialized macros that you can invoke to perform the disable/re-enable steps. It is often typical that these only take one machine instruction each and so the overhead is very low.

There is sometimes a need to perform a somewhat more robust code protection scheme in cases where the mainline program code may run with or without interrupts enabled from time to time. This case can also come up if a subroutine or library function is called from both the mainline context and the interrupt context - and the called function needs variable code protection. In these cases it is common to have to implement as:

save current interrupt enabled state (most often to stack)

disable interrupts

modify variable

restore current interrupt enable state (most often from stack)
\$\endgroup\$
  • 3
    \$\begingroup\$ I think you mean "it is highly unlikely that you have discovered a compiler problem" \$\endgroup\$ – Nick Johnson May 12 '15 at 19:00
  • \$\begingroup\$ I have tried disabling interrupts temporarily but that ended up with timed interrupt failing to fire. That is I knew that would happen but I gave it a go anyway and it indeed happened. Instead of failing to change a variable, the interrupt wasn't firing altogether. Not an option. \$\endgroup\$ – Raidho Coaxil May 12 '15 at 19:07
  • \$\begingroup\$ @NickJohnson - Thanks for pointing out my stray "not". It has been removed. \$\endgroup\$ – Michael Karas May 12 '15 at 19:08
  • 1
    \$\begingroup\$ @RaidhoCoaxil - if you are talking about the timer interrupt, then it is queued and will fire as soon as you re-activate interrupts. Which timer is it? \$\endgroup\$ – Jon May 12 '15 at 19:18
  • 1
    \$\begingroup\$ @RaidhoCoaxil - If you disable interrupts around your mainline variable modification and a timer interrupt comes along while in the disabled window then its interrupt should happen right after you re-enable. If that is not then case then you are doing something to accidentally clear the queued timer interrupt flag whilst things are disabled. \$\endgroup\$ – Michael Karas May 12 '15 at 19:20
4
\$\begingroup\$

Why do you say it is random or impossible to debug? I don't even see it as a compiler issue. It's more poor realtime software design.

You have main code which writes to a variable. You have ISR which writes to the same variable. This sounds problematic to me. You disable interrupts and code crashes. You have a shared resource, even if it is just a memory location. You have to implement some form of resource sharing or change code to eliminate problem.

You isolated problem to main code, which was interrupted while writing to variable. ISR writes to variable, but main code writes over variable when ISR returns. Voila, your ISR data is lost. Sequential code with an ISR which can occur at any time.

Lots of solutions. Easiest is a software interrupt. Have main program call ISR to write it's data. Assuming you disable ISRs in ISR.

Personally, I'd rethink how to do what you want by separating main program from ISR.

\$\endgroup\$
  • \$\begingroup\$ It didn't crossed my mind that a single 8-bit write operation wouldn't be atomic, and you don't need any concurrency measures for atomic operations, which is why there wasn't any. \$\endgroup\$ – Raidho Coaxil May 12 '15 at 20:30
  • 5
    \$\begingroup\$ But you're not doing a single 8-bit write. Your BITSET and BITCLR macros use |= and &=, both of which require read-modify-write as Michael wrote in his answer. \$\endgroup\$ – brhans May 12 '15 at 20:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.