WCM Forum

WCM Forum (http://www.wcm.at/forum/index.php)
-   Home Cockpit - Das Forum für die "Bastler" (http://www.wcm.at/forum/forumdisplay.php?f=55)
-   -   AVR-Interrupts in C (http://www.wcm.at/forum/showthread.php?t=214427)

philharmony 21.04.2007 19:04

AVR-Interrupts in C
 
Habe mich mal wieder an die Elektronik gesetzt und Promt das nächste Problem:
AVR-Studio, ATmega8535
Ich möchte ein kleines Versuchsprogramm haben, dass alle 5000Takte den PortB um einen runterzählt und bei PIND2 per Interrupt PORTB auf 0x00 setzt.
Klappt natürlich nicht, ich denke ich habe die Geschichte mit den Interrupts noch nicht 100%ig verstanden, werde aus diversen Tutorials auch nicht wirklich schlau was jetzt genau wo hin muss.
Hier mal mein Text

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


int main (void) {

int i;

DDRB=0xff;
PORTB=0xff;

GIFR=(1<<INTF0);
MCUCR =((1<<ISC01)|(1<<ISC00));
sei();

while(1) {


while(i<5000) i++;
PORTB--;
i=0;


SIGNAL (SIG_INT0)
{
PORTB=0;
}


}

return(0);
}
-----------------------------------------

Was mach ich falsch?

Bastian 21.04.2007 20:25

Versteh ich das richtig das du Port B bei einem externen Interrupt an Pin D2 auf 0 setzen willst?

Grüße
Bastian

AlTonno 21.04.2007 20:47

Wie ist denn der INT0 extern beschalten (Taster/Pullup?) bzw. wie sind den die Pegel an dem Pin so?

Die Interrupt-Routine würde ich übrigens aus dem main rausverfrachten, villeicht mag das dein compiler so nicht...


lg

philharmony 21.04.2007 21:23

Habe es bis jetzt nur im AVRstudio simuliert.
Das Prog soll von sich aus Runterzählen(an PortB) und ihn auf Interrupt von PIND2 auf 0 setzen.
Ich verstehe den Syntax der Interrupts noch nicht so ganz.
Vllt könnte jmd meinen Text exemplarisch mal so umstellen wie es funktionieren müsste...
LG

dirkan 22.04.2007 00:12

Hallo,

// Beispiel für einen Timer Interrupt:
int cnt = 0;

int main( void )
{
TIMSK |= BV(TOIE0); // Enable timer interrupt
sei(); // Enable global interrupt flag

for (;;); // Loop forever oder mache hier irgendwas
}

// Diese Funktion wird bei overrun timer counter aufgerufen
SIGNAL (SIG_OVERFLOW0)
{
PORTB = ++cnt;
}

Das geht mit allen anderen Interrupts ähnlich.
1. Das richtige enable bit setzen
2. Die SIGNAL oder INTERRUPT Fumktion schreiben SIG_ ist für
jeden Interupt unterschiedlich deklariert.

Beachten: bei vielen Ints muss das dazugehörende Register gelesen
werden (RTFM)

Bei verwendung von INTERRUPT anstatt SIGNAL können verschachtelte Interrupts auftreten. Fang mit SIGNAL an und langsam rantasten,
dann wird das schon gehen.

Viel Erfolg

Dirk

philharmony 22.04.2007 12:34

Hi Dirk,

Zitat:

Beachten: bei vielen Ints muss das dazugehörende Register gelesen werden
Genau da fängt mein Problem an, ich werd aus dem Manual da nicht schlau.
Welche Register muss ich wie und mit welcher Syntax setzen?

GIFR=(1<<INTF0) bedeutet doch, im Global Interrupt Flag Register den INTF0 auf 1 setzen oder?



EDIT:
mit

Zitat:

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

int main (void) {

int i;

DDRB=0xff;
PORTB=0xff;

GIFR=(1<<INTF0);
GICR=(1<<INT0);
MCUCR =((1<<ISC01)|(1<<ISC00));
sei();

while(1)
{

while(i<50) i++;
PORTB--;
i=0;

}
return(0);
}


SIGNAL (SIG_INT0)
{
PORTB=0x00;
}



Geht es, würde es aber Zufallstreffer nennen, habs noch nicht wirklich verstanden...

AlTonno 22.04.2007 12:59

// EDIT:

klar, das Bit ist wichtig - "External Interrupt Request 0 Enable"... ohne dem geht natürlich gar nichts ;)


lg
Chris

philharmony 22.04.2007 13:36

Oh man, diese Controller rauben mir noch den letzten Nerv, hab grad das Gefühl, je mehr ich darüber lese und damit mache, desto weniger verstehe ich letztendlich...

Ich habe jetzt noch einen 2.Externen Interrupt drin, und jedesmal wenn der aufgerufen wird, steigt der Prozess danach wieder ganz oben im MAIN ein.
Kann ich ihn auch irgendwie dazu bringen, in der Hauptschleife zu bleiben?

dirkan 22.04.2007 13:47

Hallo,

soweit bist du garnicht weg vom Ziel. Gestatte mir noch folgende Korrekturen:

1. PORTB--; geht nicht, weil ein decrement aus lesen und schreiben
besteht. Lesen eines Portes muss immer mit PINB erfolgen.
Also:
PORTB = PINB-1;

2. Wie willst du den Erfolg deiner Programmierung kontrollieren ?
Baue ein,zwei LED ein und setze sie bei einem Event. Damit debugged es sich leichter.

Last not least: ich kann an deinem Programm keinen Sinn erkennen.
Ist das nur eine Übung, oder übersehe ich was ?

Gruss Dirk

philharmony 22.04.2007 13:56

Das is erstmal eine Übung.
Ich kann den Erfolg auf dem STK500 sehen, wo der gesamte Portb an den 8 LEDs hängt und einfach binär durchzählt.
Das Program soll wenns fertig ist, über INT1 eine "run-Variable" schalten, wenn die an ist soll er zählen und sich per INT0 resetten lassen.
PORTB-- funktioniert übrigens (Durch das DDRB hab ich ja festgelegt, dass PORTB immer den Wert von PINB annimmt), das is nich das Problem, nur diese Interrupt-Steuerung raff ich nicht, also wann und wo genau ich jetzt was wie setzen/lesen muss damits läuft...

philharmony 23.04.2007 15:03

Kann mir jmd erklären, warum der Programmzeiger in der Simulation nach jedem Interrupt wieder oben in der 1.Zeile des Main-Prog einsteigt ohne den SIG_Befehl auszuführen?
Eigentlich sollte er doch in der Hauptschleife bleiben, dann das SIG_... ausführen und danach wieder in die Hauptschleife zurückspringen oder?

dirkan 23.04.2007 22:11

Hi,

der Effekt ist typisch für ATMELs, wenn du
den falschen Interruptnamen programmierst.
Der C-Compiler erstellt am Anfang des SRAM
ein Sprungbefehl nach main, gefolgt von einer
Vektortabelle, in der zu jedem Int
ein Interruptfunktionszeiger stehen.
Wenn du eine Interruptfunktion für den externen INT0
interrupt schreibst, belegt der C-Compiler den dafür
vorgesehenen Slot.
Der Interrupt wird ausgelöst, der Prozessor holt den Funktions
zeiger und jumped dahin.
Wenn jedoch da ne NULL steht, weil es keine Funktion dafür gibt,
jumped das Programm nach 0 und das ist der Start des Programms.

Du musst also deine Namen SIG_INT0 kontrollieren.
Gibt es die bei dem verwendeten Prozessor überhaupt ?
Sind in deinem Programm mehrere Interrupts enabled ?


Gruss
Dirk

philharmony 23.04.2007 22:24

Also die SIG_INT0 gibt es und hab ich weiter oben, ausserhalb vom Main definiert.
Ich habe INT0 und INT1 enabled.
Hier mal der Neue Text...

Zitat:

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

int run,i;
run=1;

SIGNAL(SIG_INT0)
{
run=0;
}

SIGNAL(SIG_INT1)
{
run=1;
}

int main (void)
{

DDRB=0xff;
GICR = ((1<<INT0) |(1<<INT1));
GIFR = ((1<<INTF0)|(1<<INTF1));
MCUCR = ((1<<ISC01)|(1<<ISC00)|(1<<ISC11)|(1<<ISC10));
sei();


for (;;)
{
if(run==1)
{while(i<5000) i++;
PORTB--;
i=0;
}

}

}






dirkan 24.04.2007 16:14

Setz den MCUCR vor GICR.
Im Manual steht ein Hinweis:
When changing the ISC2 bit, an interrupt can occur. Therefore, it is recommended to first disable INT2 by clearing its Interrupt Enable bit in the GICR Register. Then, the ISC2 bit can be changed.


MCUCR = ((1<<ISC01)|(1<<ISC00)|(1<<ISC11)|(1<<ISC10));
GICR = ((1<<INT0) |(1<<INT1));

// das brauchst du wahrscheinlich nicht: GIFR = ((1<<INTF0)|(1<<INTF1));

Viel Erfolg

Dirk

philharmony 24.04.2007 19:44

Hi Dirk, ich benutze doch aber INT2 gar nicht, sondern nur INT0 und INT1...

dirkan 24.04.2007 19:51

Hallo,

probier das doch mal. Vieeleicht gilt das ja auch für die anderen beiden INTs. Irgendwie wird das schon gehen. Haben andere ja auch schon hinbekommen.

Gruss
Dirk

philharmony 24.04.2007 22:13

Hm, keine �nderung.
Bei Wechsel von 0 nach 1 an PIND2 oder PIND3 Springt der Zeiger an die erste Stelle des Main-Blocks ohne jedoch sie SIG-Routine auszuf�hren, dh, die Variable "run" wird nicht ver�ndert...

EDIT: Ok, langsam dämmert es mit dem SIG_...
Aber wo finde ich den namen des entsprechenden Vektors, bzw wie setzt er sich zusammen?
Im Manual steht Vector No.2 bzw 3, 0x001/0x002, INT0/INT1, External Interrupt Request0/1.
Wie setzt sich das mit dem SIG zusammen?

EDIT2:
ISR(_VECTOR(1)){run=0;}
ISR(_VECTOR(2)){run=1;}
funzt, zufällig eben per Google gefunden.
Wie hätte ich jetzt darauf kommen können?

dirkan 25.04.2007 22:43

Hallo,

in den .h Dateien stehen die Deklarationen. Für deinen 8535 gilt:
#define SIG_INTERRUPT0 _VECTOR(1)
#define SIG_INTERRUPT1 _VECTOR(2)
#define SIG_OUTPUT_COMPARE2 _VECTOR(3)
#define SIG_OVERFLOW2 _VECTOR(4)
#define SIG_INPUT_CAPTURE1 _VECTOR(5)
#define SIG_OUTPUT_COMPARE1A _VECTOR(6)
#define SIG_OUTPUT_COMPARE1B _VECTOR(7)
#define SIG_OVERFLOW1 _VECTOR(8)
#define SIG_OVERFLOW0 _VECTOR(9)
#define SIG_SPI _VECTOR(10)
#define SIG_UART_RECV _VECTOR(11)
#define SIG_UART_DATA _VECTOR(12)
#define SIG_UART_TRANS _VECTOR(13)
#define SIG_ADC _VECTOR(14)
#define SIG_EEPROM_READY _VECTOR(15)
#define SIG_COMPARATOR _VECTOR(16)

Es handelt sich um direkte Adressen im RAM. _VECTOR(0) ist der Sprung
nach main() und die anderen zeigen auf die jeweilige Behandlungsroutine. Wenn du eine Behandlungsroutine schreibst, fügt der Compiler in den passenden Slot der Tabelle die Adresse zur Funktion.

Wenn du programmierst:
SIGNAL(SIG_INTERRUPT0) {}
steht nach compilieren im vector 1 ein Zeiger auf deine Funktion.

Definiere doch mal zu allen möglichen 15 Interrupts eine SIGNAL Funktion, dann wird das Programm irgendwo hinspringen, nur nicht auf 0.

Viel Erfolg

Dirk

philharmony 25.04.2007 22:55

Hi Dirk, ok, jetzt hats geklickt, und funktioniert.
Vllt noch ne etwas peinliche Frage: wo finde ich denn die *.h-files? finde im Atmel-Ordner keine und würde da ganz gerne mal reinschauen.
Könnte vllt noch ganz aufschlussreich sein...

dirkan 26.04.2007 21:32

Hallo,

bei mir liegt das unter c:\winavr\avr\include\avr
Aber verrate mir jetzt mal, was letztendlich der Grund war, der
das Programm auf Adresse Null geführt hat ?

Gruss Dirk

philharmony 26.04.2007 22:59

Der Grund war ganz einfach der, dass ich wie du schon erkannt hattest, die SIGNAL(SIG_INT0) benutzt habe, die der controller gar nicht kennt, denn sie heisst eben
SR(_VECTOR(1))
Wenn die die io.h im WINAVR-Verzeichnis zu finden ist, wie kann dann AVR-Studio darauf zugreifen? Oder gehören die Progs zusammen (weiss garnicht mehr ob ich die zs installiert habe oder einzeln)
Ich arbeite nur mitm AVR-Studio weil die Simulation damit eben möglich ist...

EDIT:
In der m8535.h stehen dann wiederrum Folgende Bezeichner:

Zitat:

/* External Interrupt 0 */
#define INT0_vect _VECTOR(1)
#define SIG_INTERRUPT0 _VECTOR(1)

/* External Interrupt 1 */
#define INT1_vect _VECTOR(2)
#define SIG_INTERRUPT1 _VECTOR(2)
Scheinen also alle möglichen Ausdrücke zu gehen...

AlTonno 30.04.2007 20:36

WinAVR ist eine avr-gcc-Distribution, die als Plug-In im AVR Studio arbeitet und das C-programmiern dort erst möglich macht.


Und wie du schon erkannt hast, die Interrupt-Routinen können entweder mit

ISR (INT0_vect) oder
ISR (SIG_INTERRUPT0) oder eben 'direkt' (ohne den Umweg über ein Define) mit
ISR (_VECTOR(1))

bezeichnet werden... durch die Defines muss man aber den Vektor nicht manuell anpassen, wenn man das Programm auf einem anderen Controller verwenden will.

lg

philharmony 01.05.2007 21:32

Ok, "klick" häts ´gmacht ;)
Als nächstes werde ich mich mal mit dem 2-Wire-Serial und mit USB-Steinen beschäftigen, dann hab ich alles zusammen was ich für diverse Panels brauche, fängt langsam richtig an Spass zu machen.
btw: hab HIER (Anfang unteres Drittel der Seite) was gefunden zur Drehrichtungserkennung von Phasenverschobenen Drehencodern. Der Beitrag mit dem "Greycode", hat das mal jemand versucht? ich verstehe da die super-kurz-kompakt C-Befehle nicht,
zb
Zitat:

DDRC &= ~ (DREH_A + DREH_B + DREH_TAST);
PORTC |= (DREH_A + DREH_B + DREH_TAST);
etc.
Mir ist auhc das Prinzip noch nicht ganz klar wie das gehn soll, aber es ist auf jeden Fall wenn ich es zum laufen kriege deutlich kürzer als meine bisherige REDEC-Programmierung...

AlTonno 01.05.2007 21:41

Die Auswertung von Greycode-Encodern erfolg im Prinzip durch periodisches Einlesen der zwei Phasen und vergleichen mit dem vorherigen Zustand... je nach Ergebnis dieses Vergleiches kann man dann auf die Drehrichtung rückschließen. Den c't-Artikel mag ich jetzt nicht lesen, aber ich vermut mal, dass wird dort eh sehr ausführlich erklärt ;)


Zum C-Code...

i += 2;
bedeutet nix anderes als
i = i + 2;

das gleiche gilt natärlich auch für andere Operanden (wie eben "&", "|", "-" usw)
Und das "~" invertiert den darauf folgenden Ausdruck bitweise.

lg

philharmony 02.05.2007 08:58

Habe den Graycode mal Zeile für Zeile auf Papier durchlaufen und komme zu folgendem Problem: Wenn ich langsam drehe, und in der 11-Position (beide Flanken high) mehrere Takte durchlaufen, dann Zählt er doch pro Takt um einen hoch.
Dazu noch das Problem dass dieses Beispiel pro Raste um 2 hochzählt (laut Beschreibung, ich komme sogar teilweise auf 3), was mache ich nun wenn ich um je einen hochzählen möchte?

philharmony 03.05.2007 23:20

Aahhaaaa, jetzt ises klar:
Der Kern liegt in dem
Zitat:

graycode >= 2;
Es wird also der jeweils vorherige Wert um zwei Stellen weitergeschoben un dann der aktuelle an die vohrherigen.
Dadurch entsteht ein vierstelliger Code mit zwei Stellen aktueller und zwei Stellen alter Wert. In der Tabelle staht dann für jede möglich Vorher-Nachher-Kombination die entsprechende Reaktion.
Wenn ich jetzt pro Raste nur um einen hoch- oder runterzählen will, dann muss in der Tabelle einfach überall eine Null stehen und nur bei zb
"vorher 00 und dann 01"-> wert++
und bei
"vorher 00 und dann 10"-> wert--

Is ja total genial und wirklich deutlich kürzer und schneller als meine "REDEC" und wenn ich es richtig sehe entfällt damit sogar ein entprellen, oder?

fluchtpunkt 04.05.2007 09:52

Hallo erstmal,


Zitat:

Original geschrieben von philharmony
Aahhaaaa, jetzt ises klar:
Der Kern liegt in dem
Zitat:

graycode >= 2;

also... graycode >=2; ist ein Vergleich ob graycode groesser oder gleich 2 ist. Was das an der Stelle soll versteh ich nicht, wird wohl ein Schreibfehler sein, gemeint war sicher graycode >>=2;


Und noch ein Hinweis zu den Interrupts, Variablen die im Interrupt geaendert werden solltest du als volatile deklarieren.
Statt "int i;" einfach "volatile int i;" schreiben. So zwingt man den Compiler vor jedem Lesezugriff auf die Variable den aktuellen Wert aus dem Speicher zu lesen und bei jedem Schreibzugriff den Wert in den Speicher zurueckzuschreiben, so verhindert man das der Compiler zu sehr optimiert.

Kleines Beispiel:
Code:

int main(void)
{
  while(1)
  {
    x++;
    if ( x >= 1000 )
      do_something();
  }
}

Der Compiler wuerde x einmal in ein Register laden und dann immer mit diesem Register rechnen.
Wenn nun ein Interrupt eintritt der wie folgt aussieht
Code:

ISR(TIMER1_COMPA_vect)
{
  x = 1000;
}

Wird zwar der Speicher von x auf 1000 gesetzt, da das main-Programm allerdings x immer nur aus dem Register liest wird es nie merken das x auf 1000 gesetzt wurde.
Hoffe das war ein wenig verstaendlich.

Mehr Informationen gibts zB hier: http://www.mikrocontroller.net/artic...R-GCC-Tutorial

philharmony 04.05.2007 13:23

Zitat:

graycode >=2
Bedeutet dann aber "um 2 verschieben" oder?
Sonst macht die ganze Überlegung keinen Sinn...
Mit dem volatile ist nach deine super Erklärung nun auch klar.
Es geht voran.
Jetzt fehlen eben nur noch dach 2wire-Serial und die USB-Vernetzung.
Dazu werd ich Euch dann sicher demnächst wieder löchern...

philharmony 04.05.2007 21:35

Hm, wisst Ihr welche *.h ich noch includen muss damit die Befehle sbi und cbi funktionieren? (set/clear bit)
Mit io.h gehts erkennt er die nicht...

AlTonno 05.05.2007 09:45

Die Funktionen sbi/cbi finden sich nur mehr in alten avr-libc Versionen... heutzutage verwendet man diese Funktionen nicht mehr.

Stattdessen werden einzelne Bits sehr übersichtlich und ISO-C konform so gesetzt:

REG |= (1<<BIT);
z.B. PORTB |= (1<<PB2);

und so gelöscht

REG &= ~(1<<BIT);
also z.B. PORTB &= ~(1<<PB2);


Das ist zwar etwas mehr Schreibarbeit, kann aber im späteren Entwicklungsstadium und bei größeren Programmen wirklich seeeeeeehr hilfreich sein.


lg

philharmony 05.05.2007 11:25

Danke

philharmony 07.05.2007 09:41

Mal wieder was.
Wenn ich den Atmega8535 jetzt ausserhalb des STK500 auf der Platine selbst einsetzen will, was gibts da zu beachten?
Werde ihn nicht direkt verlöten sondern auf einen Sockel stecken (Bauweise PDIP). Also welche Pins MÜSSEN auf Masse, braucht es zwingend ein externes Quarz, muss ich die Versorgungsspannung von einem Labornetzteil per Kondensator glätten? Wenn ja, wie sollte der dimensioniert sein etc.


Alle Zeitangaben in WEZ +2. Es ist jetzt 23:36 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
© 2009 FSL Verlag