My last blog post mentioned that the Arduino built-in delayMicroseconds() function was very inaccurate. The error was a whopping 50% when measured with an oscilloscope. As I wanted to investigate some of the intricacies of the ATmega328P registers I thought to write my own delay function.
Counter - Timer Features of the ATmega328P
There is tons of information on the web about using the ATmega timer/counters so the following will be a bit sketchy. The datasheet is a must to have available when talking about the ATmega functions. However, the datasheet is rather terse so sometimes you have to read between the lines, experiment like I am doing, or read of someone else's experience like you are doing here.
There are three timer/counters on the chip: 0, 1, and 2, They all have somewhat different features. Timer/counter 0, and 2, are eight bit counters so can only count up to a maximum of 255 (28). Timer/counter 1, has a 16 bit counter so can count up to 65535 (216).
Timer/counters 0 and 2 are almost identical. It seems that the intended use for timer/counter 2 is as a real-time clock. It kind of assumes that a 32 kHz watch crystal is connected to pin TOSC1. However, on the Gertboard, and most Arduinos (as far as I know) The 12 or 16 MHz resonator is already connected to that pin. Timer/counter 2 does have six prescaler (clock divider) selections while 0. and 1 have five.
Timer/counter 0 and 2, with their eight bit counters are well suited for controlling motors by pulse width modulation (PWM) while timer/counter 1 is best for servos and things my delay project. Timer/counter 0 and 2 have four modes of operation, a "Normal" mode, a "Clear Timer On Compare Match Mode" (CTC), and two pulse width modulator modes. Timer/counter 1 has all the modes of the other two with an additional PWM mode.
In all but two of the modes, the counters in the timer/counter circuit count up with each clock pulse received. When it reaches either its maximum value (255 or 65535), or a value you program, it starts over from zero. For pulse width modulation, you use the counter to establish a period of time. But, that only gives you half of what is necessary to control motor speed. For each time period, you need to control the percentage of time that you provide voltage to your motor. To provide that functionality, each timer/counter has two comparators (8 bit for 0 and 2, and 16 bits for timer/counter 1). You program a value into a comparator and when the count in the counter matches that value, the state of the I/O pin associated with the Timer/counter/comparator changes. When the counter reaches it's top value and returns to zero, the state of the I/O pin changes again. This results in two changes of state in each counter cycle (OFF to ON, and ON to OFF or ON to OFF, and OFF to ON). There are two modes where the counter counts down as well as up. Here, the state of the I/O pin changes when the counter count matches the comparator while counting up to its maximum value and matches it again on its way down to zero.
Since there are two comparators, each timer/counter can control two I/O pins. You can control two motors or two servos from each timer/counter circuit. The period (cycle time) would be the same for each because there is only one counter. However, each I/O can have its own percentage of ON/OFF time (because each has its own comparator).
Beyond controlling I/O pins, the Timer/counters can be programmed to issue internal interrupts. That is the feature I use for my delay script. Each of the three timer/counters has three interrupts. For the modes where the counter only counts up, and then resets to zero, an interrupt is issued when the counter overflows - when it reaches its maximum value (255 or 65535). At the next clock pulse the counter resets to zero and the overflow flag interrupt is issued. For the two modes where the counter counts up and then down, the overflow interrupt is issued when the counter reaches the bottom (zero). The interrupt routine you write will clear that flag.
The timer/counter can also be programmed to issue an internal interrupt when the counter count matches the comparator value. Since there are two comparators for each timer/counter, each has two of these interrupts.
My Counter Project
My code:
I use the timer/counter 1 in Normal mode and do not connect to either I/O pin. I don't use the comparator. I use the overflow flag interrupt. When my function is called, I do the following:
- The circuit only works for delays up to just over 5 seconds. If more is asked for, the maximum is substituted for the passed value.
- The most advantageous prescaler is selected depending upon the passed value. The lowest value possible is used to give the maximum resolution.
- The value of the count to be programmed into the time/counter 1 counter is calculated.
- A value of 1 is passed into the GPIOR0 register.
- Interrupts are turned off
- The two control registers associated timer/counter 1 are cleared assuring that the clock will not be connected to the counter (Counter will not count).
- The counter is programmed with the value calculated in step 3.
- Timer/counter Overflow Flag Interrupt is enabled.
- The prescaler is selected according to the variable "msk". This starts the clock. The clock now starts to count up with from the value we programmed into it (step 7.).
- Interrupts are enabled.
- We go into an endless loop waiting for the interrupt.
- The interrupt is issued one clock pulse after the counter reaches its maximum value of 65535.
- Processing enters the interrupt service routine which clears the interrupt.
- The value of GPIOR0 is set to zero.
- The prescaler is set to None value which stops the clock
- The Overflow Flag Interrupt is disabled.
- We exit the interrupt service routine and go back into the endless loop.
- Since GPIOR0 is now not 1, we exit the endless loop and the function.
The prescaler divides the 12MHz clock. For example, if the prescaler is set to 8, it takes eight clock cycles of the 12MHz clock to send one clock pulse to the counter. The available prescale values are None, 1, 8, 64, 256, and 1024. NONE disconnects the clock from the counter. If the prescaler is set to 1, it takes 65536 / 12MHz or 5461.3 microseconds to count from 0 to 65536 (65536 is equivalent to 0). The maximum delay for the function is obtained when the prescaler is 1024 and the variable "count" is 0, which would be 65536 x 1024 / 12MHz which is 5592405.3 microseconds or 5.5924053 seconds. Any value passed to the function greater than 5592405.3 is set to 5592405.3. I guess I could have extended the time indefinitely by waiting for multiple occurrences of the interrupt before exiting the endless loop. I saw no need to do that because the built-in delay() function is pretty accurate.
In step 3. above, I make use of the GPIOR0 register, one of three general purpose I/O registers. These registers can be used to store any information. I use it here instead of using another variable. If I had used a variable, instead, that variable would had to be declared as volatile. If I had written line 75 as "while (somevariable == 1);", and had not declared "somevariable" as volatile, the compiler would have thought the line did nothing except create an endless loop and would have eliminated it. The use of the register is kind of handy.
There are control bits that need to be programmed: Four WGM1 (waveform generator mode) bits, and two sets of two COM1 (Compare Output Mode) bits. One set of COM1 bits correspond to each comparator circuit. In this application, WGM1 is programmed to 0b0000, which selects Normal mode. Both COM1 sets are programmed to 0b00, which disconnects the two I/O pins associated with timer/counter 1 from the two timer/counter 1 waveform generator circuits. The four COM1 bits, and the lower two bits of WGM1 are part of the TCCR1A register (other two bits are not used). Therefore, TCCR1A is programmed to 0. The two higher order WGM1 bits are bits 3 and 4 of the TCCR1B register. Bits 0, 1, and 2 of TCCR1B are devoted to the prescaler. The value in the variable "msk" is used to program these bits. The range of possible values of "msk" is only be 0b00000001 through 0b00000101 (1 through 5), which keeps the bits 3 through 7 as a "0".
The overflow interrupt is selected to be enabled when interrupts are enabled. The prescaler is loaded with the value in "msk" which starts the counter counting up from the value in "count". Interrupts are enabled and we enter the endless loop. When the counter overflows (next clock after reaching 65535) the interrupt occurs and we leave the endless loop and enter the interrupt service routine. The interrupt service routine changes the value of GPIOR0 and stops the counter from counting. We leave the interrupt service routine and return to the endless loop. Since GPIOR0 is no longer 1, we leave the loop and leave the del() function.
In the next blog post I will discuss PWM. using the timer/counter features of the ATmega328P.
No comments:
Post a Comment