Forum: Mikrocontroller und Digitale Elektronik Atmega88 Pin Change Interrupt


von Sebastian (Gast)


Lesenswert?

Ich versuche über den Pin Change Interrupt den Atmega88 Aufzuwecken 
jedoch funktioniert dies nicht.

An Portb 0,1,2  Sind die taster schalten nach GND
Pullups sind aktieviert.

An Portd 0,1,2  Hab ich Leds dran,es geht keine LED an beim drücker der 
Tasten.

könnte mir einer von ihnen weiterhelfen
danke
1
#include <avr/io.h>
2
#include <stdint.h>            
3
#include <avr/interrupt.h>     
4
#include <avr/sleep.h>
5
#include <util/delay.h>
6
7
8
9
//ist im Make-File schon definiert
10
//#define F_CPU 10000000       // Taktfrequenz 10MHz des Quarzes
11
12
13
int main(void)
14
{
15
    DDRB &= ~((1 << DDB0) | (1 << DDB1) | (1 << DDB2)); 
16
    
17
    PORTB |= ((1 << PORTB0) | (1 << PORTB1) | (1 << PORTB2)); 
18
   
19
    
20
    PCICR |= (1 << PCIE0);    
21
    PCMSK0 |= (1 << PCINT0);  
22
23
// Analogcomparator ausschalten
24
 
25
    ACSR = 0x80;
26
27
    sei();                     // interrupts freigeben
28
29
    while(1)
30
    {
31
  
32
        PCICR |= (1 << PCINT0);            // externen Interrupt freigeben
33
 
34
        set_sleep_mode(SLEEP_MODE_PWR_DOWN);
35
        sleep_mode();                   // in den Schlafmodus wechseln
36
 
37
38
        // hier wachen wir wieder auf
39
        PCICR &= ~(1 << PCINT0);           // externen Interrupt sperren
40
    }
41
}
42
43
44
45
ISR (PCINT0_vect)
46
{
47
    
48
    if( !(PINB  & (1<<PB0)) )  // PCINT0 
49
    {
50
        PORTD |= (1 << PD0);            // LED1 an        
51
        PORTD &= ~(1 << PD1);           // LED2 aus  
52
        PORTD &= ~(1 << PD2);           // LED3 aus 
53
  }
54
    
55
    if( !(PINB  & (1<<PB0)) )  // PCINT1 
56
    {
57
        PORTD |= (1 << PD1);            // LED2 an        
58
        PORTD &= ~(1 << PD0);           // LED1 aus  
59
        PORTD &= ~(1 << PD2);           // LED3 aus         
60
    }
61
62
    if( !(PINB  & (1<<PB0)) )  // PCINT2 
63
    {
64
        PORTD |= (1 << PD2);            // LED3 an        
65
        PORTD &= ~(1 << PD0);           // LED1 aus  
66
        PORTD &= ~(1 << PD1);           // LED2 aus        
67
    }    
68
69
70
}

von holger (Gast)


Lesenswert?

>An Portd 0,1,2  Hab ich Leds dran,es geht keine LED an beim drücker der
>Tasten.

Natürlich geht keine LED an PORTD an. Da ist kein einziger Pin
als Ausgang definiert.

von Thomas E. (thomase)


Lesenswert?

Sebastian schrieb:
> Ich versuche über den Pin Change Interrupt den Atmega88 Aufzuwecken
> jedoch funktioniert dies nicht.

>         set_sleep_mode(SLEEP_MODE_PWR_DOWN);
>         sleep_mode();                   // in den Schlafmodus wechseln

Mach mal so:
1
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
2
sleep_enable();
3
sleep_cpu();

mfg.

von Sebastian (Gast)


Lesenswert?

Na klasse hab ich vergessen.
DDRD = (1<<PD0  ) | (1<<PD1  ) | (1<<PD2  );//als ausgang

es geht troztdem beim drücken der taster keine led an

von Sebastian (Gast)


Lesenswert?

Thomas Eckmann schrieb:
>
> Mach mal so:
> set_sleep_mode(SLEEP_MODE_PWR_DOWN);
> sleep_enable();
> sleep_cpu();
>
> mfg.

Das geht leider auch nicht.

ist denn meine Konfiguration so überhaupt richtig für den
 Pin Change Interrupt an PORTB0-2
1
#include <avr/io.h>
2
#include <stdint.h>            
3
#include <avr/interrupt.h>     
4
#include <avr/sleep.h>
5
#include <util/delay.h>
6
7
8
9
//ist im Make-File schon definiert
10
//#define F_CPU 10000000       // Taktfrequenz 10MHz des Quarzes
11
12
int main(void)
13
{   //Leds 
14
    DDRD = (1<<PD0  ) | (1<<PD1  ) | (1<<PD2  );//als ausgang
15
16
    DDRB &= ~((1 << DDB0) | (1 << DDB1) | (1 << DDB2)); 
17
    
18
    PORTB |= ((1 << PORTB0) | (1 << PORTB1) | (1 << PORTB2)); 
19
   
20
    
21
    PCICR |= (1 << PCIE0);    
22
    PCMSK0 |= (1 << PCINT0);  
23
24
// Analogcomparator ausschalten
25
 
26
    ACSR = 0x80;
27
28
    sei();                     // interrupts freigeben
29
30
    while(1)
31
    {
32
  
33
        PCICR |= (1 << PCINT0);            // externen Interrupt freigeben
34
 
35
        //set_sleep_mode(SLEEP_MODE_PWR_DOWN);
36
        //sleep_mode();                   // in den Schlafmodus wechseln
37
 
38
         set_sleep_mode(SLEEP_MODE_PWR_DOWN);
39
         sleep_enable();
40
         sleep_cpu();
41
42
43
        // hier wachen wir wieder auf
44
        PCICR &= ~(1 << PCINT0);           // externen Interrupt sperren
45
     
46
    }
47
}
48
49
50
51
ISR (PCINT0_vect)
52
{
53
    
54
    if( !(PINB  & (1<<PB0)) )  // PCINT0 
55
    {
56
        PORTD |= (1 << PD0);            // LED1 an        
57
        PORTD &= ~(1 << PD1);           // LED2 aus  
58
        PORTD &= ~(1 << PD2);           // LED3 aus 
59
  }
60
    
61
    if( !(PINB  & (1<<PB1)) )  // PCINT1 
62
    {
63
        PORTD |= (1 << PD1);            // LED2 an        
64
        PORTD &= ~(1 << PD0);           // LED1 aus  
65
        PORTD &= ~(1 << PD2);           // LED3 aus         
66
    }
67
68
    if( !(PINB  & (1<<PB2)) )  // PCINT2 
69
    {
70
        PORTD |= (1 << PD2);            // LED3 an        
71
        PORTD &= ~(1 << PD1);           // LED1 aus  
72
        PORTD &= ~(1 << PD2);           // LED2 aus        
73
    }    
74
75
76
}

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Im 'Power-Down' wird die I/O Clock gestoppt und lediglich INT0 und INT1 
sind als Wake-Up zu gebrauchen (im Level Modus).
Nur der 'Idle' Mode lässt die I/O Clock laufen, spart aber natürlich 
nicht so viel.
Siehe Kapitel 7.1 im Datenblatt.
Du musst also bei Power-Down Taster an INT0 oder INT1 anschliessen.

von Sebastian (Gast)


Lesenswert?

Also funktioniert das so wie i h mir das dachte nicht .
Ich habe an PORTB0-2   3Taster mit denen wollte ich den Atmega88 
aufwecken egal welcher Taster von den dreien gedrückt wurde.

Gibs denn da noch eine andere Möglichkeit.
Mfg

von Justus S. (jussa)


Lesenswert?

Sebastian schrieb:
> Gibs denn da noch eine andere Möglichkeit.

im Datenblatt steht doch explizit drinnen, was aus welchem Sleep Mode 
aufwecken kann...

von Thomas E. (thomase)


Lesenswert?

Matthias Sch. schrieb:
> Im 'Power-Down' wird die I/O Clock gestoppt und lediglich INT0 und INT1
> sind als Wake-Up zu gebrauchen (im Level Modus).
> Nur der 'Idle' Mode lässt die I/O Clock laufen, spart aber natürlich
> nicht so viel.
> Siehe Kapitel 7.1 im Datenblatt.
> Du musst also bei Power-Down Taster an INT0 oder INT1 anschliessen.
Ich weiss ja nicht, welchem Datenblatt du das entnommen hast.
Jedenfalls nicht dem des Atmega88.
Natürlich lässt der sich aus allen Sleepmodes mit den Pinchange
Interrupts aufwecken.

@Sebastian
> ist denn meine Konfiguration so überhaupt richtig für den
> Pin Change Interrupt an PORTB0-2
Das ist falsch, genau genommen unvollständig
> PCMSK0 |= (1 << PCINT0);

PCMSK0 |= (1 << PCINT0) | (1 << PCINT1) | (1 << PCINT2);
Bisschen verwirrende Doppelverwendung der Namen.

Damit funktioniert dein Programm auch.
1
#ifndef F_CPU
2
  #error F_CPU not defined.
3
#endif
4
5
#include <avr/io.h>
6
#include <stdint.h>            
7
#include <avr/interrupt.h>     
8
#include <avr/sleep.h>
9
#include <util/delay.h>
10
11
//ist im Make-File schon definiert
12
//#define F_CPU 10000000       // Taktfrequenz 10MHz des Quarzes
13
14
int main(void)
15
{   //Leds 
16
    DDRD = (1<<PD0  ) | (1<<PD1  ) | (1<<PD2  );//als ausgang
17
    DDRB &= ~((1 << DDB0) | (1 << DDB1) | (1 << DDB2)); 
18
    PORTB |= ((1 << PORTB0) | (1 << PORTB1) | (1 << PORTB2));     
19
    PCICR |= (1 << PCIE0);    
20
    PCMSK0 |= (1 << PCINT0) | (1 << PCINT1) | (1 << PCINT2);  
21
// Analogcomparator ausschalten
22
    ACSR = 0x80;
23
    sei();                     // interrupts freigeben
24
25
    while(1)
26
    {
27
        PCICR |= (1 << PCINT0);            // externen Interrupt freigeben 
28
        //set_sleep_mode(SLEEP_MODE_PWR_DOWN);
29
        //sleep_mode();                   // in den Schlafmodus wechseln 
30
         set_sleep_mode(SLEEP_MODE_PWR_DOWN);
31
         sleep_enable();
32
         sleep_cpu();
33
        // hier wachen wir wieder auf
34
        PCICR &= ~(1 << PCINT0);           // externen Interrupt sperren
35
    }
36
}
37
38
ISR (PCINT0_vect)
39
{    
40
    if( !(PINB  & (1<<PB0)) )  // PCINT0 
41
    {
42
        PORTD |= (1 << PD0);            // LED1 an        
43
        PORTD &= ~(1 << PD1);           // LED2 aus  
44
        PORTD &= ~(1 << PD2);           // LED3 aus 
45
    }
46
    
47
    if( !(PINB  & (1<<PB1)) )  // PCINT1 
48
    {
49
        PORTD |= (1 << PD1);            // LED2 an        
50
        PORTD &= ~(1 << PD0);           // LED1 aus  
51
        PORTD &= ~(1 << PD2);           // LED3 aus         
52
    }
53
54
    if( !(PINB  & (1<<PB2)) )  // PCINT2 
55
    {
56
        PORTD |= (1 << PD2);            // LED3 an        
57
        PORTD &= ~(1 << PD1);           // LED1 aus  
58
        PORTD &= ~(1 << PD0);           // LED2 aus        
59
    }    
60
}

In der letzten "if" war auch noch ein Fehler.

mfg.

von Sebastian (Gast)


Lesenswert?

Hallo Thomas,

Vielen dank ich wahr schon ein bischen irretiert denn mann kann den 
Atmega88 ja mit fast jeden Pin
aufwecken das wahr ja beim Atmega8 nicht so da gabs ja nur int0 und 
int1.

Nochmal eine kleine Verständisfrage
PCICR |= (1 << PCINT0); // externen Interrupt freigeben
wird denn hiermit die oben definierten pins den interrupt freigegeben 
PCINT0-2
oder nur PCINT0

Mfg

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Thomas Eckmann schrieb:
> Ich weiss ja nicht, welchem Datenblatt du das entnommen hast.
> Jedenfalls nicht dem des Atmega88.

Dann schau dir mal im ATMega 48/88/168/328 Datenblatt Version 05/11 die 
Wakeup Sources auf Seite 39 an. Und bitte Fussnote (3) beachten. 
Lediglich bei Idle gehen Pinchange Interrupts. Das leuchtet auch ein, 
denn die Flankenerkennung läuft eben nur, wenn die I/O Clock an ist. 
Level Interrupts auf INT0 und INT1 hingegen werden in allen Sleep Modes 
als Wake-Up erkannt.

von Thomas E. (thomase)


Lesenswert?

Sebastian schrieb:
> Hallo Thomas,
>
> Vielen dank ich wahr schon ein bischen irretiert denn mann kann den
> Atmega88 ja mit fast jeden Pin
> aufwecken das wahr ja beim Atmega8 nicht so da gabs ja nur int0 und
> int1.
>
> Nochmal eine kleine Verständisfrage
> PCICR |= (1 << PCINT0); // externen Interrupt freigeben
> wird denn hiermit die oben definierten pins den interrupt freigegeben
> PCINT0-2
> oder nur PCINT0
>
> Mfg

Das ist die blöde Doppelbezeichnung. PCINT0 bezeichnet einmal den 
Vektor, der dem ersten Port zugeordnet ist. Also beim Atmega88 PORTB. 
Beim Attiny4313 ist das PORTA. Ich hätte das analog zu den Ports A, B, 
C... PCINTA, PCINTB usw. genannt. Aber mich haben sie nicht gefragt.

Das andere sind die Pin-Bezeichnungen. Pin0 am ersten Port also beim 88 
PORTB0 heisst PCINT0. Um den scharf zu schalten, wird das entsprechende 
Bit im zugehörigen Maskenregister gesetzt. Und dann wird hochgezählt. 
PB1 ist PCINT1, PB7 PCINT7 usw. Das gilt aber nur fürs Maskenregister 
und hat mit der Freigabe der Interrupts nichts zu tun. PCINT8, der am 
PORTC liegt, wird im nächsten Maskenregister gesetzt und hat den 
Interruptvektor PCINT1, also PCINT1_vect.

Da muss man also, wenn man das nicht jeden Tag macht, immer konzentriert 
ins Datenblatt gucken. Wahrscheinlich wollte sich der Autor dadurch eine 
besondere Anerkennung verschaffen.

mfg.

von Justus S. (jussa)


Lesenswert?

Matthias Sch. schrieb:
> Dann schau dir mal im ATMega 48/88/168/328 Datenblatt Version 05/11 die
> Wakeup Sources auf Seite 39 an. Und bitte Fussnote (3) beachten.
> Lediglich bei Idle gehen Pinchange Interrupts. Das leuchtet auch ein,
> denn die Flankenerkennung läuft eben nur, wenn die I/O Clock an ist.
> Level Interrupts auf INT0 und INT1 hingegen werden in allen Sleep Modes
> als Wake-Up erkannt.

dann lies doch bitte auch den Text dazu...die Fussnote bezieht sich nur 
auf die Einschränkung "Level Interrupts" für INT0/1...

von Thomas E. (thomase)


Lesenswert?

Matthias Sch. schrieb:
> Thomas Eckmann schrieb:
>> Ich weiss ja nicht, welchem Datenblatt du das entnommen hast.
>> Jedenfalls nicht dem des Atmega88.
>
> Dann schau dir mal im ATMega 48/88/168/328 Datenblatt Version 05/11 die
> Wakeup Sources auf Seite 39 an. Und bitte Fussnote (3) beachten.
> Lediglich bei Idle gehen Pinchange Interrupts. Das leuchtet auch ein,
> denn die Flankenerkennung läuft eben nur, wenn die I/O Clock an ist.
> Level Interrupts auf INT0 und INT1 hingegen werden in allen Sleep Modes
> als Wake-Up erkannt.
Du Fußnote solltest du dir nicht nur anschauen, sondern auch verstehen.
Aber ich erklär dir das mal.
Da steht nämlich, daß unter den genannten Bedingungen, INT0 und INT1 nur 
im Level Mode funktionieren. Das ist auch logisch, wie du schon erkannt 
hast.
Pinchange-Interrupts sind aber grundsätzlich Level-Interrupts. Deswegen 
wird da nicht extra drauf hingewiesen, daß die eben nur so 
funktionieren.
Und deswegen lassen sich alle AVRs mit Pinchange auch aus allen 
Sleepmodes mit einem PCINT aufwecken.
Wenn dem jetzt tatsächlich nicht so sein sollte, kannst du meine 
sämtlichen AVR haben. Die sind dann nämlich alle kaputt.

Großkotzmodus aus.

mfg.

PS: Soweit ich weiss, wurde die Pinchange-Geschichte ursprünglich für 
batteriebetriebene Fernbedienungen entwickelt.

von M. N. (Gast)


Lesenswert?

Thomas Eckmann schrieb:
> Pinchange-Interrupts sind aber grundsätzlich Level-Interrupts.

Das glaube ich nicht. Das entsprechende PCIF wird durch die 
Flankenerkennung gesetzt und nicht wieder gelöscht, bevor die passende 
ISR ausgeführt wurde.
Und selbst, wenn der Pegel nicht gehalten wird, wird PCIF mit der 
nächsten Flanke (egal, ob steigend oder fallend) wieder gesetzt.

Welche kaputten AVRs bekomme ich jetzt von Dir? :-)

von Thomas E. (thomase)


Lesenswert?

M. N. schrieb:
> Thomas Eckmann schrieb:
>> Pinchange-Interrupts sind aber grundsätzlich Level-Interrupts.
>
> Das glaube ich nicht.
Und?

mfg.

von Peter D. (peda)


Lesenswert?

PCINTs reagieren auf beide Flanken. Diese Logik arbeitet jedoch 
asynchron, d.h. ohne CPU-Takt und daher kann sie aus jedem Mode 
aufwecken.

von Julius (Gast)


Lesenswert?

Peter Dannegger schrieb:
> PCINTs reagieren auf beide Flanken. Diese Logik arbeitet jedoch
> asynchron, d.h. ohne CPU-Takt und daher kann sie aus jedem Mode
> aufwecken.

Da schreibt einer der AHNUNG VON DER MATERIE HAT!!!
Genauso ist es.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Vielen Dank! Wieder was gelernt und auch gleich eine Mehrdeutigkeit (in 
meinen Augen) im Datenblatt geklärt.

Peter Dannegger schrieb:
> PCINTs reagieren auf beide Flanken. Diese Logik arbeitet jedoch
> asynchron, d.h. ohne CPU-Takt und daher kann sie aus jedem Mode
> aufwecken.

Nochmals danke. PCINTs sind eben doch nicht level-, sondern 
flankengetriggert.

von M. N. (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Diese Logik arbeitet jedoch
> asynchron, d.h. ohne CPU-Takt

Auch das glaube ich nicht :-)
Wenn man ins Datenblatt sieht, wird ein Teilschaltbild gezeigt, wie die 
Flankenänderung ausgewertet und das PCIF gesetzt werden. Die dazu 
notwendigen FFs werden mit clk getaktet - folglich synchron.

Offensichtlich verschweigt uns Atmel, wie es gemacht wird.

von Florian F. (florian_m_f)


Lesenswert?

Hallo miteinander!

Ich versuche auch gerade pin change interrupts zu verwenden. Hierzu habe 
ich noch eine Verständnisfrage die hier sehr gut anschließt und nicht 
wirklich einen neuen threat braucht.

Bezugnehmen auf den oben stehenden verbesserten code von Thomas Eckmann 
aktiviere ich pin change interrupts in folgenden Schritten:

1.) Aktivierung des scans for pin change interrupt für PortB
1
PCICR |= (1 << PCIE0);

2.) Definition welche Pins von PortB überwacht werden sollen
1
PCMSK0 |= (1 << PCINT0) | (1 << PCINT1) | (1 << PCINT2);

3.) Aktivierung globaler interrupts
1
sei();

Jetzt zu meiner Frage: Warum müssen im while-loop die Zeilen
1
PCICR |= (1 << PCINT0);
und
1
PCICR &= ~(1 << PCINT0);
stehen?

Zum einen ist doch die Verwendung von PCICR in dem Fall nicht konsistent 
weil sie am Anfang und im while-loop unterschiedlich verwendet werden 
und zum anderen sind die interrupts doch schon aktiviert?

Vielen Dank vorab!
Florian

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Florian Faessler schrieb:
> Jetzt zu meiner Frage: Warum müssen im while-loop die ZeilenPCICR |= (1
> << PCINT0);undPCICR &= ~(1 << PCINT0);stehen?

Müssen sie nicht und es ist purer Zufall, das PCINT0 zufällig an der 
gleichen Bitposition wie PCIE0 steht - deswegen gehts doch.
Richtig wäre zum Desaktivieren des PC Interrupts auf Port B
PCICR &= ~(1 << PCIE0);
und zum Aktivieren
PCICR |= (1 << PCIE0);

von Florian F. (florian_m_f)


Lesenswert?

Danke Matthias für die schnelle Antwort! :-)

Bei mir hat es selbst bei anderer Pin-Belegung auch zufällig 
übereingestimmt und war deshalb bei mir reproduzierbar. Jetzt ist alles 
klar.

von Peter U. (pulli00)


Lesenswert?

Hallo,
ich versuche mich auch mit Interrupts und bin Neuling was das angeht.

Und zwar habe ich einen ATmega88, einen Taster bei PD5 und eine LED an 
PB1.

Das Basisprogramm läuft schon einwandfrei. Jedoch soll dieses bei 
Tastendruck entsprechend unterbrochen werden und danach natürlich 
entsprechend fortgesetzt werden. Dafür habe ich vor den PinChange 
Interrupt meines Controllers zu verwenden. Vereinfacht möchte ich gerne, 
dass die LED bei Tastendruck kurz aufleutet und anschließend das 
Programm in der While-schleife weiter läuft.

Folgendes habe ich als Code:
1
#define F_CPU 8000000UL 
2
#include <avr/eeprom.h>
3
#include <avr/io.h>
4
#include <util/delay.h>
5
#include <stdint.h>
6
#include <stdlib.h>
7
#include <avr/interrupt.h>
8
#include "sensoren.h"
9
#include "uart.h"
10
11
12
#define PCIE2_vect _VECTOR(1)
13
14
void taster_init(void);
15
16
ISR(PCIE2_vect)
17
{
18
  PORTB |= ( 1 << PB1 );  //LED an
19
  _delay_ms(1000);
20
}
21
22
int main(void)
23
{
24
  uart_init();
25
  SPI_init();
26
  sensoren_init();
27
  taster_init();
28
    
29
  while(1)
30
  {
31
           PCICR |= (1 << PCINT20);
32
     PORTB &= ~( 1 << PB1 );  //LED aus
33
           //ab hier restliches Programm
34
           _delay_ms(500);
35
     PCICR &= ~(1 << PCINT20);
36
        }  
37
}
38
39
void taster_init(void)
40
{
41
  //Taster_test implementieren
42
  DDRB |= (1<< PB1);      //PB1 als Ausgang setzen. LED
43
  DDRD &= ~( 1<< PD5);    // PD5 als Eingang Taster
44
  PORTD |= ( 1<< PD5);    // Pullup am Taster einschalten
45
  PCICR |= ( 1<< PCIE2);    //
46
  PCMSK2 |= ( 1<< PCINT20);
47
  sei();
48
  _delay_ms(0.5);        // Verzögerung
49
}
Wie gesagt, bin ich noch ein Anfänger. Ich hätte gerne hilfreiche 
Hinweise, die mir sagen, warum mein Code nicht funktioniert.

Dankeschön

: Bearbeitet durch User
von Bitflüsterer (Gast)


Lesenswert?

Hinweise werden Dir nicht helfen, denn Dir fehlen wesentliche 
Grundlagen.

Lies und erprobe erstmal:

1. http://www.mikrocontroller.net/articles/AVR-Tutorial:_Tasten
2. http://www.mikrocontroller.net/articles/Multitasking

von Bitflüsterer (Gast)


Lesenswert?

Noch drei Bemerkungen, die langfristig hilfreich sind:

1. In Interrupt-Routinen werden keine delays verwendet. Man will, das 
ISR schnell reagieren und Ereignisse an die main-Schleife (die ggf. eine 
State-Machine enthält) weiterreichen.
2. Ein sei ohne vorhergehendes cli ist zwecklos, denn die Interrupts 
sind nach dem Reset ohnehin freigegeben. Siehe Datenblatt.
3. Sowas
1
while(1)
2
{
3
  PCICR |= (1 << PCINT20);
4
  ...
5
  PCICR &= ~(1 << PCINT20);
6
}

ist völlig zweckfrei und dazu noch potentielle Fehlerquelle. Überleg 
einmal selbst, wie schnell, nach dem sperren des PIN-Change-Ints in der 
letzten Zeile innerhalb der while-Schleife, die erneute Freigabe in der 
ersten Zeile erfolgt.

von Thomas E. (thomase)


Lesenswert?

Peter Ullrich schrieb:
> #define PCIE2_vect _VECTOR(1)

> ISR(PCIE2_vect)

Was versprichst du dir davon? Wenn du schon neue Vektornamen kreierst, 
dann auch bitte richtig. Aber was soll das?

Benutze den Vektor so, wie er definiert ist:
1
ISR(PCINT2_vect)
2
{
3
4
}

Tasten fragt man allerdings ohnehin nicht so ab. Guck dir den ersten 
Link von Bitflüsterer an.


mfg.

von spess53 (Gast)


Lesenswert?

Hi

>PCICR &= ~(1 << PCINT20);

In PCICR gibt es kein PCINT20. Fall das ein Versuch zum Löschen des 
Interruptflags sein soll: Das befindet sich in PCIFR und wird durch 
Setzen des Bits gelöscht.


>2. Ein sei ohne vorhergehendes cli ist zwecklos, denn die Interrupts
>sind nach dem Reset ohnehin freigegeben. Siehe Datenblatt.

Dann mach das mal schleunigst.

MfG Spess

von Bitflüsterer (Gast)


Lesenswert?

spess53 schrieb:
>>2. Ein sei ohne vorhergehendes cli ist zwecklos, denn die Interrupts
>>sind nach dem Reset ohnehin freigegeben. Siehe Datenblatt.
>
> Dann mach das mal schleunigst.
>
> MfG Spess

Habe gemacht. Peinlich.

Die Interrupts sind, anders als ich behauptet habe, nach dem Reset 
nicht freigeben.

Sorry.

von Peter U. (pulli00)


Lesenswert?

Bitflüsterer schrieb:
> Hinweise werden Dir nicht helfen, denn Dir fehlen wesentliche
> Grundlagen.
>
> Lies und erprobe erstmal:
>
> 1. http://www.mikrocontroller.net/articles/AVR-Tutorial:_Tasten
> 2. http://www.mikrocontroller.net/articles/Multitasking

Danke, ich werde es mir durchlesen. Leider scheinen die Code-Beispiele 
im Assembler geschrieben zu sein.

Mir geht es lediglich darum, dass ich verstehe wie ich den PCINT 
rifchtig initialisiere für meine Pinbelegung von Taster und LED.

Bitflüsterer schrieb:
> 1. In Interrupt-Routinen werden keine delays verwendet. Man will, das
> ISR schnell reagieren und Ereignisse an die main-Schleife (die ggf. eine
> State-Machine enthält) weiterreichen.

Der Code soll nur zu Übungszwecken dienen. Wenn ich kein Delay 
reinschreibe, dann seh ich möglicherweise gar nicht, ob es funktioniert, 
da er meine LED gleich wieder ausschaltet.
Meine Main funktion enthält noch weiteren Code, der für diese 
Interrupt-Angelegenheit aber nicht relevant ist.

Wie gesagt bin ich Anfänger und habe meinen Code durch Interpretation 
des Codes über mir weitestgehend angepasst, um es für meine Hardware zum 
laufen zu bringen.

Pin PB1 ist eine LED angeschlossen
Pin PD5 ist der Taster (auf Masse gezogen)

Wie ist es zu initialisieren?

von Karl H. (kbuchegg)


Lesenswert?

Peter Ullrich schrieb:

> Wie gesagt bin ich Anfänger

der allerwichtigste Punkt ist, dass du auch als Anfänger irgendwann 
erkennen musst, dass _delay_ms nicht die Lösung sondern oft das Problem 
darstellt.

Wenn du das erkannt hast, dann kommst du auch irgendwann zur 
Schlussfolgerung, dass du gar keinen Pin-Change Interrupt zur 
Tastenbfrage brauchst. Denn dadurch, dass das Programm anders 
organisiert ist und als Folge davon alle Delays rausfliegen, wird die 
Hauptschleife schnell genug, dass man die Tastenabfrage auch per Polling 
machen kann. Ganz im Gegenteil muss man die Tastenabfrage sogar des 
öfteren noch bremsen, weil die Hauptschleife jetzt zu schnell geworden 
ist, so dass Tastenprellen voll durchschlägt.

Was man allerdings in praktisch jedem Programm braucht, das ist ein 
Timer, der einen Systemtick erzeugt. Und genau dort ist auch der Punkt 
an dem man mit einer Tastenabfrage ansetzen kann.

: Bearbeitet durch User
von Peter U. (pulli00)


Lesenswert?

@Karl Heinz:

Natürlich gibt es immer mehrere Lösungen für ein Problem. Leider kann 
ich mit deinem Ratschlag nicht viel anfangen, sondern bin eher noch 
verwirrter.

Mein Programm umfasst Zeitschleifen, da es in bestimmten Zeitabständen 
Messergebnisse von Sensoren über UART ausgeben soll. Wie gesagt bin ich 
da mit meiner Lösung zufrieden, bzw. möchte ich nicht alles über den 
Haufen werfen. Es soll lediglich eine weitere Funktion hinzukommen, 
falls der Taster gedrückt worden ist. Der einfachheit halber und wegen 
der Anschaulichkeit, soll meine LED kurz aufleuchten, wenn der Taster 
betätigt wurde.
Wenn dies erfolgreich geschieht, passe ich die Funktion entsprechend an.
Ich hoffe ihr versteht mein Vorhaben nun etwas besser.

Die Tutorial sind wirklich gut, leider bin ich nicht erfahren genug um 
den Assembler Code in C zu übersetzen und für mich anzuwenden. Deshalb 
habe ich im Forum nach ähnlichen Vorhaben gesucht und den Code darin 
versucht auf meine Hardware zu übertragen und zu testen.

von Karl H. (kbuchegg)


Lesenswert?

Peter Ullrich schrieb:

> Der Code soll nur zu Übungszwecken dienen.

Nun denn.

Du willst also einen Pin Change am Pin PD5 detektieren.

Aus dem Datenblatt erfahren wird, dass am PD5 die Pin Change Quelle 21 
hängt.
Weiters suchen wir uns im Datenblatt raus, dass diese Quelle 21 zum Pin 
Change Interrupt 2 gehört.

Lass uns da mal anfangen. Wie immer muss ein Interrupt erst mal frei 
gegegeben werden.
Aus dem Datenblatt holen wir uns
1
PCICR – Pin change interrupt control register
2
3
  • Bit 2 - PCIE2: Pin change interrupt enable 2
4
    When the PCIE2 bit is set (one) and the I-bit in the status
5
    register (SREG) is set (one), pin change interrupt 2 is enabled.
6
    Any change on any enabled PCINT23..16 pin will cause an interrupt.
7
    The corresponding interrupt of pin change interrupt request is
8
    executed from the PCI2 interrupt vector. PCINT23..16 pins are
9
    enabled individually by the PCMSK2 register.

die Sache mit dem sei ist nicht weiter überraschend, das ist immer so 
und ein entsprechender Aufruf von sei() ist normalerweise die letzte 
Aktion vor der Hauptschleife.

ALso: in PCICR muss PCIE2 auf 1 gesetzt werden. Und der zuständige 
Vektor ist der PCI2, im avr-gcc Jargon ist das dann der PCI2_vect. 
interrupt.h inkludieren und dann müsste der Vektorname auch schon 
existieren.

Weiter im Text. Da gibt es einen Verweis auf das PCMSK2 Register. Der 
PCI2 feuert nicht einfach so bei jedem Pin, sondern jeder der 8 
möglichen Pins, die zum Pin Change Interrupt 2 gehören, muss eigens 
freigegeben werden. Also mal bei der Beschreibung des PCMSK2 nachsehen.
1
PCMSK2 – Pin change mask register 2
2
   • Bit 7..0 – PCINT23..16: Pin change enable mask 23..16
3
       Each PCINT23..16-bit selects whether pin change interrupt is
4
       enabled on the corresponding I/O pin. If PCINT23..16 is set and
5
       the PCIE2 bit in PCICR is set, pin change interrupt is enabled
6
       on the corresponding I/O pin. If PCINT23..16 is cleared, pin
7
       change interrupt on the corresponding I/O pin is disabled.

OK. Das Bit, das gebraucht wird, ist das Bit PCINT21, weil ja auch beim 
PD5 Pin in der Übersicht dabei steht, dass es sich um den PCINT21 
handelt.

Was ist also zu tun?
* Es braucht einen Interrupt Handler. Der trägt den Namen PCI2_vect
* In PCMSK2 ist das Bit PCINT21 zu setzen
* In PCICR ist das Bit PCIE2 zu setzen
* Es muss einen sei() geben

und dann sollte das eigentlich schon laufen.
Für den ersten Gehversuch empfehle ich erst mal, nicht zu künsteln. So 
simpel wie nur irgendwie möglich. D.h. ohne irgendwelche Zeitsteuerungen 
oder dergleichen. Einfach nur: Wenn Interrupt, dann LED einschalten 
(oder ausschalten). NIcht mehr.

Und jetzt vergleich mal das was notwendig ist mit dem was du im 
Eingangsposting an Code geschrieben hast.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:

> Und jetzt vergleich mal das was notwendig ist mit dem was du im
> Eingangsposting an Code geschrieben hast.

1
...
2
ISR(PCIE2_vect)
3
{

Nope. Der heißt PCI2_vect.
Wann immer du eine Fehlermeldung wegen eines anscheinend falschen 
Interrupt Namens kriegst, dann ist die richtige Aktion nicht, dass du 
dir selber ein #define dafür machst, sondern dir überlegst, warum wohl 
dein Interrupt Name falsch ist.

Das 'E' in PCIE2 steht für 'Enable'. In Langform bedeutet PCIE2 "Pin 
Change Interrupt Enable für den Pin Change Interrupt 2". PCIE2 ist der 
Name des Enable Bits. Damit wird ein Interrupt freigegeben ('enabled') 
oder ausgeschaltet ('disabled'). Aber der dadurch freigegebene Interrupt 
ist der PCI2, der 'Pin Change Interrupt 2'. Und genau so heißt dann auch 
der Vektor.
1
void taster_init(void)
2
{
3
....
4
5
  PCMSK2 |= ( 1<< PCINT20);
6
....

wieso 20?
Der PD5 ist der PCINT21!

von Karl H. (kbuchegg)


Lesenswert?

Peter Ullrich schrieb:

> Mein Programm umfasst Zeitschleifen, da es in bestimmten Zeitabständen
> Messergebnisse von Sensoren über UART ausgeben soll.

Das ist kein Grund.

> Wie gesagt bin ich
> da mit meiner Lösung zufrieden

Das solltest du aber nicht. Statt dessen solltest du lernen, wie man ein 
Programm mittels Timer organisiert, so dass es zeitliche Dinge regel.
> bzw. möchte ich nicht alles über den
> Haufen werfen.

Tja. Leider ist das aber oft unumgänglich. Vor allen Dingen wenn man 
einen untauglichen Ansatz gewählt hat, der nicht zukunftsträchtig ist.

Zeitsteuerungen laufen praktisch immer über einen Timer. Der Timer 
erzeugt mit seiner ISR einen Basistakt, von dem ausgehen über Vielfache 
davon die entsprechenden Aktionen getriggert werden. Frei nach dem 
Muster: nach 60 mal 1 Sekunde sind auch 60 Sekunden vergangen.
Dann ist auch das Hinzufügen von nahezu beliebig vielen weiteren 
Funktionalitäten kein großes Problem mehr. Und vor allen Dingen: Das 
Programm reagiert auch dann auf Benutzereingaben, wenn gerade 
irgendwelche anderen Dinge ablaufen, wie zb Wartezeiten nach denen eine 
LED wieder ausgeht.

Also etwa so
1
volatile uint8_t AbfrageDauer;
2
3
ISR( ... )   // Timer ISR, die zb alle 0.1 Sekunden aufgerufen wird
4
{
5
  if( AbfrageDauer > 0 )
6
    AbfrageDauer--;
7
}
8
9
int main()
10
{
11
  ....
12
13
  Timer initialisieren, so dass die ISR alle 0.1 Sekunden aufgerufen wird
14
15
  AbfrageDauer = 100;  // mal 0.1 Sekunden macht 10 Sekunden
16
  sei();
17
18
  while( 1 ) {
19
  
20
    if( AbfrageDauer == 0 ) {
21
      AbfrageDauer = 100;
22
23
      XXXXXX
24
    }
25
26
    .....
27
  }
28
}

Der Code bei XXXXXX wird alle 10 Sekunden ausgeführt. Und zwar ohne das 
das restliche Programm zeitlich blockiert ist.

FAQ: Timer

Lern mit Timern umzugehen! Davon hast du mehr Nutzen, als von allem 
anderen! Timer sind deine Arbeitspferde in der µC-Programmierung.

: Bearbeitet durch User
von Peter U. (pulli00)


Lesenswert?

Danke an Karl Heinz. Dieser Beitrag war sehr hilfreich und es hat auf 
Anhieb funktioniert. Du Teufelskerl ;)

Der funktionierende Code sieht also so aus:
1
#define F_CPU 8000000UL 
2
#include <avr/eeprom.h>
3
#include <avr/io.h>
4
#include <util/delay.h>
5
#include <stdint.h>
6
#include <stdlib.h>
7
#include <avr/interrupt.h>
8
#include "sensoren.h"
9
#include "uart.h"
10
11
void taster_init(void);
12
13
ISR(PCINT2_vect)
14
{
15
  PORTB |= ( 1 << PB1 );  //LED an
16
  _delay_ms(1000);
17
}
18
19
int main(void)
20
{
21
  uart_init();
22
  SPI_init();
23
  sensoren_init();
24
  taster_init();
25
    
26
  while(1)
27
  {
28
           PCICR |= (1 << PCIE2);
29
           PORTB &= ~( 1 << PB1 );  //LED aus
30
           //ab hier restliches Programm
31
           //.
32
           //.
33
           //.
34
           _delay_ms(500);
35
           PCICR &= ~(1 << PCIE2);
36
  }  
37
}
38
39
void taster_init(void)
40
{
41
  //Taster_test implementieren
42
  DDRB |= (1<< PB1);      //PB1 als Ausgang setzen. LED
43
  DDRD &= ~( 1<< PD5);    // PD5 als Eingang Taster
44
  PORTD |= ( 1<< PD5);    // Pullup am Taster einschalten
45
  PCICR |= ( 1<< PCIE2);    //
46
  PCMSK2 |= ( 1<< PCINT21);
47
  sei();
48
  _delay_ms(0.5);        // Verzögerung
49
}

Dummerweise habe ich in meinem vorherigen Code PCINT20 statt PCINT21 
verwendet. Habe mich doch tatsächlich verzählt. Dennoch war ich nah 
dran.
Ich bedanke mich nochmal herzlich bei Karl Heinz und allen anderen 
Helfern.

von Karl H. (kbuchegg)


Lesenswert?

Peter Ullrich schrieb:

> ISR(PCINT2_vect)

Da hab ich wohl daneben gehaut.
Ich hätte doch im Datenblatt in der Interrupt Tabelle nachsehen sollen, 
wie der Interrupt heißt. Dort ist er als PCINT2 gelistet und nicht als 
PCI2, wie ich oben behauptet habe. Mein Fehler, aber du hast ja 
gefunden, wie das Teil wirklich heisst.

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.