When doing microcontroller-based development, you will certainlyencounter a counter/timer peripheral. This article aims to explain theconcepts behind understanding and configuring a counter/timer using thesame techniques you probably used in high school science classes forunit conversions and stoichiometry: dimensional analysis.
As a review, let's say you need to convert two (2) miles to meters.You recall that a “5K” race is approximately 3.1 miles, so you can usethat as a conversion factor. You also know that there are 1000 metersin a kilometer.
Even though you do not know the conversion factor to go directlyfrom miles to meters, you are still able to do the conversion becauseyou can eliminate or “cancel out” units on intermediate conversionsfrom miles to kilometers to meters:
* The “mile” in the denominator of 5 km/3.1 miles cancels out the”miles” in 2 miles.
* The “km” in the denominator of 1000 meters/km cancels out the “km” in5 km/3.1 mile.
* And, finally, you are left with the only units remaining beingmeters, which are the units you wanted.
This article does not talk about a specific counter/timerperipheral, but the concepts covered will be relevant to any you willencounter. Some counter/timer peripherals may not have all the featuresmentioned in this article, or may have even more features.
Regardless of the features provided by your hardware, if you keeptrack of how various settings affect the units, you will be able tounderstand any counter/timer peripheral.
These same principles can be applied to other hardware peripheralsas well, especially the “baud rate generator” of a UART or clock rateof a serial peripheral.
Getting down to timer basics
First of all, you may be wondering, “Why is that slash in the middle of'counter/timer'?” A counter/timer peripheral is a hardware device thatcounts events such as toggles of an input pin. That explains the”counter” part. What about “timer”? A “timer” is just a counter thatuses a periodic clock signal as its input: it just counts time.
The rest of this article will refer to a counter/timer peripheral asjust a “timer,” but bear in mind that many people will use the term”counter” and others will use the term “timer” to refer to the sametype of peripheral. Some developers and chip manufacturers will evenmention “timer in event counter mode” to indicate the peripheral iscounting an event other than a clock signal.
A timer has an input source, which is the pin monitored by thehardware before updating its internal counter. Whenever that inputsource signal goes from low-to-high, or vice versa, the timer willupdate its internal count value. Some timers count up from zero, otherscount down towards zero, and some have configurable directions.
Regardless of which direction your device counts, the principle ofoperation is the same: each time that input pin changes in thespecified way, the device's internal count either increments ordecrements.
Let's say you've configured your timer's input source to be yourMCU's main clock which is running at 8 MHz. That means that theinternal counter value will change, or tick, with a frequency of 8 MHz= 8 x 106 cycles/sec or 125 ns/cycle. That is, one (1) tickevery 125 ns.
When looking at the datasheet's description for your timer, you willlikely notice a register for setting the divider or prescaler. Thatsetting allows you to slow down the ticking. If you choose a prescalervalue of 2, then for every two changes of the input source the timerwill tick only once.
If you choose a prescaler of 16, the input source would change 16times between each tick. Think “16 MCU clock cycles per timer tick.”Given the same 8 MHz MCU clock source as before, and a prescaler of 16,to calculate the number of ticks in a second, it is simply amultiplication:
Notice that the units work out to give us ticks/sec—the MCU clockcycles units “cancel out,” and we are left with ticks/sec. To calculatethe amount of time that has elapsed between each tick of the hardwaretimer, just calculate the multiplicative inverse/reciprocal so that theunits become sec/tick:
If your timer peripheral had an internal counter with infinite rangeand your peripheral gave you read access to the counter, you couldmeasure time between two software events by reading the counter valueat the first event, then read the value again at the second event; thedifference is the elapsed time.
However, on real devices, the internal counter has a finite rangeand will eventually overflow if a tick increments the value orunderflow if a tick decrements the value.
In this article, I will call an overflow or underflow condition atime-out. On a time-out, the peripheral will set a flag and may triggeran interrupt. If you had an 8 bit timer, it would time-out after 28 = 256 ticks. How long before the 8 bit timer times-out with 2microseconds between ticks?
Most timer peripherals can operate in a repeat mode where, after atime-out, they reinitialize their internal counter and start countingagain. By checking the flag that is set by the time-out or enabling theinterrupt, you can keep track of time.
Reading the time-out flag or the execution of the correspondinginterrupt service routine (ISR) often clears the time-out flagautomatically; some hardware may require an explicit “clear” of thetime-out flag.
In fact, that is how systems generally keep track of time: ahardware timer times out periodically, say every millisecond, and acurrent_time counter variable in the program or operating system isupdated. (Note that additional care is needed to ensure that thecounter variable does not unexpectedly overflow. )
In the example we've been working with, the time-out interrupt will betriggered every 512 microseconds since the hardware times-out after 256ticks with an 8 MHz source.
If your software needed to toggle an output pin approximately every4 msec, you could count the number of time-outs in a variable callednum_timeouts, and when num_timeouts reached the number corresponding to4 msec, toggle the pin and reset num_timeouts. Determining how manytime-outs that amounts to is simple if you keep track of units: it'sjust another unit conversion:
Notice the rounding up to 8 time-outs. Let's see how much time thatactually is, once again by converting units:
Maybe, for your application, toggling the pin with a period of 4.096msec, rather than 4.0 msec, is OK (2.4% error). But, if you'd like tohave it closer to 4 msec, you would be better off setting your timer totime-out closer to 500 micrsoseconds rather than every 512microseconds.
Many timer peripherals allow you to change the final/initial valueof its internal counter; instead of timing out every 256 ticks, youcould set an 8 bit timer to time-out after 200 ticks or 150 ticks.
Now, let's figure out how many timer ticks are required to get asclose to 500 microsecond time-outs as we can with our 8 MHz clocksource that is being prescaled by 16 (2 microseconds/tick). We want a500 microsecond/time-out, so let's start with that and then multiply bythe number of ticks per second so that units work out to ticks/time-out:
So, if you configure your timer peripheral to time-out every 250ticks rather than at 256 ticks, the time-out flag/interrupt will betriggered every 500 microseconds instead of 512 microsecoonds:
Now, let's figure out how many time-outs there are in 4 msec, asbefore:
There are exactly eight 8 time-outs in 4 msec. Your software coulddetermine if 4 msec had occurred by counting the number of time-outs,and when that count reached eight (8), 4 msec had elapsed. Below, youcan see a C-like ISR code that toggles the output pin every 4 msec:
If not using interrupts, this C-like code below will toggle theoutput pin every 4 msec:
Extending Timer Range
If you need greater range out of your timer, you have several options:
* change the prescaler
* change the overflow value/start count value
* extend the timer value in software
* change the clock source
Let's revisit the example we've been using, and decide that we needa time-out every millisecond rather than every half millisecond asbefore. It's an 8 bit timer we're using, so the highest time-out valuewe can set is a time-out after 256 ticks.
With the 8 MHz MCU clock being prescaled by 16, we have already seenthat it takes 512 microsecons for the hardware register to overflow(i.e., tick 256 times).
If our timer can be prescaled by 32, what's the longest time betweentime-outs we can reach? We'll start with 256 ticks and by keeping theunits correct, we'll figure out how long that takes with the 8 MHz MCUclock being prescaled by 32:
That's pretty close to our target of one (1) msec. By doubling theprescaler, we doubled the amount of time between time-outs. Byfollowing the method in the “Configurable time-outs” section, you couldget the time-out even closer to 1 msec:
Using an 8 MHz clock source prescaled by 32, one (1) millisecond is250 ticks. If the time-out value for your timer is set to a low value,you can increase the frequency between time-outs by increasing thatvalue.
If you needed an action to occur every 10 msec, you can configureyour timer to time-out every millisecond and trigger an interrupt. Inthe ISR, count the number of time-outs and when the count reaches 10,execute the 10 msec action.
If you are not using interrupts, a loop in your program could pollthe time-out flag, and when it has been set ten times, you know that 10msec has elapsed. Many embedded systems include a “real time clock”crystal which oscillates at a stable 32.768 kHz.
This is the same type crystal found in watches and clocks, and isequally divisible by powers of two, which is particularly useful fortimers that do not allow arbitrary time-out values. Using a slowerclock source than the typical multi MHz used for MCUs will allow thetime between timer ticks to be greatly increased.
Some cautions and gotchas
A common “gotcha” with beginners is setting the time-out value tosomething invalid. Be sure you check your datasheet to determine howmany bits are in the time-out value register and that the value youhave calculated does not exceed the allowed range.
If it does exceed the range, you will need to change the clocksource, the prescaler, or extend the timer range using a softwarecounter. For example, if you were to set an 8-bit timer to time-outafter 275 ticks, it is quite likely your compiler would not complain.
But your ISR would be executed too frequently because 275 is largerthan 28 (256) and the value 19 (275 & 0xFF) would beloaded into the 8 bit time-out value register, instead.
Another common problem is for people to write an ISR that takes along time to run. Some beginners hear the rule “keep your ISRs short”and mistakenly think that rule means it is OK to call functions fromthe ISR as long as the body of the ISR itself is shortlines-of-code-wise. Instead, you need to keep your interrupt servicecode short temporally.
If your timer time-out ISR takes more than a millisecond toexecute, and your timer time-outs every millisecond, you will loseinterrupts, and your device will not function properly. This is truefor all ISRs.
The easiest solution is: in your ISR, only perform the mosttime-critical operations and postpone the rest of the work to be doneoutside of ISRs using flags or a work queue. The same is true if youare not using interrupts, but are polling instead: you must ensure thatthe code between time-out flag polls takes less time to execute thanthe interval between time-outs.
A timer is a common hardware peripheral in microcontroller development.Understanding how all the settings affect the time-out interval of theperipheral is daunting to many beginners who often just copy samplecode without understanding what it means.
By understanding that many of those settings just affect howfrequently the timer “counts” and keeping track of those effects, atimer is relatively easy to figure out. To calculate how much timeelapses between timer events, just keep track of the units and do a”unit conversion” as demonstrated in the article.
Some hardware will have different settings and may have more orfewer features, but if you keep track of the units, you will have notrouble figuring out how much time elapses between events and how toconfigure your peripheral.
Zane D. Purvis has a Master's of Computer Engineering fromN.C. State University's Center for Efficient, Scalable and ReliableComputing (CESR). He has taught as an adjunct lecturer for the NCSUBiomedical Engineering and NCSU Electrical and Computer Engineeringdepartments and currently resides in Raleigh, NC. You can contact himat firstname.lastname@example.org.