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 nimm zwei Einsprungpunkte. Der eine setzt das Flag, der andere nicht. Danach sind beide gleich.
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.
Doch, Du schreibst ihm einfach ins Handbuch, was er zu machen hat. Andere OSe schreiben auch vor, wie eine ISR vorzugehen hat.
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?
:
Bearbeitet durch User
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_INTERRUPT Handler () //prio L |
2 | {
|
3 | CALL_API_ENTRY () ; |
4 | |
5 | Tu_Was(); |
6 | |
7 | CALL_API_EXIT () ; |
8 | }
|
9 | |
10 | TIMER_INTERRUPT Handler2 () //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:
1 | void (*Callbacks)() [NUM_INTS] ; |
2 | void IntPrio [NUM_INTS] ; |
3 | |
4 | int LoPrio ; |
5 | int HiPrio ; |
6 | |
7 | #define USART 0
|
8 | #define TIMER 1
|
9 | |
10 | UASRT_INTERRUPT Handler () |
11 | {
|
12 | // Flags setzen
|
13 | if (IntPrio[USART]==0) { |
14 | LoPrio=1 ; |
15 | } ; |
16 | |
17 | if (IntPrio[USART]==1) { |
18 | HighPrio=1 ; |
19 | } ; |
20 | |
21 | // Funktion aufrufen
|
22 | (*Callbacks)[USART] (); |
23 | |
24 | // Flag loeschen
|
25 | if (IntPrio[USART]==0) { |
26 | LoPrio=0 ; |
27 | } ; |
28 | |
29 | if (IntPrio[USART]==1) { |
30 | HighPrio=0 ; |
31 | } ; |
32 | }
|
33 | |
34 | TIMER_INTERRUPT Handler2 () |
35 | {
|
36 | // Flags setzen
|
37 | if (IntPrio[TIMER]==0) { |
38 | LoPrio=1 ; |
39 | } ; |
40 | |
41 | if (IntPrio[TIMER]==1) { |
42 | HighPrio=1 ; |
43 | } ; |
44 | |
45 | // Funktion aufrufen
|
46 | (*Callbacks)[TIMER] (); |
47 | |
48 | // Flag loeschen
|
49 | if (IntPrio[TIMER]==0) { |
50 | LoPrio=0 ; |
51 | } ; |
52 | |
53 | if (IntPrio[TIMER]==1) { |
54 | HighPrio=0 ; |
55 | } ; |
56 | }
|
57 | |
58 | void CALL_API_EXIT () |
59 | {
|
60 | if (HighPrio==1) { |
61 | // Sind in einem hoch-Prioren Interrupt
|
62 | Tu_Was () ; |
63 | } else if (LoPrio==1) { |
64 | // Sind in einem niedrig-Prioren Interrupt
|
65 | Tu_Was_Anderes () ; |
66 | } else { |
67 | // Sind in keinem Interrupt
|
68 | Bekomm_Panik () ; |
69 | } ; |
70 | }
|
71 | |
72 | |
73 | void RegisterInterrupt (void (*Func)(),int Vector, int Prio) |
74 | {
|
75 | Callback[Vector] = Func ; |
76 | IntPrio[Vector] = Prio ; |
77 | |
78 | 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 | void USART_Hander () |
2 | {
|
3 | CALL_API_ENTRY () ; |
4 | |
5 | Tu_Was () ; |
6 | |
7 | CALL_API_EXIT () ; |
8 | }
|
9 | |
10 | void TIMER_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!!
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.