Forum: Mikrocontroller und Digitale Elektronik ATmega16, Timer + Interrupts = Probleme


von Andy (Gast)


Lesenswert?

Hallo zusammen.

Ich hab hier ein Problem, dass ich einfach nicht gelöst bekomme. Ich 
habe an einem ATmega16 an den beiden Interrupt-Eingängen 2 Taster 
hängen. Mit dem einen soll eine gewissen anzahl an Wiederholungen 
festgelegt werden, mit dem anderen ein Ablauf gestartet werde (versteht 
man später besser).
Anders umschrieben, wenn ich 5 mal auf den einen Taster drücke, der an 
INT1 hängt, dann soll in meinem Programm ein counter um 5 hochgezählt 
werden. Drücke ich dann auf den Taster an INT2, soll z.B. eine LED 5 mal 
blinken.

Das ist ja auch alles nicht so schwer, ich bekomm es aber nicht hin, den 
Taster zu entprellen. Hab den Timer und die INT initiallisiert, es 
funktioniert auch alles. Also sowohl Timer geht, als auch die 
Interrupts.


#include <avr/io.h>
#include <avr/delay.h>
#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/signal.h>


volatile counter = 0;
volatile time = 0;
volatile start = 0;

Hiermit initiallisiere ich mein Timer:

void timer_init(void){
  TIMSK=0x01;  //Timer/Counter Interrupt Mask
  TCNT0=0x64;  //255-Registerinhalt:=100
  TCCR0=0x03;  //Timer/Counter Control Register auf CK/64
  sei();      //All Interrupt enable
}

Hier wird beim Überlauf die Variable counter um 1 hochgezählt.
Der ATMega läuft mit 1Mhz, => 100 überläufe ~ 1sec

SIGNAL(SIG_OVERFLOW0)  //IR-Handler für Counter
{
  TCNT0=0x64;  //255-Registerinhalt:=100
  counter++;  //eine Variable um 1 hochzählen
}

Hier initialisiere ich die Interrupts, wie es im acr_gcc tutorial steht.

void interrupt_init(void){
  MCUCR = 0xF;//(1<<ISC11) | (1<<ISC10);
  GICR = (1<<INT0) | (1<<INT1);

}

Wird Button A gedrückt, wird eine globale Var start auf = 1 gesetzt.

SIGNAL(SIG_INTERRUPT0)// signal handler for tcnt0 overflow interrupt
  {
  start = 1;
  }

Hier werden die Tastendrücke von Taster B gezählt.

SIGNAL(SIG_INTERRUPT1)// signal handler for tcnt0 overflow interrupt
  {
  time++;
  wait_a_little();
  }

Meine warte-funktion:

void wait_a_little(void){
  counter = 0;
    TCNT0=0x64;  //255-Registerinhalt:=100
  while(counter <= 50){
  //wait
  }
}

//hauptprogramm...

So weit so gut. Warum funktioniert das nicht? Wenn ich Taster B drücke, 
dann wird die var timer um 1 hoch gezählt und dann sollte das Programm 
in die Funktion wait_a_litte() springen. Macht sie auch, aber da 
funktioniert mein Timer dann nicht. Warum nicht???

Wenn ich im Hauptprogramm die Funktion aufrufe, dann funktioniert der 
Timer. Warum kann ich in einer 'Interrupt-Funktion' nicht einfach auf 
den Timer-Interrupt zugreifen?

Wenn ich folgendes mach, funktioniert das auch nicht:

SIGNAL(SIG_INTERRUPT1)// signal handler for tcnt0 overflow interrupt
  {
  time++;
  counter = 0;
        while ( counter <= 50 ){ //halbe sec warten
             //wait
              }
  }

Kann mir jm. sagen, wie ich die Taster mit dem Timer entprellen kann?

Hoffe, ihr versteht mein Problem!

Danke für eure Hilfe!

mfg Andreas

von Michael U. (Gast)


Lesenswert?

Hallo,

Dein ganzer Ansatz ist sehr ungünstig.

Grundregel 1: Interruptroutinen so kurz wie möglich.
Warum? Weil ein Interrupt den normalen Programmlauf unterbricht und das 
sollte nur für tatsächlich zeitkritische Sachen benutzt werden.

Das heißt letztlich, im IRQ nur festhalten, was nötig ist, und dann im 
Hauptprogramm diese Ereignisse bearbeiten.

Grundregel 2: folgt aus Grundregel 1 -> keine Warteschleifen im IRQ.

Zu Warteschleifen allgemein: wenn sie so kurz sind, daß die Benutzung 
des Timers sich dafür nicht lohnt -> ok.

Kurz sind wenige Taktzyklen.

Grundsätzlich: laut Datenblatt des AVR werden bei Aufrauf einer ISR die 
Interrupts generell gesperrt und mit RETI beim Verlassen wieder 
freigegeben.
Damit werden antürlich auch Deine Timer-IRQs nicht mehr bearbeitet...

Prinzipiell kann man in einer ISR die Interrupts natürlich geziehlt 
wieder freigeben, ob das in C ein so guter Weg ist, bezweifle ich 
allerdings.

Also Tasten in der ISR nur merken und dann im Hauptprogramm über 
Entprellen und Auswertung nachdenken.

Oder Tasten im Timerinterrupt abfragen und entprellen.

Da gibt es etliche Beispiele und fertige Routinen auch in der 
Codesammlung.

Gruß aus Berlin
Michael


von Peter (Gast)


Lesenswert?

Hi,

@Michael: die Grundregeln stimmen in den meisten Fällen, doch sind sie 
kein "Muss". Für ein einfaches System, das aus nicht mehr als den 
Interrupts besteht, gelten diese sicher nicht.

@Andreas: mein sehr einfacher Ansatz zum Entprellen funktioniert so: 
Taster werden zyklisch gepollt (z.B. alle 1 oder 5ms). Die Poll-Periode 
liegt deutlich über der Prellzeit eines Tasters, sodass das Prellen kein 
Problem mehr darstellt.
Je nachdem, was du machen möchtest, musst du in der Routine warten, bis 
der Taster wieder losgelassen wird, also die fallende Flanke pollen.
Funktioniert bei mir in etlichen Anwendungen mit minimalem Code- und 
Laufzeitbedarf problemlos.

Ciao,
Peter

von Michael U. (Gast)


Lesenswert?

Hallo,

@Peter:

solche "Regeln" sind nie ein "Muss", sollte auch nicht so verstanden 
werden.
Sie verhelfen meiner Meinung nach aber immer dazu, darüber nachzudenken, 
was passiert, wenn man diese "Regeln" "übertritt". :)

Wenn man Interrupts intensiv benutzt, sollte man genau wissen, was die 
konkrete CPU da macht. Man sollte auch genau wissen, was das eigene 
Programm machen soll.

Die Frage, weshalb sein Timer-IRQ nicht ordentlich bearbeitet wird, wenn 
er innerhalb der Timer-ISR eine halbe Sekunden Busy-Loop drin hat, 
deutet zumindest darauf hin, daß er genau das eben noch nicht weiß.

Deshalb sollte er sich damit befassen. ;)

Ich schreibe durchaus auch ASM-Programme mit mehreren IRQ, auch auf AVR, 
wo das Hauptprogramm nur aus
loop: rjmp loop
besteht.

Dann muß ich aber schon wissen, was die Routinen machen und wie lange 
sie maximal brauchen.
Würde ich trotzdem nicht unbedingt weiterempfehlen...


Gruß aus Berlin
Michael



von Andy (Gast)


Lesenswert?

Vielen Dank für eure schnelle Hilfe! :)
Ich hab das jetzt, wie vorgeschlagen, im Hauptprogramm gelöst, 
funktionier sogar wunderbar. Hab nicht gewusst, dass ich quasi keine 2 
IRQ benutzen kann.

Kann mir jetzt vielleicht noch jm. sagen, wie ich die IRQ's wo die 
beiden Taster dran hängen, deaktivieren kann, wenn ich sie nicht mehr 
brauch? Damit nix passiert, wenn ich auf einen der beiden Taster drücke. 
Der Timer muss aber weiter laufen. Geht das? Und wie kann ich die wieder 
aktivieren? Mit meiner interrupt_init() einfach wieder?

Wäre nett, wenn mir das noch jm. sagen könnte!

Vielen dank noch mal an euch!

Gruß

Andreas

von Peter (Gast)


Lesenswert?

@Michael: alles klar, da haben wir das gleiche Verständnis.

@Andreas:
Aktivieren:
  GICR |= (1<<INT0) | (1<<INT1); /* enable INT0 and INT1 */

Deaktivieren:
  GICR &= ~((1<<INT0) | (1<<INT1)); /* disable INT0 and INT1 */

Ciao,
Peter

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.