Servus allerseits
Ich benutze hin und wieder CMSIS-RTOS RTX; nicht aber Keil.
Waehrend man in uVision den Stack-Zustand sehr bequem kontrollieren
kann, zappelt man ohne diese IDE wie ein Fisch auf dem Lande.
Zumindest erging es mir so.
Mit Hilfe von http://www.keil.com/support/docs/3735.htm habe ich mir
eine Notlösung zusammengeschustert.
Die Grundsteine bilden
1
typedefenum_task_id_pool_t
2
{
3
MAIN_TASK=0,
4
SCAN_TASK,
5
BTN_TASK,
6
BTN_SHUTDOWN_TASK,
7
LED_TASK,
8
ADC_TASK,
9
SHT21_TASK,
10
MHZ19_TASK,
11
ANZ_TASKS,
12
}task_id_pool_t;
--
1
structstru_task_id_pool
2
{
3
osThreadIdid_nr;
4
constchar*name;
5
uint16_tstk_size;
6
uint16_tstk_used;
7
uint16_tstk_left;
8
uint16_tproz_used;
9
};
und
1
stru_task_id_pooltask_id_pool[ANZ_TASKS];
Einen Thread muss man nach Erschaffung in diesen pool eintragen. Z.Bsp.:
In main() wird alle 500ms die Funktion CheckThreadStacks() aufgerufen.
Desweiteren füge ich überall dort, wo ich eine expliziete Kontrolle
durchfühen will, diese Zeilen ein:
Hallo!
Vielen Dank für Deinen Code!
Ich habe genau das gleiche Problem und habe den Code implementiert.
Es sollte vielleicht erwähnt werden dass in RTX_Conf_CM.c die Option
"OS_STKINIT" auf 1 stehen muss.
Der Code in main.cpp nach osKernelStart() wird nicht ausgeführt.
Dieser sollte ggf. in einem anderen Task stattfinden.
Eine Sache verstehe ich aber nicht ganz:
Das Programm erzeugt drei Tasks, einer davon wird aber nie bearbeitet:
TASK1: 0%
TASK2: 76%
TASK3: 60%
Bei TASK1 wird immer 0% stack usage angezeigt.
Irgendeine Idee?
Grüße,
Bernhard
Wenn ich 4 Tasks prüfen lasse obwohl nur 3 in der Liste sind, klappt
auch die Anzeige des TASK1.
Kann es sein dass der Idle-Task irgendwo noch in die Quere kommt?
Ich habe in stack_check.c die Zeile
for (tcb_idx = 0; tcb_idx < max_tasks; tcb_idx++)
in
for (tcb_idx = 1; tcb_idx < max_tasks+1; tcb_idx++)
abgeändert.
[0] ist der Idle-Task und der interessiert uns nicht.
Jetzt siehts aus als würde es klappen.
Wie siehst Du das?
Zuerst einmal ein kleiner Bug-Report, den ich, weil sich für den Code
bis heute niemand interessiert hat, nicht nachgereicht habe, um kein
unnötiges Rauschen im Forum zu erzeugen:
Zeile 51:
1
*stk_used=(size-((virgin+1)))*sizeof(stk);
sollte lauten:
1
*stk_used=(size-virgin)*sizeof(stk);
Das mit dem Idle-Task hat mir am Anfang auch sehr viel Kopfzerbrechen
bereitet. Aber nachdem was ich so gelesen habe und selbst ausprobiert
habe, scheint Idle nicht analysiert zu werden.
task_id_pool[0].name ist bei mir stets die MAIN_TASK; also der erste
Eintrag im task_id_pool_t.
Desweiteren setze ich das Array immer grösser als in Wirklichkeit
gebraucht.
z.Zt. habe ich 18 Task; gesetzt habe ich aber extern stru_task_id_pool
task_id_pool[20]
Erstens, weil es nicht schadet; und zweitens, weil ich im Eifer des
Gefechts bei einem neuen Task dazu neige, das korrekte Nachzuführen
dieses Array zu vergessen.
Deshalb kann Deine Beanstandung
1
for(tcb_idx=1;tcb_idx<max_tasks+1;tcb_idx++)
durchaus richtig sein; weil Du ja das Array immer korrekt Deinem
tatsaechlichen Bedarf anpassen tust.
Werde es mal in den kommendan Tagen in Ruhe nochmals durchgen und Dir
Bescheid geben.
Danke für Dein Feedback.
Bernhard schrieb:> Der Code in main.cpp nach osKernelStart() wird nicht ausgeführt.> Dieser sollte ggf. in einem anderen Task stattfinden.
Bei mir ist das aber kein Problem. Normalerweise kommt ja nach
osKernelStart() ein osDelay(osWaitForever), oder, wie in diesem Fall,
ein Endlos-Loop.
Kannst Du mal Dein main() mal hier posten?
Danke für den Patch, den habe ich gerade implementiert!
Die for-Schleife die ich änderte betraf nicht die Struktur
task_id_pool_t sondern die Task-Control-Blocks (TCB) des RTOS.
task_id_pool_t wird korrekt von 0 an ausgelesen (siehe Schleife in
InsertValues) nur bei den TCBs ist TCB[0] offensichtlich der Idle-Task
der uns ja nicht interessiert.
Jeder Task im System beginnt ab Index 1. Daher ist nach der Änderung
auch die Ausgabe korrekt.
osKernelStart() initiiert das Task-Scheduling und kehrt nie zurück.
Trotzdem hier mein main()
Was die Größe der Arrays angeht: Ja, man kann bei genug RAM sicherlich
gleich 10 oder mehr Einträge vorsehen. Das Problem dabei ist nur das
wenn man sich um den Stack sorgen macht, meistens auch der RAM des
Controllers reich befüllt ist.
Daher gehe ich auf Nummer sicher und definiere nur das was ich wirklich
brauche.
Notfalls kann man ja auch mit einem
assert(max_tasks == (sizeof(task_id_pool)/sizeof(task_id_pool[0]));
schwerwiegende Fehler abfangen.
Bernhard schrieb:> osKernelStart() initiiert das Task-Scheduling und kehrt nie zurück.
Aus dem CMSIS-RTOS RTX Version 4.80 Reference Manual:
https://www.keil.com/pack/doc/CMSIS/RTX/html/group___c_m_s_i_s___r_t_o_s___kernel_ctrl.html#gaab668ffd2ea76bb0a77ab0ab385eaef2Returns
status code that indicates the execution status of the function.
Start the RTOS Kernel and (...). The main thread will continue executing
after osKernelStart.
Status and Error Codes
osOK: the RTOS kernel has been successfully started.
osErrorISR: osKernelStart cannot be called from interrupt service
routines.
Also wenn bei Dir osKernelStart() nicht mehr zurückkehrt, muss irgendwas
fehlgeschlagen haben.
@Mehmet: Danke für den Hinweis, wird geprüft. Kann sein dass ich hier
was verwechselt habe da ich mehrere unterschiedliche RTOS nutze. Ich
dies zu entschuldigen.
Könnte man den Code noch erweitern dass man den sonstigen noch freien
Speicherplatz im RAM angezeigt bekommt?
Soweit ich es verstanden habe, wird in RTX_Conf_CM.c der RAM-Bedarf für
das RTOS bekanntgegeben - und das wird dann beim Linken bereitgestellt.
Was die MCU sonst noch an RAM hat, interessiert das RTOS nicht.
Auch ist natürlich zu hinterfragen, welches RAM man eruieren will:
Stack? Heap?
Mit Stack ist es realiv einfach: man ruft eine Funktion auf, in der man
die Adresse einer lokalen Variablen sich anschaut.
Beim Heap ist es etwas komplizierter. Die nachfolgende Funktion ist
übrigens nicht auf meinem Mist gewachsen; ich glaube stammt von meiner
Arbeit mit ChibiOS/RT, als ich Probleme mit dem Heap hatte.
1
//========================
2
// CheckHeapSize
3
//========================
4
voidCheckHeapSize(void)
5
{
6
externuint8_t__heap_base__[];
7
externuint8_t__heap_end__[];
8
constuint32_tMIN_HEAP_SIZE=4*4096;
9
10
staticuint8_t*nextmem;
11
staticuint8_t*endmem;
12
13
14
nextmem=(uint8_t*)MEM_ALIGN_NEXT(__heap_base__);
15
endmem=(uint8_t*)MEM_ALIGN_PREV(__heap_end__);
16
//lint -e{732} Loss of sign (assignment) (int to unsigned long)
17
App.heap_size=endmem-nextmem;
18
19
assert_param(App.heap_size>MIN_HEAP_SIZE);
20
if(App.heap_size<MIN_HEAP_SIZE)
21
while(1)
22
{
23
;
24
}
25
}
heap_base und heap_end müssen im Linker-File definiert werden.
Bernhard schrieb:> Was die Größe der Arrays angeht: Ja, man kann bei genug RAM sicherlich> gleich 10 oder mehr Einträge vorsehen. Das Problem dabei ist nur das> wenn man sich um den Stack sorgen macht, meistens auch der RAM des> Controllers reich befüllt ist.
Ich finde das keine so gute Idee. Wenn ich ein Projekt anwerfe, bei dem
ich mir sicher bin, dass mir eine MCU mit 20kB RAM ausreicht, so löte
ich auf die PCB, mit der ich meine Software entwickle, nichtsdestotrotz
einen mit der max. erhaeltlichen Speichergrösse drauf.
Insbesondere bei Applikationen mir RTOS macht es echt keinen Spass, wenn
man mit RAM geizig umgehen muss. Da sollte man schon mit der grossen
Kelle ausschenken. Erst dann, wenn alles mehr oder weniger sauber
laeuft, kann man dazu übergehen sich darüber Gedanken zu machen, wo ein
Kaviarlöffel angebrachter waere.
Wenn ich ein Projekt selbst in der Hand hätte, dann würde ich auch einen
Controller mit viel RAM nutzen.
Anders wenn man für eine vorgegebene Hardware eine Software entwickeln
soll.
Derzeit hat mein Controller 8kB RAM. Andere pincompatible Controller mit
mehr RAM gibts leider nicht.
In der Tat kann man da nicht mit der Kelle hantieren :)
Noch ein Tipp: alles was nicht einer Task gehört, gehört der Main-Task.
Wenn Du also waehend der Entwicklung noch RAM zu vergeben hast, dann
vergebe es der Main-Task.
Das hatte ich anfaenglich nicht beachtet.
Eine Frage: ist es Dir gelungen in RTX_Conf_CM.c den Wert von OS_STKSIZE
zu aendern? Da kann ich machen was ich will; es bleibt stur bei 200
Bytes.
Der Wert von OS_STKSIZE steht bei mir fest auf 50.
Defaultwert sind 200 Words, also 400 Bytes.
Das funktioniert einwandfrei.
Gerade habe ich nochmal nachgeprüft, nach dem Aufruf von
osKernelStart(); kehrt die Funktion definitiv nicht zurück.
Meine main() Funktion hatte ich oben schon gepostet.
Geprüft habe ich das folgendermaßen:
1
// Start RTOS
2
osKernelStart();
3
4
for(;;){
5
osDelay(500);
6
CheckThreadStacks(3,task_id_pool);
7
}
Die Werte im task_id_pool stehen auch nach Minuten immernoch auf 0. Hier
wird nichts berechnet.
Wenn ich die Funktion CheckThreadStacks in eine andere Task setze, dann
klappts einwandfrei.
Ich kann übrigens auch ein NVIC_SystemReset(); nach dem osKernelStart();
setzen, das System läuft ohne Unterbrechung weiter...
Bernhard schrieb:> Der Wert von OS_STKSIZE steht bei mir fest auf 50.
Schon klar; ich wollte nur wissen, ob Du diesen Standartwert aendern
kannst. Ich konnte es naemlich nicht.
Wenn Du aus dem osKernelStart() nicht mehr zurückkehrtst ... hast Du
möglicherweise Tasks mit höherer Prioritaeten? In diesem Fall musst Du
aber schon dafür sorgen, dass auch Tasks mit niedrigen Prioritaeten zum
Zug kommen.
>Schon klar; ich wollte nur wissen, ob Du diesen Standartwert aendern>kannst. Ich konnte es naemlich nicht.
Ja, den konnte ich ändern, funktioniert einwandfrei.
Umgebung: gcc auf Konsole :)
>Wenn Du aus dem osKernelStart() nicht mehr zurückkehrtst ... hast Du>möglicherweise Tasks mit höherer Prioritaeten?
Die Tasks haben alle osPriorityNormal.
main() selbst ist bei mir kein eigener Task, das wäre nur bei z.B. Keil
mit der Microlib so. Siehe RTX_CM_lib.h ab Zeile 254 (RTX v4.80)
Eigentlich brauche ich nur 2 Tasks, ein Main-Task und ein Task als
Treiber für einen Kommunikationschip. Als IPC nutze ich nur
SendMessage/GetMessage
Im Grunde würde ein einfacher Task-Scheduler ohne Prioritätem schon
reichen, CMSIS RTX war bisher die erste Wahl da gut dokumentiert, frei
verwendbar und einfach zu implementieren.
Hi,
ich hab ähnliches beim alten RTX gemacht. Mich interessierte aber nur
die maximale Stack Auslastung zwecks Optimierung.
Dazu einfach die Stacks (User defined Stacks bei mir) vor der OS
Initialisierung (im CMSIS RTOS in der SystemInit() bzw. pre-main) per
memset auf 0xcc setzen und dann zur Laufzeit mit einen Memory Window
beobachten.
Protothreads ist leider nicht verwendbar.
Ich brauche ein richtiges OS mit getrennt voneinander laufenden Tasks.
CMSIS RTX macht das eigentlich schon ganz gut, auch hinsichtlich
Resourcenverbrauch.
Wenn jemand was anderes kennt bin ich gerne Interessiert.
Hallo,
ich nutze:
RTX 5.0.0
CMSIS-RTOS2 Version 2.1.1
gibt es hier auch eine Möglichkeit die Stack Kontrolle zu
implementieren?
bei mir scheitert es daran das os_Stackinfo und os_active_TCB ( beide
jeweils in der RTX_CM_lib.h definiert) unbekannt sind -->RTX_CM_lib.h
gibts nicht bei der V2)
hat das schon mal jemand probiert?
Nutzt jemand die V2.1.1 und hat eine Lösung parat die proz. sich
anzeigen zu lassen?
für die Stacksize gibt es ja eine implementierte Funktion.
Grüße
Andi
Andi schrieb:> Hallo,>> ich nutze:>> RTX 5.0.0> CMSIS-RTOS2 Version 2.1.1>> gibt es hier auch eine Möglichkeit die Stack Kontrolle zu> implementieren?>> bei mir scheitert es daran das os_Stackinfo und os_active_TCB ( beide> jeweils in der RTX_CM_lib.h definiert) unbekannt sind -->RTX_CM_lib.h> gibts nicht bei der V2)>> hat das schon mal jemand probiert?>> Nutzt jemand die V2.1.1 und hat eine Lösung parat die proz. sich> anzeigen zu lassen?>> für die Stacksize gibt es ja eine implementierte Funktion.>> Grüße>> Andi
Hallo,
bitte obigen Beitrag ignorieren. Ich hatte einen Denkfehler:) Hat sich
alles geklärt...
Grüße
Andi
Mehmet K. schrieb:> In main() wird alle 500ms die Funktion CheckThreadStacks() aufgerufen.
Ich sehe nicht so recht, wozu das überhaupt gut sein sein soll. Man muß
sowieso auf maximale Stack Usage auslegen, und die durch Ausprobieren
ermitteln zu wollen, ist grober Pfusch. Das ermittelt man durch den Call
Graph.
Andreas H. schrieb:> und das soll wie genau funktionieren? Kannst du das bitte genauer> erklären?
Mit Keil drückt man auf "build project" und guckt sich dann den Call
Graph an. Der wirft einem automatisch die maximale Stacktiefe jeder
Funktion mit Unteraufrufen aus. Der Bedarf eines Tasks ist dann die
Tiefe der Entry-Funktion. Plus noch Sicherheitsspielraum, damit man
nicht bei jeder kleinen Code-Änderung erneut gucken muß.
Beim GCC ist das etwas hakeliger. Man gibt -fstack-usage mit, bekommt
dann aber nur pro Funktion den Bedarf ohne Aufrufe. Hab aber mal im Netz
dazu ein Perl-Script gesehen, was das aufaddieren kann, solange man
keine Rekursion verwendet. Oder, solange es ein überschaubares Projekt
ist, kann man das von Hand machen. Muß man dann aber pro Aufruf noch ein
paar Bytes extra einrechnen, das hängt von der Plattform und der
verwendeten calling convention ab.
Mit reinem Testen wäre das nur dann akzeptabel, wenn man gleichzeitig
100% Code-Coverage bekommt, denn sonst weiß man doch gar nicht, ob der
Zweig mit der schlimmsten Tiefe überhaupt durchlaufen wurde (speziell,
wenn es dabei um Fehlerbehandlung geht), und die Maximal-Zahl ist von
fragwürdiger Bedeutung.
Ob der Stackbedarf für Interrupts auf die Task-Stacks addiert werden muß
oder nicht, hängt davon ab, ob man keinen separaten Interrupt-Stack hat
(dann ja) oder doch - dann nein, aber der dedizierte Interrupt-Stack muß
groß genug sein für das worst-case-nesting. Jedenfalls darf man die
nicht bei der Betrachtung vergessen.
Übrigens kann es hier fatal wirken, wenn man im Debug-Build brav testet
und dann beim Release sich wundert, wieso es knallt. Na weil im
Debug-Build mit O0 nicht ge-inlined wird, im Release mit O2 aber schon.
Der Effekt kann der sein, daß zwei Stack-intensive Funktionen, die
vorher nacheinander aufgerufen werden, dann in dieselbe Funktion
rutschen. Je nachdem, was der Compiler dann macht, kann sich deren
Stackbedarf dann addieren.
Das ist speziell wahrscheinlich, wenn man genau deswegen stackintensives
Zeugs in eine eigene Funktion ausgelagert hat, die aber nur einmal
aufgerufen wird. Abhilfe kann dann durchaus sein, an den richtigen
Stellen mit noinline-Attributen das Inlining zu verhindern. Dazu muß man
sich halt den Call Graph bei der relevanten Optimierungsstufe angucken.
Nachtrag: das funktioniert natürlich nicht mehr, wenn man innerhalb der
Tasks mit Funktionspointern arbeitet, die erst zur Laufzeit bestimmt
werden. Was auch der Grund ist, wieso man das nach MISRA nicht darf, und
dann muß man halt 100% Coverage hinbekommen. Oder man macht's nach dem
Prinzip Hoffnung, wenn es letztlich egal ist, wie zuverlässig das Gerät
am Ende arbeitet.