Timer holding with a timer interrupts the integrated microcontroller

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

I saw several times the following pattern for saving time:

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

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

Main line code (without interruption):

 sec_counter = 500; // 500 seconds while (sec_counter) { // .. do stuff } 

The main line code can be repeated, set the counter to various values ​​(not just seconds), etc.

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

Interestingly, if the assigned value is less than 256, the destination is atomic, so there is no problem.

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

  • Disabling interrupts before each assignment of sec_counter and turning on after is not a good thing.
  • Do not use interrupt, but a separate timer that starts and then polls. It is clean, but uses the entire timer (in the previous case, the 1 second on timer can be used for other purposes).

Any other ideas?

+4
source share
11 answers

PIC architecture is as atomic as it is. This ensures that all read-modify-write operations in the memory file are "atomic". Although it takes 4 cycles to complete the entire read-modify-write, all 4 cycles are consumed in one command, and the next instruction uses the next four-cycle cycle. This is how the pipeline works. In 8-hour mode, two instructions are under development.

If the value is greater than 8 bits, this becomes a problem, since the PIC is an 8-bit machine, and larger operands are processed by several instructions. This will lead to atomic problems.

+2
source

Write a value, then check that this desired value would seem to be the easiest alternative.

 do { sec_counter = value; } while (sec_counter != value); 

By the way, you should make volatile variable when using C.

If you need to read a value, you can read it twice.

 do { value = sec_counter; } while (value != sec_counter); 
+1
source

You need to disable the interrupt before setting the counter. However ugly it is, it is necessary. It is good practice to ALWAYS disable the interrupt before setting up hardware registers or program variables that affect the ISR method. If you write in C, you should consider all operations as non-atomic. If you find that you need to view the generated assembly too often, then it is best to abandon C and the program in the assembly. In my experience, this is rarely the case.

Regarding the issue under discussion, I propose the following:

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

and counter settings:

 // make sure the countdown isn't running sec_counter = 500; countDownFlag = true; ... // Countdown finished countDownFlag = false; 

You need an additional variable, and it is best to wrap everything in a function:

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

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

+1
source

Since access to the sec_counter variable is not atomic, there really is no way to avoid disabling interrupts before accessing this variable in your trunk code and restoring the interrupt state after access if you want deterministic behavior. This would probably be a better choice than allocating an HW timer for this task (if you do not have a surplus of timers, in which case you can also use it).

+1
source

If you download the Microchip free TCP / IP Stack, there are routines that use timer overflows to keep track of elapsed time. In particular, tick.c and tick.h. Just copy these files to your project.

Inside these files you can see how they do it.

+1
source

Not so curious that less than 256 moves are atomic - moving an 8-bit value is one opcode, so it will be atomic like you.

The best solution for a microcontroller such as PIC is to disable interrupts before changing the timer value. You can even check the value of the interrupt flag when you change the variable in the main loop and process it if you want. Make it a function that changes the value of a variable, and you can even call it from the ISR.

+1
source

Well, what does the comparison assembly code look like?

Taking into account that it counts down and that it simply compares zero, it should be safe if it first checks the MSB and then the LSB. There may be corruption, but it really doesn’t matter if it falls in the middle between 0x100 and 0xff, and the damaged comparison value is 0x1ff.

0
source

Now that you use your timer, it won’t count for whole seconds, because you can change it in the middle of a loop. So, if you do not care. The best way, in my opinion, would be to read the value and then just compare the difference. It takes several OP more, but does not have problems with multiple threads. (Since the timer has priority)

If you are more strict about the time value, I would automatically turn off the timer when it counts the value to 0, and clear the internal timer counter and activate it when you need it.

0
source

Move the part of the code that will be on main () to the correct function, and conditionally call it using ISR.

In addition, to avoid any delays or missing ticks, select this ISR timer to interrupt with high priority (PIC18 has two levels).

0
source

One approach is to have an interrupt that stores a byte variable, and have something else that is called at least once every 256 times when a counter is encountered; do something like:

  // ub == unsigned char;  ui == unsigned int;  ul == unsigned long
 ub now_ctr;  // This one is hit by the interrupt
 ub prev_ctr;
 ul big_ctr;

 void poll_counter (void)
 {
   ub delta_ctr;

   delta_ctr = (ub) (now_ctr-prev_ctr);
   big_ctr + = delta_ctr;
   prev_ctr + = delta_ctr;
 } 

A small deviation, if you don't mind making the interrupt counter stay in sync with the LSB of your big counter:

  ul big_ctr;
 void poll_counter (void)
 {
   big_ctr + = (ub) (now_ctr - big_ctr);
 }
0
source

No one has considered the problem of reading multi-byte hardware registers (for example, a timer. A timer can roll over and increase its second byte while you read it.

Say it 0x0001ffff and you read it. You can get 0x0010ffff or 0x00010000.

The 16-bit peripheral register is unstable for your code.

For any mutable "variables" I use the double read method.

 do { t = timer; } while (t != timer); 
0
source

All Articles