Hallo,
der 8051 hat zwei Interrupt Prioritäten, low and high.
Damit könnte es eine L1 Interrupt Rotuine mit low prio und eine zweite
L2 Interrupt Routine mit high prio geben. Die L2 Interrupt Routine kann
die L1 Interrupt Routine unterbrechen.
Gibt es beim 8051 die Möglichkeit zu erkennen ob man jetzt in der L1
oder L2 Interrupt Routine gerade ist? Bei allen anderen CPUs war das bis
jetzt kein Problem, entweder man konnte es im Program Status Word oder
in irgendeinem Interrupt Register erkennen aber beim 8051 finde ich
keine Lösung.
Hallo Guest,
die Anforderungsbit des UART bleiben bei Eintritt in die ISR (Interrupt
Service Routine) erhalten, sie müssen immer per Software gelöscht
werden.
Für die restlichen Interrupts ist der Vorschlag von Oliver doch
geeignet!
Gruß. Tom
Danke, aber ein Flag setzen hilft nicht, da nicht sichergestellt ist das
nicht vor dem Setzen des Flags im L1 Interrupt dieser nicht schon durch
den L2 Interrupt unterbrochen wurde.
Letztlich geht es darum das beide Interrupts die gleiche Funktion
aufrufen und ich in dieser erkennen muss, ob der Aufruf aus dem L1
Interrupt kam.
Dann bleibt noch die benötigten Interrupts zu sperren und per Software
zu pollen. Dabei bleiben die Anforderungsflags erhalten.
Oder setz beide Int's auf gleiche Priorität und gib den 2. innerhalb der
1. Routine frei - nach setzen eines Flag?
Danke für die Ideen, aber leider alles so nicht umsetzbar.
Letzlich geht es um ein RTOS bei dem nur aus dem L1 Interrupt ein
Taskwechsel ausgeführt werden darf. Ich habe keinen Einfluß darauf was
der Anwender in seiner Interrupt Routine macht.
Bis auf wenige Ausnahmen (Maxim) sind diese Flags beim 8051 nicht
zugreifbar.
Einige 8051 (Atmel) haben auch 4 Prioritäten.
Ich wüßte jetzt nicht, warum man in Interrupts hoher Priorität das RTOS
aufrufen sollte.
Die Interrupts sollten ja so kurz wie möglich sein. Funktionsaufrufe,
die nicht inline sind, bewirken aber erstmal eine riesige
Push/Pop-Orgie.
Bastler schrieb:> Doch, Du schreibst ihm einfach ins Handbuch, was er zu machen hat.> Andere OSe schreiben auch vor, wie eine ISR vorzugehen hat.
Das hilft leider nicht.
Stell die vor du rufst in beiden ISR eine Funktion StartInt() auf, die
letztlich einen Zähler inkrementiert. Anhand dieses Zählers könntest du
erkennen ob du im L1 oder L2 Interrupt bist...wenn nicht das Problem
wäre das der L2 Interrupt schon ausgeführt werden könnte bevor der erste
Befehl im L1 Interrupt ausgeführt wurde. Hilft also nicht.
>Ich wüßte jetzt nicht, warum man in Interrupts hoher Priorität das RTOS>aufrufen sollte.>Die Interrupts sollten ja so kurz wie möglich sein. Funktionsaufrufe,>die nicht inline sind, bewirken aber erstmal eine riesige>Push/Pop-Orgie.
Das die Performance beim 8051 bescheiden ist steht außer Frage.
Aber stell dir z.B. vor der Systick Timer läuft mit low prio und der
Uart Interrupt läuft mit high prio. Aus beiden Interrupts muss ein
preemptiver Taskwechsel möglich sein.
Wenn du ein RTOS baust, bei dem Interrupts niedriger Priorität RTOS APIs
aufrufen dürfen, Interrupts hoher Priorität aber nicht, dann ist es
letztendlich Sache des Anwenders des RTOS, sich daran zu halten. In
solchen Systemen gibts kein Vollkasko.
Hallo,
Wenn Du keinen Zähler verwendest, sondern tatsächlich zwei Flags setzt,
stellt die Interrupt-Priorität und das direkte unterbrechen kein Problem
dar (außer eventuell wenn die Routine nicht nur den aktuellen Level,
sondern auch den letzten Level wissen muss - da fällt mir aber kein
Grund zu ein).
Beispiel:
Normaler Programmablauf -> L1 interrupt -> L1 Flag setzen -> Irgendetwas
-> Funktion aufrufen, die stellt fest, dass nur L1 Flag gesetzt ist,
also L1 aktiv -> Irgendwas -> L1 Flag löschen -> Interrupt Ende ->
Normaler Programmablauf
Normaler Programmablauf -> L1 interrupt -> L1 Flag setzen -> Irgendwas
-> L2 Interrupt -> L2 Flag setzen -> Irgendetwas -> Funktion aufrufen,
die stellt fest, dass L1 & L2 Flag gesetzt ist, also L2 aktiv ->
Irgendwas -> L2 Flag löschen -> Interrupt beenden -> Irgendwas ->
Funktion aufrufen, die stellt fest L1 ist gesetzt, also in in L1 ->
Irgendwas -> L1 Flag löschen -> Interrupt Ende -> Normaler
Programmablauf
Normaler Programmablauf -> L1 interrupt -> L2 Interrupt -> L2 Flag
setzen -> Irgendetwas -> Funktion aufrufen, die stellt fest, dass nur L2
Flag gesetzt ist, also L2 aktiv -> Irgendwas -> L2 Flag löschen ->
Interrupt beenden -> L1 Flag setzen -> Irgendwas -> Funktion aufrufen,
die stellt fest nur L1 ist gesetzt, also L1 aktiv -> Irgendwas -> L1
Flag löschen -> Interrupt Ende -> Normaler Programmablauf
Du siehst, auch eine Unterbrechung des L1 bevor das Flag gesetzt ist,
stellt kein Problem dar, wenn die L1 und L2-Flags in der Routine
entsprechend priorisiert werden.
Schöne Grüße,
Martin
Die Idee mit dem Flag ist gut.
Jeder Interrupt muß beim Eintritt eine Variable incrementieren und vor
dem RETI decrementieren. Damit weißt Du immer, wieviel Level gerade
laufen.
Ein high Level Interrupt vor dem INC bzw. nach dem DEC ist kein Problem,
es ist ja noch nichts passiert bzw. alles abgeschlossen.
Peter Dannegger schrieb:> high Level Interrupt vor dem INC
Abgesehen davon, dass grundsätzlich mindestens 1 Befehl ausgeführt wird,
ehe der nächste Sprung erfolgt. Ein "INC #xxx" zerstört keine Register
und darf daher problemlos als erste Befehl in der Routine stehen.
Jeder Funktionsaufruf rettet vor dem Sprung zur Funktion die
Rücksprungadresse auf den Stack.
Schau' Dir an, wo die hingelegt wird und werte Sie in der nachfolgenden
Funktion aus.
Du brauchst dazu nur den aktuellen Stackpointer + den Offset für die
abgelegte Rücksprungadresse.
Damit weißt Du sicher, woher der Funktionsaufruf kam.
Peter Dannegger schrieb:> Die Idee mit dem Flag ist gut.> Jeder Interrupt muß beim Eintritt eine Variable incrementieren und vor> dem RETI decrementieren. Damit weißt Du immer, wieviel Level gerade> laufen.
Ne, das funktioniert halt nicht, wie ich oben schon beschrieben habe.
Nämlich für den Fall das vor dem Inkrementieren im L1 Interrupt dieser
schon durch den L2 Interrupt unterbrochen wurde...
Mit den Flags geht leider auch nicht, denn beide Interrupt Routinen
rufen die gleiche!! API Funktion auf. Woher soll diese denn wissen,
welches Flag sie setzen soll? Wenn ich weiß welches Flag ich setzen
muss, dann weiß ich auch in welcher Interrupt Routine ich bin und das
ich aus einem L2 Interrupt keinen Taskwechsel machen darf...dann brauche
ich aber auch die Flags nicht.
>Abgesehen davon, dass grundsätzlich mindestens 1 Befehl ausgeführt wird,>ehe der nächste Sprung erfolgt
Ist das so beim 8051?
Wenn dem so ist, dann wäre die Lösung als ersten Befehl die Interrupts
zu sperren. Anschließend kann man ganz einfach mit einem Zähler
arbeiten.
Ich bin bis jetzt davon ausgegangen, das dies beim 8051 nicht sicher so
ist.
GB schrieb:> Jeder Funktionsaufruf rettet vor dem Sprung zur Funktion die> Rücksprungadresse auf den Stack.> Schau' Dir an, wo die hingelegt wird und werte Sie in der nachfolgenden> Funktion aus.> Du brauchst dazu nur den aktuellen Stackpointer + den Offset für die> abgelegte Rücksprungadresse.> Damit weißt Du sicher, woher der Funktionsaufruf kam.
Sowas würde funktionieren, wenn z.B. das PSW mit auf dem Stack gerettet
wird und dieses z.B. die Information enthält, ob man gerade einen
Interrupt ausführt oder nicht. Bei anderen CPUs mache ich das genau so.
Damit kann ich dann erkennen, das vorher kein Interrupt ausgeführt wurde
und das dann der L1 Interrupt sein muss.
Aber nur über Rücksprungadresse wird es schwer. Woher soll ich in meinem
OS wissen wo der Anwender seine Funktionen im Code hat? Es geht ja hier
nicht um eine statische Anwendung sondern mein OS ist nur Teil einer
Applikation.
Das ist richtig, aber für die Interrupts gibt es Interrupt-Vektoren,
diese liegen an einer festen Position. In diesem wird normalerweise der
Sprung zur Interrupt-Service-Routine gemacht.
Also nachschauen, wohin er von den Interruptvektoren aus springt und mit
aktueller Rücksprungadresse vergleichen.
GB schrieb:> Das ist richtig, aber für die Interrupts gibt es> Interrupt-Vektoren,> diese liegen an einer festen Position. In diesem wird normalerweise der> Sprung zur Interrupt-Service-Routine gemacht.> Also nachschauen, wohin er von den Interruptvektoren aus springt und mit> aktueller Rücksprungadresse vergleichen.
Die Idee ist so pervers, das sie schon wieder gut ist ;-).
Danke, ich werde darüber nachdenken.
Das Problem ist aber das in der IVT nur Einsprungsadressen stehen aber
keine Information darüber wie groß die Interrupt Routinen sind. Der L2
Interrupt muss ja den L1 Interrupt nicht unbedingt beim ersten Befehl
unterbrechen.
Guest schrieb:> Wenn dem so ist, dann wäre die Lösung als ersten Befehl die Interrupts> zu sperren.
Das sollte klappen. Das "CLR EA" muß dann der 1. Befehl in der
Vektortabelle sein. Da je Vektor 8 Byte Platz sind, kein Problem.
Guest schrieb:> Das Problem ist aber das in der IVT nur Einsprungsadressen stehen aber> keine Information darüber wie groß die Interrupt Routinen sind. Der L2> Interrupt muss ja den L1 Interrupt nicht unbedingt beim ersten Befehl> unterbrechen.
IVT:
L1: Sprung nach 0x01000
L2: Sprung nach 0x02000
Rücksprungadresse größer gleich 0x01000 und kleiner 0x02000 => L1
Rücksprungadresse größer gleich 0x02000 => L2
Hier eine kleine Beschreibung des Ablaufs beim Auftreten eines
Interrupt.
Zitat:
Zwischen Auftreten eines Interrupt-Signals und Beginn der
Interrupt-Service-Routine vergeht einige Zeit
1 Befehlszyklus für das speichern der Anforderung
1 Befehlszyklus für das Polling der Anforderung
2 Befehlszyklen für den Aufruf (LCALL..) der ISR
Die Verzögerung zwischen Auftreten einer Interrupt-Anforderung und der
Interrupt-Ausführung kann allerdings noch wesentlich länger dauern.
1. Wenn gerade ein Interrupt abgearbeitet wird.
2. Wenn der Befehlszyklus, in dem das Polling durchgeführt wird,
nicht der letzte Zyklus des auszuführenden Befehls ist.
3. Wenn gerade ein RETI-Befehl oder ein Zugriff auf eines der
SFRegister IE oder IP durchgeführt wird.
Im ersten Fall ist die Verzögerung abhängig von der Länge der noch
auszuführenden ISR. Nach dem RETI-Befehl wird noch ein Befehl im zuvor
unterbrochenen Programm ausgeführt, erst dann wird der neue Interrupt
Interrupt bearbeitet.
Im zweiten Fall wird der Befehl noch ganz abgearbeitet (max. 4
Befehlszyklen bei MUL bzw. DIV), danach wird der Interrupt bearbeitet.
Im dritten Fall wird noch der daruffolgende Befehl abgearbeitet, da
Befehle, welche die Interrupt-Logik (Hardware) beeinflussen keinen
Interrupt zulassen.
Zitat Ende!
So wie ich das lese, wird da nicht zwingend ein Befehl in der ISR
ausgeführt. Das müsste man evtl. mit einem guten Simulator überprüfen.
Um welchen Baustein der 51er-Familie handelt es sich den konkret? Da
gibt es ja eine Vielzahl verschiedener Typen, mit Teilweise abweichenden
Funktionen.
Gruß. Tom
Hallo Guest,
wenn ich ein wenig gründlicher über dein Problem nachdenke, fällt mir
Folgendes ein.
Im laufenden Proramm haben alle INT's die gleiche Priorität. Beim
auftreten eines INT, setzt du deine benötigteen Flags und teilst dann
den benötigten INT's die höhere Priorität zu. Ab da sind sie in der Lage
deine ISR zu unterbrechen und trotzdem hast du den Überblick!
Gruß. Tom
TomA schrieb:> Um welchen Baustein der 51er-Familie handelt es sich den konkret? Da> gibt es ja eine Vielzahl verschiedener Typen, mit Teilweise abweichenden> Funktionen.
Das OS muss auf jedem 8051 funktionieren, daher kann ich leider nicht
von einem speziellen Device ausgehen. Ich stimme dir auch zu das nicht
sicher gestellt ist, das der erste Befehl ausgeführt wird, den zitieren
Absatz habe ich auch schon in der Doku gelesen.
TomA schrieb:> Im laufenden Proramm haben alle INT's die gleiche Priorität. Beim> auftreten eines INT, setzt du deine benötigteen Flags und teilst dann> den benötigten INT's die höhere Priorität zu. Ab da sind sie in der Lage> deine ISR zu unterbrechen und trotzdem hast du den Überblick!
Auch sehr nette Idee. Hieße aber das ich mir für jeden Interrupt merken
muss, ob auf welcher Prio er laufen soll.
Wahscheinlich wird es erstmal darauf hinauslaufen, das OS API Aufrufe
aus high prio Interrupt Routinen nicht zulässig sind.
Btw. Danke an alle Beteiligten für dieses konstruktive Brainstorming!!
Guest schrieb:> Ist das so beim 8051?
Ich muss mich korrigieren. Das mit der Ausführung mindestens eines
Befehls gilt nicht generell für alle Befehle. In der Hardware
Beschreibung des 8051 steht sogar etwas versteckt drin, dass der höher
priorisierte Interrupt in den ersten 2 Befehlszyklen nach Erkennung des
niedrig priorisierten Interrupts noch gewinnen kann. Nur nach einem RETI
wird mindestens ein Befehl ausgeführt, ehe der nächste Interrupt eine
Chance hat.
Zu der Erkennung des "Woher" über den Stack: Was passiert, wenn der böse
User in seiner Routine einen PUSH einschiebt? Wie unterscheidet man
Daten vom PC, wenn man auf den Stack schaut?
Hallo,
sind meine folgenden Annahmen korrekt?
- Du möchtest also ein RTOS mit API-Funktionen haben, die von
verschiedenen Interrupts aufgerufen werden kann.
- Du möchtest, dass der Anwender die Adresse der API-Funktionen direkt
in den Interrupt-Vektor eintragen kann; abhängig vom Vektor muss diese
Funktion dann aber unterschiedlich reagieren.
Wenn das der Fall ist, würde ich den ersten Satz des zweiten Punktes
noch einmal überdenken; normalerweise würde so etwas durch Callbacks
(oder Hooks oder wie Du es nennen möchtest) mit entsprechenden Wrappern
und Registrierungsfunktionen gelöst. Dabei implementiert das RTOS für
jeden Interrupt-Vektor eine kleine Routine (die dann z.B. auch die Flags
setzen kann), welche nach einer möglichen Verwaltungsarbeit eine oder
mehrere Funktionen aufruft, die vorher von dem Anwendungsprogramm
während der Initialisierung für diesen Vektor registriert worden sind.
Bei der Registrierung kann ggf. noch zusätzlich die Prio gesetzt werden.
Kostet nur ein paar Takte mehr pro Interrupt, dafür hast Du die
gewünschte Funktionalität.
Schöne Grüße,
Martin
Martin L. schrieb:> - Du möchtest, dass der Anwender die Adresse der API-Funktionen direkt> in den Interrupt-Vektor eintragen kann; abhängig vom Vektor muss diese> Funktion dann aber unterschiedlich reagieren.>> Wenn das der Fall ist, würde ich den ersten Satz des zweiten Punktes> noch einmal überdenken;
Nicht ganz. Ich habe natürlich für jeden Interrupt einen eigenen
Interrupt Handler. Der Anwender muss aber am Anfang und Ende der
Interrupt Routine eine API Funktion aufrufen. Das Eintragen in die IVT
macht in dem Fall schon der IAR für mich. Die API Funktion am Ende der
Interrupt Routine muss jetzt entscheiden können ob sie aus dem L1
Interrupt aufgerufen wurde und darf nur dann einen Taskwechsel falls
nötig ausführen.
Martin L. schrieb:> Dabei implementiert das RTOS für> jeden Interrupt-Vektor eine kleine Routine (die dann z.B. auch die Flags> setzen kann), welche nach einer möglichen Verwaltungsarbeit eine oder> mehrere Funktionen aufruft,
Hilft nicht, man hat immer noch das prinzipelle Problem wie oben
beschrieben.
Wenn du das Flag setzt weißt du nicht, ob du im L1 oder L2 Interrupt
bist.
Deine Idee funktionert nur wenn Interrupts nicht nestable on entry sind.
Sind sie aber beim 8051.
Guest schrieb:> Ich habe natürlich für jeden Interrupt einen eigenen> Interrupt Handler. Der Anwender muss aber am Anfang und Ende der> Interrupt Routine eine API Funktion aufrufen.
Warum muß er das?
Wenn ich höher priorisierte Interrupts benutze, dann weil sie sauschnell
sein müssen. Dann möchte ich nicht durch Unterfunktionsaufrufe mit
Push/Pop Orgien die Geschwindigkeit wieder zunichte machen.
Ist der Interrupt aber nicht zeitkritisch, dann brauche ich die hohe
Priorität ja nicht.
Hallo,
ich glaube, wir reden etwas aneinander vorbei. Wenn ich Dich richtig
verstehe, dann sieht das bei Dir etwa wie folgt aus (Pseudo-Code)
Anwenderprogramm:
1
UASRT_INTERRUPTHandler()//prio L
2
{
3
CALL_API_ENTRY();
4
5
Tu_Was();
6
7
CALL_API_EXIT();
8
}
9
10
TIMER_INTERRUPTHandler2()//prio H
11
{
12
CALL_API_ENTRY();
13
14
Tu_Was();
15
16
CALL_API_EXIT();
17
}
18
19
main()
20
{
21
Init()
22
EnableInterrupts();
23
while(1);
24
}
Die Handler wird von dem Linker in die Vektortabelle eingetragen. In
diesem Fall hast Du tatsächlich das Problem, dass Nested-Interrupts
nicht sauber erkannt werden können bzw. die Priorisierung ohne das der
Anwender etwas macht nicht sauber bestimmbar ist.
Mein Vorschlag sieht allerdings so aus:
RTOS:
SetInterruptPrio(Vector,Prio);// Setzt die Priorität in der Hardware
79
EnableInterrupt(Vector);// Schaltet den Interrupt in der Hardware ein
80
}
Der User-Teil sieht dann so aus:
1
voidUSART_Hander()
2
{
3
CALL_API_ENTRY();
4
5
Tu_Was();
6
7
CALL_API_EXIT();
8
}
9
10
voidTIMER_Hander()
11
{
12
CALL_API_ENTRY();
13
14
Tu_Was_Anderes();
15
16
CALL_API_EXIT();
17
}
18
19
main()
20
{
21
Init();
22
23
RegisterInterrupt(USART_Handler,USART,1);
24
RegisterInterrupt(TIMER_Handler,TIMER,0);
25
26
while(1);
27
}
Die User-Handler werden also nicht von dem Linker eingebunden.
Stattdessen werden RTOS-Handler eingebunden, die die User-Handler
aufrufen. Dies sollte wie gewünscht funktionieren, wenn der
Low-Prio-Interrupt nur vom High-Prio-Interrupt unterbrochen werden kann
und der High-Prio-Interrupt nicht unterbrechbar ist (eingeschränkte
Interrupt-Verschachtelung). Wenn man die Flags zu Countern ausbaut,
gehen auch vollständig verschachtelbare Interrupt-Strukturen; ebenso
kann man natürlich auch die Anzahl der Flags erhöhen, wenn mehr
Prioritätsstufen hinzukommen.
Oder sieht jemand einen Fehler?
Schöne Grüße,
Martin
Hallo Martin,
Martin L. schrieb:> Bekomm_Panik () ;
rofl...sehr geil ;-).
Ja, du hast mich richtig verstanden, genauso sieht es aus.
Dein Vorschlag macht auf jeden Fall Sinn.
Allerdings muss ich mich an eine bestehende API halten, bin mir nicht
ganz sicher ob ich das so umsetzen kann.
Ich werde mir aber nochmal Gedanken dazu machen.
Danke!!