Bascom Vorzeitiges Return in ISR führt zu RESET??

Jogi864

Neues Mitglied
29. Sep. 2014
15
0
1
Sprachen
  1. BascomAVR
Hallo liebe Bascom User,

ich programmiere schon eine Weile mit Bascom. Aber jetzt brauche ich mal Eure Hilfe weil ich mir das einfach nicht erklären kann.

Zur Ausrüstung: Bascom Vollversion 2077. AVRISP MK2.

Ich programmiere gerade an einer Stempeluhr die die Buchungen im Sram speichert. Daher der große Atmega 1284P. Die CPU ist aber nicht das Problem.

An Port A ist eine 12er Tastatur angeschlossen, und zwar A0=Reihe1, A1=Reihe2...A5=Spalte1, A6=Spalte2...usw.
Alle Tastaturleitungen gehen über 470 Ohm an den AVR.
Hier wird der PCINT von Port A genutzt um einen Tastendruck zu erkennen. Da PCINT aber bei jedem Tastendruck zwei mal IRQ auslöst wollte ich einen einfach "herausfiltern"
Nach meiner Kenntnis kann ich ein Unterprogramm und eine ISR jederzeit mit einem Return verlassen.

Bei dieser Variante macht der Controller einen Reset. Und zwar immer wenn MA 1 ist, also er in das vorzeitige Return reingeht.

Code:
$regfile = "m1284pdef.dat"
$crystal = 14745600
$hwstack = 128
$swstack = 128
$framesize = 128
$baud = 9600

Dim Ma As Byte
Dim Z As Byte

Ddra = &B11110000
Porta = &B00001111

Print "                      ready..."

Enable Pcint0
Pcmsk0 = &B00001111
On Pcint0 Taste

Enable Interrupts
Ma = 1

Open "com1:" For Binary As #1


Do

Incr Z
Print #1 , "Zähler:" ; Z
Print #1 , "Loop"
Print #1 , "Ma: " ; Ma
Wait 3


Loop

Taste:

Print #1 , "Jetzt in ISR"

If Ma = 1 Then
     Ma = 2
     Pcifr.0 = 1
     Return
End If

Print #1 , "Jetzt ganz in ISR"

Pcifr.0 = 1
Ma = 1

Return

Eine Erklärung hierfür habe ich noch nicht gefunden.
Aber jetzt kommts. Das hier geht:

Code:
$regfile = "m1284pdef.dat"
$crystal = 14745600
$hwstack = 128
$swstack = 128
$framesize = 128
$baud = 9600

Dim Ma As Byte
Dim Z As Byte

Ddra = &B11110000
Porta = &B00001111

Print "                      ready..."

Enable Pcint0
Pcmsk0 = &B00001111
On Pcint0 Taste

Enable Interrupts
Ma = 1

Open "com1:" For Binary As #1


Do

Incr Z
Print #1 , "Zähler:" ; Z
Print #1 , "Loop"
Print #1 , "Ma: " ; Ma
Wait 3


Loop

Taste:

Print #1 , "Jetzt in ISR"

Select Case Ma

Case 1:
      Print #1 , "Jetzt ganz in ISR"
      Ma = 2

Case 2:
      Ma = 1

End Select

Pcifr.0 = 1

Return


Kann mir einer erklären warum das so ist??

Gruß Jogi
 
Bist Du sicher, daß er 'n Reset macht, und nicht aufgrund eines Fehlers bei der Stackverwaltung (Rücksprungadresse) Amok läuft?
Ums genau zu wissen, müßte man das Reassemblieren (was ich mit der aktuellen Bascom-Version und dem aktuellen Studio nicht hinbekomme)...
Ich vermute, daß Bascom das Return im If-Block beim auflösen des IRQs nicht erkennt, und dort deswegen gerettete Register nicht korrekt wiederherstellt. Oder das nur das erste (also das im IF-Block) als ISR-Return interpretiert. Also daß noch irgend'n gerettetes Register auf dem Stack liegt, und beim Return als Rücksprungadresse (werweißwohin)verwendet wird.

Hintergrund: RET(urn) als Assembler Befehl nimmt 2 Bytes vom Stack, interpretiert die als Adresse (im ProgramFlash), und springt dahin. RETI macht dasselbe und schaltet gleichzeitig die Interrupts scharf.

Bascom übersetzt ein Return entsprechend als RET oder RETI, wenn es sich um eine ISR handelt, und da liegt das Problem - Bascom muß wissen, daß es sich dabei um eine solche handelt, das Return eine ISR abschließt. Beim Eintritt in eine ISR werden nämlich diverse Register und das SREG von Bascom auf den Stack gesichert, und vor dem ISR-Return wiederhergestellt (also auch wieder vom Stack genommen), bevor der Rücksprung (RETI) erfolgt...

Wenn das auftretende Return aber nicht als Bestandteil der ISR erkannt wird, erfolgt ein Sprung an die, als Adresse interpretierte Zahl der zuletzt gesicherten Register im Stack (woauchimmerdasseinmag - wahrscheinlich irgendwo "hinter" dem Code, also im bereich wo nur noch NOPs stehen. dann macht er solange nichts bis der Programmcounter überläuft, und fängt (wie bei einem Reset aber ohne Reinitialisierung der I/O-Register) quasi wieder bei Adresse0 an)...
 
Hallo LotadaC,

danke für die Antwort. Und wenn ich Sie noch zehn mal lese - von wirklich verstehen kann keine Rede sein.
Das er das Return in der IF Anweisung falsch interpretiert liegt nahe

"Bist du sicher das er einen Reset macht?"

Nein, sicher bin ich mir da nicht. Ich sehe nur an dem Print " Ready" das er wieder von vorne losgeht.
Das der Stack da durcheinander kommt kann ich mir nicht vorstellen. Der ist ja groß genug. Und sollte eigentlich leer sein.

Habe aber gerade die Bascom Online Hilfe durchsucht. Dort verlassen sie eine ISR vorzeitig mit " Exit Sub".
Werde das gleich mal testen.

Wenn alles fertig ist, kommt eine Tastaturabfrage mit Interrupt heraus. Ohne Schnikschnak.


Gruß Jogi
 
Hallo Jogi,

danke für die Antwort. Und wenn ich Sie noch zehn mal lese - von wirklich verstehen kann keine Rede sein.
Das er das Return in der IF Anweisung falsch interpretiert liegt nahe

das ist der Unterschied zwischen Hochsprache (Basic) und Maschinensprache (Assembler). Bei Assembler weiß man wirklich was er macht da man es selber zusammengedreht hat. Bei einer Hochsprache weiß man ncht wirklich was für Befehle der Controller nun abbekommt. Außer man reassembliert den Hexcode und sieht sich dann die Maschinenbefehle an :p

Die Erklärung von LotadaC ist auf jeden Fall plausibel.

Bei Assembler (was der Controller ja eigentlich ausführt) gibt es zwei Return-Befehle:
RET = Return from Subroutine
RETI = Return from Interrupt Subroutine

Die machen beide quasi das selbe. Bis auf die Freischaltung der Interrupts die in der ISR ja global gesperrt sind. Das macht nur der RETI. Der RET schaltet die Interrupts nicht frei.

In einer Subroutine mußt du alle möglichen Register auf den Stack sichern. Das sind die "Speicher" mit denen in der Hauptroutine gearbeitet wurde. Da ja nun eine Subroutine durch den Interrupt angesprungen wird müssen die Infos aus der Hauptroutine auf den Stack gerettet werden um danach an der Stelle wieder weitermachen zu können. Am Ende der Subroutine müssen die geretteten Infos aber auch wieder vom Stack geholt werden.

Bascom nimmt für normale und auch für die ISRs nur den Befehlsnamen RETURN. Es erkennt anhand des Einsprungs ob es sich um eine ISR oder eine normale Subroutine handelt und compiliert dann entweder ein RET oder ein RETI in den Hexcode. Das sichern (push) und wiederherstellen (pop) der Arbeitsregister wird auch noch reincompiliert ohne das du davon was siehst.

Mach doch mal bei dem ersten RETURN einfach ein GOTO zu einem Label der direkt vor dem zweiten RETURN liegt.
Code:
Taste:

Print #1 , "Jetzt in ISR"

If Ma = 1 Then
     Ma = 2
     Pcifr.0 = 1
     GOTO Isr_ausgang      ' Return (erstes)
End If

Print #1 , "Jetzt ganz in ISR"

Pcifr.0 = 1
Ma = 1

Isr_ausgang:
Return    ' das zweite

Damit hast du das "Erkennungsproblem" von Bascom umgangen. Das sind dann so die Lösungen wo ein GOTO mal erlaubt ist :p

Gruß
Dino
 
Beim Eintritt in eine ISR wird zuerst die aktuelle Adresse als Rücksprungadresse auf den Stack gepackt, und dann der Interruptvektor (in der Hardware fest vorgegeben) angesprungen. Bascom platziert dort mit "On Interruptname ISR" die Sprunganweisung in die eigentliche ISR. In der ISR selbst sichert Bascom als erstes einige Rechenregister und das Statusregister auf den Stack, die liegen also auf der Rücksprungadresse. Dann folgt Dein Code in der ISR. das nächste Return wird vom Parser als Return from Interrupt interpretiert, Bascom übersetzt das also zu: erst Statusregister und Rechenregister wieder aus dem Stack wiederherstellen (also vom Stack nehmen), dann die Adresse und unter Interruptfreigabe dahin zurückspringen.

Das andere Return wird vom Parser nicht mehr der ISR zugeordnet, also nur zu "nimm 2 Bytes vom Stack als Adresse und spring dahin" übersetzt.
Landest Du also zur Laufzeit bei diesem Return, wurden vorher zwar Rücksprungadresse und Register auf den Stack gepackt, vor dem Rücksprung aber die Register nicht runtergenommen -> der Rücksprung geht woandershin und die eigentliche Adresse verbleibt auf dem Stack.

Überall dort im Flash, wo Du (und Bascom) keinen Code platzierst, steht NOP - No Operation. Der Controller setzt einen Takt lang aus. Insbesondere alle "Zeilen" hinter Deinem Programm. Wenn der also mit dieser falschen Sprunganweisung dort landet, arbeitet der Controller die ganzen NOPs ab (wartet), irgendwann läuft der Programmzähler auf 0 über, das Programm beginnt von vorn. (Ähnlich einem Reset, aber es ist keiner)
 
Hallo Dino03,

vielen Dank für die Antwort. Ich habe jetzt gelernt das Return nicht gleich Return ist. Da muss man erst mal drauf kommen. (Bei Assembler wird es bei mir ganz dunkel).
Die Lösung mit dem Goto in ein anderes Label funktioniert wenn das Goto auch wirklich in ein anderes Label weist. Sonst bleiben da Daten auf dem Stack, denke ich mal.

@LotadaC

ich war immer der Meinung das in so einem Fall wie hier (if... then... je nach dem wie Variable Ma gerade steht) entweder der eine oder der andere Weg "abgearbeitet" wird. Folglich
könnte die CPU auch immer nur auf eins der beiden Returns treffen. Und müsste dies auch als reti einstufen. Aber man lernt ja dazu :)
Bei MCS hat man mich nach meiner Frage an die Hilfe "on Interrupt" verwiesen. Nach dem ist es so, das wenn das Return in einer Condition steht wird es als RET interpretiert. Erst ein Return außerhalb von Conditions wird als RETI interpretiert.

Jetzt muss ich meine diversen Programme durchforsten....

Also vielen Dank Euch noch mal
und ich werd's auch nicht wieder machen. (Das mit dem Return)

Gruß Jogi
 
PS:

hab es jetzt so gemacht: (Ma als Bit def.)

Code:
Taste:

Print #1 , "Jetzt in ISR"

If Ma = 1 Then

   Print #1 , "Jetzt ganz in ISR"

End If

Pcifr.0 = 1
Toggle Ma

Return

...und es funktioniert.
 
...
ich war immer der Meinung das in so einem Fall wie hier (if... then... je nach dem wie Variable Ma gerade steht) entweder der eine oder der andere Weg "abgearbeitet" wird. Folglich
könnte die CPU auch immer nur auf eins der beiden Returns treffen. Und müsste dies auch als reti einstufen.
...
Der Knackpunkt ist, daß die MCU nie auf ein Return oder sonstwelchen Bascom-Code trifft, sondern immer auf irgendwelchen, zur Laufzeit feststehenden assembleräquivalenten Maschinencode.

Der Parser von Bascom versucht aus Deinem Bascom-Text diesen Assembler-äquivalenten Maschinencode zu erzeugen, er muß also (auch) das Return zu irgendwas übersetzen. Egal, wann das zur Laufzeit dann auftritt (oder ob überhaupt).
Und woran unterscheidet der Parser jetzt in Deinem Text ob es sich um ein Interrupt-Return oder irgendein anderes Return (*) handelt? Ich denke, wenn irgendein Label durch "On Interruptname Ziel" angesprungen wird, interpretiert Bascom diesen Label als Beginn einer ISR, macht das nächste Return auf das er beim parsen trifft zum Interrupt-Return. Damit ist für ihn die ISR erledigt, verzweigst Du nun in der ISR mit mehreren Interrupt-Returns, wird das vom Parser nicht erkannt.

(*) RET wird auch zur Rückkehr aus Subroutinen genutzt, generell kann man auch irgend'ne Adresse auf den Stack packen, und mit RET dahinhopsen (Damit kann man zur Laufzeit variabel weit springen lassen ohne IJMP (indirekt jump) zu verwenden (wenn der Controller zB IJMP nicht kann, oder man den Z-Pointer dafür nicht verwenden kann/will).
 
Hi Jogi,

Die Lösung mit dem Goto in ein anderes Label funktioniert wenn das Goto auch wirklich in ein anderes Label weist. Sonst bleiben da Daten auf dem Stack, denke ich mal.

:confused::confused: da weiß ich jetzt nicht was du mit dem Satz sagen willst :confused::confused:
Das GOTO ist dafür da um den Programmbereich zu überspringen, der hinter deinem ersten Return war und den du ja durch dieses erste Return nicht ausführen lassen willst. Da du aber nun nur noch ein Return am Ende der ISR hast mußt du ja irgendwie an diese Ende mit dem Return gelangen und zwar ohne den bis dahin existierenden Rest auszuführen. Dafür gibt es dann dieses GOTO. Das GOTO benötigt natürlich auch ein Sprungziel (also diesen Label) beim Return.

Du kannst natürlich statt dem Label auch die Speicheradresse angeben. Zum Beispiel GOTO &H326 . Da du aber nicht wirklich weißt welche Flash-Adresse dieses Return hat das du anspringen willst, benutzt du statt dessen ein Label. Der Name des Labels wird dann beim GOTO-Befehl (nach dem compilieren in Assembler ist das dann ein Jump) durch die Adresse ersetzt an dem sich der Label im Programmablauf befindet. In deinem Fall durch die Flash-Adresse an dem sich der Bereich befindet der aus dem compilierten RETURN wurde.

Gruß
Dino
 
...
Ich vermute, daß Bascom das Return im If-Block beim auflösen des IRQs nicht erkennt, und dort deswegen gerettete Register nicht korrekt wiederherstellt
...
Wenn das auftretende Return aber nicht als Bestandteil der ISR erkannt wird, erfolgt ein Sprung an die, als Adresse interpretierte Zahl der zuletzt gesicherten Register im Stack (woauchimmerdasseinmag - wahrscheinlich irgendwo "hinter" dem Code, also im bereich wo nur noch NOPs stehen. dann macht er solange nichts bis der Programmcounter überläuft, und fängt (wie bei einem Reset aber ohne Reinitialisierung der I/O-Register) quasi wieder bei Adresse0 an)...

...
Die Erklärung von LotadaC ist auf jeden Fall plausibel.

Bei Assembler (was der Controller ja eigentlich ausführt) gibt es zwei Return-Befehle:
RET = Return from Subroutine
RETI = Return from Interrupt Subroutine

Die machen beide quasi das selbe. Bis auf die Freischaltung der Interrupts die in der ISR ja global gesperrt sind. Das macht nur der RETI. Der RET schaltet die Interrupts nicht frei.
...

Bascom Onlinehilfe schrieb:

Interessant ist übrigens auch der Satz danach:
While the label is supported because the old GW-BASIC supported it, it is best to use a Sub routine which you can end with End Sub.
Hat das schon wer von den Bascom-Experten getan? Cassio? Markus?
 
Das wird doch immer so gemacht.
Früher, als die PCs noch Monochrom Monitore hatten, da war Basic (wie GW-Basic) etwas anders.

Code:
10: GOTO 100
20: PRINT "Blubb"
30: GOTO 10
100: BEEP
110: GOTO 20

Hatte aber den Nachteil dass es sehr unübersichtlich war und wenn man etwas ändern wollte schnell mit den Zeilennummern, welche fortlaufend sein mussten, in die Enge kam.

Heute:
Code:
Do
  Call MakeNoise()
  Print "Blubb"
Loop

Sub MakeNoise()
  BEEP
End Sub
ist es leichter lesbar und erweiterbar. Der Code ansich tut das selbe.
 
Jain... es ging mir nicht um das allgemeine ersetzen von goto's/labeln zu call's/subs, sondern um den konkreten Fall "On interrupt"
Hintergrund ist ja, daß das Interruptevent bereits die Rücksprungadresse auf den Stack packt, in der IVT muß also zwangsweise ein Sprung (und kein Call) erscheinen (wenn man von Hardcore-ISRs direkt in der IVT absieht->ASM).
Wenn "On interrupt" ein simples Label (Name:)als Ziel hat, wird das erste Return (ausserhalb einer condition) zum RETI, auf das Registerretten geht die Hilfe nicht konkret ein. Meiner Meinung nach muß es so sein, daß direkt hinter einem Label welches mit "ON Interrupt" angesprungen werden kann die üblichen Register (Nosafe, all...) gerettet werden, und im korrespondierenden Return (das RETI) wiederhergestellt.
Springt man dieses Label ohne Interrupt mit einem Gosub an, werden eben auch die Register gerettet/wiederhergestellt (was nicht stört, nur zeit und stack kostet), beachtet werden muß nur, daß nebenbei am Ende die Interrupts freigegeben werden (da RETI).

Daß "On Interrupt" auch zu einem mit "sub Name" generiertem Ziel springen kann (deklarierte Sub), ist mir neu.
Dann wird (wahrscheinlich) jedes Return als RET interpretiert (gosubs innerhalb der Sub), RETI und Registerwiederherstellen erfolgt mit "End Sub".
"Exit Sub"s springen dann (goto) dorthin, womit auch in diesen Fällen die Register korrekt wiederhergestellt werden.

Ist das bei den Subs jetzt auch so, daß eine Sub die Ziel eines "On Interrupt" ist immer Register rettet/wiederherstellt (nosafe, all) und Interrupts freigibt?
 
Das war von mir jetzt generell gehalten wegen dem
While the label is supported because the old GW-BASIC supported it, it is best to use a Sub routine which you can end with End Sub.
(jetzt schreib ich schon [end quote] -.-)

Ich glaube die meinten diesen Unterschied damit :)
 
...Früher, als die PCs noch Monochrom Monitore hatten, da war Basic (wie GW-Basic) etwas anders...
Kommt drauf an, ich hab vor gut 25 Jahren an'nem 10MHz-80086 (mit HCG-Grafikkarte) mit Turbo BASIC (und später Power BASIC) programmiert. Das waren bereits Compiler(!), die eben auch Subroutine-Calls kannten. Damals konnte man übrigens noch direkt im Grafikspeicher "rummachen" (-> Poke), Wenn man die Basisspeicheradresse der Grafikkarte kannte (0xB0000 beim Hercules, 0xA0000 beim CGA, für den B310+ hab ich hier noch'n Fetzen im Handbuch gefunden, wo ich 'n definiertes Speichersegment bei 0xB800 nutzen wollte - da kann ich mich jetzt aber konkret nicht mehr dran erinnern...
Konnte man übrigens auch inline-Maschinencode miteinbauen ;)
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)