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
volatileuint16_tnewest_pulslength;
7
volatileuint8_tinterrupt_unhandeled=0;
8
9
intmain(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
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.
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
... 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..
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.
> 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
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).
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
volatileunsignedinticr_alt=0;
2
volatileunsignedintzeit=0;
3
volatileunsignedchartime=0;
4
5
ISR(TIMER1_CAPT_vect)
6
{
7
time=1;
8
zeit=ICR1-icr_alt;
9
icr_alt=ICR1;
10
}
11
12
13
intmain(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.
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).
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
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.