Forum: Mikrocontroller und Digitale Elektronik Problem bei Frequenzmessung über externes Interrupt


von Stefan H. (speed2win)


Lesenswert?

Hallo,

ich plane eine Frequenz mittels einem Reedkontakt und einem Magneten zu 
messen. Das ganze geschieht mit einem Atmega8 am externen Interrupt INT0 
(der ICP Pin wird anderweitig für eine zweite Frequenzmessung benötigt).

Der Reedokontakt hängt also an PD2 (INT0) und ist gegen Masse 
geschaltet. Interner Pullup ist an diesem Pin aktiv.
Zusätzlich hängt an PB1 eine LED (aktiv low), die im Takt des 
Reedkontaktes leuchtet. Sie wird zunächst bei der fallenden Flanke (Reed 
zieht an) eingeschaltet und bei steigender Flanke (Reed fällt ab) 
ausgeschaltet. Pustekuchen, geht leider nicht (immer). Irgendwie bleibt 
die LED manchmal an.

16-bit Timer liest die Frequenz ein bzw. die Zeit von fallender zu 
fallender Flanke.
Der 8-bit Timer dient zum Entprellen und wurde so vorgeladen, dass er 
nach 1ms überläuft.
Der Reedkontakt hat eine 0,5ms Schaltzeit laut Datenblatt inkl. 
Prellzeit.
Daher habe ich das Programm so geschrieben und der 8-bit Timer mit 1ms 
konfiguriert:
Ext. Interrupt tritt auf --> ext. Interrupt sperren, sodass keine 
Flanken durch prellen erkannt werden. Zeit vom 16-bit Timer wird 
eingelesen, 8-bit Timer wird vorgeladen, sodass er nach 1ms überläuft.
Die ISR des 8-bit Timer (1ms vergangen) prüft nun, ob der INT0-Pin 
immernoch auf Masse gezogen ist, wenn ja, Signal erkannt&entprellt, wenn 
nein, Neustart der Messung. Zusätzlich schaltet die ISR noch die Flanken 
um, sodass nun die nächste Flanke am INT0 als steigend erkannt wird und 
aktiviert wieder die externen Interrupts.
Nun kommt die steigende Flanke in das Interrupt und gleiches Spiel 
wieder. INT0 sperren, 8-bit Timer vorladen, prüfen ob INT0 auf Masse 
gezogen ist--> wenn nein--> high und damit Steigende Flanke. Aber genau 
dies wird oft nicht erkannt und er springt mir bei der if-Abfrage in den 
else-Zweig zum die Messung neu starten (da Flanke nicht erkannt). Da 
dieser Zweig eigentlich die LED wieder ausschalten soll, aber diese 
manchmal an bleibt, muss hier irgendetwas scheitern. Wenn ich in den 
else-Zweig schreibe "LED ausschalten", dann wird diese auch 
ausgeschaltet, aber dadurch wird nichts gemessen, der er immer wieder 
die Messung neu startet aufgrund der nicht erkannten Flanke.
Das Programm ist ausgiebig kommentiert und sollte daher gut lesbar sein.
Ich wäre dankbar, wenn jemand eine Idee hätte.
--> zum Programm:
http://ebay321.pytalhost.com/frequenz.c

von Stefan H. (speed2win)


Lesenswert?

Hallo,

ich weiß, dass die Antworten hier im Forum auf freiwilliger Basis 
geschehen. Aber hat denn keiner eine Idee?
Ich wäre auch dankbar, wenn jemand mir das Konzept von der Idee 
bestätigen kann oder Dinge aufzeigt, wo der Fehler mit großer 
Wahrscheinlichkeit nicht liegt, sodass man ein Teil des Codes 
ausschließen kann.

Danke.

von Thomas R. (tinman) Benutzerseite


Lesenswert?

ehm, ohne jetzt genau draufgeguckt zu haben, warum sind da so viele 
sachen in den int routinen ? Rufst du jetzt wirklich ein timer overflow 
int in dem int0 ? Ob das gut geht ...

Ach, und für bessere sichtbarkeit könntest du dein source als 
dateianhang statt link posten.

von Stefan H. (speed2win)


Lesenswert?

Hallo,

der Gedanke dabei ist, das das externe Interrupt beim ersten Schließen 
des Reedkontakts anspringt. Wird sofort dann in der INT Routine das 
externe Interrupt deaktiviert (dadurch fange ich mir keine erneuten 
Reedkontaktprellungen ein). In dieser Interrupt Routine wird dann nur 
der Timer0 vorgeladen, dass ab sofort in 1ms überlauft und evtl. 
anstehende Interrupt Flags des Timers gelöscht, falls dieser zufällig in 
der Zwischenzeit beim Abarbeiten der externen Interrupt Routine 
übergelaufen wäre.
Nach 1ms wird dann die Timer0 Interrupt Routine aufgerufen und prüft, ob 
der Reedkontakt noch betätigt ist. Wenn ja, müsste er nicht mehr 
prellen, da Schaltzeit inkl. Prellzeit bei 0,5ms liegen (laut 
Datenblatt).

Viele Sachen sind das eigentlich nicht, es werden nur Interrupts 
freigeschalten und gesperrt, sowie eben wegen der Prüfung auf fallende 
oder steigende bzw. 1. und 2. Flanke ein paar If-Anweisungen. Aber in 
den If-Anweisungen steht jedesmal mehr oder weniger das selbe, eben 
Interrupts sperren, freigeben, rücksetzen von Zählern... jedoch eben nur 
auf die jeweilige Flanke konfiguriert.

von STK500-Besitzer (Gast)


Lesenswert?

Um welche Frequenzen handelt es sich denn? (scheinbar kleiner als 500Hz)
Zur Not wäre ein Polling des Pins per 8bit-Timer sinvoller als auf ein 
Interrupt zu reagieren (mit dem damit verbundenen Aufwand mit Entprellen 
etc.)

von Januar2010 (Gast)


Lesenswert?

Hallo Stefan,

nur um sicher zu gehen: so erwarten wir das prellende Signal, oder?

                       ______________
Schalter offen _______|               |________
                                  _____
pegel an int0  __________/\/\/\/\/       \/\/\/\____
                      |-- <0.5ms--|


Erstmal ist es wirklich wichtig zu wissen, mit welcher Frequenz fs dein 
Signal maximal schaltet. Bei einer Schaltzeit von 0.5ms muss fs/2 < 
1/0.5ms = 2kHz, fs also kleiner 1kHz sein. Wenn deine Prellunterdrückung 
1ms aktiv ist muß die Frequenz wie STK500-Besitzer schon andeuted < 
0.5kHz sein. Wenn dieser Teil des Codes erreicht wird, dann hast du 
sowieso ein Problem:

//Messung wird nicht gewertet, Variablen rcksetzen, so das neue Messung 
beginnen kann

Entweder a) dauert das Prellen länger als 1ms oder b) deine Frequenz ist 
größer als 0.5kHz. Kannst du Fall a) ausschließen, müsstest das Ergebnis 
deiner Messung f>0.5kHz sein.

Was mir auffällt: Du schalltest den Timer0 im timer 0 interrupt nicht 
ab. Angenommen du hast eine fallende Flanke (Reed auf Masse) detektiert, 
und auch nach 1ms ist dieser noch auf Masse. Dann schaltest du die LED 
ein. Angenommen nach 2ms ist Reed immer noch auf Masse und der 2. 
Overflow Interrupt des Timer0 tritt auf. In diesem Fall gilt

(MCUCR & (1 << ISC00)) ist wahr da du es beim 1. Overflow interrupt 
gesetzt hast:

  MCUCR = (1 << ISC00) | (1 << ISC01)
gleichzeitig hattest du hier den interrupt aber nicht ab sonder nochmal 
angeschaltet:
  GICR |= (1 << INT0) ;

(PIND & (1 << Reedkontakt) ist beim 2. Overflow false, da du *immer 
noch* mit dem Reed auf 0 bist.


Falls es das nicht ist:

Da dein Problem nur in eine Richtung auftritt könnte man nach 
Unterschieden der beiden Schaltrichtungen suchen.

1. Dein Code reagiert aufgrund eines Fehler auf positive Flanken anders 
als auf negative Flanken . Um das zu analysieren wäre es sicherlich 
hilfreich Teile des Codes der in allen if-else zweigen gleich ist in 
eine Funktion zu verschieben und diese jeweils aufzurufen.

2. Die Schaltung reagiert auf positive Flanken anders als auf negative 
Flanken. Da fällt mir auf den ersten Blick nur der Pull-Up ein. Wenn 
dein Kontakt sich schließt, dann wird der Pegel des Pins sehr schnell 
auf 0 gezogen. Wenn dein Kontakt sich öffnet, dann zieht der Pull up den 
Pegel auf 1. Dabei muss eine eventuelle Kapazität am Pin umgeladen 
werden. Je größer diese Kapazität und je größer der Pull-up, desto 
länger dauert dies. Beispiel

                  ________________
Schalter offen  _|                  |_____
                 |-umladen-|--1ms---:-- -|
                           :________:__  :
Pegel an int0   ___________|        :  |_:nach 1ms Pegel schon wieder 0
                                    :  :
                                    :->: pull down schnell

Was gegen diesen Effekt spricht ist, dass deine Frequenz ja scheinbar 
unter 100Hz liegt (du kannst die LED ja noch beobachten).



Ich würde die Frequenzberechnung an deiner Stelle sowieso in den 
Interrupt Handler ziehen. Damit wuerdest du etwas 
Synchronisationsaufwand (MessungAusgewertet,...) sparen. Meine Idee wäre 
folgende:

1. In timer0 oder INT0 Interrupt die Anzahl der Flanken zählen (N++).
2. In timer1 Interrupt die Anzahl der Überläufe zählen
3. Alle N timer0 Interrupts dann die Durchschnittsfrequenz über die 
letzten N/2 vollen Takte berechnen (T=((überläufe*timer1_max)+timer1)/N 
und die Anzahl der timer1 Überläufe zurücksetzen.

So kannst du N so lange erhöhen bist du mit der Frequenzberechnung 
schnell genug bist, sie innerhalb von N/2 vollen Signaltakten 
abzuarbeiten.

Ich hätte auch noch eine Idee, wie du timer0 sparen kannst, indem du:

1. den timer 0 interrupt durch den compare match interrupt des timer1 
ersetzt
2. den compare Wert des timer1 in der INT0 service Routine auf
  ( aktuellen timer Wert + 1ms ) mod timer1_max
setzt.

von Gastofatz (Gast)


Lesenswert?

>Zur Not wäre ein Polling des Pins...

Manchmal frag ich mich, ob die Leute es als unanständig ansehen, einen 
Taster (oder Reedkontakt) schlicht in festen, geeigneten, von einem 
einzigen Hardwaretimer erzeugten Zeitintervallen abzufragen und zu 
entprellen. Als würde es Taster irgendwie adeln, wenn man sie an 
externen Interrupts betreibt (dem µC-Feature mit der magischen 
Anziehungskraft) und mindestens zwei bis drei Hardwaretimer mit der 
Entprellung beschäftigt. Das immer gleiche Ergebnis kann man dann in 
diesem oder 84 anderen Threads nachlesen.

von Thomas Schattat (Gast)


Lesenswert?

Hi,
habe selbst schon massig Erfahrung mit Reedkontakt und Interrupt und 
kann aus der Praxis bestätigen dass die ganze Softwareentprellerei 
zumindest in den Fällen wo ich das mache, unnötig ist. Schaltung:

4,7K Widerstand von 5V an den Reed, Reed mit 100nF Kondensator parallel 
gegen Masse. Heißes Ende des Reed an den Interrupt, dieser feuert bei 
fallender Flanke.
Hab damit schon diverse Motorradtachos gebaut die tausende Kilometer 
ohne Problem gezählt haben. Geschwindigkeitsmessung klappt auch 
wunderbar. Beim Oszilografieren entsteht eine messerscharfe fallende 
Flanke gefolgt von einer recht steilen Aufladekurve. Prellen ade!! Ist 
aber möglicherweise nicht bei schnelleren Drehzahlen realisierbar (evtl. 
tüfteln). Funktioniert bei einem 21 Zoll Vorderrad bis über 150 Km/h.

Gruß
Thomas

von Stefan H. (speed2win)


Lesenswert?

Hallo,

also die Frequenz ist in der Tat ziemlich gering so max. an die 40Hz, 
eher geringer. Die 40Hz treten nur selten als Maximalwert auf.
Das mit dem Prellen/Signalverlauf ist, sowie du es schreibst/skizziert 
hast
@Januar2010,
Du hast recht mit dem Timer0 Interrupt, hier hatte ich einen Denkfehler.
Also habe ich nun in die Timer0 ISR direkt anfang den Timer0 deaktiviert
1
TIMSK &= ~(1 << TOIE0);              /*Timer0 (8-bit) Interrupt wird gesperrt, sodass kein Timer0-Interrupt auftreten kann, wird immer 
2
                            erst wieder aktiviert, durch externes Interrupt an INT0*/

Und aktiviere diesen jeweils wieder in den einzelnen Verzweigungen des 
externen Interrupts.
Nur jetzt macht das Programm folgendes:
Die LED blinkt nur einmal, d.h. wird einmal angeschaltet und einmal 
abgeschaltet beim Drüberziehen des Magneten über den Reedkontakt, danach 
reagiert die LED nicht mehr und blinkt nicht mehr.
Daraus folgerte ich, dass die Timer0 ISR irgendwie nicht mehr aufgerufen 
wird (da nur diese ja die LED's schaltet). Dies kann nur geschehen, 
wenn:
1
if (MessungAusgewertet){
FALSE ist und er in den else-Zweig springt, wo nur die Anweisung 
"return;" steht. Also geschaut, wann MessungAusgewertet=FALSE wird, und 
das nur, wenn im Timer0 ISR der Zähler "Messung_vollstaendig" 2x erhöht 
wurde.
Testweise habe ich den Zähler dann auf 10 gesetzt und siehe da, die LED 
blinkt 5x.
Aber die MessungAusgewertet wird doch im Hauptprogramm wieder direkt auf 
TRUE gesetzt, wenn es ausgewertet wird?!?
Außerdem was seltsam ist, dass wenn der Zähler Messung_vollstaendig auf 
2 steht, leuchtet die LED nur 1x auf. Irgendwie scheint es mir, als 
prellt da doch noch was, aber wieso?
Anbei nochmal das Programm mit dem Timer0 Änderung.
http://ebay321.pytalhost.com/frequenz1.c

von Stefan H. (speed2win)


Angehängte Dateien:

Lesenswert?

@Thomas Schattat
Meinst du das Entprellen so, wie ich es im Anhang als Schaltplan 
eingefügt habe? Reed1 nud Reed2 stellen die Kontakte des Reeds dar.
Hast du die internen Pullups dabei aktiviert?
Wenn es dir nichts ausmacht und du das willst, wäre ich dankbar, wenn du 
mir ein Programm mal zum Vergleich bereitstellen würdest, gerne auch per 
e-mail: merryj@gmx.de

Danke.

von Ralph (Gast)


Lesenswert?

Nimm anstatt Magnet mit REED Kontakt, einen Manget mit Hall Element mit 
nachgeschaltetem Schmitttrigger mit Hysterese.
Damit entfallen alle Prelleffekte die ein mechanischer Kontakt immer 
hat.

Sowas erleichtert die Arbeit ungemein.

von oldmax (Gast)


Lesenswert?

Hi
Also, die Schaltung in der Skizze wird so nicht funktionieren.

Vcc -------
      !
     ---
     ! !
     ! ! R
     ---
      !
      !---- Port x
  S   \  !
      ! ---
      ! --- C
      !  !
Gnd -------
Funktion:
S offen  wird über R geladen, Port x erhält irgendwann einmal ~Vcc
S geschlossen, C wird entladen und Prellsignale werden kurzgeschlossen.
So sollte es in der Regel funktionieren. Bei max. 40 Hz würd ich aber 
die Eingänge pollen, einen Grund für eine IO -ISR sehe ich nicht und 
macht nur unnötig das Leben schwer.
Gruß oldmax

von Thomas Schattat (Gast)


Lesenswert?

Hi,
bin gerade beruflich im Ausland. Kannste aber hier finden:

Beitrag "Standard LCD mit Controller steuern"

Hatte da mein Programm mal für solche wie dich hinterlegt, aber da war 
das Augenmerk auf die LCD Steuerung gerichtet.
Hoffe es hilft.
Ach so: Die Schaltung im Bild oben ist genau so wie ich es meine.
Der interne Pullup ist ein, (wie groß ist der eigentlich so??).
Kannst nach mir gugeln, dann findeste auch Fotos und so...
Viel Erfolg,
Gruß
Thomas

von Januar2010 (Gast)


Lesenswert?

Gastofatz schrieb:
> Manchmal frag ich mich, ob die Leute es als unanständig ansehen, einen
> Taster (oder Reedkontakt) schlicht in festen, geeigneten, von einem
> einzigen Hardwaretimer erzeugten Zeitintervallen abzufragen und zu
> entprellen.

Die Frage ist ob das "zu entprellen" hier unbedingt einfacher ist. Man 
kann aber auch mit einem externen Interrupt basierten Ansatz und einem 
Timer auskommen. Man kann das so programmieren, dass man dann pro 
Schaltflanke nur 2 ISR abarbeiten muss. Ich weiß nicht genau mit welcher 
Frequenz man Timer-Interrupt gesteuert pollen muß.

Ich würde auch nicht den externen Beschaltungsaufwand  erhöhen um die 
Software etwas einfacher zu machen. Bei 40Hz ist eine Softwarelösung 
sicher billiger und flexibler als teurere "Hardware".

Stefan, ich kann leider den Link zu deinem Sourcecode nicht mehr öffnen. 
Versuche doch mal im Hauptprogramm zu Testzwecken nur

while(1)
{
   MessungAusgewertet = TRUE;
}

Dann hast du MessungAusgewertet als einzige Variable auch nicht als 
volatile deklariert. Versuche das auch nochmal.

Viele Gruesse,

von Thomas Schattat (Gast)


Lesenswert?

Hallo nochmal,
erstmal sehe ich den Unterschied nicht zwischen der Schaltung A und B, 
die sind doch gleich nach meinem Verständnis. Wieso die erste nicht 
funktionieren soll ist mir unklar, ich hab das so schon wiederholt 
erfolgreich gemacht.
Der Grund für den IRQ und kein Polling ist, dass der Prozessor wegen 
Stromsparen dauernd pennt und immer nur für den Timer oder den 
Reedkontakt aufwacht.
Jaja, das geht auch mit Polling, ich weiß. Da ich auch die 
Geschwindigkeit messen will ist es auch erwünscht, den Kontakt SOFORT zu 
erkennen.

Sodann: Wenn ich 40 Hz habe so dauert eine Umdrehung also 25ms. Der Reed 
schließt nur weniger als 10% der Umdrehung, den Rest braucht der Magnet 
um herumzufahren. Ich muss also dann mindestens jede ms pollen, in 
meiner Schaltung pennt der Prozessor aber ganze 16ms ausser wenn der IRQ 
feuert.

Ein Kontakt MUSS sicherlich nicht per IRQ eingelesen werden, man sollte 
es aber auch nicht als total daneben abtun. Ist sicherlich eine Frage 
der Philosophie.

Warum hab ich keinen Hallsensor genommen: Weniger ist manchmal mehr. Der 
braucht wieder Stromversorgung (meine Batterien halten so etwa drei 
Monate) und er braucht drei statt zwei Drähte. Reed ist eben einfacher 
und billiger.
KISS (Keep it simpel stupid...)

Gruss und schönen Sonntag

Thomas

von Jobst M. (jobstens-de)


Lesenswert?

Thomas Schattat schrieb:
> Wieso die erste nicht
> funktionieren soll ist mir unklar

Weil der Portpin direkt an Masse liegt ...


Gruß

Jobst

von Thomas Schattat (Gast)


Lesenswert?

Stimmt, jetzt seh ich es auch.

von Stefan H. (speed2win)


Lesenswert?

Hallo,

ich wollte mich auch mal zurückmelden. Das Programm läuft jetzt.
Es lag tatsächlich an dem blöden Fehler mit dem volatile (arrgh).
Im Moment entprelle ich den Reed nach der obigen Schaltung von oldmax, 
habe die Softwareentprellung mit dem Timer noch drin.
Morgen probiere ich mal nur mit Software und mal nur mit Hardware zu 
entprellen. Je nachdem, ob ich am Ende noch einen Timer übrig habe oder 
einem noch die Entprellungsaufgabe zuschreiben kann, wird dann ggf. per 
Software entprellt, ansonsten per Hardware.
Vielen Dank an alle für die Unterstützung.

von Stefan H. (speed2win)


Lesenswert?

Hallo Thomas,

eine Frage bleibt mir jedoch noch:
In deinem Beispielprogramm von oben schreibst du, dass du mit 1µF (ich 
denke Elko) entprellst (+4k7).
Hier in dem Beitrag hast du geschrieben 100nF.
Was ist sinnvoller oder geht beides problemlos?
Den internen Pullup aktivierst du ja trotzdem an dem INT-Pin, so wie ich 
dem Beispielprogramm entnehmen kann, richtig?

Danke

von Thomas Schattat (Gast)


Lesenswert?

Hallo Sefan,
das hat sich historisch entwickelt, die ersten Versuche waren eher 
"konservativ", also lieber mal mehr entprellen. Es stellte sich heraus 
dass 100nF zum Schluss ausreichend waren.

Gruss

Thomas

von Thomas Schattat (Gast)


Lesenswert?

Und noch: Ich betreibe es mit dem internen Pullup und 4,7K parallel. Zum 
Stromsparen schalte ich den 4,7K ab wenn das Ding abgeschaltet wird. Der 
interne reicht aus um den wieder aufzuwecken, dann schalte ich den 
externen 4,7K wieder zu.
Das Ding ist batteriebetrieben, das muss man schon auf den 
Stromverbrauch achten.

Alles klar?

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.