6
\$\begingroup\$

Since an 8-bit MCU can't read the whole 16-bit timer in one cycle, this creates a race condition where the low-word can roll over between reads. Does the community have a preferred method of avoiding these race conditions? I'm currently considering stopping the timer during reads, but I'd like to know if there's a more elegant solution.

FYI, this is for a PIC16F690.

\$\endgroup\$
  • \$\begingroup\$ related: stackoverflow.com/questions/5162673/… \$\endgroup\$ – davidcary Mar 13 '11 at 21:38
  • 2
    \$\begingroup\$ This is a problem all microcontrollers take care of, AFAIK. When one byte is read, the other one is latched, and reading the other byte will read from the latch. Problem solved. \$\endgroup\$ – stevenvh Jul 7 '11 at 12:26
9
\$\begingroup\$

Windell's right, if you're talking about PICs, the datasheet (and hardware) already handle this for you.

If you're not using a PIC, the general method I use is to do this:

byte hi, lo;
word timer_value;

do {
    hi = TIMER_HI;
    lo = TIMER_LO;
} while(hi != TIMER_HI);

timer_value = (hi << 8) | lo;

What this does is to read the upper byte followed by the lower byte, and to continue doing so until the hi byte doesn't change. This readily handles the case where the low byte of the 16-bit value overflows between reads. It assumes, of course, that there are no side effects from reading the TIMER_HI register multiple times. If your particular microprocessor doesn't allow that, it's time to throw it out and use one that isn't quite as braindead. :-)

This method ALSO assumes that your timer isn't changing so rapidly that you run the risk of overflowing the low 8 bits within a processor fetch cycle or two. If you're running a timer so fast (or a microprocessor so slow) then it's time to re-think your implementation.

\$\endgroup\$
  • 1
    \$\begingroup\$ You can also use this technique to read a timer made up of a hardware timer and a software counter incremented in the overflow interrupt. \$\endgroup\$ – starblue Aug 4 '10 at 7:57
  • \$\begingroup\$ Thanks, I actually like this technique better than stuttering the timer during a read. \$\endgroup\$ – ajs410 Aug 4 '10 at 15:18
  • 1
    \$\begingroup\$ Finally found a Microchip app note, "PIC Micro Mid-range MCU family". It looks to be old, but seems to describe the 16F series in greater detail than the datasheet. The technique suggested for reading the 16-bit free-running timer matches quite well with your suggestion, so I'm marking it as the answer. \$\endgroup\$ – ajs410 Aug 4 '10 at 16:22
  • 2
    \$\begingroup\$ O_Engenheiro The loop executes twice for the case where an overflow occurs, and once in all other circumstances. If you are looping more than that then either your timer clock is WAY too fast or your CPU is WAY too slow. \$\endgroup\$ – akohlsmith Aug 5 '10 at 5:36
  • 1
    \$\begingroup\$ @Andrew Kohlsmith: Debugging async stuff isn't too hard if one assumes signals will go out of their way to change at the worst possible times. Actually, in many cases I prefer totally unsynchronized timers to those which attempt to avoid the problems associated with being asynchronous, since I have on a number of occasions had to deal with hardware bugs related to such matters. For example, one undocumented annoyance on many PIC timers is that if the timer holds a value other than FFFF, and it's written with FFFF, the overflow interrupt... \$\endgroup\$ – supercat May 28 '11 at 22:05
10
\$\begingroup\$

Check your datasheet! It will talk about this, in gory detail, for your particular chip.

I'm not certain that this is true for all 16-bit counter/timer registers on all PICs (maybe someone else can answer that!) but at least for the PIC 18F that I use, the datasheet specifically talks about how this is handled.

When you read the low byte, it buffers the high byte into a temporary register for the high byte, so that when you read the high byte next, it gives you a full instantaneous snapshot of the timer value.

The same basic process is used on AVR (and Arduino of course), and for most other cases where you need to read or write two-byte registers at once on 8-bit MCUs.

\$\endgroup\$
  • \$\begingroup\$ i too recall reading this in AVR docs \$\endgroup\$ – JustJeff Aug 4 '10 at 2:34
  • \$\begingroup\$ Of course I checked the datasheet, but it's actually not as gory as you might think. Nothing in the text for timer1 (chapter 6) indicates that the high byte gets buffered. The there's a subsection under the async counter (6.5.1) that says "for writes, it is recommended that the user simply stop the timer and write the desired values". Nothing is mentioned for reads. That's why I came to the community \$\endgroup\$ – ajs410 Aug 4 '10 at 15:15
  • \$\begingroup\$ It does make it much easier now that you've posted the actual chip that you're using. ;) And... I'd call it pretty gory: it explicitly says that overflow can occur between the reads. Your solution, stopping the timer, is indeed inelegant but perhaps a safe one, depending on your application. \$\endgroup\$ – Windell Oskay Aug 4 '10 at 20:41
3
\$\begingroup\$

The text you referred to in your comment (Section 6.5.1) refers to asynchronous timer operation, where the timer is clocked from a source external to the microcontroller.

The disclaimer is suggesting starting and stopping the timer because Andrew's general method (really any method) won't work because there may be many ticks (More than 256, even) of the external clock and thus the timer in a single cycle of the processor. I assume you're not running with this edge case.

If, instead, you're using the internal oscillator as a reference, you'll be fine. Even with a prescaler of 1, you've got up to 255 cycles after you read the high byte to read the low byte. This should be a 2 cycle operation. You still need the do/while loop in case your high byte changes (What is the difference between 'hi' and 'TIMER_HI' if the value of 'lo' is 0?) between these times. With a prescaler of 2 or greater, you don't need this test anymore. It's only with prescalers much less than 1 that it starts to become a problem, and this is impossible without the external clock.

\$\endgroup\$
  • \$\begingroup\$ Yeah, I know it was async timer, but it's the closest the datasheet got to addressing the problem. Although now it makes more sense why they would suggest stopping the clock as opposed to mentioning Andrew's solution (even though they don't even mention that in the datasheet, but some separate app-note) \$\endgroup\$ – ajs410 Aug 4 '10 at 16:38
1
\$\begingroup\$

There is an example in the PIC Mid-Range MCU Family Reference Manual for Reading and Writing Timer1 in Asynchronous Counter Mode.

Example: Reading a 16-bit Free-Running Timer

; All interrupts are disabled
MOVF TMR1H, W ; Read high byte
MOVWF TMPH ;
MOVF TMR1L, W ; Read low byte
MOVWF TMPL ;
MOVF TMR1H, W ; Read high byte
SUBWF TMPH, W ; Sub 1st read with 2nd read
BTFSC STATUS,Z ; Is result = 0
GOTO CONTINUE ; Good 16-bit read
;
; TMR1L may have rolled over between the read of the high and low bytes.
; Reading the high and low bytes now will read a good value.
;
MOVF TMR1H, W ; Read high byte
MOVWF TMPH ;
MOVF TMR1L, W ; Read low byte
MOVWF TMPL ;
; Re-enable the Interrupt (if required)
CONTINUE ; Continue with your code
\$\endgroup\$
  • \$\begingroup\$ Yup, that's the manual which confirmed that Andrew's template was the best compromise. Although I modified his example to look more like the assembly; i.e. instead of do-while, I just used an if. \$\endgroup\$ – ajs410 Aug 5 '10 at 16:57

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.