Forum: Mikrocontroller und Digitale Elektronik Frequenz messen mit Interrupt und Timer


von Oz z. (ozzy)


Lesenswert?

Hi,

ich probiere mit einem ATMega128 eine Frequenz zu messen (rund 40Khz). 
Der ATmega ist getaktet mit 14,7456Mhz. Dafür habe ich einen Interrupt 
definiert, und das Signal an INT0 angelegt. Hier mein Code:
1
void init_interrupt( void ) {
2
  DDRD  = 0x00;
3
  MCUCR |= (1<<ISC00)|(1<<ISC01);
4
  EICRA |= (1<<ISC01)|(1<<ISC00);
5
  EIMSK |= (1<<INT0);
6
} 
7
8
void init_timer( void ) {
9
  TCCR1B |= (1<<CS10);
10
  DDRB=0xff;
11
} 
12
13
14
ISR(INT0_vect) {
15
  cli();
16
  char s[7];
17
  uint8_t start = TCNT1;
18
  uart_sendstring( itoa( start, s, 10 ) );
19
  uart_sendchar( '\n' );
20
  TCNT1 = 0;
21
  sei();
22
}

Mein Problem ist, dass ich in einer relativ geringen Spanne um 40Khz 
messen möchte, die Werte aber immer gleich sind, nämlich immer bei 
51-52. Aber das verstehe ich eben nicht, denn mit 14,7456 müsste man 
diese Frequenzen doch ganz gut messen können, oder? Oder seht Ihr 
vielleicht meinen Fehler?

MfG, und vielen Dank schon einmal, Ozzy

von Karl H. (kbuchegg)


Lesenswert?

Ohne jetzt die ganzen Timer und Interrupt Einstellungen
kontrolliert zu haben:

Wie denkst du über Folgendes:

Bei 40Khz kommt ein Interrupt in welchen Zeitabständen?
Wie lange dauert es wohl um einen itoa und eine komplette
UART Übertragung zu machen?

von Oz z. (ozzy)


Lesenswert?

Hi,

deshalb deaktiviere ich doch die Interrupts, während ich per UART sende. 
Erst wenn ich das gemacht habe, setze ich den Timer wieder auf null und 
aktiviere die Interrupts. Wenn ich dann einige Messungen verpasse, ist 
das (relativ) egal, da ich weiß, dass das Signal lange genug anliegt, um 
definitiv mehrere Messungen zu machen.

MfG, Ozzy

von Karl H. (kbuchegg)


Lesenswert?

Christoph O. wrote:
> Hi,
>
> deshalb deaktiviere ich doch die Interrupts,

Das kannst du dir sparen.
Die Interrupts sind während der Ausführung einer ISR
sowieso gesperrt.

> während ich per UART sende.
> Erst wenn ich das gemacht habe, setze ich den Timer wieder auf null und
> aktiviere die Interrupts.

Das bringt dir aber nichts. Wenn am Int Eingang ein
Puls auftritt dann wird zunächst mal das zugehörige
Flag gesetzt und bei nächster Gelegenheit die zugehörige
ISR aufgerufen. Läuft also während deiner UART
Ausgabe ein Puls am INT0 Eingang ein, so wird die ISR
ausgeführt, sobald das globale Interrupt Flag wieder
freigegeben ist (also beim Beenden der momentan laufenden ISR).

> Wenn ich dann einige Messungen verpasse, ist
> das (relativ) egal, da ich weiß, dass das Signal lange genug anliegt, um
> definitiv mehrere Messungen zu machen.

Dein Problem ist nicht, dass du eine Messung verpasst.
Dein Problem ist, das während der Ausgabe schon wieder der
nächste Interrupt getriggert wird.

Du müsstest das Interrupt Flag kurz vor Beendigung der ISR
zurücksetzen. Aber auch das hilft dir nichts. Du willst ja nicht
die Zeitspanne von irgendeinem fiktiven Punkt auf der Zeitachse
bis zum nächsten Auftreten des INT0 messen. Du willst die Zeitspanne
zwischen dem Auftreten 2-er INT0 Ereignisse messen.

Egal wie du es drehst und wendest: Die INT0 Behandlung muss so kurz
wie möglich sein. Eine UART Ausgabe passt da einfach nicht in diese
Anforderung.

von Oz z. (ozzy)


Lesenswert?

Hi,

danke für Deine ausführliche Erklärung!!! Die UART-Ausgabe war auch eher 
zu Debugging-Zwecken da. Dann werde ich mal mein Programm etwas 
umschreiben.

Vielen Dank noch einmal, Ozzy

von Hannes L. (hannes)


Lesenswert?

Du solltest Dein Programm in mehrere parallel arbeitende Tasks 
aufteilen. Die ISR des ext.Int (ICP wäre besser geeignet) misst die 
Periodendauer (nicht die Frequenz) und legt den Messwert unbehandelt 
(also binär) ab. Der UART-Interrupt sendet ein Zeichen aus einem 
Ringbuffer und setzt eine Semaphore, wenn der Buffer leer ist. Die 
Mainloop wandelt bei leerem Buffer (wenn Semaphore gesetzt ist) den 
gerade aktuellen Messwert in ASCII um und legt ihn in den Buffer. Nach 
getaner Arbeit geht es in den Sleep-Mode, geweckt wird per ISR. Auf 
diese Art hat das Programm noch viele Reserven für andere Tasks. Nur ein 
großer Controller und ein schneller Takt alleine garantieren noch lange 
kein gutes Projekt...

...

von Kai G. (runtimeterror)


Lesenswert?

Wenn ich mich nicht verrechnet habe brauchst du für das USART einen 
Teiler von 4 um die gewünschte Datenmenge durchzutreten... macht 
schlappe 3,6864 MHz für die serielle Schnittstelle!

Bist du dir sicher, dass du nach jedem Intervall das Messergebnis 
schicken willst, oder reicht es auch nach jedem 256sten? Macht immer 
noch 156 Messungen pro Sekunde und verursacht deutlich weniger Rauschen 
der Messwerte.

Musst du die Werte als ASCII schicken? Bin jetzt nicht so fit in C, aber 
itoa() erstellt die Dezimalrepräsentation deiner Messung, oder? Wenn du 
das auf Hex umstellst sollte man nochmal einiges an Rechenzeit sparen. 
Den Versand der Messwerte würde ich interruptgesteuert durchführen, um 
die Messungen nicht unnötig zu stören.

Gruß

Kai

ps: bis eben habe ich noch refreshed ;)

von Oz z. (ozzy)


Lesenswert?

Hi,

da ich mir die Daten erst einmal nur zu Testzwecken ausgeben lassen 
wollte (später wird das alles mal ganz anders) habe ich mir folgendes 
gedacht: Ich mache ein paar Messungen, und ziehe dann den Stecker wieder 
von INT0 ab, und erzeuge danach durch eine Verbindung mit 5V einen 
Interrupt auf INT1. Soweit mein Code:
1
void init_interrupt( void ) {
2
  DDRD  = 0x00;
3
  MCUCR |= (1<<ISC00)|(1<<ISC01);
4
  MCUCR |= (1<<ISC10)|(1<<ISC11);
5
  EICRA |= (1<<ISC01)|(1<<ISC00);
6
  EICRA |= (1<<ISC11)|(1<<ISC10);
7
  EIMSK |= (1<<INT0);
8
  EIMSK |= (1<<INT1);
9
}
10
11
ISR(INT0_vect) {
12
  signal = TCNT1;
13
  TCNT1 = 0;
14
}
15
16
ISR(INT1_vect) {
17
  //char s[7];
18
  //uart_sendstring( itoa( start, s, 10 ) );
19
  //uart_sendchar( '\n' );
20
  uart_sendchar( 'i' );
21
}

Das "uart_sendchar( 'i' );" hatte ich erst einmal nur testweise 
reingenommen, aber nun passiert folgendes: selbst wenn ich die Frequenz 
an INT0 anlege, werden "i"'s ausgegeben. Also sowohl bei INT0, als auch 
bei INT1.

Könnt Ihr mir sagen, wo da mein Fehler liegt?

MfG, Ozzy

von Karl H. (kbuchegg)


Lesenswert?

Wie hast du den INT1 Eingang beschaltet?

Wenn du den offen gelassen hast, darfst du dich nicht wundern,
dass der Eingang sich so ziemlich jede elektromagnetische
Strahlung in der Umgebung einfängt, die er nur kriegen kann.
Also zumindest einen Widerstand nach Masse darfst du dem
schon spendieren (10k sollten reichen).

von Karl H. (kbuchegg)


Lesenswert?

Aber warum machst du das eigentlich so kompliziert mit einem
zusätzlichen Interrupt etc, etc.
1
volatile uint16_t signal;
2
3
....
4
5
ISR(INT0_vect) {
6
  signal = TCNT1;
7
  TCNT1 = 0;
8
}
9
10
....
11
12
int main()
13
{
14
  uint16_t tmp;
15
  char s[7];
16
17
   ....  alle init
18
19
20
  sei();
21
22
  while( 1 ) {
23
    cli();
24
    tmp = signal;
25
    sei();
26
27
    uart_sendstring( itoa( tmp, s, 10 ) );
28
    uart_sendchar( '\n' );
29
  }
30
}

und schon sendet dir der µC ständig seinen aktuellen
Messwert.

Das
    cli();
    tmp = signal;
    sei();
ist notwendig, damit dir der Interrupt nicht hinterrücks den
Messwert ändern kann, während der itoa ihn in einen ASCII
String verwandelt. Also wird der momentan aktuelle Messwert
in einer Zwischenvariablen gesichert. Und damit während
der Sicherns auch noch sicher gestellt ist, dass signal seinen
Wert nicht ändern kann, werden kurzfristig die Interrupts
verboten.


(Wenn das klappt, dann solltest du dir mal im Datenblatt den
Input Capture Mode des Timers ansehen und überlegen, wie
du denn nutzbringend einsetzen kannst)

von Gast (Gast)


Lesenswert?

Vielleicht habe ich das Problem nicht verstanden, aber ich würde das 
Signal an ICP1 anlegen und mir die aktuelle Periodendauer aus dem Wert 
im ICR1 abzüglich letzter Wert vom ICR1 errechnen. Wenn die Auflösung 
höher werden sollte, würde man dann nicht nur eine Periode messen, 
sondern beispielsweise 100, sodaß dann alle 2,5ms ein neuer Meßwert 
verfügbar wäre.
Aber vielleicht habe ich das Problem auch nicht verstanden.

von Hannes L. (hannes)


Lesenswert?

Man kann den größten und schnellsten Controller mit einer einzigen 
Aufgabe überfordern, wenn diese Aufgabe schlecht programmiert ist. Also 
sollte man ein Programm so strukturieren, dass die Aufgaben in 
Teilstücke zerlegt werden, die (bei Bedarf) immer nur einen Schritt 
ausführen und sofort zur Mainloop zurückkehren, wenn der nächste Schritt 
jetzt noch nicht ausgeführt werden kann. Dabei sollten die ISRs so 
kurz wie möglich sein, also nur die anfallenden Daten sichern, ggf. eine 
Semaphore setzen und dann sofort zur Mainloop zurückkehren. Nur so kann 
erreicht werden, dass sich die verschiedenen zu erledigenden Jobs nicht 
gegenseitig stören bzw. ausbremsen.

In Deinem Falle böte sich an:

- ICP-ISR (oder ext.-Int) ermittelt Differenz zwischen letztem und
  aktuellen Zeitstempel und legt diese in einer globalen Variable ab.

- UDRE-ISR schiebt ein Byte aus einem Array (String) über UART raus.

- Mainloop prüft Array-Zeiger und generiert einen neuen String, wenn das
  Array komplett ausgegeben wurde. Während des Kopierens der Daten muss
  natürlich die Interruptfreigabe kurzzeitig gesperrt werden, damit das
  Kopieren atomar erfolgt, also nicht mittendrin der Wert geändert wird.

...

von Oz z. (ozzy)


Lesenswert?

@Karl heinz Buchegger: vielen Dank für Deine Antwort. Jetzt funktioniert 
es ohne Probleme!!!

Vieln Dank noch einmal, Ozzy

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.