Forum: Mikrocontroller und Digitale Elektronik ATtiny 2313A Interruptflag löschen


von john (Gast)


Lesenswert?

Hallo,
Ich versuche derzeit ein Infrarot-Herzschlag-Messgerät zu basteln. Die 
Hardware funktioniert inzwischen, aber am Code hängt's noch.

Am INT1-Eingang des ATtiny 2313A (da es bei AVRDude nur den ATtiny 2313 
gibt programiere ich ihn ohne das A) kommt ein digitaler High-Puls an, 
wenn ein Herzschlag gemessen wurde. Das ganze soll dann ihn folgendem 
(zu Anschauungszwecken gekürztem) Code verarbeitet werden:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#define F_CPU 4000000UL
4
#include <util/delay.h>
5
6
volatile uint16_t newest_pulslength;
7
volatile uint8_t  interrupt_unhandeled = 0;
8
9
int main(void){
10
cli();                  //interrupts verbieten
11
  DDRB = 0xff;
12
  DDRD = 0x00;
13
  DDRD |= (1 << PD5) | (1 << PD6);
14
15
  MCUCR |= (1 << ISC11) | (1 << ISC10);  //interupt an int1-Pin bei steigender flanke
16
  GIMSK |= (1 << INT1);          //Interrupt an int1-Pin aktivieren
17
18
  TCCR1A = 0x00;              //16bit-Timer 1 in normalem modus
19
  TCCR1B = 0x00;
20
  TCCR1B |= (1 << ICNC1) | (1 << CS12);  //prescaler 256 und noise unterdrückung an
21
22
  sei();                  //interrupts erlauben
23
24
  while(1)
25
  {
26
    while(interrupt_unhandeled == 0);           //auf interrupt warten
27
    interrupt_unhandeled = 0;
28
29
...code... (hier wird der Puls errechnet und auf einem display angezeigt)
30
31
    while(4000 >= TCNT1);    //zeit abwarten (bis signal wieder low ist - ca 250ms)
32
33
    EIFR = (1 << INTF1);    //interruptflag loeschen, damit interrupt nicht direkt nach sei() ausgeloest wird
34
    sei();
35
  }
36
}
37
38
ISR(INT1_vect)
39
{
40
  cli();
41
  interrupt_unhandeled = 1;
42
  newest_pulslength = TCNT1;                              //Timer wert in variable schieben
43
  newest_pulslength = (newest_pulslength * 64) / 1000;                //sollte zeit in ms ausgeben (bei f_cpu=4000000 und prescaler 256)
44
  TCNT1 = 0x0000;                                    //timer zuruecksetzen
45
}

Die Main-Schleife wartet also auf den Interrupt, errechnet dann den Puls 
und zeigt ihn an. Danach wird mit einer weiteren Schleife ca. 250ms 
gewartet, damit der Interrupt nicht mehrfach beim gleichen Puls auslöst.
Nach der Warteschleife soll der Interrupt-Flag gelöscht werden (damit 
ein Interrupt der während der Wartezeit aufgenommen wurde nicht 
ausgeführt wird) und auf den nächsten Interrupt gewartet werden.

Hier liegt aber das Problem: Wenn ein Puls am Mikrocontroller ankommt 
erscheint auf dem Display ein Neuer Messwert (soweit so gut), aber dann 
nach geschätzten 250ms (also nach der Warteschleife) wird das Display 
noch einmal neu Beschrieben (natürlich mit einem falschen Wert).

Ich vermute dass der Interrupt-Flag nicht richtig gelöscht wurde, aber 
ich habe schon halb Google durchsucht und finde meinen Fehler nicht.

von katastrophenheinz (Gast)


Lesenswert?

Ich glaube, du hast das mit dem I-Flag nicht richtig verstanden:
Bei Aufruf der Interrupt-Routine wird automatisch das I-Flag gelöscht,
damit keine nested Interrupts auftreten können, beim Verlassen 
automatisch wieder gesetzt. Dein händisch eingefügtes cli() hat 
überhaupt keine Auswirkungen, ebensowenig wie das sei() in der 
While-schleife.

Weiterhin: Kannst du sicher sein, daß TCNT1 immer kleiner als 2^10 ?
Ansonsten produzierst du hier einen Überlauf:

newest_pulslength = (newest_pulslength * 64) / 1000;

Gruss, Heinz

von katastrophenheinz (Gast)


Lesenswert?

... wenn du das so lösen möchtest, wie ich vermute, daß du das 
vorhattest, dann musst du das Attribut ISR_NOBLOCK verwenden:

ISR(INT1_vect,ISR_NOBLOCK)

Mit dem schrägen cli() in der ISR und dem sei() in der while-loop ist 
dein Programm dann aber -sagen wir mal- ziemlich speziell..

von john (Gast)


Lesenswert?

Ok also Vorab: Danke für den Hinweis, TCNT1 wird definitiv größer als 
2^10. Darf ein Zwischenergebnis denn nicht größer als 2^16 sein? Wie 
könnte ich das umgehen.

Und zu meinen cli(), sei() und Interrupt-Flag Verwendungen: Ich will am 
Ende, dass der Eingang INT1 am µC für ca. 250ms ignoriert wird nachdem 
ein Interrupt ausgelöst wurde. Was in den 250ms passiert ist mir egal, 
wichtig ist nur das direkt nach den 250ms kein Interrupt ausgeführt wird 
welcher während dieser Wartezeit aufgetreten wäre (ich hoffe ihr 
versteht was ich meine...).
Deßhalb lösche ich das Interruptflag bevor die Interrupts mit sei() 
wieder erlaubt werden.

von katastrophenheinz (Gast)


Lesenswert?

> ISR(INT1_vect,ISR_NOBLOCK)
... das ist auch Quatsch, denn auch hier wird mit dem Ende der ISR durch 
das finale RETI das I-Flag wieder gesetzt.

Der richtige Weg ist der, den INT1-Interrupt zu verhindern, und zwar 
nicht über cli(), sondern über gezieltes Sperren des INT1-Interrupts, 
also
statt cli() in der ISR:

GIMSK &= ~(1 << INT1);          //INT1 deaktivieren

und statt sei() in der while-schleife INT1 wieder gezielt aktivieren:

EIFR = (1 << INTF1);    //clear pending INT1-Interrupts
GIMSK |= (1 << INT1);   // INT1 wieder zulassen

Zu dem Überlauf: Kürzen von
>newest_pulslength = (newest_pulslength * 64) / 1000;

führt zu
newest_pulslength = (newest_pulslength * 8) / 125;
->jetzt darf TCNT1 fehlerfrei Werte bis 2^13-1 annehmen.

oder erst die Division (auf kosten der Genauigkeit):
newest_pulslength = (newest_pulslength / 125) * 8;


Wenn das nicht reicht, mit temporärer uint32_t variable rechnen

Gruss, Heinz

von john (Gast)


Lesenswert?

Ich bin begeistert mit GIMSK funktioniert's!
Ich werd mich wohl nochmal genauer mit der funktion von cli und sei 
beschäftigen ;)

Jetz muss ich bloß noch das Problem mit newest_pulslength lösen. Aber 
mit der temporären 32bit variable hast du wohl schon den richtigen 
Ansatz gegeben. Falls ich nicht schaffe melde ich mich nochmal.

Danke für deine Hilfe! Das nenn ich ne schnelle Problembeseitigung (ich 
hab mir echt n paar Stunden lang Gedanken gemacht wo das Problem liegen 
könnte).

von Thomas E. (thomase)


Lesenswert?

john schrieb:
> Hallo,
> Ich versuche derzeit ein Infrarot-Herzschlag-Messgerät zu basteln. Die
> Hardware funktioniert inzwischen, aber am Code hängt's noch.
>
> Am INT1-Eingang des ATtiny 2313A (da es bei AVRDude nur den ATtiny 2313
> gibt programiere ich ihn ohne das A) kommt ein digitaler High-Puls an,

Ein 2313A ist aber nicht das selbe. Mit Avrdude wird nur das Programm 
auf den Controller gebrannt. Da sollten 2313 und 2313A sich gleich 
verhalten.
Aber du solltest wenigstens mit 2313A kompilieren.

> wenn ein Herzschlag gemessen wurde. Das ganze soll dann ihn folgendem
> (zu Anschauungszwecken gekürztem) Code verarbeitet werden:
>

> #include <avr/io.h>
> #include <avr/interrupt.h>
> #define F_CPU 4000000UL
> #include <util/delay.h>
>
> volatile uint16_t newest_pulslength;
> volatile uint8_t  interrupt_unhandeled = 0;
>
> int main(void){
> cli();                  //interrupts verbieten

Überflüssig. Nach Reset sind die Interrupts gesperrt.

>   DDRB = 0xff;
>   DDRD = 0x00;
>   DDRD |= (1 << PD5) | (1 << PD6);
>
>   MCUCR |= (1 << ISC11) | (1 << ISC10);  //interupt an int1-Pin bei
> steigender flanke

Dieser Interrupt wird bei Änderung von Low auf High genau einmal 
ausgelöst. Das ganze Gehampel mit dem Interrupt, was darauf folgt, 
kannst du dir somit komplett sparen.

>   GIMSK |= (1 << INT1);          //Interrupt an int1-Pin aktivieren
>
>   TCCR1A = 0x00;              //16bit-Timer 1 in normalem modus
>   TCCR1B = 0x00;
>   TCCR1B |= (1 << ICNC1) | (1 << CS12);  //prescaler 256 und noise
> unterdrückung an

Welches Rauschen willst du denn da unterdrücken? So verkehrt ist 
eigentlich das gar nicht. Aber im Moment unterdrückst du nur das 
Rauschen im Walde.

>
>   sei();                  //interrupts erlauben
>
>   while(1)
>   {
>     while(interrupt_unhandeled == 0);           //auf interrupt warten

Man wartet nicht auf einen Interrupt. Entweder pollt man das 
Interrupt-Flag
oder man lässt das Programm laufen.

>     interrupt_unhandeled = 0;
>
> ...code... (hier wird der Puls errechnet und auf einem display
> angezeigt)
>


>     while(4000 >= TCNT1);    //zeit abwarten (bis signal wieder low ist
> - ca 250ms)
>
>     EIFR = (1 << INTF1);    //interruptflag loeschen, damit interrupt
> nicht direkt nach sei() ausgeloest wird
>     sei();

Wie schon oben erwähnt: Völlig unnötig.
>   }
> }
>
> ISR(INT1_vect)
> {
>   cli();
>   interrupt_unhandeled = 1;
>   newest_pulslength = TCNT1;                              //Timer wert
> in variable schieben
>   newest_pulslength = (newest_pulslength * 64) / 1000;
> //sollte zeit in ms ausgeben (bei f_cpu=4000000 und prescaler 256)
>   TCNT1 = 0x0000;                                    //timer
> zuruecksetzen
> }
>

Was du mit deinem Programm machen willst, ist eine Zeitmessung. Ob du 
Herzfrequenzen oder Motordrehzahlen misst, ist dabei völlig egal.

Zum Messen von Zeiten hat der Timer1 des 2313 den ICP-Mode. Und auch 
einen extra Interrupt dafür: CAPT.
Beim Auftreten einer Flanke, je nachdem, welche eingestellt ist, wird 
der Inhalt des TCNT in das ICR1-Register geschrieben und ein Interrupt 
ausglöst.

In der ISR sicherst du den Wert in einer Variablen. Beim nächsten 
Interrupt errechnest du aus dem aktuellen ICR1-Wert und dem zuletzt 
gespeicherten die Zeit, die vergangen ist.
1
volatile unsigned int icr_alt = 0;
2
volatile unsigned int zeit = 0;
3
volatile unsigned char time = 0;
4
5
ISR(TIMER1_CAPT_vect)
6
{
7
  time = 1;
8
  zeit = ICR1 - icr_alt;
9
  icr_alt = ICR1;
10
}
11
12
13
int main (void)
14
{
15
//Init
16
17
while(1)
18
{
19
  if(time)
20
  {
21
    time = 0;
22
    //Herzfrequenz aus "zeit" berechnen und anzeigen
23
  }
24
}

Bei Verwendung des Capture Modes macht dann auch die Noisereduction 
einen Sinn. Die wartet 4 CPU-Takte nach Auftreten des Zustandwechsels am 
ICP1, bis sie den Interrupt auslöst und den TCNT-Wert ins ICR schreibt.

> Danke für deine Hilfe! Das nenn ich ne schnelle Problembeseitigung (ich
> hab mir echt n paar Stunden lang Gedanken gemacht wo das Problem liegen
> könnte).
Du hast den Teufel mit dem Beezlebub ausgetrieben oder Pest durch 
Cholera ersetzt.

mfg.

von john (Gast)


Lesenswert?

Also das mit der Warteschleife hab ich eingebaut, weil das externe 
Signal nicht immer ganz sauber ist. Manchmal kommt nach dem richtigen 
Herzschlag noch ein zweiter schwächerer Schlag rein (wenn die Potis im 
Analogteil meiner Schaltung nicht richtig/präzise eingestellt wurden).

Dein vorgeschlagener Code sieht natürlich wunderbar simpel aus, da muss 
ich mich wohl nochmal zum ICP-Modus belesen.
Aber dennoch muss ich dann in der Hauptschleife auf den Interrupt warten 
wenn ich die Werte mitteln will (ich darf ja erst neu mitteln wenn ein 
neuer Puls gemessen wurde - ich will das mitteln ja auch nicht in den 
Interrupt schieben).

von katastrophenheinz (Gast)


Lesenswert?

Thomas Eckmann schrieb:
> Du hast den Teufel mit dem Beezlebub ausgetrieben oder Pest durch
> Cholera ersetzt.

John, lass dich nicht verrückt machen. Eine Implementierung, die 
funktioniert und die du verstehst, ist erstmal eine gute 
Implementierung.

Danach kann man dann etwas verbessern, optimieren oder eleganter 
umsetzen. Aber schließlich wurde auch Rom nicht an einem Tag erbaut.

Gruss, Heinz

von Thomas E. (thomase)


Lesenswert?

john schrieb:
> Also das mit der Warteschleife hab ich eingebaut, weil das externe
> Signal nicht immer ganz sauber ist. Manchmal kommt nach dem richtigen
> Herzschlag noch ein zweiter schwächerer Schlag rein (wenn die Potis im
> Analogteil meiner Schaltung nicht richtig/präzise eingestellt wurden).

> Die Hardware funktioniert inzwischen, aber am Code hängt's noch.

Also funktioniert die Hardware noch nicht.

Du solltest den Fehler dort beseitigen, wo er auftritt und nicht mit 
einer kruden Programmierung irgendwie an den Symptomen rumdoktern.
Wenn du es mit der Hardware nicht  besser hinbekommst, prüfst den 
Messwert auf Plausibilität und ignorierst ihn gegebenenfalls. Damit wird 
dein Eingangssignal gewissermassen entprellt.
1
ISR(TIMER1_CAPT_vect)
2
{
3
  zeit = ICR1 - icr_alt;
4
  if(zeit > was_weiss_ich)
5
  {
6
    icr_alt = ICR1;
7
    time = 1;
8
  {
9
}

> John, lass dich nicht verrückt machen. Eine Implementierung, die
> funktioniert und die du verstehst, ist erstmal eine gute
> Implementierung

Etwas anderes darf man von jemandem, der sich Katastrophenheinz nennt, 
wohl auch nicht erwarten. Nomen es omen. Passt aber perfekt zur 
"Lösung".

mfg.

: Bearbeitet durch User
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.