Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keeping time using timer interrupts an embedded microcontroller

This question is about programming small microcontrollers without an OS. In particular, I'm interested in PICs at the moment, but the question is general.

I've seen several times the following pattern for keeping time:

Timer interrupt code (say the timer fires every second):

...
if (sec_counter > 0)
  sec_counter--;
...

Mainline code (non-interrupt):

sec_counter = 500; // 500 seconds

while (sec_counter)
{
  // .. do stuff
}

The mainline code may repeat, set the counter to various values (not just seconds) and so on.

It seems to me there's a race condition here when the assignment to sec_counter in the mainline code isn't atomic. For example, in PIC18 the assignment is translated to 4 ASM statements (loading each byte at the time and selecting the right byte from the memory bank before that). If the interrupt code comes in the middle of this, the final value may be corrupted.

Curiously, if the value assigned is less than 256, the assignment is atomic, so there's no problem.

Am I right about this problem? What patterns do you use to implement such behavior correctly? I see several options:

  • Disable interrupts before each assignment to sec_counter and enable after - this isn't pretty
  • Don't use an interrupt, but a separate timer which is started and then polled. This is clean, but uses up a whole timer (in the previous case the 1-sec firing timer can be used for other purposes as well).

Any other ideas?

like image 774
Eli Bendersky Avatar asked Mar 01 '23 17:03

Eli Bendersky


2 Answers

The PIC architecture is as atomic as it gets. It ensures that all read-modify-write operations to a memory file are 'atomic'. Although it takes 4-clocks to perform the entire read-modify-write, all 4-clocks are consumed in a single instruction and the next instruction uses the next 4-clock cycle. It is the way that the pipeline works. In 8-clocks, two instructions are in the pipeline.

If the value is larger than 8-bit, it becomes an issue as the PIC is an 8-bit machine and larger operands are handled in multiple instructions. That will introduce atomic issues.

like image 176
sybreon Avatar answered Mar 08 '23 11:03

sybreon


You definitely need to disable the interrupt before setting the counter. Ugly as it may be, it is necessary. It is a good practice to ALWAYS disable the interrupt before configuring hardware registers or software variables affecting the ISR method. If you are writing in C, you should consider all operations as non-atomic. If you find that you have to look at the generated assembly too many times, then it may be better to abandon C and program in assembly. In my experience, this is rarely the case.

Regarding the issue discussed, this is what I suggest:

ISR:
if (countDownFlag)
{
   sec_counter--;
}

and setting the counter:

// make sure the countdown isn't running
sec_counter = 500;
countDownFlag = true;

...
// Countdown finished
countDownFlag = false;

You need an extra variable and is better to wrap everything in a function:

void startCountDown(int startValue)
{
    sec_counter = 500;
    countDownFlag = true;
}

This way you abstract the starting method (and hide ugliness if needed). For example you can easily change it to start a hardware timer without affecting the callers of the method.

like image 39
kgiannakakis Avatar answered Mar 08 '23 11:03

kgiannakakis