Ich versuche gerade einen Impulszähler mit einem ATmega16 zu bauen. Er soll die Impulse an einem Reedkontakt zählen und auf einem LCD die Gesamtanzahl der Impulse und die Impulse pro Sekunde ausgeben. Mein Problem ist, dass ich nicht weiß, wie ich die einzelnen Schritte zeitlich einplanen soll. Gedacht habe ich mir das so: LCD initialisieren Timer und Interrupts initialisieren Auf Interrupts warten: Bei Timerüberlauf: { Impulse zwischen zwei Interrupts durch Zeit zwischen zwei Überläufen dividieren und als Impulse/Sekunde auf LCD ausgeben Gesamtanzahl der Impulse auf LCD ausgeben } Bei externem Interrupt (ausgelöst durch Reedkontakt): { Gesamtanzahl der Impulse erhöhen Anzahl der Impulse seit dem letzten Timerüberlauf erhöhen Warteschleife 10ms (wegen Schalterprellen) } Allerdings gefällt mir das ganze nicht wirklich, da ich die LCD-Ausgabe in die ISR eingebaut habe und ich auch nicht weiß was passiert, wenn diese durch die zweite ISR unterbrochen wird. Außerdem hält die Zeitschleife gegen das Schalterprellen den Timer auf. Wie würde man das besser einplanen? Erfahrung mit Programmieren habe ich eigentlich nicht, das ganze ist recht neu für mich. Die Ansteuerung des LCDs funktioniert jedoch ganz gut (mit dem Code aus dem GCC-Tutorial).
Hallo zähler, Du solltest unbedingt die Anzeige und die Erfassung trennen. Typischerweise macht man das für einfache Programme so, dass man in main() in einem festen Zeitraster, beispielsweise alle 200ms das Display aktualisiert. Die Kopplung zwischen der "main()"-Ebene und der Interruptebene geschieht über statische globale Variablen mit dem Attribut volatile, deren Zugriff aus main() heraus über "ATOMIC_BLOCK" geschützt werden muß, damit der Inhalt für einen Durchlauf konsistent ist (jedenfalls bei Werten die größer als 1 Byte sind), da es passieren kann, dass die ISR den Wert zwischem dem Lesen des LSB und des MSB aktualisiert. Schlimmstenfalls passiert das beim Übergang 0x1FF -> 0x200. In Main kommt dann kurzzeitig gerne auch mal 0x2FF an. Beispiel:
1 | static volatile uint16_t isr_var; /* written within ISR, read from main */ |
2 | |
3 | main() |
4 | {
|
5 | while (1) { |
6 | uint16_t local_copy_var; |
7 | |
8 | /* get values from ISR */
|
9 | ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { |
10 | local_copy_var = isr_var; |
11 | }
|
12 | |
13 | /* processing (and display) of local_copy_var */
|
14 | |
15 | delay_ms(200); |
16 | }
|
17 | }
|
18 | |
19 | ISR(TIMER1_WHATEVER_vect) { |
20 | isr_var++; |
21 | }
|
Desweiteren sind Delays in ISRs wie Du ja auch andeutest kritisch zu sehen. Wenn die Erfassung so hochgenau sein muß, dass sie in einen Interrupt muß, dann solltest Du über Hardware-Capture nachdenken, bei dem der Wert eines freilaufenden Zählers beim Eintreten eines Hardware-Ereignisses (beispielsweise Reedkontakt) in ein Register kopiert wird und der Interrupt aufgerufen wird. In der ISR kann dann unabhängig von Latenzen bis zur Ausführung der ISR die Zeit bearbeitet werden. Prellen kannst Du so beispielsweise dadurch erkennen, dass zu wenig Zeit zwischen zwei ISR-Aufrufen vergangen ist. Erst wenn die letzte ISR lange genug her war, wird der Interrupt wieder als gültiges Zählereignis gewertet.
1 | ISR(TIMER1_WHATEVER_vect) { |
2 | static uint32_t timestamp_last = 0; |
3 | |
4 | /* debounce */
|
5 | if ((CURRENT_TIME - timestamp_last) > MIN_DIST) { |
6 | isr_var++; |
7 | }
|
8 | timestamp_last = CURRENT_TIME; |
9 | }
|
Wenn das Prellen sehr stark und lange sein kann, dann kann das den Programmablauf stören, da immer wieder die ISR laufen muß und zu wenig Zeit für andere wichtige Aufgaben bleibt. In diesem Fall kann man auch darüber nachdenken, in der ISR den eigenen Interrupt zu sperren und über einen zweiten Timer nach Ablauf der erwarteten Prelldauer wieder aktivieren zu lassen. Gruß, Bernd
> Bei externem Interrupt (ausgelöst durch Reedkontakt): Das ist Käse. Ein Interrupt auf einem mechanischen Kontakt wird dir niemals Freude bereiten. Das gibt garantiert ein Gemurkse... Ich würde in der beschriebenen Anwendung genau 1 Interrupt initialisieren: den Timerinterrupt auf 1ms. Da rein eine brauchbare Entprellroutine (z.B. die kampferprobte von Peter Dannegger: http://www.mikrocontroller.net/search?query=Dannegger&forums[]=1&forums[]=19&forums[]=9&forums[]=10&forums[]=2&forums[]=4&forums[]=3&forums[]=6&forums[]=17&forums[]=11&forums[]=8&forums[]=14&forums[]=12&forums[]=7&forums[]=5&forums[]=18&forums[]=15&forums[]=13&forums[]=16&max_age=-&sort_by_date=0). Und an die Entprellung dann den Zähler angeschlossen und fertig. Die komplette Displaygeschichte läuft in der Hauptschleife (aka mainloop). Die Entprellerei und Zählerei in der Interruptroutine. So läuft das bombensicher.
>Ich würde in der beschriebenen Anwendung genau 1 Interrupt >initialisieren: den Timerinterrupt auf 1ms. Da rein eine brauchbare >Entprellroutine (z.B. die kampferprobte von Peter Dannegger: >http://www.mikrocontroller.net/search?query=Danneg...). >Und an die Entprellung dann den Zähler angeschlossen und fertig. Ich verstehe nicht wirklich, was die Entprellroutine in der ISR des Timers tun soll. Der Taster kann ja zu jedem beliebigen Zeitpunkt gedrückt werden.
> Der Taster kann ja zu jedem beliebigen Zeitpunkt gedrückt werden. Ja, aber er wird verglichen mit dem Timerinterrupt sehr langsam gedrückt. Oder kannst du den Taster 500 mal pro Sekunde drücken und loslassen? > Ich verstehe nicht wirklich, was die Entprellroutine in der ISR des > Timers tun soll. Sie fragt im Millisekundentakt den Zustand des Tasters ab. Das nennt man Pollen. Polling ist idR. einfacher und deterministischer umzusetzen, als so ein Pin-Change-Interrupt, der dir irgendwann irgendwo in den Programmablauf reinfunken kann.
zähler schrieb: > Ich verstehe nicht wirklich, was die Entprellroutine in der ISR des > Timers tun soll. Der Taster kann ja zu jedem beliebigen Zeitpunkt > gedrückt werden. Solange nur die ISR um einiges öfter aufgerufen wird, als Zeit zwischen 2 Drücken passiert, spielt das keine Rolle. Wenn du dir eine Badewanne einlässt, bleibst du ja auch nicht daneben sitzen um nur ja nicht das Vollwerden zu verpassen. Wenn du alle 2 bis 3 Minuten nachsiehst, ob die Wanne schon voll ist, reicht das locker.
> Lieber einen Optokoppler als ein Reedrelais verwenden.
Und wenn man sich das nicht raussuchen kann? :-/
Vielleicht sollte man erst mal am Erfassungspunkt anfangen. Was wird da gezählt? Warum muss es ein Reed-Kontakt sein? Gibt es Möglichkeiten diesen Kontakt durch etwas anderes zu ersetzen? Mit wievielen Pulsen in welchem zeitlichen Abstand ist zu rechnen? Ansonsten: Wenn das Prellen des Kontaktes eindeutig ausfilterbar ist und die Reed-Kontakte nicht verhandelbar sind, hat Lothar ja schon gesagt, wie man das softwaremässig aufzieht.
Um erst einmal Land zu sehen, würde ich vorschlagen, den Reedkontakt durch ein RC-Glied zu entprellen. Die ATmega haben eine kleine Hysterese an den Eingängen, sodass die Impulse richtig erkannt werden können. Wenn dann die Programmiererfahrung zunimmt und die Ansprüche steigen, ist es kein Problem, die Entprellung per Software zu ergänzen. Aber zunächst ist ein Erfolgserlebnis wichtig!
Ok das werde ich machen. Außerdem ist mir eingefallen, dass ich auch noch verhindern muss, dass ein Impuls mehrfach gezählt wird, weil der Reedkontakt geschlossen bleibt, also eine Flankenerkennung. Verwendet werden soll das ganze an einem Windrad (Umdrehungen zählen). Wirklich sinnvol muss es nicht sein, es dient eher zum Programmiererfahrung sammeln. Zum RC-Glied: Der Reedkontakt soll Active-High geschaltet sein, R=22KOhm und C=1mikroF müssten zum Entprellen doch hinkommen?
>Zum RC-Glied: Der Reedkontakt soll Active-High geschaltet sein, R=22KOhm >und C=1mikroF müssten zum Entprellen doch hinkommen? Kannste machen, weil es ja sowieso nicht sinnvoll sein soll.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.