Forum: Mikrocontroller und Digitale Elektronik Interrupt an PIC16F886


von R. S. (frederenke)


Angehängte Dateien:

Lesenswert?

Hallo,
ich habe ein Problem mit dem genannten PIC.
Am Pic sind nur Stromversorgung und an den PORTB Pins je eine LED mit 
Vorwiderstand (außer an RB0 natürlich)
Im Anhang mein Quelltext. Es wird eine Dauerschleife aufgerufen in der 
RB5 blinkt. Beim Interrupt sollte RB7 aufleuchten.

Wenn ich high oder low an RB0 anlege, passiert einfach nix.

Übersehe ich was? Ich bin recht neu in dem Thema - vielleicht hab ich ja 
was grundlegendes falsch verstanden.....

von holger (Gast)


Lesenswert?

Was passiert wenn du

  call  write_ports

aus dem Interrupt raus nimmst?

von holger (Gast)


Lesenswert?

>Was passiert wenn du
>
>  call  write_ports
>
>aus dem Interrupt raus nimmst?

Vergiss das.

Wieso so umständlich?
Setz PB7 doch direkt.

von holger (Gast)


Lesenswert?

Von w_temp solltest du ausser im Interrupt die Finger
weglassen. Das ist ausschliesslich zum retten des
W Registers im Interrupt da.

von R. S. (frederenke)


Angehängte Dateien:

Lesenswert?

Danke für deine Hilfe.
Das write_ports benutze ich, weil ich immer Probleme mit meinen Ports 
hatte. Wenn ich die Bits des Ports einzeln setze, werden die anderen 
immer genullt.
Ich habe nun aber den angehängten Code ausprobiert. Da wird in dem 
Interrupt kein write_ports mehr aufgerufen und es wird stattdessen 
gewartet. Zudem gibt es nun ein zweites W_temp. Aber es funktioniert 
nicht.

Also wenn in dem init Programm nichts falsch ist, muss ich langsam davon 
ausgehen, dass der PIC kaputt ist. Ich werde mir heute mal einen neuen 
besorgen.

von nicht holger (Gast)


Lesenswert?

Das Interrupt Flag INTCON,RBIF muss am Ende der Interrupt Routine noch 
gelöscht werden.

von W.S. (Gast)


Lesenswert?

R. S. schrieb:
> Übersehe ich was?

Ja. Also mal was zur äußeren Form: man braucht nicht ORG 0x000 zu 
schreiben, ORG 0 reicht aus, dito ORG 4.

Bei Unterprogrammen solltest du dir angewöhnen, einen Kommentar zu deren 
Funktion davor zu schreiben. Das hilft auch dir nach nem halben Jahr.
Ansonsten gilt für die Interrupt-Routine folgendes:
1. Register muß man retten, wenn sie in der Interrupt-Routine benutzt 
werden, sonst nicht.
2. Vorsicht beim Retten und Restaurieren von STATUS, da ist angesagt, 
nicht nachträglich den Status (insbesondere Zero-Flag) zu versauen.
3. Interrupt-Signale, die zu einem Interrupt geführt haben, müssen auch 
wieder gelöscht werden.

Ansonsten würde ich GIE zu allerletzt setzen, nachdem ich die Peripherie 
eingerichtet, benötigte Interrupts freigegeben und vorsorglich gelöscht 
habe, damit mir kein von zuvor stehengebliebenes Interrupt-Signal in die 
Quere kommt.

Und zuletzt: Den tieferen Sinn deines Unterprogramms "write_ports" 
verstehe ich nicht. Was soll das bitte?
    movwf  w_temp         ; aha, W retten, wo auch der Int W hinrettet?
    movf  PORTB_SHDW,w    ; Variable nach W
    movwf  PORTB          ; W nach Port
    RETLW  w_temp         ; Adresse von Variable nach W ???

Früher gab es mal Versuche mit dem Programm "crashme", die bestanden im 
Wesentlichen darin, einen Bereich im RAM mit Zufallszahlen zu füllen und 
hineinzuspringen. Gewertet wurde, ob das OS das aushält, ohne selbst 
abzustürzen. Crashme mit ner allgemeinen Schutzverletzung abzuwürgen war 
erlaubt. Du betreibst hier was ähnliches.

W.S.

von GroberKlotz (Gast)


Lesenswert?

Hallo R.S.

Dein PIC 16F886 ist wohl nicht kaputt, dafür aber Dein Code! Einiges 
haben ja schon andere kommentiert. Hier noch mein "Senf" dazu:

In der ISR solltest Du das nunmehr gesetzte Interrupt-Flag wieder 
löschen.
        bcf   INTCON,INTF
und am Ende der ISR, wenn der Interrupt wieder aktiv sein soll den 
automatisch gesperrten Interrupt mit
        bsf   INTCON,GIE
wieder zulassen.

Zum Interrupt, ausgelöst durch steigende/fallende Flanke Sind diese 
Register/Bits korrekt, ungefähr in dieser Reihenfolge zu initialisieren:

Init
       bcf       INTCON,GIE  ;Alle Interupts abschalten
;---------PORTB
       clrf      PORTB
       banksel   TRISB
       movlw     0x01    ;RB0 als Eingang!
       movwf     TRISB
       bsf       STATUS,RP0
;---------INTCON    S. 33 Interrupts S.222
  bsf  INTCON,PEIE   ;b6 Peripheral Interrupt Enable
  bsf  INTCON,INTE   ;INT External Interrupt Enable (RB0)
  bcf  INTCON,INTF   ;Clear Flag INT-interrupt occurred
                              ;(must be cleared in software
;---------OPTION_REG;
  banksel  OPTION_REG
  bsf  OPTION_REG,INTEDG ;b6=1 Interrupt on rising Edge
          ;0/1 falling/rising edge
  bsf  STATUS,RP0    ;bank 0
  ;
Main
         bsf  INTCON,GIE    ;Interrupt global enabled
Main_Loop
     ;
     ;irgendein Programmcode bei dessen Ausführung irgendwann mal
     ;eine steigende oder fallende Flanke an RB0 auftreten kann.
     ;
GOTO Main_Loop

Subroutinen........

END

P.S. Schau doch mal die Beispielprogramme bei www.sprut.de an, dann 
fällt es Dir wie Schuppen von den Augen!

weiterhin viel Spass
GroberKlotz

von R. S. (frederenke)


Lesenswert?

Vielen Dank für die Hilfe aller Beteiligten.
Der Code von GroberKlotz funktioniert soweit. Sollte mal jemand das 
gleiche Problem haben; in dem Code müssen an den Stellen mit Statusbits 
die Bits gelöscht und nicht gesetzt werden. Ansonsten läuft es aber so.
Eine Frage hätte ich noch; bei Sprut steht, dass jede Interrupt Routine 
mit RETFIE beendet werden muss, weil durch das RETFIE Kommando das GIE 
gesetzt wird und die Adresse des Aufrufes in den PC geschrieben wird.
Nun meine Frage; ist es möglich an eine andere Stelle zu springen, indem 
ich GIE manuell setze und ein GOTO verwende? Oder muss ich definitiv 
zurück an die Stelle wo der Interrupt eingesprungen ist?
Danke!

von GroberKlotz (Gast)


Lesenswert?

R. S. schrieb:
> Eine Frage hätte ich noch; bei Sprut steht, dass jede Interrupt Routine
> mit RETFIE beendet werden muss, weil durch das RETFIE Kommando das GIE
> gesetzt wird und die Adresse des Aufrufes in den PC geschrieben wird.
> Nun meine Frage; ist es möglich an eine andere Stelle zu springen, indem
> ich GIE manuell setze und ein GOTO verwende? Oder muss ich definitiv
> zurück an die Stelle wo der Interrupt eingesprungen ist?

NEIN!

Da hast Du was Prinzipielles mißverstanden! Lese doch bitte das 
Datenblatt zum 16F886, da hast Du alle Infos aus erster Hand! Befasse 
Dich auch damit wozu es einen Stack gibt wie "tief" dieser ist, ob das 
Last in - First Out Prinzip angewandt wird usw...

Zitat (Datenblatt S.220) zum Interrupt
.....
The Return from Interrupt instruction, RETFIE, exits
the interrupt routine, as well as sets the GIE bit, which
re-enables unmasked interrupts.
.....

When an interrupt is serviced:
• The GIE is cleared to disable any further interrupt.
• The return address is pushed onto the stack.
• The PC is loaded with 0004h.

Im Datenblatt erhältst Du praktisch alle Antworten auf Deine Fragen. 
Ganz am Ende gibt es sogar einen Index mit Stichworten...

mfG GroberKlotz

von R. S. (frederenke)


Lesenswert?

Hallo,
ich dachte das mit dem Stack hätte ich soweit verstanden. Ich dachte 
bisher, der 8 Ebenen (für meinen Fall) tiefe Stack wird immer dort 
ausgelesen, wo er zuletzt „bespeichert“ wurde. Beim nächsten Speichern 
wird einfach die nächste Speicherstelle im Stack verwendet und eben auch 
diese nächste beim nächsten Auslesen ausgegeben.
Dementsprechend dachte ich, dass es kein Problem sein sollte den Stack 
nach dem Beschreiben (was beim Auslösen des Interrupts geschieht) NICHT 
auszulesen, indem man am Ende der ISR kein RETFIE verwendet sondern ein 
GOTO setzt. Die im Stack abgelegte Adresse verweilt dann halt dort – 
stört doch nicht; zumindest dachte ich das. Ich war mir halt nicht 
sicher ob ich etwas Wichtiges übersehe, was mit RETFIE passiert, und mit 
meiner GOTO Lösung nicht. Deshalb die Frage im letzten Beitrag.
Warum diese Lösung nicht geht, verstehe ich dann auch nicht nachdem ich 
das Datenblatt an den entsprechenden Stellen nochmals gelesen (und 
hoffentlich auch verstanden habe) und zudem noch etwas in Netz geforscht 
habe.
Entschuldigt, dass ich vielleicht etwas unbeholfen rüber komme – aber 
glaubt mir, dass ich mir zunächst immer Mühe gebe die Probleme selbst zu 
lösen. Sei es mit Kontroll LED’s in meinem Programm oder mit einer 
Recherche.

Kleine Info am Rande; der RB0 des 16f886 war wirklich defekt. Ich habe 
den Code vor meinem letzten Beitrag zu einem 16F628A portiert, wo er 
tadellos läuft.

von GroberKlotz (Gast)


Lesenswert?

Hallo R.S.

Meine Antwort "NEIN" bezog sich auf Deine Frage:
>..meine Frage; ist es möglich an eine andere Stelle zu springen, indem
> ich GIE manuell setze und ein GOTO verwende?

Möglicherweise habe ICH dabei die Frage so missverstanden... "Springen 
durch manuelles setzen von GIE :-("

Solange die ISR nicht beendet ist, kannst du von dieser aus überall 
herumspringen, Unterprogramme aufrufen usw. und danach mit RETFIE oder 
auch RETURN an der Stelle im Code nach dem Auftreten des Interrupts 
weiterarbeiten. GIE kannst Du setzen/löschen wann Du willst.

Hoffe zur Klarheit hiermit beigetragen zu haben.

mfG GroberKlotz

von K. J. (Gast)


Lesenswert?

Hi hab mal ne frage zu dem von beispiel von GroberKlotz, fragt ihr 
sicherheitshalber nicht noch ab welches ISR bit ausgelöst hat ?

Meine lösung:
1
int  ; Interupt STATUS und W sichern
2
  movwf  wtemp      ; W Sichern
3
  swapf  STATUS, w    ; Status Sichern
4
  movwf  stemp      ; Status Speichern
5
  banksel  PIR1
6
7
  btfss  PIR1, RCIF    ; Interupt vom USART wenn nicht
8
  goto  intend      ; Interupt beenden
9
10
  Interupt Code uart
11
  banksel  RCSTA
12
  movf  RCSTA      ; Uart echo
13
  call  uart_send
14
  call  uart_ferr    ; Auf Frame Errors Testen
15
  call  uart_oerr    ; Auf Overrunerrors Testen
16
17
intend  ; Interupt beenden und STATUS + W zurückspielen
18
  banksel  PIR1
19
  bcf  PIR1, RCIF    ; Interuptflag Löschen
20
  swapf  stemp, w    ; Status restore
21
  movwf  STATUS
22
  swapf  wtemp, f    ; W restore
23
  swapf  wtemp, w
24
  retfie

von GroberKlotz (Gast)


Lesenswert?

Na ja, es ging hier ja lediglich um den alleine eingerichtete RB0/INT 
und nicht um mehrere Interruptquellen. Klar dass bei mehreren 
INT-Sources dann die entsprechenden  Flags abzufragen sind, um auf den 
aktuellen Interrupt richtig reagieren zu können.

mfG GroberKlotz

von R. S. (frederenke)


Lesenswert?

GroberKlotz schrieb:
> Solange die ISR nicht beendet ist, kannst du von dieser aus überall
> herumspringen, Unterprogramme aufrufen usw. und danach mit RETFIE oder
> auch RETURN an der Stelle im Code nach dem Auftreten des Interrupts
> weiterarbeiten. GIE kannst Du setzen/löschen wann Du willst.
>
> Hoffe zur Klarheit hiermit beigetragen zu haben.
>
> mfG GroberKlotz

Hallo,
das ist nicht ganz was ich meinte. Ich wollte wissen, ob ich die ISR 
wirklich mit RETFIE beenden muss, oder ob es genügt GIE zu setzen. So 
könnte ich nach der ISR direkt in einen anderen Programmteil springen. 
Ich würde so nicht zu der Stelle zurückspringen, wo der Interrupt 
eingesetzt hat.
Geht das?

von stepp64 (Gast)


Lesenswert?

Wenn du die Interruptoutine beenden willst, dann geht das nur mit 
RETFIE. Innerhalb der Routine kannst du natürlich hinspringen wo du 
willst, aber beenden kannst du sie nur mit RETFIE. Überleg doch mal: Ein 
Interrupt kann an jeder beliebiger Stelle im Hauptprogramm auftreten. 
Irgendwie muss die CPU sich doch merken an welcher Adresse sie gerade 
war, bevor der Interrupt eingetreten ist bzw. eigentlich merkt sich die 
CPU die Adresse des nächsten Befehls im Hauptprogramm der kommen würde, 
wäre nicht der Interrupt dazwischen gekommen. Diese Adresse wird im 
Stack abgelegt und der PC mit 0x04 überschrieben. Damit macht die CPU 
bei 0c04 weiter. Am Ende der ISR kommt dann das RETFIE damit lädt die 
CPU den letzten Wert des Stacks in den PC und springt damit genau an die 
Stelle, wo er vor Eintritt in die ISR gewesen war. Wenn die die ISR mit 
RETFIE nicht beendest wird dein Stack überlaufen und das Programm sich 
verabschieden.

Gruß
Sven

von Dieter W. (dds5)


Lesenswert?

stepp64 schrieb:
> Wenn die ISR nicht mit RETFIE endet wird dein Stack überlaufen und das
> Programm sich verabschieden.

Selbst wenn man den Stack manuell bereinigen würde (geht bei PICs mit 
Hardware-Stack allerdings nicht) ist es in den meisten Fällen unsinnig, 
das Programm bei einer festen Adresse fortsetzen zu wollen, ohne zu 
wissen was vorher als Letztes gemacht wurde.

von R. S. (frederenke)


Lesenswert?

Ok, das mit dem Überlauf ist natürlich blöd, wenn das Zwangsweise zum 
Absturz führt.
Ich dachte aber, dass immer die nächste Zelle im Stack geschrieben wird 
und wenn man bei der letzten ankommt, wieder die erste geschrieben wird. 
Wie kann das zum Überlauf führen? Oder ist meine Annahme falsch?

von stepp64 (Gast)


Lesenswert?

OK. Hab gerade noch mal nachgelesen. Überlauf ist nicht das richtige 
Wort gewesen. Der Stack ist tatsächlich als Ringpuffer ausgelegt. Sobald 
du ein 9. Mal eine PUSH Operation durchführst (also CALL oder Interrupt) 
wir der 1. Wert des Stacks überschrieben. Damit kommst du aber auch nie 
wieder an diese Stelle im Programm zurück. Spätestens wenn du das 9. mal 
aus der ISR oder dem UP zurück springen willst wird dein Programm 
komische Sachen machen, da es nicht an die von dir erwartete 
Rücksprungadresse springen wird.

Allerding versteh ich nicht, was dein Problem ist. Eine ISR ist ein 
Unterprogramm, welches durch ein Ereigniss (Interrupt) ausgeführt wird. 
Im allgemeinen springt man aus einem Unterprogramm irgendwann wieder in 
das Hauptprogramm und arbeitet dort weiter. Eine ISR aufzurufen aus der 
du nicht wieder zurück kommst macht doch gar keinen Sinn.

Zum GIE: Dieses BIT erlaubt/verbietet lediglich die Ausführung von 
Interrupts. Es beendet nicht die ISR! Die kannst in einer laufenden ISR 
durchaus das GIE wieder ein schalten, allerdings wirst du daran keine 
Freude haben, da verschachtelte Interruptroutinen bei diesem PIC nicht 
vorgesehen sind.

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.