Forum: Mikrocontroller und Digitale Elektronik External Interrupt INT0 Problem


von Steve-o B. (steve_o)


Lesenswert?

Hallo Leute,

habe mal eine Frage bezüglich dem externen Interrupt INT0.

Ich habe den Interrupt so eingestellt, dass er bei einem Falling-Edge 
ausgelöst wird.

Hierzu habe ich einen Taster an den PIND2 meines ATmega328P 
angeschlossen, der wiederrum mit GND verbunden ist. Zudem habe ich den 
internen Pull-Up-Widerstand an PIND2 aktiviert und den PIN als INPUT 
definiert.

Nun zu meinem Problem:

Sobald ich das Programm (siehe unten) starte und die Funktion 
Initialize_INT0 aufgerufen wurde, wird immer (ohne Ausnahme) ein 
Interrupt ausgelöst. Das geschieht nur 1x am Anfang! Ich rühre den 
Taster in diesem Zeitraum nicht an!

Könnt Ihr mir sagen, was ich da flasch gemacht habe?

Ich programmiere übrigens mit Atmel Studio 6 / C++.


Hier mein Programm:



*********************Programm*******************************


#ifndef F_CPU
#define F_CPU 16000000
#endif

#include <avr/io.h>
#include <avr/interrupt.h>


void Initialize_USART ()
{
  #define BAUD 67800
  #include <util/setbaud.h>

  UBRR0H = UBRRH_VALUE;//set Baud-Rate in UARTBaudRateRegister (UBBR)
  UBRR0L = UBRRL_VALUE;

  #if USE_2X
  UCSR0A |= (1 << U2X0);
  #else
  UCSR0A &=  ~(1 << U2X0);
  #endif

  UCSR0B |= (1 << RXEN0) | (1 << TXEN0);//activate Receive & Transmit
}


void Initialize_TIMER0 ()
{
  TCCR0A |= (1 << WGM01);  //activate TIMER0 CTC-Mode
  TIMSK0 |= (1 << OCIE0A);//activate interrrupt if compare value is 
reached
  OCR0A |= 249;  //set compare value
  TCCR0B |= (1 << CS01) | (1 << CS00);//start TIMER0 Prescaler 64
}


void Initialize_INT0 ()
{
  EICRA = 0x02;   //INT0 is triggered by falling edge
  EIMSK |= (1 << INT0);  //activate INT0
}


int main (void)
{

  DDRD = 0xFF;  //all PINS are Output
  PORTD = 0x00;  //all Outputs are LOW

  DDRD &= ~(1 << PIND2);  //PIND2 is Input
  PORTD |= (1 << PIND2);  //activate internal pull-up of PIND2 (INT0)

  DDRB = 0xFF;  //all PINS are Outputs
  PORTB = 0x00;  //all Outputs are LOW


  Initialize_USART();  //initializes USART

  sei();  //activates global interrupts

  Initialize_TIMER0();  //initializes TIMER0

  Initialize_INT0();  //initializes INT0

  while(1)
  {
    //do something
  }

  return 0; //never reached
}


ISR (INT0_vect)
{
  //do something if button was pressed
  //ie: toggle LED
}

ISR (TIMER0_COMPA_vect)
{
  //count time and debounce button
}


*********************Programm_ENDE******************************


Vielen Dank für Eure Hilfe,

Steve

von Steve-o B. (steve_o)


Lesenswert?

Hat keiner eine Idee, Vorschläge, Anschiss oder ähnliches zu posten?
Bin für alles dankbar, solange es mich hier weiter bringt!

Greets,

Steve

von Cyblord -. (cyblord)


Lesenswert?

Und wie überprüfst du ob ein Interrupt ausgelöst wird? Da steht nichts 
drinn. Bitte kompletten Code posten, es könnte ja auch daran liegen.
An sich seh ich keinen Fehler mit dem Interrupt Zeug.
Ein einfaches Toggeln ist nicht gut, weil durch das Prellen mehrfache 
Interrupts ausgelöst werden und du dann evt. gar nicht siehst ob die LED 
toggelt oder nicht. Quick & Dirty: Mach ein delay in die ISR nach dem 
Toggeln der LED. Damit siehst du wenigstens was passiert. Schön ist aber 
anders, und ein Taster gehört eben deshalb nicht an einen Interrupt. 
Weil er prellt.

Es kann sein dass bei Aktivierung von sei, bereits ein Interrupt Flag 
gesetzt war, und somit die ISR sofort angesprungen wird. Daher wird 
erstmal ein Interrupt ausgelöst.

Funktioniert die HW? Hast du mal mit nem Multimeter am Port gemessen? 
Hat der Hi-Pegel im Ruhezustand und geht er auf Lo-Pegel wenn du den 
Taster drückst?

gruß cyblord

von Steve-o B. (steve_o)


Lesenswert?

Hallo cyblord,

hier mal der Code, mit mehr Inhalt! Ich hoffe das Hilft!

Ich möchte eine Stoppuhr programmieren, die auf die Millisekunde genau 
Reaktionszeiten misst.

Also: LED:AN -> Taster:Drücken
Zeit zwischen LED:AN und Taster:Drücken an PC schicken.

Deshalb dachte ich mir ich lege den Taster an einen Interrupt, um die 
Verzögerung mit Sicherheit zu minimieren.
Hab das ganze auch mit einem Oszi überprüft, der PIN am Taster geht auf 
LOW wenn er gedrückt wird und auf HIGH wenn ich ihn loslasse.

Wenn das allerdings auch ohne Taster am Interrupt geht dann les ich mir 
gerne was dazu durch, wenn du einen Link hast.



*********************Programm*******************************


#ifndef F_CPU
#define F_CPU 16000000
#endif

#include <avr/io.h>
#include <avr/interrupt.h>

//hoffe ich habe keine globalen vars vergessen

volatile uint16_t ms;

volatile uint16_t ms_Button_Click;

volatile uint8_t iDebounce;

volatile char cDebounceFlag;

volatile char cSendFlag;


void Initialize_USART ()
{
  #define BAUD 67800
  #include <util/setbaud.h>

  UBRR0H = UBRRH_VALUE;  //set Baud-Rate in UART Baud Rate Register 
(UBBR)
  UBRR0L = UBRRL_VALUE;

  #if USE_2X
  UCSR0A |= (1 << U2X0);
  #else
  UCSR0A &=  ~(1 << U2X0);
  #endif

  UCSR0B |= (1 << RXEN0) | (1 << TXEN0);  //activate Receive & Transmit
}

uint8_t Receive_Character ()
{
  while (!(UCSR0A & (1 << RXC0)))  //wait while data register is full
  {}
  return UDR0;
}

void Send_Single_Char (unsigned char cSingle)
{
  while(!(UCSR0A & (1 << UDRE0)))  //is UART register ready to receive 
value
  {}

  UDR0 = cSingle;
}

void Send_Time (char* cWord)
{
  while(*cWord)  //as long as cWord[i] is != "\0"
  {
    Send_Single_Char(*cWord); //send cWord[i]
    cWord++;//i++
  }
}


void Initialize_TIMER0 ()
{
  TCCR0A |= (1 << WGM01);  //activate TIMER0 CTC-Mode
  TIMSK0 |= (1 << OCIE0A);//activate interrrupt if compare value is 
reached
  OCR0A |= 249;  //set compare value
  TCCR0B |= (1 << CS01) | (1 << CS00);//start TIMER0 Prescaler 64
}


void Initialize_INT0 ()
{
  EICRA = 0x02;   //INT0 is triggered by falling edge
  EIMSK |= (1 << INT0);  //activate INT0
}


int main (void)
{
  char cTransmit[7];
        char cStart;

  DDRD = 0xFF;  //all PINS are Output
  PORTD = 0x00;  //all Outputs are LOW

  DDRD &= ~(1 << PIND2);  //PIND2 is Input
  PORTD |= (1 << PIND2);  //activate internal pull-up of PIND2 (INT0)

  DDRB = 0xFF;  //all PINS are Outputs
  PORTB = 0x00;  //all Outputs are LOW


  Initialize_USART();  //initializes USART


  while(cStart != 1)
  {
    cStart = Receive_Character();
  }


  sei();  //activates global interrupts

  Initialize_TIMER0();  //initializes TIMER0

  Initialize_INT0();  //initializes INT0


  while(cStart == 1)
  {
    if(cSendFlag == 1)
    {
      Send_Time("Taster_Click: ");
      Send_Time(utoa(ms_Button_Click, cTransmit, 10));
      cSendFlag = 0;
    }
  }

  return 0; //never reached
}


ISR (INT0_vect)
{
  ms_Button_Click == ms;  //Send actual time

  Send_Time("FirstInterrupt\r\n");  //send text to hterm (quick&dirty) 
nur ein test ob alles funkt

  cSendFlag = 1;
  cDebounceFlag = 1;
  EIMSK &= ~(1 << INT0);
}


ISR (TIMER0_COMPA_vect)
{
  ms++; //zählt die Zeit

  //Hier ist noch einiges falsch, das weiß ich bereits, aber wenn ihr 
denkt, das gehört komplett in den
//Müll dann mach ich gerne auch was ganz anderes

        //debounce button
  if (cDebounceFlag == 1 && !(PIND & (1 << PIND2)) && iDebounce < 20)
  {
    iDebounce ++;
  }

  if (iDebounce >= 20 && (PIND & (1 << PIND2)))
  {
    iDebounce ++;
    if (iDebounce >= 40)
    {
      EIMSK = 0x01;    //activate INT0
      cDebounceFlag = 0;
      iDebounce = 0;
    }
  }
}

von Peter D. (peda)


Lesenswert?

Steve-o Bane schrieb:
> DDRD = 0xFF;  //all PINS are Output
>   PORTD = 0x00;  //all Outputs are LOW
>
>   DDRD &= ~(1 << PIND2);  //PIND2 is Input
>   PORTD |= (1 << PIND2);  //activate internal pull-up of PIND2 (INT0)

Inputs sollte man niemals (auch nicht kurzzeitig) als Ausgang 
definieren. Laß sie Input bleiben.
Das kann schonmal ne Flanke erzeugen, die das Interruptflag setzt.

Wenn man frühere Ereignisse nicht haben will, ist es gute Praxis, vor 
der Freigabe das Flag zu löschen.

Und die Standardantwort: Externe Interrupts nimmt man nicht für Tasten.


Peter

von Karl H. (kbuchegg)


Lesenswert?

Steve-o Bane schrieb:

> Könnt Ihr mir sagen, was ich da flasch gemacht habe?

Da hast eine Annahme getroffen.
Nämlich die, dass Interrupt auslösende Ereignisse erst dann registriert 
werden, wenn du den Interrupt freigibst. Dem ist aber nicht so.

Bei allen Interrups spielen immer 2 Bits zusammen.
Da gibt es ein Bit welches das Ereignis an sich registriert.
Und es gibt ein Bit welches die Bearbeitung dieses Ereignisses freigibt.

Konkret.
Im Register EIFR gibt es das Bit INTF0. Wenn immer die eingestellte 
Interrupt-Bedingung zutrifft, dann wird dieses Bit gesetzt. Die 
eingestellte Bedingung ist beim Mega328, wenn er aus dem Reset kommt: 
Ein Low-Level am INT0-Pin triggert dieses Bit.

Dann gibt es im Register EIMSK das Bit INT0, welches regelt, ob bei 
Vorliegen eines 1-Bits in EIFR/INTF0 die zugehörige ISR angesprungen 
wird. (Natürlich nur wenn zusätzlich auch sei() gemacht wurde). Ist 
dieses Bit gesetzt UND ist EIFR/INTF0 auch gesetzt worden, dann wird die 
ISR ausgeführt und EIFR/INTF0 wieder zurückgesetzt.

Aber: Bei dir IST nach dem Reset EIFR/INTF0 gesetzt! Denn: Der Default 
für ISC01 und ISC00 im EICRA ist: Interrupt Ereignis wird ausgelöst, 
wenn der physikalische Pin auf 0 Pegel ist. Und den Fall hast du nach 
dem Reset: Der Pin ist auf Eingang und da der Taster nichts macht bzw. 
der Pullup noch nicht eingeschaltet ist, hast du 0-Pegel. Der ändert 
sich zwar mit dem Einschalten des Pullup, das macht aber nichts. 
EIFR/INTF0 ist zu diesem Zeitpunkt schon lange gesetzt. Und wenn du dann 
im EIMSK/INT0 freigibst, dann liegt der Fall vor:
   sei()        ist freigegeben
   EIFR/INTF0   ist gesetzt
   EIMSK/INT0   ist freigegeben

und damit wird die ISR aufgerufen. Denn genau das sind die Bedingungen, 
unter denen dieser Aufruf erfolgt.


Ereignisse die Interrupts auslösen können, werden auch dann registriert, 
wenn der Interrupt an sich gar nicht freigegeben ist! Sobald dann die 
Freigabe erfolgt, wird konsequenterweise dann auch die ISR angesprungen.

von Karl H. (kbuchegg)


Lesenswert?

Steve-o Bane schrieb:

> Deshalb dachte ich mir ich lege den Taster an einen Interrupt, um die
> Verzögerung mit Sicherheit zu minimieren.

* Die Verzögerung die du durch deinen Taster hast, liegt 
größenordungsmässig über dem Bereich der Millisekunden.
* Hast du eine Vorstellung davon, wie lang eine Millisekunde für einen
  µC ist? Das ist für den eine halbe Ewigkeit.

von Stefan E. (sternst)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Aber: Bei dir IST nach dem Reset EIFR/INTF0 gesetzt! Denn: Der Default
> für ISC01 und ISC00 im EICRA ist: Interrupt Ereignis wird ausgelöst,
> wenn der physikalische Pin auf 0 Pegel ist. Und den Fall hast du nach
> dem Reset: Der Pin ist auf Eingang und da der Taster nichts macht bzw.
> der Pullup noch nicht eingeschaltet ist, hast du 0-Pegel. Der ändert
> sich zwar mit dem Einschalten des Pullup, das macht aber nichts.
> EIFR/INTF0 ist zu diesem Zeitpunkt schon lange gesetzt.

Allerdings sagt das Datenblatt:
1
•Bit 0 – INTF0: External Interrupt Flag 0
2
...
3
This flag is always cleared when INT0 is configured as a level interrupt.
Da der Level-Interrupt ein Zustands-Interrupt ist, und kein 
Ereignis-Interrupt, wird auch kein Flag zum Festhalten eines Ereignisses 
benötigt.

Also ich sehe bei dem gezeigten Code nicht, wo das gesetzte Flag 
herkommen soll.

von Stefan E. (sternst)


Lesenswert?

Nachtrag:
Was ich mir vorstellen könnte, ist, dass das Umschalten der 
Konfiguration (ISC0x) das Flag setzen kann, so wie es ja auch beim 
Input-Capture-Interrupt ist.

von Steve-o B. (steve_o)


Lesenswert?

Hallo Leute,

danke schon mal für eure Hilfe. Leider haben die Vorschläge bisher 
nichts gebracht...

Ich habe an einen Interrupt gedacht, da ich den Tastendruck unmittelbar 
abfangen möchte.

Da ich allerdings nur auf eine ms genau jeden Tastendruck benötige, 
könnte ich ihn ja auch im TimerInterrupt abfragen, das eh jede ms 
ausgeführt wird. Denkt ihr das funkt dann? So würd ich nämlich ohne den 
Interrupt auskommen?!
Oder gibt es noch bessere Lösungen?

Bei menschlichen Reaktionszeiten um die 200ms sind zB 10 ms Verzögerung 
bei der Zeiterfassung schon ziemlich viel...

Vielen Dank!

Steve

von Steve-o B. (steve_o)


Lesenswert?

Nachtrag:

Merce, hatte Eure antworten noch nicht gesehen, das scheint es gewesen 
zu sein!

Vielen Dank, ich werd´s mir merken!

Jetzt werde ich aber trotzdem mal an einer Version ohne Interrupt INT0 
arbeiten!

Vielen Dank nochma,

Steve

von Thomas E. (thomase)


Angehängte Dateien:

Lesenswert?

Stefan Ernst schrieb:
> Karl Heinz Buchegger schrieb:
>> Aber: Bei dir IST nach dem Reset EIFR/INTF0 gesetzt! Denn: Der Default
>> für ISC01 und ISC00 im EICRA ist: Interrupt Ereignis wird ausgelöst,
>> wenn der physikalische Pin auf 0 Pegel ist.

> Also ich sehe bei dem gezeigten Code nicht, wo das gesetzte Flag
> herkommen soll
Da hat er ausnahmsweise mal Recht.

mfg.

von holger (Gast)


Lesenswert?

>Bei menschlichen Reaktionszeiten um die 200ms sind zB 10 ms Verzögerung
>bei der Zeiterfassung schon ziemlich viel...

Und absoluter Schwachsinn weil nicht reproduzierbar.
Wenn du Zeiten im 1ms Bereich erfassen willst dann nimmst
du halt keinen Taster und keinen Menschen der da draufdrückt.

von Martin (Gast)


Lesenswert?

Danke Peter Dannegger,

damit hast du auch mein Problem gelöst!
Die Externen Interrupts sind einfach zu langsam für Drehgeber.

Gruß Martin

von Charly B. (charly)


Lesenswert?

Martin schrieb:

> Die Externen Interrupts sind einfach zu langsam für Drehgeber.

das haett i aber gerne genauer erklaert....

von Martin (Gast)


Lesenswert?

Als ich für meinen Drehgeber 24imp/U einen zähler fertig hatte lief er 
druch die ISR sehr schnell durch aber trotz allem war der zähler in der 
ISR nur ~2s schnell/langsam. Also sind fast alle pulse durchgerutscht 
ohne sie zu zählen..
Und via INT on change ging das dann wie erwartet! Warum ist das denn so?

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.