This is an old revision of the document!
Counter, welche man auch in einigen Fällen Taktgeber nennen kann, sind einer der wichtigsten Unterfunktionen eines Microcontrollers. Diese ermöglichen es Prozesse exakt zu timen, Signale zu generieren und Ereignisse zu zählen. Ein Zähler konvertiert die Nummer der Inputzyklen in einen binären Wert mit Hilfe eines Arrays an Triggern. Die maximale Nummer der gezählten Zyklen hängt von der Länge des Arrays ab und diese ist markiert von der Länge des binären Codes. Der AVR hat 8- und 16- Bit Counter. Wenn ein Timer seinen maximalen Wert erreicht hat(255 bei 8-Bit und 65535 bei 16-Bit), generiert der nächste Zyklus ein Overflow und der Zähler macht ein Reset auf 0. Das Taktsignal eines Counters kann vom Taktsignal des Microcontrollers kommen und in diesem Fall ist es möglich den Wert mit einem Prescaler herunterzusetzen. Einige AVRs haben einen internen unabhängigen Taktgeber, welcher mit einem Frequenzmultiplikator modifiziert werden kann, um schneller zu laufen. Counter unterscheiden sich auch in Applikationsfällen und Arbeitsmodi.
Im Default-Modus macht ein Counter nichts anderes, als fortlaufend Nummer zu zählen Sein wert kann natürlich jederzeit ausgelesen, und von einem Programm verändert werden. Die einzige zusätzliche Funktion im Default-Modus ist, dass er ein Interrupt beim Counter-Overflow verursacht. Der Default-Modus wird normalerweise benutzt, um eine Sektion eines Programms in bestimmten Intervallen laufen zulassen.
Example
Aufgabe: Ein 8 MHz ATmega128 soll einen Interrupt alle 10ms (100 Hz) auslösen. Dafür ist der 8-Bit Counter 0 brauchbar.
#include <avr/interrupt.h> ISR(TIMER0_OVF_vect) { // Gibt den Counter einen Wert, // dass der nächste Overflow in 10Hz passiert. // Formel: 256 - 8 MHz / 1024 / 100 Hz = 177,785 = ~178 TCNT0 = 178; } int main() { // Um den ersten Interrupt in 10ms auszulösen, // muss der Counter initialisiert werden. TCNT0 = 178; // Prescaler Wert 1024 TCCR0 = 0x07; // Erlaubt Overflow Interrupts TIMSK |= (1 << TOIE0); // Erlaubt global Interrupts sei(); // Endlosschleife while (1) continue; }
Der Counter in diesem Beispiel wird den Interrupt nicht in exakt 10 ms generieren, dafür müsste man dem Counter einen Dezimalwert geben, das ist aber nicht möglich. Um einen präzisen Intervall zwischen den Interrupts zu verwirklichen, wird sowohl der Prescaler Wert, als auch der Wert des Counter müssen so ausgesucht werden, dass ihre Division in einer exakten Nummer endet. Das ist nicht immer möglich, vor allem mit 8-Bit Countern, da ihr Wertebereich sehr klein ist. Um einen präziseren oder größeren Intervall zu bekommen, kann ein 16-Bit counter gewählt werden.
It is also possible to use an external clock source as a counter's clock signal. AVR has a pin called Tn for this purpose, n marking the number of the counter. External clock signal and the polarity can be selected using the prescaler register.
Since the counters allow timing operations, more complex AVR microcontrollers have an option to time specific events on a hardware level. This part of the counter is called an input capture unit. There is a choice between two events: the logical change in the value of a special input pin or in the value of the analog comparator result. If the selected event occurs, the counter's value is written to a special register, from where it can be read at any time. If the event is longer than the overflow time of the counter, the program has to count the overflows as well and take them into account when calculating the final result.
Example
Task: Measure the frequency of an external 122 Hz - 100 kHz logical square signal using an 8 MHz ATmega128. The measurement has to be at 1 Hz precision. The program uses a 16-bit counter with 1 input capture unit.
#include <avr/interrupt.h> unsigned long frequency; // Interrupt for the event ISR(TIMER1_CAPT_vect) { // Counter to 0 TCNT1 = 0; // The result is valid only if the counter // has not overflown yet if (!(TIFR & (1 << TOV1))) { // Calculating the frequency from the period frequency = (unsigned long)8000000 / (unsigned long)ICR1; } else { // Frequency is less than 122 Hz frequency = 0; // Set the counter's overflow flag to 0 TIFR &= ~(1 << TOV1); } } int main() { // Register a rising front, prescaler value 1 TCCR1B = (1 << ICES1) | (1 << CS10); // Allow event interrupts TIMSK = (1 << TICIE1); // Allow interrupts globally sei(); // Endless loop while (1) continue; }
The program fires an interrupt each time a rising front occurs in the external signal. During the interrupt, the counter is checked for overflows - this can happen if the frequency of the signal is below 122 Hz (8 MHz / 216) and in this case the value of the counter doesn't reflect a real period any more. The frequency is calculated using 32-bit numbers to get the inverse of the period. First thing is to set the counter to 0, because the timer works on the same clock signal as the processor and each instruction execution occurring after the external event shortens the measured period corrupting the result. The maximum measured frequency is limited by the time spent in the interrupt program.
Catching events and registering the time it took for them to occur can also be resolved at the software level. It is possible to use external or other interrupts and read the value of the counter during these events. The hardware-level event catching is meant to run independently form the main program and time relatively short (or frequent) events.
More complex counters can generate a signal, in addition to timing the length of one. For this purpose the counter has an output compare unit and a compare match output unit. The output compare unit has registers with the same bit-width as the counter and the values of these registers are compared to the value of the counter while it is running. An interrupt can be generated and special pins' values can be changed each time the counter's value is equal to the value in the compare unit register. At this moment a pin can either be set high or low or inversed. The signal is generated by changes in the value of the output pin.
In some signal generating modes, the counter's maximum value can be altered. The counter's physical size will remain the same, but a comparison register is used to reset the counter at a specific count. The previous examples could also be solved by using this method, but the function is rather for changing the period of the signal. In addition to that, a counter can be configured to a mode where it works with both incrementing and decrementing.
The counters and the signal generating modes using them are one of the most complex peripheral modules in an AVR. Writing about all of them here would be a huge waste of time and typically there is no need to know everything in order to use them. The following describes one of the most common PWM signals in robotics. The rest can be read from the AVR documentation.
Pulse width modulation (PWM) is a type of signal, where the frequency and period (typically) are both constant, but the length of the half-periods changes. PWM signals are used for controlling electromechanical, optical and other devices. For example, the servo motors known from modeling use a PWM signal of 50 Hz and have a high half-period of 1 to 2 ms.
Example
Task: using an 8MHz ATmega128, generate two speed regulating servo motor signals. Use pin PB5 (OC1A) to generate a pulse width of 1 ms and pin PB6 (OC1B) to generate pulse width of 2 ms.
#include <avr/io.h> int main() { // Set pins as outputs DDRB |= (1 << PIN5) | (1 << PIN6); // Set outputs A and B low for comparison, // "Fast PWM" mode, prescaler value 8 TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11); TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); // Maximum value of the counter. Formula: // TOP = 8 MHz / 8 / 50 Hz ICR1 = 20000; // Half-period of the first motor is 1 ms, and second 2 ms OCR1A = 1000; OCR1B = 2000; // Endless loop while (1) continue; }