Hi Leute,
ich hab hier ein PICDEM2 PLUS Demo Board vor mir das mit einem
PIC18F4320 bestückt ist. Ich möchte jetzt testweise eine ISR schreiben,
welche bei Overflow des Timer0 die LEDs ansteuert. Ich habe folgende
Konfigurationen an dem Timer und Interrupt vorgenommen:
T0CON = 0x87;
TMR0H = 0x00;
TMR0L = 0xFF;
INTCON = 0xA0;
INTCON2 = 0x84;
INTCON3 = 0x00;
RCONbits.IPEN = 1;
INTCONbits.GIE = 0;
Den Timer0 habe ich dabei auf hohe Priorität gesetzt und diese auch
aktiviert. Der Code wird so fehlerfrei compiliert.
GIE setze ich in der mainloop auf 1.
Meiner Meinung nach müssten die Register alle korrekt initialisiert
sein, aber ich hab leider zu wenig Ahnung von dem Mechanismus wie man
eine ISR unter MPLAB erstellt. Ich hab das mal so versucht, anhand eines
Beispielprogrammes das ich gefunden hab:
#pragma code InterruptVectorHigh = 0x08
void InterruptVectorHigh (void){
PORTB = LED1;
/*
if(PORTB == OFF)
PORTB = LED0;
else if(PORTB == LED0)
PORTB = LED1;
else if(PORTB == LED1)
PORTB = LED2;
else if(PORTB == LED2)
PORTB = LED3;
else if (PORTB == LED3)
PORTB = LED0;
*/
TMR0H = 0x00;
TMR0L = 0xFF;
INTCONbits.TMR0IF = 0;
}
Die ISR wird aber nie ausgeführt. Ich hab schon die Hilfe von MPLAB
durchforstet, werde aber nicht wirklich schlau daraus. Könnte mir jemand
erklären, wie man die ISR an die richtige Stelle legt?
Danke!
Danke für die Hilfe! Leider funktioniert es immer noch nicht. Könntest
du mir mal kurz erklären was da genau passiert?
#pragma code high_vector=0x08
diese Zeile bestimmt den Ort des Vektors, an den die ISR mit höherer
Priorität verzweigt, aber wo taucht "high_vector" im Code wieder auf?
Der Controller verzweigt doch automaisch an diese Adresse und arbeitet
den Code dort ab, auch ohne diese Adresse explizit anzugeben, oder? Und
ist das ein Schlüsselwort oder kann der Vector jeden beliebigen Namen
bekommen?
void interrupt_at_high_vector(void)
{
_asm GOTO high_isr _endasm
}
Ist hier "interrupt_at_high_vector" auch ein Schlüsselwort, oder kann
auch hier die Funktion jeden beliebigen Namen bekommen?
_asm und _endasm sind für den Assemblerbefehl GOTO notwenig, oder?
was genau macht #pragma code?
#pragma interruptlow low_isr
diese Anweisung speichert die Funktion low_isr an den Vector 0x18, der
weiter oben angegeben ist, oder?
Irgendwie ist es mir noch nicht wirklich klar, wie genau die ISR an der
richtigen Adresse landet. Wäre schön wenn mir das jemand noch mal genau
anhand der ganzen Befehle erklären könnte!
Danke!
Hallo Armin,
also, IRQ Routinen brauchen etwas Extra-Code am Anfang und Ende. In
diesem werden bestimmte Register gesichert (beim Start der Routine) und
dann wiederhergestellt (beim Verlassen der Routine). Was
gespeichert/wiederhergestellt werden muss bestimmt dann der Compiler
selber. Damit dieser aber weiss was denn nun eine IRQ Routine ist, und
ob sie eine High- oder Low-Priority Routine ist, gibt es die
1
#pragma interruptlow low_isr
und
1
#pragma interrupt high_isr
Pragma's. Bzgl. der Vektoren selber gibt es noch ein weiteres Problem,
nämlich das der High-Vektor bei 0x08 und der Low-Vektor bei 0x18 liegt.
Da man in die 0x10 Worte kaum Code reinbekommt, sollte da immer eine
Sprunganweisung stehen. Also das Grundgerüst wie es Holger gezeigt hat.
Zu deinem eigentlichen Problem: Du initialisierst zwar die Timer- und
Interrupt-Register, machst das aber alles "in einem Rutsch", also durch
zuweisen von Werten für das ganze jeweilige Register. Besser man macht
es nacheinander.
Das ganze sollte eher so aussehen:
(Keine Garantie auf Fehlerfreiheit, rein aus dem Kopf gemacht grad...)
Grüße,
Chris
Edit: Achja, je nach Taktquelle des PIC kann es beim 16-Bit Timer plus
Prescaler schon recht lange dauern bis sich da was tut. Also
entsprechend TMR0H/TMR0L setzen beim Init als auch im IRQ selber (Habe
ich hier im IRQ-Code nicht gemacht). Oder aber den Prescaler
entsprechend Umkonfigurieren.
Tip: TMR0H und TMR0L Register immer am Anfang der IRQ-Routine setzen. So
hat man immer die gleiche Latenz, egal wieviel Code anschliessend noch
folgt.
Ok, erst mal vorweg, die interrupts kommen jetzt. Ich hatte leider im
ganzen wirrwarr das Richtungsregister für die LEDs auskommentiert, darum
konnten sie nicht mehr angesteuert werden. Der Code von Holger
funktioniert also auf meinem Board. Ich versuch jetzt noch mal
zusammenzufassen, was ich bisher gelernt hab:
Die Zeile
#pragma code irq_high=0x08
legt fest, dass die Funktion mit dem Namen "irq_high" an die Adresse
0x08 gelegt wird. Danach folgt die Definition dieser Funktion:
void irq_high(void)
{
_asm GOTO high_isr _endasm
}
Diese besteht im Grunde nur aus einem Sprungbefehl, da der Platz für den
Code nicht ausreicht um eine umfangreiche Funktion aufzunehmen. Die
Anweisungen _asm und _endasm deuten an, dass der Befehl GOTO als
Assemblerbefehl zu interpretieren ist. Ist das soweit richtig?
Der Sprung geht dann zu der Funktion "high_isr", die später definiert
wird.
Dazu hätte ich aber gleich noch mal eine Frage. In dem Code von Holger
steht
#pragma code high_vector=0x08
void interrupt_at_high_vector(void){...}
Es funktioniert trotzdem, obwohl die Funktionen verschiedene Namen
haben, "high_vector" und "interrupt_at_high_vector". Warum ist das so?
Auf den ersten Blick wirkt es für mich doppelt gemoppelt, zunächst die
Funktion "irq_high" an die Adresse 0x08 zu legen mit dem Sprungbefehl zu
"high_isr" und später dann nochmal diese als ISR zu kennzeichnen. Denn
der Prozessor springt ja beim Interrupt an die Adresse 0x08 und danach
dann gleich zur entsprechenden Funktion. Und tatsächlich, wenn ich die
Zeile
#pragma interrupt high_isr
weglassen, funktionert das ganze immer noch. Warum muss ich diese Zeile
dann mit rein bringen?
Und auch die Zeile
#pragma code
die da so einzeln rumschwirrt, kann ich weglassen ohne dass sich was
ändert. Du hast diese Zeile ja auch nicht drin. Wozu ist diese
notwendig?
Das waren jetzt mal wieder ne ganze Menge fragen, aber ich versuche
einfach nur zu verstehn, wie das ganze Funktionert.
Danke!
Armin schrieb:> Die Zeile>> #pragma code irq_high=0x08>> legt fest, dass die Funktion mit dem Namen "irq_high" an die Adresse> 0x08 gelegt wird. Danach folgt die Definition dieser Funktion:>> void irq_high(void)> {> _asm GOTO high_isr _endasm> }>> Diese besteht im Grunde nur aus einem Sprungbefehl, da der Platz für den> Code nicht ausreicht um eine umfangreiche Funktion aufzunehmen. Die> Anweisungen _asm und _endasm deuten an, dass der Befehl GOTO als> Assemblerbefehl zu interpretieren ist. Ist das soweit richtig?
Ja, das ist richtig.
> Dazu hätte ich aber gleich noch mal eine Frage. In dem Code von Holger> steht>> #pragma code high_vector=0x08> void interrupt_at_high_vector(void){...}>> Es funktioniert trotzdem, obwohl die Funktionen verschiedene Namen> haben, "high_vector" und "interrupt_at_high_vector". Warum ist das so?
Weil der Compiler die Code-Adresse für allen nachfolgenden Code durch
das #pragma bestimmt. Trotzdem sollte man beidem den gleichen Namen
geben, damit man auch später noch weiss das es zusammengehört. Sonst
kann man sich in komplexeren Projekten dann schnell verirren wenn man
viel Code umherschiebt/umorganisiert und dann mal ein #pragma übersehen
hat.
> Auf den ersten Blick wirkt es für mich doppelt gemoppelt, zunächst die> Funktion "irq_high" an die Adresse 0x08 zu legen mit dem Sprungbefehl zu> "high_isr" und später dann nochmal diese als ISR zu kennzeichnen.
Nein. Die Kennzeichnung als interrupt/interruptlow anstelle von code ist
hier wichtig. Schau dir mal im Disassembler den generierten Programcode
an, einmal mit #pragma interrupt und einmal mit #pragma code. Du wirst
sehen das er beim interrupt noch extra Code am Start und Ende der
Routine einfügt. Dört werden dann, je nach Bedarf, die Register für
Stackzugriff, Arbeitsregister, etc. gespeichert. Auch das return selber
ist ein anderes.
Speziell bei deinem sehr einfachen Code merkt man den Unterschied nicht,
sweil ja sonst nichts weiter gemacht wird in der main. Sobald Du aber
neben dem IRQ noch weiteren Code ausführst, wird es ganz schnell zu
Problemen kommen wenn die IRQ's nicht als solche angelegt sind.
Beispiel: Du machst eine Berechnung. Währenddessen kommt der IRQ. In dem
rechnest Du ebenfalls etwas. Wenn jetzt die temporären Register für die
Mathefunktionen, sowie der Stackzeiger nicht gesichert und
wiederhergestellt werden im IRQ, kommt nur Murks bei raus.
> Und auch die Zeile>> #pragma code>> die da so einzeln rumschwirrt, kann ich weglassen ohne dass sich was> ändert. Du hast diese Zeile ja auch nicht drin. Wozu ist diese> notwendig?
Wirklich notwendig nicht, ist aber guter Stil damit man sieht das wieder
der normale Code weitergeht. Im Normalfall betrachtet der Compiler nur
die Funktion, die direkt auf das #pragma interrupt bzw. interruptlow
folgt, als IRQ Code. Aber, es kann ja auch mal ein Bug in einer
Compilerversion sein. Oder sie ändern das. Dann sucht man sich dämlich
und weiss nicht was der Fehler ist. Das ich das in meinem Beispiel nicht
drin hatte war mein Fehler, ich hätte es mit dazupacken sollen.
> Das waren jetzt mal wieder ne ganze Menge fragen, aber ich versuche> einfach nur zu verstehn, wie das ganze Funktionert.>> Danke!
Kein Problem!
Grüße,
Chris
>> Und auch die Zeile>>>> #pragma code>>>> die da so einzeln rumschwirrt, kann ich weglassen ohne dass sich was>> ändert. Du hast diese Zeile ja auch nicht drin. Wozu ist diese>> notwendig?>Wirklich notwendig nicht, ist aber guter Stil damit man sieht das wieder>der normale Code weitergeht.
@Christian
Erstmal danke das du da so viel erklärt hast.
Dieses rumschwirrende #pragma code ist schon wichtig.
Wenn man mit diesen ganzen #pragma Anweisungen Code
an feste Adressen legt sollte man tunlichst so schnell
wie möglich wieder auf die Standard Code Section umschalten.
Bei Verwendung eines Bootloaders muss auch der Startup Code
an eine feste Adresse gelegt werden. Wenn das #pragma code
nicht dort steht wo es ist wird auch der komplette Code der
folgt ab dieser Adresse eingefügt. Das kann dann mit anderen
#pragma Anweisungen kollidieren. Spätestens beim linken knallt
es dann weil sich Code Sections überschneiden.
Vielen Dank für die vielen Infos!!
Mick M. schrieb:> All diese genannten Informationen stehen übrigens auch im C18 Compiler> User's Guide von Microchip.
Ich hab mich schon gefragt, woher ihr das alles wisst. Ich hab das
Handbuch vom Controller gelesen und die Hilfe von MPLAb durchforstet,
aber nix gefunden was mir weiter geholfen hat.
holger schrieb:> @Christian> Erstmal danke das du da so viel erklärt hast.
Ebenfalls Danke.
> Dieses rumschwirrende #pragma code ist schon wichtig.> Wenn man mit diesen ganzen #pragma Anweisungen Code> an feste Adressen legt sollte man tunlichst so schnell> wie möglich wieder auf die Standard Code Section umschalten.> Bei Verwendung eines Bootloaders muss auch der Startup Code> an eine feste Adresse gelegt werden. Wenn das #pragma code> nicht dort steht wo es ist wird auch der komplette Code der> folgt ab dieser Adresse eingefügt. Das kann dann mit anderen> #pragma Anweisungen kollidieren. Spätestens beim linken knallt> es dann weil sich Code Sections überschneiden.
Da hast Du natürlich vollkommen Recht.
Und der Vollständigkeit halber: Man muss den High-ISR natürlich nicht
unbedingt mit einem GOTO anspringen, wenn es auf jeden Takt ankommt. Da
kann man auch fies tricksen und den Bereich ab 0x08 nutzen. Man muss nur
Sicherstellen das dann bei 0x18 in dem Code das GOTO zum Low-ISR
auftaucht. Z.B. sowas hier:
Hi Leute!
Ich hab hier noch ein Problem mit dem Externen Interrupt an INT0. Ich
hab folgenden Code:
void main(void)
{
initBoard();
while(1)
{
LATAbits.LATA0 = 0;
for(i = 0; i < 5; i++);
LATAbits.LATA0 = 1;
for(i = 0; i < 500; i++);
}
return;
}
#pragma code high_vector = 0x08
void high_vector(void)
{
_asm GOTO high_isr _endasm
}
#pragma code
void high_isr(void)
{
if(INTCONbits.INT0IF)
{
INTCONbits.INT0IF = 0;
LATB = LED1;
for(i = 0; i < 100; i++);
LATB = OFF;
}
}
void initBoard (void)
{
/* Timer0 Config */
T0CON = 0x06; /* prescale 1:256 */
T0CONbits.TMR0ON = 0; /* start timer */
T0CONbits.T08BIT = 0; /* 16 Bit Modus */
T0CONbits.T0CS = 0; /* Internal Clock */
T0CONbits.PSA = 0; /* Prescaler active */
TMR0H = 0xFF; /* Timer0 High Byte */
TMR0L = 0xF0; /* Timer0 Low Byte */
/* Interrupt Config */
INTCON = 0x00;
INTCONbits.GIE = 1; /* Global Interrupt Enable High (IPEN =
1)
* or Global Interrupt Enable (IPEN = 0)
*/
INTCONbits.PEIE = 1; /* Global Interrupt Enable Low (IPEN =
1)
* or Peripheral Interrupt Enable (IPEN
= 0)*/
INTCONbits.TMR0IE = 0; /* Timer0 IE */
INTCONbits.INT0IE = 1; /* External Interrupt INT0 IE */
INTCONbits.RBIE = 0; /* PortB IE */
RCON = 0x00;
RCONbits.IPEN = 0; /* Interrup Priotity */
INTCON2 = 0x00;
INTCON2bits.TMR0IP = 1; /* Timer0 High Priority */
INTCON2bits.INTEDG0 = 1; /* Interrupt bei steigender Flanke */
INTCON3 = 0x00;
/* Port Config */
TRISA = 0x00; /* Port A Ausgang */
TRISB = 0x00; /* Port B Ausgang */
TRISBbits.RB0 = 1; /* INT0 Pin Eingang */
TRISC = 0x00;
//TRISD = 0;
PORTB = OFF; /* LEDs aus */
}
In der main-Schleife erzeuge ich immer an dem Pin RA0 eine steigene
Flanke. Auf dem Board habe ich diesen Pin mit dem INT0 Pin verbunden. In
der entsprechenden ISR soll dann beim Interrupt eine LED kurz
aufleuchten. Das mit den for-Schleifen ist natürich kein schöner Stil,
aber es ist nur zu Testzwecken ob der Interrupt kommt. Leider tut sich
nichts. Sieht jemand wo der Fehler liegt? In der Init-Datei sollten alle
Interrupts an sein und auch die Flanke an INT0 richtig einegestellt
sein.
Danke!
Sorry is bei copy,paste verloren gegangen. Vor der ISR steht noch
#pragma interrupt high_isr
Auch die includes und Prototypen hab ich noch drin, hab ich jetzt aber
der Übersichtlichkeit halber weg gelassen.
> T0CONbits.TMR0ON = 0; /* start timer */
Hmmm, wirklich ?
Wohl eher nicht... , du solltest dein Programm etwas besser selbst
durchsehen.
In wenigen Sekunden habe ich diese merkwürdige Zeile gefunden.
Frag doch mal GOOGLE: "T0CONbits.TMR0ON"
Gruss
Also wenn das LED1 immer noch mit 0x01 definiert ist, dann wird sich da
auch nichts tun. Denn das wäre Bit 0, was bei Ausgabe auf PORTB dann
eben genau der Pin ist, den Du als Eingang benutzen willst...
Woher kommt die Variable "i" in den Schleifen? Global definiert etwa?
Das dürfte daneben gehen wenn Du das dann im Program als auch im
Interrupt benutzt. So wie Du den Code gepostet hast dürfte der nicht
einmal Compilieren, sondern schon da Fehler ausgeben. Eben weil nirgends
eine Definition von "i" zu sehen ist.
Grüße,
Chris
Edit: Es ist ratsam immer den gesamten Code zu posten, am besten als
Anhang zum Beitrag, in normalem ASCII.
Also die LED1 ist bei mir die 2. LED, also mit 0x02 definiert, der Pin
fällt also nicht mit INT0 zusammen. Ich hab jetzt diese LED auch direkt
angesprochen und eine zusätzliche Zählvariable für die ISR spendiert
damit ich nicht auf das globale i zurückgreifen muss. Leider kommt der
Interrupt immer noch nicht korrekt.
Nochmal eine kleine Zusammenfassung was der Code macht.
In der Main Loop wird PIN RA0 periodisch von 0 auf 1 gesetzt um die
Flanke zu erzeugen. Dieser Pin ist als Ausgang definiert und am Board
direkt mit dem RB0/INT0 Pin verbunden. RB0 ist entsprechend als Eingang
definiert. Somit sollte dann ein Interrupt ausgelöst werden. Wenn dieser
kommt, wird in der ISR die LED1 (RB1) eingeschaltet. Leider bleibt die
LED aus was daruf hinweist, dass der Interrupt nicht ausgelöst wird.
Ich hab den kompletten Code angehängt.
Danke!
Du hast in dem INT0 nur einen ganz, ganz kleinen Loop der bis 100 zählt.
Das dürfte so schnell abgearbeitet sein das Du das kurze aufblitzen kaum
bis garnicht siehst.
Versuche es mal mit:
1
if(INTCONbits.INT0IF)
2
{
3
INTCONbits.INT0IF = 0;
4
5
LATBbits.LATB1 = !LATBbits.LATB1;
6
}
Auch den Zähler in der main, wo Du den Pin auf LATA toggelst, kannst Du
ruhig größer machen, so 5000 bis 10000.
Grüße,
Chris
Die Anzahl der Schleifendurchläufe hab ich gestestet, 100 sieht man auf
jeden Fall, sogar 20 - 50 sind noch sichtbar. Scheinbar läuft der
Prozessor auf einer niedrigen Taktzahl. Der Fehler muss irgendwo anders
liegen.
Achja, ganz vergessen: Es fehlt bei deinem Code die Initialisierung des
ADC Moduls. Standardmäßig sind die Pins, die auch als ADC Eingang dienen
können, eben als analoge und nicht als digitale Pins konfiguriert. Wenn
der PIC einen internen Komparator hat, der die Analogpins nutzen kann,
muss der auch auf digital gestellt werden. Also mal im DB zu dem PIC
nach ADC und Comparator schauen.
Nehme doch aus der IRQ routine mal die timergesteuerten LED Sachen raus,
vielleicht haste da ja ein doofes Timing erwischt. TMR0IP setzt Du auf
1, hast aber IPEN auf 0, also IRQ Prioritäten ausgeschaltet. Auch etwas
komisch.
Hast Du mal getestet wie der Code reagiert wenn Du auf RB0 selber mit
einem Draht mal 0V/5V anlegst, anstatt das einen Portpin machen zu
lassen?
Grüße,
Chris
Ich hab im DB mal was gelesen über die konfiguration von digitalen und
analogen Pins, allerdings konnte ich das entsprechende Register dazu
nicht ansprechen, warum auch immer. Darum habe ich das nicht mehr
konfiguriert.
Den Code mit den Timergesteuerten LEDs habe ich auskommentiert, meinst
du das?
Ich habe erst versucht, mit einem Draht den RB0 auf 5V zu legen,
allerdings gab es die gleiche Reaktion (nämlich garkeine) und nachdem
ich aufgrund der eng aneinanderliegenden Pins paar mal mit dem 5V Draht
gegen Masse gekommen bin, hab ich es sein lassen und es über die Pins
und eine feste Verdrahtung versucht.
Ich schau ob ich das mit den digitalen Pins hinbekomme und dann meld ich
mich hier noch mal.
Danke!
Ach ja, Timer 0 hab ich einfach mal auf high priority gesetzt, weil bei
abgeschalteter Priorität die ISR an der hochprioren Adresse gesucht
wird. Ich dachte wenn ich den auf niedrig setzte dass dann vielleicht
kein Interrupt ausgelöst wird. Dieser Teil des Codes, also die LED
ansteuerung über Timer und Interrupt hat aber funktioniert.
Sorry für die Mehrfachposts, aber ich muss doch noch was loswerden. Kann
es sein dass ich einen Eingangspin nicht mit 5V auf High schalten kann?
ich hab nämlich ein einfachstes Programm geschrieben, dass einfach nur
einen Pin auf High setzt und wartet, bis ein zweiter Eingangspin auf
High geht. Beide sind auf dem Board miteinader verbunden. Leider geht
der Eingang nie auf High.
Warum springst du so hin und her. Lass eine LED blinken erstmal ohne IRQ
und Timer.
Und eben die analogen Eingänge auf digital schalten.
Dein letztes Post, welche Pins denn ?
Holger
Also ich hatte zunächst einmal nur die LEDs blinken lassen. Im nächsten
Schritt wollte ich dann Timer und Interrupts hinzufügen. Diese haben
auch funktioniert, bis auf den externen Interrupt eben. Im Moment
versuche ich so ein Capsense zum Laufen zu bringen, wofür ich eben den
Interrupt benötige:
http://www.arduino.cc/playground/Main/CapSense
Die Pins die ich im letzten Post verwendet hatte, waren RA0 und RA1. Ich
hab das gleiche Programm mit Pins aus dem Port D verwendet und auf
einmal hats funktioniert. Es könnte also wirklich daran liegen, dass sie
als analoge Pins konfiguriert sind. Ich hatte zu Beginn auch versucht,
diese digital zu schalten, aber das Register CONFIG3H mit dem laut DB
diese Einstellung vornimmt, ließ sich nicht ansprechen.
Nochmal kurz zur Info, der externe Interrupt funktioiert mittlerweile.
Das Problem war wir schon vermutet, dass der Pin analog war. Leider war
mir nicht ganz klar, wie genau man die Konfig ändert. Im Datenblatt ist
zwar das Register erwähnt, welches zur Konfiguration dient, leider kann
man dieses nicht direkt verwenden. Statt dessen muss man diese Konfig
mit einem Pragma einstellen:
#pragma config PBAD = DIG
Mit dieser Zeile im Code hats dann auch geklappt. Im Datenblatt war es
leider nicht ganz erichtlich, wie man das genau macht. Es war nur nach
einigem experimentieren dann klar wie das geht.
Danke an alle die sich hier beteiligt haben!