Forum: Mikrocontroller und Digitale Elektronik Pin Change Interrupt PCINT0


von Hans M. (lukem)


Lesenswert?

Hallo zusammen,

ich habe einen Atmega328P und spiele mit externen Interrupts. Genauer 
mit den externen Pin Change Interrupts. PC0 bis PC3 ist meine Ausgabe, 
PB0 bis PB2 sind meine Eingabe Pins. Findet ein beliebiger Pegelwechsel 
an PB0 bis PB2 statt, soll die Interruptroutine ISR (PCINT0_vect) 
aufgerufen werden und eine Zählsequenz 1 bis 15 an PC0 bis PC3 
ausgegeben werden. Beim Eintritt in die ISR deaktiviere ich die globalen 
Interrupts. Bei Austritt gegebe ich sie wieder frei. Meine 
Erwartungshaltung war nun diese, dass beim ersten Pegelwechsel die 
Zählsequenz genau einmal abläuft. Da ich ja beim Eintritt die globalen 
Interrupts deaktiviert habe. Nun ist es aber so, dass wenn ich einen 
Taster an z.B PB0 schließe und sofort wieder öffne, die Sequenz zweimal 
abläuft. Halte ich den Taster jedoch geschlossen wird erst beim öffnen 
die Sequenz wieder durchlaufen.

Vielleicht kann mir jemand erklären, warum die Sequenz beim schließen 
und sofortigen öffnen des Tasters trotz dann deaktivierter interrupts 
zweimal abläuft. Danke!
1
#define F_CPU 16000000
2
#include <util/delay.h>
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
6
int main(void)
7
{
8
  // -- Konfiguration der Status-LEDs PC0, PC1, PC2, PC3
9
  DDRC |= ((1<<DDC0)|(1<<DDC1)|(1<<DDC2)|(1<<DDC3)|(1<<DDC4));
10
  PORTC &= ~((1<<PORTC0)|(1<<PORTC1)|(1<<PORTC2)|(1<<PORTC3)|(1<<PORTC4));
11
  
12
  
13
  // -- Konfiguration PCINT0 Interrupt an Pin PB0, PB1 & PB2 --
14
  DDRB &= ~((1<<DDB0)|(1<<DDB2)|(1<<DDB2));
15
  PORTB |= ((1<<PORTB0)|(1<<PORTB1)|(1<<PORTB2));
16
  
17
  PCICR |= (1<<PCIE0);
18
  PCMSK0 |=((1<<PCINT0)|(1<<PCINT1)|(1<<PCINT2));
19
  sei(); // Globale Interrupts aktivieren
20
  
21
  while(1)
22
  {
23
    PORTC = 0x03;    //Kein Interrupt? Dann LED 1&2 anschalten.
24
  }
25
  return 0;
26
}
27
28
ISR (PCINT0_vect)
29
{
30
  cli();          //Interrupt erkannt? Interrupts deaktivieren.
31
  for (unsigned char i = 1; i <=15; i++)
32
  {
33
    PORTC = i;
34
    _delay_ms(200);
35
  }
36
  sei();          //LED Sequenz beendet? Interrupts aktivieren.
37
}

von toni (Gast)


Lesenswert?

Blödsinn die Interrupts in der ISR zu sperren, da sie da sowieso 
gesperrt sind bzw andere ISRs nicht ausgeführt werden können wenn eine 
ISR gerade läuft.

Das Pinchange Interrupt Flag wird trotzdem durch eine Flanke gesetzt. 
Den Interrupt zu unterdrücken wäre, das PCINT Flag vor Verlassen der ISR 
zu löschen.

von Hans M. (lukem)


Lesenswert?

Ok, danke erstmal. Die Sache mit dem Sperren der Interupts in der ISR 
habe ich nur gemacht, weil ich mir nicht erklären kann, warum beim 
schließen und sofortigen öffnen eines Tasters die ISR zweimal aufgerufen 
wird (Gebastelt).

1
ISR (PCINT0_vect)
2
{
3
  for (unsigned char i = 1; i <=15; i++)
4
  {
5
    PORTC = i;
6
    _delay_ms(200);
7
  }
8
  PCIFR |= (1<<PCIF0);
9
}

So geht es nun. Aber warum wird das PCIF0 Flag nicht selbständig nach 
der Abarbeitung gelöscht?

: Bearbeitet durch User
von Planlos (Gast)


Lesenswert?

Hans M. schrieb:
> Aber warum wird das PCIF0 Flag nicht selbständig nach
> der Abarbeitung gelöscht

Es wird nur einmal gelöscht, vor Begin der Abarbeitung. Damit kein Event 
verloren geht, auch wenn die ISR mal wieder länger dauert.
Wenn eine neue Flanke während der ISR ankommt, wird das Flag neu 
gesetzt, und sofort nach Beendigung der ISR wird diese neu aufgerufen.

von Dennis K. (scarfaceno1)


Lesenswert?

Planlos schrieb:
> Hans M. schrieb:
>> Aber warum wird das PCIF0 Flag nicht selbständig nach
>> der Abarbeitung gelöscht
>
> Es wird nur einmal gelöscht, vor Begin der Abarbeitung. Damit kein Event
> verloren geht, auch wenn die ISR mal wieder länger dauert.
> Wenn eine neue Flanke während der ISR ankommt, wird das Flag neu
> gesetzt, und sofort nach Beendigung der ISR wird diese neu aufgerufen.

Und genau deswegen führt sich die ISR auch 2mal aus. Du hast ein Event 
bei JEDEM Pegelwechsel. Drückst du den Schalter hast du ein Event. Lässt 
du ihn wieder los hast du das zweite Event.

Da sich nach dem Drücken der Taste das PCIF0 löscht wird es durch das 
Loslassen der Taste erneut gesetzt. Ist der Interrupt dann zu Ende sieht 
der MCU das gesetzte Flag und führt die Routine erneut aus.

Löscht die nun das Flag am Ende des Interrupts so löscht du das 2te 
Event.

von Carl D. (jcw2)


Lesenswert?

Prellt der Schalter (Warscheinlichkeit 100%), hast du mehrere INT's in 
Folge.

von Peter D. (peda)


Lesenswert?

Hans M. schrieb:
> Beim Eintritt in die ISR deaktiviere ich die globalen
> Interrupts. Bei Austritt gegebe ich sie wieder frei.

Das ist Murks. Man sollte schonmal das Datenblatt über den Ablauf bei 
Interrupts überflogen haben.

Hans M. schrieb:
> Meine
> Erwartungshaltung war nun diese, dass beim ersten Pegelwechsel die
> Zählsequenz genau einmal abläuft. Da ich ja beim Eintritt die globalen
> Interrupts deaktiviert habe.

Das deaktiviert nur den Aufruf des Handlers für diese Zeit. Die 
Pending-Flags müssen natürlich immer gesetzt werden, sobald das Ereignis 
eintritt. Alles andere wäre fatal, Interrupts dürfen niemals verloren 
gehen können!

Will man vergangene Ereignisse absichtlich nicht mehr berücksichtigen, 
löscht man einfach deren Pending-Flag. Kleine Fallgrube der AVRs, 
Pending-Flags löscht man durch Setzten.

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.