Der Designer der NVIC Architektur der Cortex-Reihe dürfte sich vermutlich etwas dabei gedacht haben, als der die Prioritäten linksbündig definierte. Mir kam das recht geschickt vor, weil dadurch die Prioritäten definiert werden können, ohne zwingend die Anzahl real implementierter Prioritäten im Auge behalten zu müssen. Wenn man also die Prioritäten 0x40 und 0x50 definiert, dann sind das beim STM32 (4 Bits) verschiedene Ebenen, beim CM0 gleiche (3 Bits). Aber der Level 0xC0 hat nie höhere Priorität als der Level 0x20, egal wieviel Bits implementiert sind. Warum gibt sich also CMSIS erkennbar Mühe, diesem IMHO sinnvollen Design des NVIC entgegenzuwirken, indem der Parameter von NVIC_SetPriority den Parameter rechtsbündig definiert und intern in die linksbündige Definition der Hardware umrechnet? Ich erkenne zwar in den Encode/Decode-Funktionen den Versuch, die Teile der Hardware-Specifikation sichtbar und entschlüssel/verschlüsselbar zu machen. Aber muss es wirklich sein, dass man nach der Lektüre der Hardware-Spezifikation so klug ist wie vorher, weil CMSIS eine völlig andere Konvention verwendet als die Hardware? Einmal mehr mein Problem mit solchen Libs. Der Designer der Lib hat ein anderes Verständnis als der Designer der Hardware und der Entwicklungsaufwand verdoppelt sich, statt sich zu reduzieren, weil man spätestens beim Debugging ohnehin beide Konventionen kennen muss. Mit dem Risiko, das gelegentlich zu verwechseln.
Da sonst keiner antwortet: Du hast meine volle Zustimmung zu dieser Beobachtung. Ich weiß jetzt, dass ich nicht alleine bin :-) Die aktuelle CMSIS Methode erzeugt sogar das potentielle Problem einer Prioritätsinversion. Gruß Marcus http://www.doulos.com/arm/
Jedenfalls hat nun ein weiteres Element der mitgelieferten Libs den Weg in meinen Papierkorb gefunden und wurde durch eigene Implementierungen ersetzt. Nix gegen diese Encode/Decode-Ansätze. Aber das Format für die NVIC_SetPriority Funktion sollte der Hardware-Konvention entsprechen, allein schon um nicht zusätzlich Verwirrung zu stiften.
> Warum gibt sich also CMSIS erkennbar Mühe, diesem IMHO sinnvollen Design > des NVIC entgegenzuwirken, indem der Parameter von NVIC_SetPriority den > Parameter rechtsbündig definiert und intern in die linksbündige > Definition der Hardware umrechnet? Hi, das ist gemacht, um die Programmierung des NVIC einfacher zu gestalten. Ausserdem ist das sogar portierbarer, als wenn man die Prioritäten in seiner SW gleich linksbündig definiert. Es erhöht zudem die lesbarkeit. Der NVIC ist ohnehin schon ein sehr komplexes Peripheral, was die wenigsten verstehen. Und jemand, der die komplette Prioritätsverwaltung beim NVIC selbst macht, wird auch nicht auf die CMSIS zurückgreifen. Nehmen wir z.B. jmd. aus der AVR oder 8051 Welt. Der kratzt sich am Kopf, was der Kram alles soll. Und genau der freut sich über die schön einfachen Funktionen. > Encode/Decode-Funktionen Die sind nach mir dazugekommen, muss ich mir mal ansehen. > Die aktuelle CMSIS Methode erzeugt sogar das potentielle Problem einer > Prioritätsinversion. konkretes Beispiel? Lösungsansatz? Ok, Problem besteht, wenn jmd. Prioritäten zuweist, die ausserhalb der unterstützten Bandbreite liegen. Werde mir dazu noch mal Gedanken machen. VG, /th.
Random ... schrieb: >> Warum gibt sich also CMSIS erkennbar Mühe, diesem IMHO sinnvollen Design > das ist gemacht, um die Programmierung des NVIC einfacher zu gestalten. > Ausserdem ist das sogar portierbarer, als wenn man die Prioritäten in > seiner SW gleich linksbündig definiert. Es erhöht zudem die lesbarkeit. Ansichtssache. Für mich besteht der Ansatz des NVIC darin, 256 Prioritäten zu definieren, die je nach Implementierung unterschiedlich fein aufgelöst werden. Da kann es mal vorkommen, dass zwei bei STM32 verschiedene Prioritäten beim LPC1100 gleich sind, aber jedenfalls sind sie garantiert nie verkehrt herum ... >> Prioritätsinversion. > konkretes Beispiel? ... was bei den CMSIS Prioritäten 4 und 10 durchaus vorkommen wird. Beim STM32 sind das intern 4=>0x40 und 10=>0xA0, beim LPC1100 4=>0x80 und 10=>0x40, also invers. > Lösungsansatz? NVIC_SetPriority reicht die 8-Bit Prioritäten der Hardware-Konvention direkt durch. Die Bastelei mit Ebenen, Gruppen usw. ist dann Aufgabe der Encode-Funktion. Der Sinn von NVIC_SetPriority sollte m.E. nicht darin bestehen, die Hardware-Konvention umzucodieren, sondern den Wert ins richtige Register zu schreiben. Unterschiedliche Konventionen in CMSIS und Hardware erschweren zudem das Debugging, weil man beim Blick ins Register die Hardware-Konvention sieht, im Parameter und im Quellcode die CMSIS-Konvention. Irgendwann bringt man das mal durcheinander. Und kennen muss man dann beide Konventionen.
Etwas inkonsequent ist es übrigens, in NVIC_SetPriority die System-Exceptions mit aufzunehmen, in den Pendants für Einzelbits aber nicht, obwohl es auch dafür teilweise Bits im SCB gibt.
Hier gibt es weiterhin ein generelles Problem. Als die Lib (ich rede nur von der core_cm3.h, V1) entworfen wurde, war der Ansatz, einem Liebhaber der Registerprogrammierung die Arbeit zu erleichtern. Um den Layer möglichst schmal und trotzdem universal zu halten, war der Ansatz, pro Funktion nur ein Register zu abstrahieren. Das wurde für den NVIC auch so durchgezogen (Die SysTick Funktion ist eigentlich nur für RTOS gedacht). Hinzu kommt, dass man dem Programmierer etwas an Intelligenz zuschreiben muss, d.h. er sollte zumindest wissen, was sein System kann (also Zahl der Prio Bits). Wenn ich weiss, ich habe 4 Prio Bits, ist es ein leichtes, sich die Prios 0...15 auszudenken, aber schon ziemlich komplex, die lückenbehafteten Werte von (0<<4) ... (1<<4) umzusetzen (oder denken wir an 3, 5 Bits). Die aufwendige Berechnung von der Interrupt Nummer zu irgendeinem Bit in einem riesen Haufen übernimmt dann die Funktion:
1 | static __INLINE void NVIC_EnableIRQ(IRQn_Type IRQn) |
2 | {
|
3 | NVIC->ISER[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); /* enable interrupt */ |
4 | }
|
5 | |
6 | ...
|
7 | |
8 | static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) |
9 | {
|
10 | if(IRQn < 0) { |
11 | SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for Cortex-M3 System Interrupts */ |
12 | else { |
13 | NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for device specific Interrupts */ |
14 | }
|
Dass man einen Inliner nicht für alle Fälle wasserdicht bekommen kann, ist klar. Man kann den Programmierer nur bedingt vor seiner eigenen Blödheit schützen. Auf jeden Fall geht das immer zu Lasten der Codegrösse und Geschwindigkeit. Dieser Layer nimmt dem Programmierer lediglich diese komplexe Berechnung ab, nicht aber das denken. Dafür ist dann die ST Library oder die LumiLib da. VG, /th.
> NVIC_SetPriority reicht die 8-Bit Prioritäten der Hardware-Konvention > direkt durch. Die Bastelei mit Ebenen, Gruppen usw. ist dann Aufgabe der > Encode-Funktion. Falsch! Auch bei der CMSIS muss der Benutzer sich die Gruppen selbst ausdenken. Die CMSIS kümmert sich nur um den richtigen shift, _mehr nicht_.
Random ... schrieb: > Der NVIC ist ohnehin schon ein sehr komplexes Peripheral, was die > wenigsten verstehen. Und jemand, der die komplette Prioritätsverwaltung > beim NVIC selbst macht, wird auch nicht auf die CMSIS zurückgreifen. Wenn man sich nicht mit Prioritäten befasst, braucht man NVIC_SetPriority nicht. Und wenn man sich damit befasst, dann hat man die Controller- oder Cortex-Doku gelesen, evtl. noch den Definitive Guide und/oder den Hitex-Wälzer, kämpft dann mit zwei verschiedenen Konventionen und tut deshalb gut daran, sich den entsprechenden CMSIS-Zirkus zu ersparen. Folglich vereinfacht CMSIS hier garnichts. Ich halte es für sinnvoll, mit solchen Funktionen die Verteilung von Bits und Bytes auf die NFIC-Register abzuwickeln. Die Prioritäten umzucodieren irritiert mehr als es hilft.
> Etwas inkonsequent ist es übrigens, in NVIC_SetPriority die > System-Exceptions mit aufzunehmen, in den Pendants für Einzelbits aber > nicht, obwohl es auch dafür teilweise Bits im SCB gibt. Würde die Funktion nur unnötig aufblähen, da sich die Bits überall in verschiedenen Registern verstecken. Man müsste jeden Fall abfangen... --- Nochmal: Die CMSIS ist keine eierlegende Wollmilchsau, sie ist eine vereinfachte Library für den Core, richtet sich an Um- und Einsteiger, und dient weiterhin super dazu, den komplexen NVIC zu verstehen, ohne sich in der DL erst durch tonnenweise Funktionen und #defines wühlen zu müssen.
Random ... schrieb: > Würde die Funktion nur unnötig aufblähen, da sich die Bits überall in > verschiedenen Registern verstecken. Man müsste jeden Fall abfangen... Yep, aber weshalb ist das in NVIC_SetPriority drin?
> Ich halte es für sinnvoll, mit solchen Funktionen die Verteilung von > Bits und Bytes auf die NFIC-Register abzuwickeln. Die Prioritäten > umzucodieren irritiert mehr als es hilft. Man könnte überlegen, ob man die MSB Shifts der Prios in eine eigene Funktion packt. Somit bleibt für die einen die Positionierung anhand der INT Nr., für die anderen als geschachtelten Funktionsaufruf eine Hilfe. VG, /th.
> Yep, aber weshalb ist das in NVIC_SetPriority drin?
Wie lange hast du gebraucht, um zu verstehen, wie der NVIC funktioniert?
Hattest du dazu dieses Beispiel vor der Nase?
:-)
Es bleibt bei einer Hilfestellung, keiner nimmt dir das Denken ab!
---
Wenn du so argumentierst, kannste die ganze CMSIS killen **g**
---
Ich seh schon, damit haben wir uns ganz schön in die Nesseln gesetzt :-)
Eigentlich sollte das Zeug etwas Licht in den chaotischen Wald der
Vendors bringen (die structs auf Register), und ein klein wenig
Einstiegs-/Codinghilfe dazu...
Random ... schrieb: > Man könnte überlegen, ob man die MSB Shifts der Prios in eine eigene > Funktion packt. Somit bleibt für die einen die Positionierung anhand der > INT Nr., für die anderen als geschachtelten Funktionsaufruf eine Hilfe. Ja, exakt dies hatte ich oben gemeint. NVIC_SetPriority reicht die Prio 1:1 durch, und eine andere Funktion beliebigen Namens codiert eine Darstellung von Prio,Gruppe,wassweissich in die Hardware-Konvention um.
Random ... schrieb: > Wie lange hast du gebraucht, um zu verstehen, wie der NVIC funktioniert? Ich bin schon ein paar Jahrzehnte im Geschäft und habe einiges gesehen, so krass war's für mich nicht. Die Sache mit den Prios selbst hat mir kein Kopfzerbrechen bereitet, im Gegenteil, ich fand den Ansatz sehr gut. Interessanter fand ich den Zusammenhang zwischen aktuellem Level und den Core-Exceptions, also ob und wann man in einer Memexception oder gleich im Hardfault landet. Das ist eher selten anzutreffen. > Hattest du dazu dieses Beispiel vor der Nase? Die Doku von Architektur, vom Core und den Definitive Guide. Das einzige Beispiel nach dem ich explizit suchte betraf PendSV. > Wenn du so argumentierst, kannste die ganze CMSIS killen **g** Nein. Wie schon gesagt, die Abbildung der IRQ-Nummer auf die Register ist sehr sinnvoll. Aber Verständnis wächst mit Konsequenz, so zumindest bei mir. Entweder man handhabt alle NVIC-Funktionen gleich, indem man Prioritäten und Exceptions auf Bytes und Bits verteilt, oder man lässt die Exceptions ganz draussen. Aber es mal so mal so zu machen dient m.E. niemandem. Denn das kapiert nur derjenige, der diese Info aus dem Quellcode der CMSIS in Verbindung mit der Kenntnis vom Core rauszieht. Und genau der ist davon laut deiner Erklärung nach nicht adressiert.
ungetestet:
1 | static __INLINE uint32_t NVIC_ConvertPrioToHW(uint32_t prio) |
2 | {
|
3 | return(((prio & ((1<<__NVIC_PRIO_BITS)-1)) << (8 - __NVIC_PRIO_BITS)) & 0xff); |
4 | }
|
5 | |
6 | |
7 | static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) |
8 | {
|
9 | if(IRQn < 0) { |
10 | SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = (priority); } /* set Priority for Cortex-M3 System Interrupts */ |
11 | else { |
12 | NVIC->IP[(uint32_t)(IRQn)] = (priority); } /* set Priority for device specific Interrupts */ |
13 | }
|
Man beachte den kleinen, aber feinen Unterschied (den man auch in die jetztige Fkt. mit einbauen könnte, dann wäre das PrioInv problem vom Tisch). Die prio wird jetzt auf die max. mögliche Prio begrenzt, bevor sie zurückgegeben und geschrieben werden kann. VG, /th.
Random ... schrieb: > Man beachte den kleinen, aber feinen Unterschied (den man auch in die > jetztige Fkt. mit einbauen könnte, dann wäre das PrioInv problem vom > Tisch). Keineswegs, denn mit der Maskierung machst du nur genau das, was die Hardware auch tut. Wer 10 übergibt kriegt beim CM0 so oder so effektiv 2, egal ob die Maskierung in der Funktion oder beim Schreiben ins Register erfolgt. Erst mit einer min() Operation ist die Inversion tatsächlich vom Tisch. Nur ist das in der neuen Form kein Problem mehr, denn NVIC_ConvertPrioToHW bezieht sich nun explizit auf die Prioritäten der jeweiligen Implementierung (sollte man dokumentieren) und wer dort bei Prios 0..7 den Wert 10 übergibt ist selbst schuld. NVIC_SetPriority entspricht nun dem, was ich selbst verwende. Ich habe allerdings getrennte Funktionen für die System Interrupts und die NVIC Interrupts.
Random ... schrieb: > Wenn ich weiss, ich habe 4 Prio Bits, ist es ein leichtes, sich die > Prios 0...15 auszudenken, aber schon ziemlich komplex, die > lückenbehafteten Werte von (0<<4) ... (1<<4) umzusetzen (oder denken wir > an 3, 5 Bits). Nicht wenn man das von vorneherein nicht dezimal sondern Hex macht. Dann habe ich einen Bereich 00-FF und weiss, dass ich bei jedem Cortex in jeder Implementierung den gesamten Bereich verwenden kann ohne invertierende Maskierungen zu riskieren. Kann sein, dass rechts was verloren geht, aber das ist weit weniger kritisch als links. D.h. ich verteile die Prios nicht per #define UART1_Prio (4<<4) #define UART2_Prio (5<<4) sondern per #define UART1_Prio 0x40 #define UART2_Prio 0x50 und wenn's dann auf einem CM0 auf's Gleiche rausläuft, dann stört mich das erstmal nicht, ist vielleicht sogar gewollt.
Stimmt, das Problem ist damit trotzdem nicht vom Tisch ... Man müsste variabel shiften, sozusagen ein MSB alignment manuell vornehmen, wenn man dem User das Berechnen der Prio Werte abnehmen will. VG, /th.
solange du in Hex bist und prios von 4bit hast, ists leicht. Aber nehme mal 3, 5, 7 ...
Random ... schrieb: > Man müsste variabel shiften, sozusagen ein MSB alignment manuell > vornehmen, wenn man dem User das Berechnen der Prio Werte abnehmen will. Dann kommt nur noch der Bereich 0x80-0xFF raus, alles drunter wird auf 0x80 abgebildet ;-). Die Inversion vermeidet man eher mit min(prio,(1<<__NVIC_PRIO_BITS)-1)
Ich könnte mir jetzt eine Fkt. ausdenken, die ein MSB Alignment macht, die restlichen Stellen abschneidet, und die Fkt. hinzufügen. Aber: Ich garantiere dir, ich krieg trotzdem wieder Haue :-)
Random ... schrieb: > solange du in Hex bist und prios von 4bit hast, ists leicht. Aber nehme > mal 3, 5, 7 ... Ich schrieb oben, dass ich schon etwas länger im Geschäft bin. Also auf Maschinen angefangen habe, bei denen man an der Konsole nur Hex zu sehen kriegte. Keine Sorge, sowas kann ich im Schlaf. ;-) Aber darum geht es nicht unbedingt. Wenn man explizit verschiedene Prios braucht, dann muss man drauf achten. Wenn man eine Lösung auf die zur Verfügung stehenden Prios verteilen muss, dann ist man implementierungsabhängig und nehme die Convert.. Funktion. Wenn's nicht ganz so knackig ist, dann kann man das auf den Bereich 00-FF abbilden und muss nur das realistische Minimum von 3 Bits um Auge behalten. Der Rest ist dann Zugabe. Man kann auch beim STM32 1 Bit für die Gruppe kassieren, dann sind dieser und ein CM0 mit seinen 3 Bits im Verhalten fast gleich und man kann den gleichen Bereich nutzen.
Random ... schrieb:
> Aber: Ich garantiere dir, ich krieg trotzdem wieder Haue :-)
Versprochen! Bei sowas ganz sicher! ;-)
Ich muss jetzt diesen alten Thread nochmal rauskramen. Bin ein STM32 Späteinsteiger und habe mich jetzt mal durch die STM32F103 NVIC-Specs und die CMSIS Libs durchgewühlt. Diese ganzen Prioritäts-Gruppierungen in CMSIS sind m.E. Unfug. Letztendlich läuft es doch einfach auf 15 Prioritäten raus, die im High Nibble vom PR[n]stehen. Es zählen doch nur die ogenannten Pre-emption Levels. Diese Subprioritäten haben doch gar keien Einfluss. Im CortexM3 Programming-manual ist das zum Glück viel einfacher beschrieben. Beim STM32F103 sieht daher meine NVIC Prioritätsfestlegung z.B. so aus:
1 | PR[0] = 0x00; // Prioritäten sind 0x00(hoch) bis 0xF0 (niedrig) |
2 | PR[1] = 0x00; |
3 | PR[2] = 0x10; |
4 | PR[3] = 0xF0; |
5 | PR[4] = 0x30; |
6 | .....
|
7 | PR[40] = 0x00; |
8 | PR[41] = 0xF0; |
9 | PR[42] = 0xF0; |
Oder sehe ich da was falsch ?
Sorry hatte das NVIC vergessen:
1 | NVIC->PR[0] = 0x00; // Prioritäten sind 0x00(hoch) bis 0xF0 (niedrig) |
2 | NVIC->PR[1] = 0x00; |
3 | NVIC->PR[2] = 0x10; |
4 | NVIC->PR[3] = 0xF0; |
5 | NVIC->PR[4] = 0x30; |
6 | .....
|
7 | NVIC->PR[40] = 0x00; |
8 | NVIC->PR[41] = 0xF0; |
9 | NVIC->PR[42] = 0xF0; |
Klar - die Preemption-Prioritäten sind die wichtigeren. Aber die Subprioritäten haben die nützliche Eigenheit, dass Interrupts innerhalb einer jeweiligen Gruppe sich garantiert nicht gegenseitig unterbrechen können. Und dennoch definierbar ist, welcher davon vorrangig dran kommt. Diese Subprioritäten sind ungefähr das, was bei AVR die Prioritäten durch Vektor-Reihenfolge darstellen, aber wählbar.
Diese Subprioritäten (innerhalb eine INT-prio-Gruppe) muss es immer geben, da ja nicht (wenn mehrere auslösen) per Zufall irgent ein Vector angesprungen werden kann.
MCUA schrieb: > Diese Subprioritäten (innerhalb eine INT-prio-Gruppe) muss es immer > geben, da ja nicht (wenn mehrere auslösen) per Zufall irgent ein Vector > angesprungen werden kann. Weshalb eigentlich nicht? ;-) Aber der Knackpunkt hier ist, dass sie frei wählbar sind.
Jetzt habe ichs hoffentlich verstanden: In der misc.c Funktion steht die Funktion NVIC_PriorityGroupConfig. Die Trennungslinie (Binary point)zwischen Group-Priority(PreEmption) und Sub-Priority wird dort einmalig festgelegt. Ich hatte mich gefragt, wie die Aufteilung mit 4 Bit funktionieren soll. Auszug aus der misc.c [c] void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup) { /* Check the parameters */ assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup)); /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */ SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup; } [\c]
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.