Forum: Mikrocontroller und Digitale Elektronik Anfängerfrage zum Interrupt


von Thomas D (Gast)


Lesenswert?

Hallo,

bin µC Neuling. Ich möchte, dass beim Überlauf des Timer 0 etwas 
bestimmtes passiert. Hier der riesengroße bisherige Code:

main ()            // Hauptprogramm, startet bei Power ON und Reset
{
  TCCR1A=(1<<CS00)|(1<<CS00);    // Timer 0: Clock/1024
  TIMSK=(1<<TOIE0)      // Timer 0: Interrupt bei Überlauf
  while(1)
  {

  }
}

Im GCC Tutorial steht zu TOIE0: "Das Global Enable Interrupt Flag muss 
selbstverständlich auch gesetzt sein. "


Ist der gesetzt? Welches Register ist das?



Und die mainfrage: Wie schreibe ich jetzt eine Funktion was beim 
Überlauf passieren soll?

Vielen Dank.

von Frank L. (franklink)


Lesenswert?

Hallo Thomas,

schau mal hier rein, da wird Dir geholfen.

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Die_Timer.2FCounter_des_AVR

Gruß
Frank

von Robert K. (molch) Benutzerseite


Lesenswert?

Hi,

da das nicht direkt in deinem Post steht treffe ich folgende Annahmen:
- du verwendest den WinAVR Comiler
- du nutzt einen Atmel AVR µC

Das globale Interrupt Flag I befindet sich im Statusregister (SREG).
Das kannst du setzen mit sei() (alle Int. erlauben) und löschen mit 
cli() (alle Int. sperren).

Die Interruptfunktion dann wie folgt:
1
ISR(TIMER0_OVF_vect){
2
  dein Code;
3
}

Zu deinem Code:
1
main ()            // Hauptprogramm, startet bei Power ON und Reset
2
{
3
  TCCR1A=(1<<CS00)|(1<<CS00);    // Timer 0: Clock/1024
4
  TIMSK=(1<<TOIE0)      // Timer 0: Interrupt bei Überlauf
5
  while(1)
6
  {
7
8
  }
9
}

Du beschreibst wenn ich das richtig sehe das Controlregister A für Timer 
1 willst aber den Timer 0 verwenden. Solltest also TCCR0 verwenden.
Und wenn du den Timer 1 verwenden willst stehen die Clock Select Bits 
(CS02 ..CS00) nicht im TCCR1A sondern im TCCR1B (zumindest beim 
ATmega8).
Dazu kommt das du zweimal das selbe CS bit setzt, ansich nicht schlimm 
nur dein Vorteiler ist dann nicht so eingestellt wie du möglicherweise 
vorhattest.

Gruß Robert


@Frank: Ich glaube da war er schon, findet nur vor lauter Informationen 
nicht das richtige :)

von ... (Gast)


Lesenswert?

> Ich glaube da war er schon ...

Ich glaube nicht, die Infos stehen da ab Kapitel 18 und speziell ab 18.5

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmieren_mit_Interrupts

von Thomas D (Gast)


Lesenswert?

Hallo,

mann da hab ich ja einiges übersehen.


Vielen Dank an euch!

von Thomas D (Gast)


Lesenswert?

Ich steuere mit dem ATMega8 eine 4-fach 7 Segmentanzeige an die 
multigeplext wird.

Die globale Variable "number" wird auf der Anzeige ausgegeben, das 
funktioniert soweit.


Jetzt möchte ich noch den 16 Bit Timer dazunehmen. Er soll number jede 
Sekunde um 1 erhöhen.


Komischerweise kennt der Compiler OCR1H und OCR1L nicht. Fehlermeldung:

main.c:69: error: 'OCR1H' undeclared (first use in this function)
main.c:69: error: (Each undeclared identifier is reported only once
main.c:69: error: for each function it appears in.)
main.c:70: error: 'OCR1L' undeclared (first use in this function)



Woran liegt das?


Hier der Code:


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

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

void showDigit(unsigned int digit);

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

unsigned int number=1234;  // number wird ständig angezeit
unsigned int digitNo[4];  // Die einzelnen Ziffern von number

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

ISR(TIMER0_OVF_vect){  // Multiplexing: Ansteuern der nächsten 7 Segment 
Anzeite
  if((PINB&0b00000001)==0b00000001)
  {
    PORTD=0b01111111;    // Ausschalten der 7 Segment Anzeige
    PORTB=0b00000010;    // Aktivieren der nächsten Ziffer
    showDigit(digitNo[1]);  // Anschalten der 7 Segment Anzeige
  }
  else if((PINB&0b00000010)==0b00000010)
  {
    PORTD=0b01111111;
    PORTB=0b00000100;
    showDigit(digitNo[2]);
  }
  else if((PINB&0b00000100)==0b00000100)
  {
    PORTD=0b01111111;
    PORTB=0b00001000;
    showDigit(digitNo[3]);
  }
  else
  {
    PORTD=0b01111111;
    PORTB=0b00000001;
    showDigit(digitNo[0]);
  }
}

ISR(TIMER1_OVF_vect){  // 1 Sekunde ist vergangen
  number++;
  /* digits berechnen */
  digitNo[0]=number%10;
  digitNo[1]=(number/10)%10;
  digitNo[2]=(number/100)%10;
  digitNo[3]=(number/1000);
}


int main (void)              // Hauptprogramm, startet bei Power ON und 
Reset
{
  DDRB=0b00001111;          // Ziffern 0 bis 3
  DDRD=0b01111111;          // 7 Segmente
  /* digits berechnen */
  digitNo[0]=number%10;
  digitNo[1]=(number/10)%10;
  digitNo[2]=(number/100)%10;
  digitNo[3]=(number/1000);
  sei();                // Interrupts aktivieren
  TCCR0=(1<<CS01);          // Timer 0: Clock/8
  TIMSK=(1<<TOIE0);          // Timer 0: Interrupt bei Überlauf

  TCCR1B=(1<<CS11)|(1<<CS10)|(1<<CTC1);    // Timer 1: Clock/64, Löschen 
von TCNT1L/H bei erreichen von OCR1L/H
  OCR1H=15625/256;  // <= FEHLER!!!!!!!!!!!
  OCR1L=15625%256;        // <= FEHLER!!!!!!!!!!!

  while(1)
  {

  }
  return 0;
}
//----------------------------------------------------------------------

void showDigit(unsigned int digit)
{
  switch(digit) {
    case 0:
    {
      PORTD=0b01000000;
      break;
    }
    case 1:
    {
      PORTD=0b01111001;
      break;
    }
    case 2:
    {
      PORTD=0b00100100;
      break;
    }
    case 3:
    {
      PORTD=0b00110000;
      break;
    }
    case 4:
    {
      PORTD=0b00011001;
      break;
    }
    case 5:
    {
      PORTD=0b00010010;
      break;
    }
    case 6:
    {
      PORTD=0b00000010;
      break;
    }
    case 7:
    {
      PORTD=0b01111000;
      break;
    }
    case 8:
    {
      PORTD=0b00000000;
      break;
    }
    case 9:
    {
      PORTD=0b00010000;
      break;
    }
  }
}



Schonmal vielen dank für Hilfe!

von Johannes M. (johnny-m)


Lesenswert?

Klar kennt der Compiler die nicht. Schau bitte ins Datenblatt. Da steht, 
wie die Register heißen!

Außerdem kann man so langen Code besser als Anhang posten.

von Thomas D (Gast)


Lesenswert?

Alles klar.

Vielen Dank!

von Thomas D (Gast)


Lesenswert?

Leider will der 16 Bit Timer immernoch nicht.


Hier ist der Quellcode zum testen des Timers. Jede Sekunde soll B0 von 0 
auf 1 bzw 1 auf 0 spring.


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


ISR(TIMER1_COMPA_vect){  // 1 Sekunde ist vergangen
  TCNT1L=0;
  TCNT1H=0;
  if((PINB&0)==0) PORTB=0b00000001;
  else PORTB=0b00000000;

}


int main (void)              // Hauptprogramm, startet bei Power ON und 
Reset
{
  DDRB=0b00001111;          // Ziffern 0 bis 3
  DDRD=0b01111111;          // 7 Segmente
  PORTD=0b00001111;

  TCCR1B=(1<<CS11)|(1<<CS10);    // Timer 1: Clock/64
  OCR1AH=15625/256;          // OCR1A = 15625
  OCR1AL=15625%256;

  TIFR=(1<<OCF1A);  // Output Compare Flag Timer1 A aktiviert

  while(1)
  {

  }
  return 0;
}

von Justus S. (jussa)


Lesenswert?

sei fehlt und
1
TIFR=(1<<OCF1A);

ist falsch

von Thomas D (Gast)


Lesenswert?

Stimmt. Funktioniert aber leider trotzdem noch nicht.

Ein kleiner Fehler im Interrupt Aufruf:

if((PINB&0)==1) PORTB=0b00000001; muss es heißen.


Hat damit aber nichts zu tun da er da nochnichtmal reingesprungen ist.

von Thomas D (Gast)


Lesenswert?

Bis eben sah ich nur "sei fehlt"


Ich check mal ob ichs check...

von Justus S. (jussa)


Lesenswert?

Thomas D wrote:

>
> Ich check mal ob ichs check...

ein Blick ins Datenblatt oder ins Tut würde helfen...

von Thomas D (Gast)


Lesenswert?

Habs hinbekommen!

Vielen, vielen Dank!!! ;)

von Thomas D (Gast)


Lesenswert?

So, nächstes Problem: Beide Timer laufen lassen. Timer 1 soll beim 
overflow resetten und Timer1 nach 1 Sekunde zu number einen hinzufügen.


Die main sieht so aus:

int main (void)              // Hauptprogramm, startet bei Power ON und 
Reset
{
  DDRB=0b00001111;          // Ziffern 0 bis 3
  DDRD=0b01111111;          // 7 Segmente
  /* digits berechnen */
  digitNo[0]=number%10;
  digitNo[1]=(number/10)%10;
  digitNo[2]=(number/100)%10;
  digitNo[3]=(number/1000);
  sei();                // Interrupts aktivieren
  TCCR0=(1<<CS01);          // Timer 0: Clock/8
  TIMSK=(1<<TOIE0);          // Timer 0: Interrupt bei Überlauf

  TCCR1B=(1<<CS11)|(1<<CS10);    // Timer 1: Clock/64, Löschen von 
TCNT1L/H bei erreichen von OCR1L/H
  OCR1AH=15625/256;
  OCR1AL=15625%256;

  TIMSK=(1<<OCIE1A);

  while(1)
  {

  }
  return 0;
}



Es leuchtet nichts. Hab das Programm leicht umgeschrieben, sodass jede 
Sekunde etwas leuchten soll und es geht. In den TIMER1_COMPA_vect 
Interrupt spring er also rein. Das gleiche mit dem TIMER0_OVF_vect, da 
springt er nicht rein.

Sobald ich die Zeile TIMSK=(1<<OCIE1A); rausnehme springt er auch in den 
TIMER0_OVF_vect.


Wie kann das sein? Der Interrupt 1 funktioniert immer, Interrupt 0 nur 
wenn TIMSK=(1<<OCIE1A); nicht dabei ist.

von Johannes M. (johnny-m)


Lesenswert?

In Deinem letzten Code (von 16:52) ist noch die Zugriffsreihenfolge bei 
TCNT1H/L falsch! Bitte lies im Tutorial den Abschnitt über 
16-Bit-Register. Dein C-Compiler kann Dir die Arbeit der Einzelzugriffe 
nämlich abnehmen (das gilt auch für OCR1AH/L). Die Reihenfolge ist bei 
den Registern essentiell wichtig!

Und einen Timer in einem Compare-Interrupt Handler zurückzusetzen ist 
unsinnig. Dafür gibt es die CTC-Betriebsart. Auch dazu gibt es im 
Tutorial bei der Timer-Beschreibung einen Abschnitt.

von Johannes M. (johnny-m)


Lesenswert?

Thomas D wrote:
> Wie kann das sein? Der Interrupt 1 funktioniert immer, Interrupt 0 nur
> wenn TIMSK=(1<<OCIE1A); nicht dabei ist.
Ganz einfach: Weil Du beim Aktivieren des Compare-Interrupt den anderen 
wieder löschst...

Der Artikel zum Thema Bitmanipulation ist in dem Zusammenhang auch 
empfehlenswert.

von Johannes M. (johnny-m)


Lesenswert?

Thomas D wrote:
> if((PINB&0)==1) PORTB=0b00000001; muss es heißen.
BTW: Die Bedingung wird nie wahr! Eine VerUNDung mit Null liefert 
immer Null (false) zurück.

von Thomas D (Gast)


Lesenswert?

Jop, es funktioniert jetzt endlich! ;)


Vielen Dank!

Hab den CTC angemacht und aus dem TIMSK=(1<<OCIE1A); ein 
TIMSK|=(1<<OCIE1A); gemacht.


Über die Reihenfolge der Register habe ich so schnell nichts gefunden 
aber es funktioniert auch mit der alten Reihenfolge.

Vielen Dank nochmal. :)

von Thomas D (Gast)


Lesenswert?

"> if((PINB&0)==1) PORTB=0b00000001; muss es heißen.
BTW: Die Bedingung wird nie wahr! Eine VerUNDung mit Null liefert
immer Null (false) zurück."


Stimmt. if((PINB&1)==1) PORTB=0b00000000; else PORTB=0b00000001; sollte 
es heißen.

von Johannes M. (johnny-m)


Lesenswert?

Thomas D wrote:
> Über die Reihenfolge der Register habe ich so schnell nichts gefunden
> aber es funktioniert auch mit der alten Reihenfolge.
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#16-Bit_Register_.28ADC.2C_ICR1.2C_OCR1.2C_TCNT1.2C_UBRR.29

Dass es mit der "alten" Reihenfolge in diesem Fall vielleicht klappt, 
ist purer Zufall!

> Stimmt. if((PINB&1)==1) PORTB=0b00000000; else PORTB=0b00000001; sollte
> es heißen.
Auch das ist so nicht richtig. Wenn Du den Portpin umschalten willst, 
dann solltest Du nicht PINB einlesen, sondern PORTB. PINB nur dann, 
wenn der Pin als Eingang konfiguriert ist und der von außen 
vorgegebene Zustand eingelesen werden soll.

Außerdem lässt sich der ganze if-else-Kram da oben als eine einzige 
Anweisung schreiben:
1
PORTB ^= 1;
oder der besseren Lesbarkeit halber
1
PORTB ^= PB1;

BTW: Auch Du solltest die Formatierungsmöglichkeit für C-Code hier im 
Forum nutzen!

von Thomas D (Gast)


Lesenswert?

Alles klar. Danke für die Hilfe.

Lesen: Erst Low, dann High
Schreiben: High, dann Low

PIND==... einlesen der Eingänge
PORTD==... einlesen der Ausgänge
PORTD=... schreiben der Ausgänge / (de)aktivieren der Pullup Widerstände 
der Eingänge
PIND=... gibts nicht?


... trotzdem hatte es ja komischerweise funktioniert.

Frage: ^= ist die invertierung der Bits? Dann würde das hier ja nicht 
ganz stimmen: PORTB ^= 1; Ne, kann nicht sein. Was ist ^= ? Sehe es zum 
ersten mal.

von Thomas D (Gast)


Lesenswert?

Test zum Code schreiben:

[CODE]int main(void) {
return 0;
}
[\CODE]

von Thomas D (Gast)


Lesenswert?

Ach, da stehts ja...

[c]int main(void) {
return 0;
}
[\c]

von Thomas D (Gast)


Lesenswert?

Zum 3ten und diesmal mit Vorschau.
1
int main(void) {
2
return 0;
3
}

Okay danke.

von Johannes M. (johnny-m)


Lesenswert?

Thomas D wrote:
> PIND=... gibts nicht?
Bei neueren AVRs (z.B. ATMega48/88/168) gibt es auch das. Das toggelt 
dann den Pin.

von Thomas D (Gast)


Lesenswert?

Toggeln heißt invertieren...? Wenn ich dich richtig verstehe invertiert 
es die Ausgangspins?

Also

DDRD=0b00001111;
PORTD=0b00001111;
PIND=0b00001111; <- setzt in diesem Fall D0...D3 auf 0?

von Hannes Lux (Gast)


Lesenswert?

Schau doch einfach mal ins Datenblatt, da sind die I/O-Register sehr 
genau beschrieben.

...

von Thomas D (Gast)


Lesenswert?

Jo, habs gefunden. PINx sind auch bei ATmega48/88/168 nur Leseregister.

von Johannes M. (johnny-m)


Lesenswert?

Thomas D wrote:
> PINx sind auch bei ATmega48/88/168 nur Leseregister.
Hatte mich vertan. Die Mega48/... waren mit die letzten AVRs, die das 
noch nicht hatten...

von Thomas D (Gast)


Lesenswert?

So die nächste Aufgabe ist den Reset auszuschalten, sodass ich den PC6 
benutzen kann.


Im Datenblatthabe ich gefunden: "If the RSTDISBL Fuse is programmed, PC6 
is used as an I/O pin."

Nur leider weiß ich nicht in welchem Register ich den RSTDISBL 
programmieren kann.

Auf Seite 62 Tabelle 26 findet sich dann: "Overriding Signals for 
Alternate Functions in PC6..PC4"

Unter der Spalte Reset gibt es die Möglichkeit "RSTDISBL", "0" oder "1" 
einzustellen. Da RSTDISBL invertierte Logik ist würde ich sagen muss da 
eine "0" rein. Unter PC4 und PC5 muss es denke ich auch auf "0" somit 
ist "Signal Name" = "PVOV"


Jetzt weiß ich leider nicht wie ich einprogrammiere, dass diese 
Einstellung übernommen werden soll.

von Johannes M. (johnny-m)


Lesenswert?

Thomas D wrote:
> Nur leider weiß ich nicht in welchem Register ich den RSTDISBL
> programmieren kann.
RSTDISBL ist ein Fusebit und das muss beim Programmieren des Controllers 
separat gesetzt werden. Das geht nicht durch das Anwenderprogramm. Und 
wenn der Reset abgeschaltet ist, ist der AVR nicht mehr per ISP 
programmierbar, nur noch über HV-Programmierung mit einem speziellen 
Programmer (z.B. STK500). Siehe AVR Fuses!

von Thomas D (Gast)


Lesenswert?

Okay. Vielen Dank!

von Hannes Lux (Gast)


Lesenswert?

Übrigens wärst Du nicht der Erste, der sich durch unüberlegtes Spielen 
an den Fuses ausgesperrt hat, bevor er das erste sinnvolle Programm für 
(und in) seinen AVR geschrieben hat. Dieses Forum ist voller solcher 
Hilferufe...

Wenn Du den AVR (und seine Architektur) wirklich kennen lernen und 
verstehen willst, dann mache Deine ersten Schritte in Assembler, denn 
das ist real, da gilt (nur) das Datenblatt und der Instruktionssatz. 
Wenn Du das halbwegs verstanden hast, fällt es Dir in einer Hochsprache 
bedeutend leichter.

...

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.