www.mikrocontroller.net

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


Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
#include <avr\io.h>                  // Header-Datei für I/O Konfiguration (intern werden weitere Dateien zugefügt)
#include <avr\interrupt.h>              // Header-Datei für globale Interrupt Aktivierung
#include <stdlib.h>
#include <stdint.h>                  // Header-Datei zur Definition der Zahlentypen (int, char, unsigned char usw.)
#include <stdbool.h>                // Header-Datei zur Definition der 1-Bit-Variablen bool
#include "i2c.h"
#include "defines.h"
#include "pwm.h"


#define PHASE_A2  (PINB & 1<<PB1)        // Channel A of Enc 2
#define PHASE_B2  (PINB & 1<<PB0)        // Channel B of Enc 2


// Function prototypes
void PCI_init(void);
void Timer1Init(void);


volatile char s = 0, direction;
unsigned int impulse = 0, impulse_second = 0;

//--------------------------------------------------------------------------------------

int main (void)
{
  unsigned char HiChar, LoChar;

  i2c_init();
  pwm_init();
  PCI_init();
  Timer1Init();

  PORTB |= (1<<PB2);              // Internen Pull-Up-Widerstand des Pins 2 des Ports B einschalten
  DDRB |= (1<<DDB2);              // Pin 2 des Ports B (-->LED1) als Ausgang konfigurieren
  DDRD |= (1<<DDD2);

  i2cSetLocalDeviceAddr(I2C_SLAVE);      // Slave Adresse (Regler)

  sei();                    // Interrupt ermöglichen

  pwm_motor(127,0,127,0);            // FORWARD = 0, BACKWARD = 1

  for(;;)
  {
    if(s == 1)
    {
//      PORTB ^= (1<<PB2);          // LED1 toggeln zum debuggen

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

      impulse = 0;
      s = 0;
    }

    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();
  }

  return 0;
}

//--------------------------------------------------------------------------------------

void PCI_init(void)
{
  DDRB &= ~(1<<PB1);
  PCICR |= (1<<PCIE0);
  PCMSK0 |= (1<<PCINT0);
}

//----------------------------------------------------------------------------------

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;
  }

}

//----------------------------------------------------------------------------------
//Initialisierung des Timer1 zur Erzeugung eines getakteten Interrupts,

void Timer1Init(void)
{
  TCCR1B |= (1<<WGM12) | (1<<CS12) | (1<<CS10);    // Setzen des Vorteilerwerts auf 1024
  TIMSK1 |= (1<<OCIE1A);                // Ermöglichen des Output Compare A Match-Interrupts
  TIFR1 |= (1<<OCF1A);

  OCR1A = 18000;                             // Setzt den Vergleichswert = F_CPU/(1024*1)
}

//----------------------------------------------------------------------------------

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

Autor: Lord Ziu (lordziu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wo initialisiert du denn den PCINT?

Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Lord Ziu (lordziu)
Datum:

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

Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Lord Ziu (lordziu)
Datum:

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

Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
void PCI_init(void)
{
  DDRB &= ~(1<<PB1);
  DDRB &= ~(1<<PB0);
  PCICR |= (1<<PCIE0);
  PCMSK0 |= (1<<PCINT1);
}

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

Autor: Lord Ziu (lordziu)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Lord Ziu (lordziu)
Datum:

Bewertung
0 lesenswert
nicht 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:
PORTB = PINB ^ (1<<PB2);

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Welche Drehzahlen erwartest du eigentlich?

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

Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Oliver (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
if(s == 1)
{
//    PORTB ^= (1<<PB2);          // LED1 toggeln zum debuggen

      cli();
      impulse_second = impulse;
      impulse = 0;
      s = 0;
      sei();
}

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

i2c_write_slave(HiChar);
i2c_write_slave(LoChar);
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?!

Autor: Oliver (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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
cli();
HiChar = (unsigned char) (impulse_second >> 8);
LoChar = (unsigned char) (impulse_second & 0xff);
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

Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ach so, alles klar, jetzt weiß ich auf was du hinaus wolltest.

hab das cli() und sei() ebenfalls rausgenommen.
void i2c_write_slave(unsigned char Data)
{
  TWDR = Data;              // Load Data into TWDR Register.
  TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWEA);  // Clear TWINT bit in TWCR to start transmission of Data

  while(!(TWCR & (1<<TWINT)));      // Wait for TWINT Flag set. This indicates that the Data has been    
                      // transmitted, and ACK/NACK has been received.
}

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

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
s muss auch volatile sein.

Autor: Thomas (Gast)
Datum:

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

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

Autor: Oliver (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@  Thomas (Gast)

>versteh ich net ganz, sorry :)

Versuchs mal mit
ISR(PCINT0_vect)

MFG
Falk

Autor: Thomas (Gast)
Datum:

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

Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Thomas (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 :)

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.