Forum: Mikrocontroller und Digitale Elektronik Problem mit Pin Change Interrupt und Timer Interrupt


von Thomas (Gast)


Lesenswert?

hallo erstmal,

ich bin grad dabei mit einem ATmega88 mit 18,432 MHz einen magnetencoder 
auszuwerten. es handelt sich dabei um den AS5040. ich betreibe ihn als 
inkrementalsensor, sprich ich will nur die Spuren A und B mit meinem µC 
auslesen. auslösung in diesem modus ist 8 bit = 256 inkremente.

ich muss 2 encoder einlesen und sowohl drehzahl als auch drehrichtung 
bestimmen. ich kann aber pro encoder nur einen einzigen timer/counter 
benutzen. um es etwas einfacher zu beginnen will ich erstmal nur die 
daten eines encoders auswerten, was in meinem fall encoder 2 ist. 
angeschlossen ist er so:

ENC2:
Spur A geht auf PB1
Spur B geht auf PB0

ich komme also um einen pin change interrupt nicht herum, um drehzahl 
und drehrichtung zu bestimmen. aber das dürfte kein problem sein, da ich 
recht saubere signale bekomme.

im pin change interrupt frage ich die drehrichtung ab und zähle bei 
jedem high-wert (steigende flanke) meine inkremente um eins hoch. die 
drehrichtung hab ich dadurch dass Spur B entweder 0 oder 1 ist wenn Spur 
A 1 ist.
die drehzahl bestimme ich mit dem timer, den ich auf einen takt von 1 
sekunde eingestellt hab. bei jeder sekunde wird ein interrupt ausgeführt 
der ein flag auf 1 setzt, welches ich in meiner hauptschleife abfrage. 
so kann ich die umdrehungen pro sekunde bestimmen, die ich dann per I2C 
an einen anderen ATmega88 schicke. dieser wiederrum sendet meine 
drehzahl und drehrichtung per UART an meinen PC.

mein problem ist, dass der interrupt für den sekundentakt zwar ohne 
probleme ausgeführt wird, der pin change interrupt aber keinen mucks 
vonsich gibt.

ich hoffe ihr könnt mit helfen.

hier der code:
1
#include <avr\io.h>                  // Header-Datei für I/O Konfiguration (intern werden weitere Dateien zugefügt)
2
#include <avr\interrupt.h>              // Header-Datei für globale Interrupt Aktivierung
3
#include <stdlib.h>
4
#include <stdint.h>                  // Header-Datei zur Definition der Zahlentypen (int, char, unsigned char usw.)
5
#include <stdbool.h>                // Header-Datei zur Definition der 1-Bit-Variablen bool
6
#include "i2c.h"
7
#include "defines.h"
8
#include "pwm.h"
9
10
11
#define PHASE_A2  (PINB & 1<<PB1)        // Channel A of Enc 2
12
#define PHASE_B2  (PINB & 1<<PB0)        // Channel B of Enc 2
13
14
15
// Function prototypes
16
void PCI_init(void);
17
void Timer1Init(void);
18
19
20
volatile char s = 0, direction;
21
unsigned int impulse = 0, impulse_second = 0;
22
23
//--------------------------------------------------------------------------------------
24
25
int main (void)
26
{
27
  unsigned char HiChar, LoChar;
28
29
  i2c_init();
30
  pwm_init();
31
  PCI_init();
32
  Timer1Init();
33
34
  PORTB |= (1<<PB2);              // Internen Pull-Up-Widerstand des Pins 2 des Ports B einschalten
35
  DDRB |= (1<<DDB2);              // Pin 2 des Ports B (-->LED1) als Ausgang konfigurieren
36
  DDRD |= (1<<DDD2);
37
38
  i2cSetLocalDeviceAddr(I2C_SLAVE);      // Slave Adresse (Regler)
39
40
  sei();                    // Interrupt ermöglichen
41
42
  pwm_motor(127,0,127,0);            // FORWARD = 0, BACKWARD = 1
43
44
  for(;;)
45
  {
46
    if(s == 1)
47
    {
48
//      PORTB ^= (1<<PB2);          // LED1 toggeln zum debuggen
49
50
      cli();
51
      impulse_second = impulse;
52
      sei();
53
54
      impulse = 0;
55
      s = 0;
56
    }
57
58
    cli();
59
    HiChar = (unsigned char) (impulse_second >> 8);
60
    LoChar = (unsigned char) (impulse_second & 0xff);
61
    i2c_write_slave(HiChar);
62
    i2c_write_slave(LoChar);
63
    i2c_write_slave(direction);
64
    sei();
65
  }
66
67
  return 0;
68
}
69
70
//--------------------------------------------------------------------------------------
71
72
void PCI_init(void)
73
{
74
  DDRB &= ~(1<<PB1);
75
  PCICR |= (1<<PCIE0);
76
  PCMSK0 |= (1<<PCINT0);
77
}
78
79
//----------------------------------------------------------------------------------
80
81
ISR(PCINT1_vect)
82
{
83
  PORTB &= ~(1<<PB2);              // LED1 toggeln zum debuggen
84
85
  if(PHASE_A2)
86
  {
87
    impulse++;
88
  }
89
90
  if((PHASE_A2 == 1) & (PHASE_B2 == 0))    // Drehrichtung vorwärts
91
  {
92
    direction = 0;
93
  }
94
95
  if((PHASE_A2 == 1) & (PHASE_B2 == 1))    // Drehrichtung rückwärts
96
  {
97
    direction = 1;
98
  }
99
100
}
101
102
//----------------------------------------------------------------------------------
103
//Initialisierung des Timer1 zur Erzeugung eines getakteten Interrupts,
104
105
void Timer1Init(void)
106
{
107
  TCCR1B |= (1<<WGM12) | (1<<CS12) | (1<<CS10);    // Setzen des Vorteilerwerts auf 1024
108
  TIMSK1 |= (1<<OCIE1A);                // Ermöglichen des Output Compare A Match-Interrupts
109
  TIFR1 |= (1<<OCF1A);
110
111
  OCR1A = 18000;                             // Setzt den Vergleichswert = F_CPU/(1024*1)
112
}
113
114
//----------------------------------------------------------------------------------
115
116
ISR(TIMER1_COMPA_vect)           // Modul Taktgenerator: Aufruf jede Sekunde
117
{
118
  s = 1;
119
  TCNT1 = 0;              // Timer 1 auf null
120
}

von Lord Z. (lordziu)


Lesenswert?

Wo initialisiert du denn den PCINT?

von Thomas (Gast)


Lesenswert?

mit der funktion: void PCI_init(void)
direkt unter der main.

aber ich seh grad da steckt ja ein fehler drin. das müsste anstatt: 
PCMSK0 |= (1<<PCINT0);

PCMSK0 |= (1<<PCINT1); heißen.

von Lord Z. (lordziu)


Lesenswert?

Wolltest du nicht beide Pins als Eingang haben? Du setzt im DDR nur 
einen Pin.

von Thomas (Gast)


Lesenswert?

tatsache, hast recht, hab ich total übersehn. aber leider hat sich 
nichts geändert. hab immernoch das problem dass das programm garnicht in 
den pin change interrupt springt.

von Lord Z. (lordziu)


Lesenswert?

Poste doch nochmal die (jetzt angepasste) Initialisierung der PCINTs. 
Bitte mit Code-Tags!

von Thomas (Gast)


Lesenswert?

hab ein bissle rumexperimentiert.

irgendwie kommen sich die beiden interrupts in die quere. anscheinend 
scheint der pin change interrupt doch irgendwie zu funktionieren obwohl 
er den code nicht ausführt.
sobald ich den angeschlossenen motor stoppe und somit keinen pin change 
interrput mehr habe läuft mein 1 sekundentakt. lass ich den motor wieder 
laufen, oder drehe das rad per hand greift der pin change interrupt 
wieder und mein 1 sekundentakt wird jetzt nicht mehr ausgeführt. 
irgendwie seltsam.

hier nochmal der code:
1
void PCI_init(void)
2
{
3
  DDRB &= ~(1<<PB1);
4
  DDRB &= ~(1<<PB0);
5
  PCICR |= (1<<PCIE0);
6
  PCMSK0 |= (1<<PCINT1);
7
}

sorry wegen meinem ersten beitrag, hab nicht gesehn das es auch einen 
extra c-code tag gibt.

von Lord Z. (lordziu)


Lesenswert?

Thomas schrieb:
> scheint der pin change interrupt doch irgendwie zu funktionieren obwohl
> er den code nicht ausführt.

Vielleicht liegts an fehlenden Klammern: (PINB & (1<<PB0))

Ich weiß gerade nicht, wie die Operatorenwertigkeit ist.

von Thomas (Gast)


Lesenswert?

habs grad ausprobiert. daran liegts leider auch nicht.

ich nehme mal an dass die interrupts auch keine zulange bearbeitungszeit 
haben, zumal ich da eigentlich drauf geachtet hab. echt ein merkwürdiges 
verhalten.

von Lord Z. (lordziu)


Lesenswert?

Also zeitlich behindern sich die Interrupts nicht, dafür sind sie zu 
kurz. Und selbst wenn beide "gleichzeitig" feuern, wird sich das vom 
Controller gemerkt. Du verlierst also keinen Interrupt.

Nimm doch mal deine Toggle-LED und setze sie in die verschiedenen 
if-Abfragen deiner PCINT-ISR. Dann kannst du recht schnell sehen, was 
ausgeführt wird und was nicht.

Toggeln macht man übrigens so:
1
PORTB = PINB ^ (1<<PB2);

von Karl H. (kbuchegg)


Lesenswert?

Welche Drehzahlen erwartest du eigentlich?

Den Artikel kennst du?
http://www.mikrocontroller.net/articles/Drehgeber

von Thomas (Gast)


Lesenswert?

also wenn ich das rad nicht drehe, nicht per pwm und nicht per hand, 
dann funktioniert der 1 sekundentakt laut toggle-led. auch die 
dazugehörige funktion in der main funktioniert.

hab versucht nur die led im pin change interrupt einzuschalten, nichtmal 
das funktioniert. und wie gesagt, sobald ich das rad drehe, wird mein 1 
sekundentakt unterbrochen und damit auch das blinken der led. lass ich 
das rad wieder stillstehn, so läuft mein 1 sekundentakt wieder.

von Thomas (Gast)


Lesenswert?

ja den artikel kenne ich. hab es mit deinem programm auch schon 
versucht, allerdings bekomm ich damit keine drehzahlbestimmung hin.

meine maximale drehzahl liegt bei 6 kHz.

von Falk B. (falk)


Lesenswert?

@  Thomas (Gast)

>das funktioniert. und wie gesagt, sobald ich das rad drehe, wird mein 1
>sekundentakt unterbrochen und damit auch das blinken der led. lass ich
>das rad wieder stillstehn, so läuft mein 1 sekundentakt wieder.

Und das wundert dich?

>unsigned int impulse = 0, impulse_second = 0;

impulse wird auch im Interrupt verwendet, es muss volatile sein.

>      cli();
>      impulse_second = impulse;
>      sei();
>      impulse = 0;

Das ist KEIN Atomarer Zugriff. Das Löschen muss auch vor dem sei() 
passieren, siehe Interrupt.

>    cli();
>    HiChar = (unsigned char) (impulse_second >> 8);
>    LoChar = (unsigned char) (impulse_second & 0xff);
>    i2c_write_slave(HiChar);
>    i2c_write_slave(LoChar);
>    i2c_write_slave(direction);
>    sei();

Das ist vollkommen NOGO!!! Warum steckst du die I2C Sachen VOR das 
sei()? Damit bremst du deine Interrupts ZIEMLICH lange aus.

>ISR(PCINT1_vect)
>{
>  PORTB &= ~(1<<PB2);              // LED1 toggeln zum debuggen

>  if(PHASE_A2)
>  {
>    impulse++;
>  }

>  if((PHASE_A2 == 1) & (PHASE_B2 == 0))    // Drehrichtung vorwärts
>  {
>    direction = 0;
>  }

>  if((PHASE_A2 == 1) & (PHASE_B2 == 1))    // Drehrichtung rückwärts
>  {
>    direction = 1;
>  }

Das Thema ist auch alt. Einen Drehgeber wertet man so NICHT aus. Die 
CPU wird bei dir wahrscheinlich durch Prellen mit Interrupts überflutet.

>ISR(TIMER1_COMPA_vect)           // Modul Taktgenerator: Aufruf jede Sekunde
>{
>  s = 1;
>  TCNT1 = 0;              // Timer 1 auf null
>}

Macht man so nicht, auch wenn es hier funktioniert. Dafür nutzt man den 
CTC Modus.

MfG
Falk

von Oliver (Gast)


Lesenswert?

Hast du mal drüber nachgedacht, wie lange in deinem Programm die 
Interrupts tatsächlich freigegeben sind?

Ich würde mal sagen, weniger als 0.1% der verfügbaren Zeit. Das kann so 
nicht funktionieren.

Oliver

von Thomas (Gast)


Lesenswert?

vielen dank für die hilfe :)

@falk
hab deine tipps umgesetzt. die variable "impulse" hab ich als volatile 
deklariert.
ich hoffe ich hab den rest richtig verstanden, darum post ich den 
veränderten code nochmal:
1
if(s == 1)
2
{
3
//    PORTB ^= (1<<PB2);          // LED1 toggeln zum debuggen
4
5
      cli();
6
      impulse_second = impulse;
7
      impulse = 0;
8
      s = 0;
9
      sei();
10
}
11
12
cli();
13
HiChar = (unsigned char) (impulse_second >> 8);
14
LoChar = (unsigned char) (impulse_second & 0xff);
15
sei();
16
17
i2c_write_slave(HiChar);
18
i2c_write_slave(LoChar);
19
i2c_write_slave(direction);

das stimmt natürlich, die drehrichtung werde ich noch ändern. aber das 
ist vorerst nicht so das problem. mein problem ist einfach dass ich gar 
nicht in den pin change interrupt hinein komme.

@Oliver
ich verstehe nicht ganz was du meinst. ich hab jetzt lediglich 2 
situationen in denen ich die interrupts sperre, weil es wichtig ist dass 
ich die richtigen werte übertragen kann. müsste doch genug zeit für die 
interrupts zur verfügung stehen?!

von Oliver (Gast)


Lesenswert?

>ich hab jetzt lediglich 2
>situationen in denen ich die interrupts sperre, weil es wichtig ist dass
>ich die richtigen werte übertragen kann. müsste doch genug zeit für die
>interrupts zur verfügung stehen?!

Jetzt ja, vorher nicht. Vorausgesetzt natürlich, die i2c-Funktionen 
sperren die Interrupts nicht auch sofort wieder...

Im übrigen ist hier
1
cli();
2
HiChar = (unsigned char) (impulse_second >> 8);
3
LoChar = (unsigned char) (impulse_second & 0xff);
4
sei();

cli und sei auch noch überflüssig, da impulse_second ja niemals in einer 
ISR benutzt wird. Da darf das ruhig unterprochen werden.


Oliver

von Thomas (Gast)


Lesenswert?

ach so, alles klar, jetzt weiß ich auf was du hinaus wolltest.

hab das cli() und sei() ebenfalls rausgenommen.
1
void i2c_write_slave(unsigned char Data)
2
{
3
  TWDR = Data;              // Load Data into TWDR Register.
4
  TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWEA);  // Clear TWINT bit in TWCR to start transmission of Data
5
6
  while(!(TWCR & (1<<TWINT)));      // Wait for TWINT Flag set. This indicates that the Data has been    
7
                      // transmitted, and ACK/NACK has been received.
8
}

die I2C funktion hab ich aus einem code-schnipsel, die dürfte nicht so 
gewaltig stören.

von Falk B. (falk)


Lesenswert?

s muss auch volatile sein.

von Thomas (Gast)


Lesenswert?

jep das hatt ich schon ursprünglich so drin:
1
volatile char s = 0, direction;

werd mal nochmal das datenblatt durchlesen, vllt hab ich da was bei der 
initialisierung des PCI übersehen.

von Oliver (Gast)


Lesenswert?

Ähem,...

>ENC2:
>Spur A geht auf PB1
>Spur B geht auf PB0

>The pin change interrupt PCI0
>will trigger if any enabled PCINT7..0 pin toggles.

>ISR(PCINT1_vect)

Das passt irgednwie nicht so richtig zusammen ;-)

Oliver

von Thomas (Gast)


Lesenswert?

versteh ich net ganz, sorry :)

ich bin hiernach vorgegangen:
"Each PCINT7..0 bit selects whether pin change interrupt is enabled on 
the corresponding I/O
pin. If PCINT7..0 is set and the PCIE0 bit in PCICR is set, pin change 
interrupt is enabled on the
corresponding I/O pin. If PCINT7..0 is cleared, pin change interrupt on 
the corresponding I/O pin
is disabled."

das is die beschreibung des PCMSK0-Registers. da ich nur den PCI an PB1 
aktiviert habe, stimmt doch der PCI handling vector. alle anderen sind 
ja abgeschaltet.

von Falk B. (falk)


Lesenswert?

@  Thomas (Gast)

>versteh ich net ganz, sorry :)

Versuchs mal mit
1
ISR(PCINT0_vect)

MFG
Falk

von Thomas (Gast)


Lesenswert?

alles klar, ich werds mal damit testen. nur heut komm ich leider nicht 
mehr dazu. werde morgen berichten :)

von Thomas (Gast)


Lesenswert?

moin moin,

sorry, konnte gestern abend nicht mehr weiter testen, musste leider zur 
nachtschicht :)

ich habs mit "ISR(PCINT0_vect)" mal versucht und es funktioniert 
wunderbar. genauso wie es soll. jetzt muss ich nur noch die 
drehrichtungserkennung verbessern.

vielen dank an euch :)

allerdings versteh ich nicht ganz warum jetzt grad PCINT0_vect 
funktioniert. ich hab doch explizit PCINT1 an PB1 freigegeben und auch 
über den zugehöriger handler angesprochen. müsste er jetzt nicht 
eigentlich jeden PCI am pin PB0 erkennen?

von Falk B. (falk)


Lesenswert?

@  Thomas (Gast)

>allerdings versteh ich nicht ganz warum jetzt grad PCINT0_vect
>funktioniert. ich hab doch explizit PCINT1 an PB1 freigegeben

;-)
Hehe, böse Falle. PCINT1 bezeichnet hier ein Bit im Register PCMSK0, 
mämlich Bit#1.
Der Interruptvector PCINT0_vect ist für diese Register zuständig, also 
die Bits PCINT0..7
Da hat Atmel mit der Namensgebung etwas daneben griffen, ein und der 
selbe!!! Name für zwei verschiedne Dinge!

MFG
Falk

von Thomas (Gast)


Lesenswert?

ach so ist das. haben die wohl eingebaut um die leute zu ärgern ;)

ich hab mich einfach nach der pin-übersicht des ATmega88 auf seite 2 des 
datenblattes gerichtet. da steht dass PCINT1 zu PB1 gehört, aber wenn 
das nur das register beschreibt is das ganz schön irreführend.

nochmals danke, war schon echt am verzweifeln :)

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.