Hallo zusammen,
ich habe das Gefühl, dass das Problem des "zu langsam zählenden
Timer/Counter" schon oft genug behandelt wurde, aber leider kam ich
bislang auf keinen Lösungsansatz.
Kurz zu meinem Anliegen:
- jede Millisekunde ein Interrupt auslösen
- Interrupt als ADC Trigger nutzen und ADC-Messung auslösen
- ADC-Interrupt (Messung beendet) nutzen, um Flag (Variable) zu setzen
- Messwert in Hauptschleife weiterverarbeiten
Da ich allerdings erstmal gucken wollte, ob das soweit von der Idee
hinhaut, war der erste Ansatz die Frequenz von den angedachten 1 kHz auf
5 Hz zu reduzieren und mit dem Interrupt bzw. der ISR eine LED zu
toggeln. Damit wäre dann nämlich sichergestellt, dass Zähler und
entsprechende ISR auch wirklich wie gewollt funktionieren. Ein Schalten
der LED mit 5 Hz sollte man zudem noch gut wahrnehmen können.
Den Atmega328p betreibe ich mit einem externen 16 MHz Quarz. Die Fuses
gibt mir AVRDUDE wie folgt aus:
- H: 05
- L: FF
- E: D8
Gemäß der "Rückwärtsfunktion" des FuseCalculators
(http://www.engbedded.com/fusecalc) ist "CKDIV8" also nicht aktiv. Die
Taktfrequenz f_CLK sollte daher auch wirklich die verfügbaren 16 MHz
sein.
Für meine weiteren Darstellungen beziehe ich mich auf das Datenblatt des
Atmega328p, was es hier gibt:
http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf
Da die Schaltfrequenz von 5 Hz nicht direkt über den Timer Overflow
erreicht werden kann, betreibe ich den Timer im CTC-Modus. Mit einem
Vorteiler von 256 ergibt sich gemäß der Formel aus Kapitel 20.12.2
"Clear Timer on Compare Match (CTC) Mode" (Seite 162) für OCR1A einen
Wert von 6249.
Gemäß Tabelle auf Seite 172 entspricht der angedachte Anwendungsfall dem
Mode 4, sodass ich die entsprechenden Register so geschrieben habe, wie
auch aus dem Code deutlich wird:
1 | #include <avr/io.h>
|
2 | #include <avr/interrupt.h>
|
3 | #include <util/delay.h>
|
4 | #include <stdlib.h>
|
5 | #include <stdint.h>
|
6 |
|
7 | #define F_CPU 16000000UL
|
8 | #define LED PB5
|
9 |
|
10 |
|
11 | ISR (TIMER1_COMPA_vect) {
|
12 | // toggle LED
|
13 | PORTB ^= (1<<PB5);
|
14 | }
|
15 |
|
16 |
|
17 | void LED_Init(void) {
|
18 | // define pin LED as output
|
19 | DDRB |= (1<<LED);
|
20 | }
|
21 |
|
22 |
|
23 | void TC1_Init(void) {
|
24 | // enable CTC mode
|
25 | TCCR1A = (1<<WGM12) | (0<<WGM11) | (0<<WGM10);
|
26 |
|
27 | // timer prescaler
|
28 | TCCR1B |= (1<<CS12) | (0<<CS11) | (0<<CS10); // 256
|
29 |
|
30 | // set compare value (for calculation see data sheet p. 162)
|
31 | OCR1A = 6249; // for 256 prescaler
|
32 |
|
33 | // enable CTC interrupt
|
34 | TIMSK1 |= (1<<OCIE1A);
|
35 | }
|
36 |
|
37 |
|
38 | int main(void) {
|
39 | TC1_Init();
|
40 | sei();
|
41 |
|
42 | while(1) {
|
43 | // do nothing in here
|
44 | }
|
45 | return 0;
|
46 | }
|
Die LED wird geschaltet, was zeigt, dass der Interrupt prinzipiell
ausgelöst wird. Gemäß meiner Berechnung sollte dies mit 5 Hz geschehen.
Selbst, wenn man evtl. Toleranzen in Quarz und so berücksichtigt, dann
sollte die LED definitiv mehrmals pro Sekunde schalten.
Leider habe ich kein Oszilloskop oder Logikanalysator, um mal den Pin
der LED "abzuhorchen". Die LED blinkt allerdings nicht mit den
gewünschten 5 Hz, sondern lediglich mit etwas um 1 Hz. Sie bleibt also
etwa eine Sekunde an und geht dann für eine Sekunde aus usw.
Hat jemand einen Hinweis, woran das liegen könnte? Nachdem ich das
Datenblatt wieder und wieder gelesen, nochmals den Code und auch die
Fuses geprüft habe, habe ich leider keinen Ansatz mehr, dem Grund für
mein Problem auf die Schliche zu kommen. Vielleicht habe ich aber auch
einen typischen Anfängerfehler gemacht (bin ja auch ein Anfänger) oder
einfach was übersehen...
Vielen Dank für eure Anmerkungen und Hilfestellungen.