Forum: Mikrocontroller und Digitale Elektronik Probleme mit externem Interrupt 0 Atmega 32


von Markus K. (markus1992)


Angehängte Dateien:

Lesenswert?

Hallo Leute!

Ich will mit diesem Programm Drehzahl messen. Der externe Interrupt 
macht mir aber Probleme. Ich starte das Programm und die LED (PORTD6) 
schaltet sich ein und aus. Ich sende 2 mal ein "n" von meinem PC an den 
µC und die Drehzahl wird auch zum PC gesendet. Nach diesem Ablauf hört 
die LED aber auf zu blinken. D.h. der Atmega reagiert auf den externen 
Interrupt nicht mehr. Woran kann das liegen.

Falls ich ein paar wichtige Informationen vergessen habe, einfach 
posten.

Mit freundlichen Grüßen

Markus

von Klaus T. (gauchi)


Lesenswert?

Versuch mal, das UDR direkt in der Interruptroutine auszulesen, ich 
glaube sonst kommt der Interrupt immer wieder, auch wenn keine neuen 
Daten auftauchen.

von Markus K. (markus1992)


Lesenswert?

Danke für die rasche Antwort. Ich habe deinen Tip gleich mal angewandt. 
Mein Programm funktioniert zwar noch nicht so wie ich es mir vorstelle, 
aber das Problem, dass der Interrupt nicht mehr auslöst, ist 
geschichte!!!
Ich danke dir sehr.

mfg Markus

von Markus K. (markus1992)


Angehängte Dateien:

Lesenswert?

So nach deinem Tip hat der externe Interrupt weiterhin auch nach 
Auftreten eines seriellen Interrupts ausgelöst.
Jetzt wollte ich das Programm weiter optimieren und den Timer zu starten 
und den ext. Interrupt zu aktivieren, wenn ein "n" vom PC gesendet wird.

Sollablauf:
n wird empfangen und externer Interrupt wird aktiviert. Externer 
Interrupt löst aus und schaltet den Timer ein. Dort wird die Variable 
counter(volatile) bei jedem Interrupt um eins erhöht. Bei dem zweiten 
Auslösen des externen Interrupts wird die Drehzahl berechnet (Zeit des 
Timers * counter = Periodendauer => Drehzahl). Dann soll die 
Drehzahl(n_ist) an den PC gesendet werden. Drehzahl ist immer -1. Ich 
habe auch die Variable counter mal gesendet und diese ist 0. Das würde 
-1 erklären als Ergebnis von (240000/0). Das würde bedeuten der Timer 
löst nicht aus. Ich finden keine Erklärung.

Ich bitte wieder um Hilfe.

mfg Markus

von Karl H. (kbuchegg)


Lesenswert?

Markus Kammerhofer schrieb:
> So nach deinem Tip hat der externe Interrupt weiterhin auch nach
> Auftreten eines seriellen Interrupts ausgelöst.

Nur um das richtig zu stellen.
Er hat auch vorher schon ausgelöst.

Nur hat dein ständig aufgerufener RXC Interrupt keine Rechenzeit mehr 
übrig gelassen um irgendetwas anderes zu bearbeiten.

von Klaus T. (gauchi)


Lesenswert?

Hier
1
          while(n_berechnet == 0)
2
          {
3
          }
kommt dein Programm nie wieder raus, weil n_berechnet nicht im Interrupt 
geändert wird.

Wie schnell kommen denn eigentlich deine Lichtschrankensignale? Und wie 
schnell läuft der Timer?

von Karl H. (kbuchegg)


Lesenswert?

Wenn ich mir deinen Code so ansehe, habe ich das starke Gefühl du 
trickst dich im Moment mit deinen vielen Flags (und ob die jetzt 0 oder 
1 sein müssen/sollen) gewaltig selber aus. Du errichtest dir hier eine 
Komplexität die nicht notwendig ist. Das Geflecht, welches Flag wie 
stehen muss, damit an anderer Stelle was passiert, wird schön langsam 
immer undurchschaubarer. Variablennamen ala hs_a, hs_n, a_hilf tragen 
dann auch nicht gerade dazu bei, das etwas durchschaubarer zu machen.

Auch wenn es immer heißt: Interrupt Routinen so kurz wie möglich, so 
muss man das auch nicht als Dogma sehen. Ein bischen Arbeit darf man 
schon auch in einer Interrupt Routine machen lassen. So darf eine 
Interrupt Routine ruhig auch die ermittelte Zeit in eine Drahzahl 
umrechnen. Die Hauptschleife liefert dann bei entsprechender Anfrage 
über UART einfach den zuletzt ermittelten Wert.

Das andere ist, dass du unnötigerweise deine ganzen Flags als int machst 
und dadurch dem Prozessor unnötige 16 Bit Operationen aufzwingst, wo es 
8 Bit Operationen auch tun. Dann müsstest du dir um atomare Absicherung 
der Zugriffe auch keine Gedanken machen. Aber das dürfte momentan noch 
gar nicht das Problem sein.

Alles in allem ist der Code so verworren, dass ich ihn neu schreiben 
würde, wenn ich den übernehmen müsste.

von Markus K. (markus1992)


Lesenswert?

Also ich danke dir für diesen Ratschlag. Der Punkt ist dass ich solch 
ein großes Programm wie in dieser HTL - Diplomarbeit noch nie schreiben 
musste. Ich habe also keinerlei Erfahrung. Jetzt bin ich mal eine Woche 
weg und werde dann mit Hilfe eines Flussdiagrammes versuchen eine 
funktionierende Lösung zu finden.
Mfg Markus

von Markus K. (markus1992)


Lesenswert?

So ich bin zurück aus dem Urlaub.
Ich habe jetzt begonnen den Code neu zu schreiben. Werde es diesmal so 
machen, dass ich Teil für Teil versuche.
Der 1. Teil ist jetzt die Drehzahlmessung.

ISR(INT0_vect)
{
  if(messen == 0)
  {
    TCNT2 = 0;
    counter = 0;

    //TCCR2 |= (1<<CS21);
    TIMSK |= (1<<OCIE2);

    messen = 1;
  }

  else if(messen == 1)
  {
    //TCCR2 &= ~(1<<CS21);
    TIMSK &= ~(1<<OCIE2);

    n_ist = 240000/counter;
    GICR &= ~(1<<INT0);
    sprintf(string, "%d\r", counter);
    uart_puts(string);
    messen = 0;
  }

}

ISR(TIMER2_COMP_vect)
{
  counter++;
}


So der Rest des Programmes funktioniert. Über die serielle Schnittstelle 
wird der externe Interrupt aktiviert. Das Programm reagiert somit 2 mal 
auf den externen Interrupt, dann wird er wieder deaktiviert. Zwischen 
den beiden Interrupts sollte der Timer die Variable Counter hochzählen.

Alle Variablen die in den Interrupts verwendet werden sind Volatile. Der 
Wert von Counter bewegt sich zwischen 50 und 300. Die Drehzahl wird aber 
nicht verändert.

Timerzyklus dauert 0,000125 Sekunden.
Systemclock = 16MHz
Die Zeit die zwischen den beiden Interrupts vergeht beträgt 0,0502 
Sekunden bei 600rpm.

counter sollte somit ca. 400 ergeben.

Ich hoffe ihr könnt mir wieder helfen.

mfg

von Karl H. (kbuchegg)


Lesenswert?

Markus Kammerhofer schrieb:

> Alle Variablen die in den Interrupts verwendet werden sind Volatile. Der
> Wert von Counter bewegt sich zwischen 50 und 300.

Ich nehme an, das weißt du wegen dem sprintf/uart_puts der jetzt noch 
temporär in der ISR sitzt und dann wieder rausfällt.

Auch wenn ich vorher gesagt habe, dass man in einer ISR schon auch etwas 
machen darf, Ein/Ausgaben auf LCD/UART sind meistens zuviel des Guten. 
Die dauern einfach zu lange. Dadurch werden Interrupts zu lange 
blockiert und man verpasst den einen oder anderen Interrupt.

Du bist jetzt von einem Extrem ins Gegenteil verfallen.

> Die Drehzahl wird aber
> nicht verändert.

woher weißt du das?

> Ich hoffe ihr könnt mir wieder helfen.

Zeig immer möglichst vollständigen Code. Zeig auch immer die Datentypen 
der beteiligten Variablen.

von Markus K. (markus1992)


Angehängte Dateien:

Lesenswert?

Ich habe einen Gleichstrommotor an einem Netzteil mit konstanter 
Spannung angehängt. Der Motor dreht im Leerlauf. Da ist die Drehzahl 
weitesgehend konstant. Die Werte von Counter sind ja vollkommen falsch.

So jetzt habe ich versucht die ISR so kurz wie möglich zu halten. Das 
Ergebnis bleibt aber das selbe. Es dürfte jedoch stimmen, dass der 
Timer2Interrupt teilweise blockiert wird weil die counter-Variable ist 
immer kleiner als der Normalwert und nie größer. In der ISR wird nun nur 
noch der Timer ein und ausgeschaltet. Leider resultiert daraus eine 
if-Verzweigung. Deshalb habe ich jetzt den gesamten Code gesendet.

Ist die if-Verzweigung auch zu lang oder ist etwas anderes falsch? Ich 
habe das Programm auch mal versucht so zu verändern, dass bei jedem INT0 
die Drehzahl berechnet wird und der Timer somit nie gestoppt wird. Da 
funktioniert die Messung tadellos. Aber einen Timer mit 0,000125s 
ständig laufen zu lassen und den INT0 immer aktiviert zu haben, wird 
denke ich auf Dauer zu Problemen führen, weil das Programm noch um 
einiges größer werden wird.

mfg

von Karl H. (kbuchegg)


Lesenswert?

Markus Kammerhofer schrieb:
> Ich habe einen Gleichstrommotor an einem Netzteil mit konstanter
> Spannung angehängt. Der Motor dreht im Leerlauf. Da ist die Drehzahl
> weitesgehend konstant. Die Werte von Counter sind ja vollkommen falsch.

OK.
Das hattest du vorher so noch nicht gesagt :-)


> Ist die if-Verzweigung auch zu lang oder ist etwas anderes falsch?

Das wird dir nicht gfallen.
Die ganze Systematik ist sch...e.
Sowas macht man nicht mit externen Interrupts u.dgl sondern mit einem 
Input Capture vom Timer und zugehörigem Interrupt.

Sieh dir mal an, wie Frequenzzähler arbeiten. Du baust im Grunde auch 
nichts anderes. Code für sowas findet sich zuhauf im Forum.


> Ich
> habe das Programm auch mal versucht so zu verändern, dass bei jedem INT0
> die Drehzahl berechnet wird und der Timer somit nie gestoppt wird. Da
> funktioniert die Messung tadellos. Aber einen Timer mit 0,000125s
> ständig laufen zu lassen und den INT0 immer aktiviert zu haben, wird
> denke ich auf Dauer zu Problemen führen, weil das Programm noch um
> einiges größer werden wird.

Was hat das damit zu tun.
die ISR verbraucht ein paar Prozentpunkte Rechenzeit, wenn überhaupt. 
Nichts gravierendes, solange du keine großartigen Ausgaben machst.

von Markus K. (markus1992)


Lesenswert?

OK. Ich habe mich jetzt ein wenig eingelesen. Nur fürs Verständnis. Ich 
hänge meine Lichtschranke an den ICP1-Pin. TCNT1 zählt aufwärts bis eine 
Flanke am ICP1-Pin ankommt. ICP1 * N/f_clkI/O ergibt somit die 
Periodendauer einer Umdrehung. Falls 2^16 nicht ausreicht (n ist zu 
klein) verwendet man den Timer1 Overflow Interrupt. In dieser ISR lasse 
ich eine Zählvariable laufen, sagen wir mal x. In dem ICP1 Interrupt 
berechne ich dann die Periodendauer mit dieser Formel: (x * 2^16 + 
ISP1)*N/f_clkI/O.
Ich hoffe das ist richtig wie ich mir das denke.
Aber wie kann ich x*2^16 rechnen? Das ergibt ja gigangtische Werte.
mfg

von Karl H. (kbuchegg)


Lesenswert?

Markus Kammerhofer schrieb:
> OK. Ich habe mich jetzt ein wenig eingelesen. Nur fürs Verständnis. Ich
> hänge meine Lichtschranke an den ICP1-Pin. TCNT1 zählt aufwärts bis eine
> Flanke am ICP1-Pin ankommt.

Genau.
Der springende Punkt ist, dass dir die Capture Hardware den zum Ereignis 
gehörenden Timer Wert taktgenau wegsichert. Du hast daher im Interrupt 
alle Zeit der Welt (bis zum nächsten Capture) um diesen Wert 
aufzuarbeiten.


> ICP1 * N/f_clkI/O ergibt somit die
> Periodendauer einer Umdrehung.

Ich interpretiere das mal als: Genau!

> Falls 2^16 nicht ausreicht (n ist zu
> klein) verwendet man den Timer1 Overflow Interrupt. In dieser ISR lasse
> ich eine Zählvariable laufen, sagen wir mal x. In dem ICP1 Interrupt
> berechne ich dann die Periodendauer mit dieser Formel: (x * 2^16 +
> ISP1)*N/f_clkI/O.

Kann man machen.
Iregndwo gibt es immer eine Grenze an der man anstelle der Periodendauer 
die Anzahl Pulse pro Zeiteinheit misst. Ist eine Frage der Genauigkeit 
bzw. Messdauer.
Man kann ja auch einfach den Vorteiler des Timers höher drehen.

> Ich hoffe das ist richtig wie ich mir das denke.
> Aber wie kann ich x*2^16 rechnen? Das ergibt ja gigangtische Werte.

long ist in C schon erfunden. Und eine Multiplikation mit 2^16 ist auf 
Assemblerebene eher trivial und sieht nur im C Code etwas wild aus.

In der ISR die Periodendauer aus der Differenz zum vorhergehenden Wert 
ausrechnen. Die tatsächliche Drehzahl kann man dann in aller Ruhe in der 
Hauptschleife berechnen und irgendwo anzeigen.

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.