Forum: Mikrocontroller und Digitale Elektronik uC/OS-II portieren


von Adelbert (Gast)


Lesenswert?

Hallo Leute,
ich würde gerne in meinen Bastelprojekten uC/OS-II einsetzen. Für 
nichtkommerzielle Zwecke darf man das ja, also habe ich es mir 
runtergeladen.
Doch nun sind im dem Zip-Archiv nur ein paar c-Dateien. Was soll ich 
denn nun damit anfangen? Wo ist der Code, der die Tasks switcht?
Allgemein, gibt es irgendwie ein Tutorial, wie man an die Portierung de 
uC/OS-II heran geht? Ich stehe im Moment ziemlich auf dem Schlauch und 
die paar Application Notes auf der Micrium Website helfen mir auch nicht 
so sehr weiter.
Wer hat selber uC/OS-II im Einsatz und kann mir ein bisschen weiter 
helfen?

Ich würde mich über Hilfe von euch sehr freuen.

Vielen Dank schon im Voraus!

von Daniel W. (daniel_w)


Lesenswert?

Hi,
was fuer einen Mikrocontroller verwendest du du?

Hab vor ner Weile uC/OS auf nem AVR und ARM verwendet. Es gibt plattform 
spezifische und plattform unabhaengige Dateien. Die plattform 
spezifischen waren schwer auf der Homepage zu finden, wie ich mich 
erinnere. Man muss nur aufpassen, das der Port auch zum Compiler passt, 
da verschiedene c Compiler intern verschiedene Register verwenden.
Wenn man selbst eine Portierung realisieren will, sollte man vieleicht 
einen Blick in das passende Buch zu uC/OSII werfen. Ohne die 
Funktionsweise der context switches zu verstehen, koennte es ziehmlich 
schwierig sein.

Hoffe das hilf ein bisschen.

Cheers Daniel

von mlp (Gast)


Lesenswert?

tip mal "uC/OS-II rapidshare" in google ein. dann das vierte Ergebnis

von 900ss (900ss)


Angehängte Dateien:

Lesenswert?

Hi,

J.J. Labrosse (Erfinder von uc/OS) hat ein schönes Buch dazu 
geschrieben. Da steht drin, wie man das portiert. Mußt dich aber 
beeilen, das wird es nicht mehr lange zu kaufen geben, da eine neues 
Buch erschienen ist, das bezieht sich aber auf die uc/OS-III Version. 
Nur uc/OS-II ist frei für private Zwecke.  ISBN: 978-1578201037

Weiter im Anhang eine sehr detailierte Beschreibung, wie das OS auf ARM 
portiert wurde.

Für ATMEGA128 gibt es bei Micrium auf der Homepage eine fertige 
Portierung (hab ich aber noch nicht genutzt).
www.micrium.com -> Download -> Ports
Du mußt dich dort anmelden (kostet nichts).

Alles was mit Tasks zu tun hat steht im Modul os_task.c.
Der Taskswitch ist dort aber nicht drin, das ist ein Modul, welches zu 
dem Port der jeweiligen CPU gehört.

900ss

von Adelbert (Gast)


Lesenswert?

Daniel,
ich möchte das OS auf einem ARM720T laufen lassen.

mlp,
leider führt mich Suchergebnis Nr. 4 auf eine Seite, wo ich Games 
downloaden kann? :-O Sieht ziemlich mysteriös aus.

900sss,
danke für das Dokument. Ich werde es mir zu Gemüte führen und hoffe, es 
hilft mir etwas mehr, als die Micrium AppNotes :-)

von 900ss (900ss)


Lesenswert?

Adelbert schrieb:
> ich möchte das OS auf einem ARM720T laufen lassen.

Für ARM gibt es auch Ports bei Micrium. Sie mal nach, vielleicht findest 
du dort sogar den passenden. Sonst nimm einen, wo der CPU-Kern dem 
Deines ARM720T entspricht und passe ihn an.

900ss

von Adelbert (Gast)


Lesenswert?

Ich habe schon einen passenden Code gefunden, es ist sogar ein 
Beispielprojekt für IAR EWARM dabei. Das einzige, was mir daran nicht 
gefällt, ist, dass der Code für ein ganz spezifisches Board geschrieben 
ist. Ich habe natürlich mein eigenes Board. Des weiteren ist er leider 
völlig überladen mit einem unnötig grossen Beispielprogramm, und das ich 
uC/OS-II bis jetzt noch nicht so gut kenne, ist es sehr schwierig für 
mich, herauszufiltern, welche Dateien vom Sourcecode ich wirklich 
brauche, und welche zum Beispielprojekt gehören....

BTW: weisst du einen guten TCP-Stack, welcher zum uC/OS-II passt? Den 
von Micrium bekommt man ja nicht, wenn man nicht einen gewissen, 
sichrlich astronomisch hohen, Betrag bezahlt.

von 900ss (900ss)


Angehängte Dateien:

Lesenswert?

Adelbert schrieb:
> TCP-Stack

Die folgenden beiden sollten sich ohne große Probleme für uc/OS-II 
eignen:
http://www.sics.se/~adam/uip/index.php/Main_Page
http://www.sics.se/~adam/lwip/

Wobei der erst genannte mit weniger Ram auskommt, aber die Performance 
auch geringer ist. Der ist eher für kleine 8-Bit Systeme gedacht. Ein 
Vergleich der beiden Stacks findest du im Anhang.

Adelbert schrieb:
> Das einzige, was mir daran nicht
> gefällt, ist, dass der Code für ein ganz spezifisches Board geschrieben
> ist. Ich habe natürlich mein eigenes Board.

Also ein wenig solltest du schon selber machen. Wo bleibt denn sonst der 
Spaß?

Mit den Appnotes von Micrium sollte eigentlich deutlich werden, welche 
Sourcen wofür sind und was du brauchst. AN1014, da steht es doch recht 
gut beschrieben.

von Adelbert (Gast)


Lesenswert?

Danke 900sss.
lwip kenne ich bereits; ich hatte allerdings gehofft, dass es noch 
andere gibt.

Bezüglich uC/OS-II.
Ich hab jetzt im IAR Workbench nochmals versucht, ein neues Projekt 
anzulegen. Dabei jammert er mir allerdings immer, dass

OS_TASK_TMR_PRIO

nicht definiert ist, sowie diverse Hook-Funktionenn.
Was für einen Wert muss OS_TASK_TMR_PRIO haben, und wo muss es definiert 
werden, und wozu wird es gebraucht? Im Manual taucht die Konstante 
leider nicht auf.
Muss ich die Hook-Funktionen schreiben, oder sollten da nicht 
standardmässige Hooks im uC/OS drin sien im Stil von


xyzHook(void)
{
#if OS_CPU_HOOK_EN > 0
  app_cpu_hook();
#endif
}

Sodass die Funktion vorhanden ist, aber nichts macht, wenn die Hooks 
ausgeschaltet sind. So steht es zumindest in einem Beispiel im AN-1014, 
aber ich finde die Sourcecode-Datei nicht, wo das drin steht.

Des weiteren beklagt sich der Linker über eine fehlende Funktion 
"OS_CPU_ExceptHndlr". Muss ich die schreiben, oder sollte die auch 
irgendwi in den untiefen de uC/OS sein? Was soll die Funktion tun?

Irgendwie vermisse ich eine Step by Step Anleitung, wie man das Teil 
einrichtet. So im Stil von "Erst muss diese und jene Datei erstellt 
werden, welche die Funktionen xyz und abc enthalten soll ... " etc.
Gibts kein Tutorial? Ich Steh' vor dem Sourcecode wie ein Ochs' vor dem 
Berg :O

von 900ss (900ss)


Lesenswert?

Adelbert schrieb:
> OS_TASK_TMR_PRIO

Die Konstante sollte wie folgt in app_cfg.h definiert werden:
#define  OS_TASK_TMR_PRIO              (OS_LOWEST_PRIO - 2)

Sie sollte auch "OS_LOWEST_PRIO - 2" sein!
Sonst kommt die Timertask evtl. nicht dran und deine Timer gehen zum 
Mond.

Wenn du Google mit dem define fütterst, dann werden recht viele 
Fundstellen ausgespuckt, die dir geholfen hätten.

> Muss ich die Hook-Funktionen schreiben, oder sollten da nicht
> standardmässige Hooks im uC/OS drin sien im Stil von

Weißt du was ein Hook ist, wofür er da ist? Wenn du dir das überlegst, 
dann wirst du auf die Lösung selber kommen.

> Sodass die Funktion vorhanden ist, aber nichts macht, wenn die Hooks
> ausgeschaltet sind. So steht es zumindest in einem Beispiel im AN-1014,

Da bist du ja schon auf dem richtigen Weg mit deinen Gedanken. :-)

> aber ich finde die Sourcecode-Datei nicht, wo das drin steht.

Die Hook-Funktionen sind vom Anwender bereitzustellen. Die gehören zu 
dem jeweiligen Anwenderprojekt. Bei deinen ARM-Port, den du dir bei 
Micrium geholt hast, sollte alles dabei sein. Ich vermute du hast 
Dateien aus dem Projekt entfernt, die das benötigte enthalten. 
Andernfalls wäre das Projekt nicht konsistent.

Adelbert schrieb:
> Irgendwie vermisse ich eine Step by Step Anleitung

Bei den neueren Ports oder den Kernel-Sourcen sind in der Regel zwei 
PDFs dabei...
1) "uCOS-II-CfgMan.pdf" da steht einiges auch zum portieren drin, ist 
aber keine Step-by-Step Anleitung.
2) "uCOS-II-RefMan.PDF" da ist das API beschrieben, damit du es später 
auch nutzen kannst.

Versuche den letzen Kernel-Source V2.90 von Micrium zu laden. Dort sind 
die PDFs auch enthalten. Weiter stehen wichtige Infos in den 
Releasenotes der jeweiligen Version drin.

von Adelbert (Gast)


Lesenswert?

Hallo 900sss,
ich habe nun im IAR Simulator versucht, das uC/OS zum Laufen zu kriegen. 
Es geht jetzt, aber er wechselt die Tasks nicht?! Der Interrupt ruft 
jeweils den ExceptHndlr auf, und dieser ruft OSSchedNew auf. Dort wird 
anhand der Variable OSRdyGrp bestimmt, welcher Task als nächstes laufen 
soll. Richtig?

Bei mir hat diese Variable jetzt den konstanten Wert 0x81. Kein einziges 
mal wird irgend ein Task umgeschaltet. Und  wenn ich alle 
Sourcecode-Dateien nach dieser Variable durchsuche, dann finde ich auch 
keine einzige Funktion, die dieser Variablen einen Wert zuweist! Was 
muss ich tun, damit das umschalten der Tasks funktioniert?

von 900ss (900ss)


Lesenswert?

Adelbert schrieb:
> ....
> anhand der Variable OSRdyGrp bestimmt, welcher Task als nächstes laufen
> soll. Richtig?

Nööö.

> Was muss ich tun, damit das umschalten der Tasks funktioniert?
Das Dokument, was ich dort
Beitrag "Re: uC/OS-II portieren"
als Anhang bereitgestellt habe, lesen.
Dort steht genau drin, was passieren muß. Ab Kapitel 4 bis 4.2.4 lesen, 
und(!) verstehen. Vorkauen hab ich keine Lust zu. Sorry.

von Adelbert (Gast)


Lesenswert?

Hallo 900sss,
sorry, so war das nicht gemeint, mit dem Vorkauen. Ich will es ja schon 
gerne selber verstehen lernen und anwenden, doch wenn man bisher noch 
nichts mit RTOSen zu tun hatte, ist das ganze nicht sehr einfach zu 
verstehen. Bisher habe ich immer mit dem Mainloop mit Interrupts 
gearbeitet, aber das ist mühsam und manchmal kann das sehr 
unübersichtlich werden, deshalb erhoffe ich mir mit einem RTOS ein paar 
Vereinfachungen. (Ausserdem ist es viel toller, wenn man behaupten kann, 
dass auf dem selber gebauten Board ein RTOS läuft ;-)).

Ich habe nun das Reference Manual zu uC/OS studiert. Oder jedenfalls 
schon mal die wichtigsten Teile, wo es um den Context Switch und den 
Scheduler geht. Und dabei ist mir etwas aufgefallen.
Kann es sein, dass bei uC/OS IMMER der Task mit der höchsten Priorität 
die CPU belegt? wenn ich also 3 Tasks habe:

Task A mit Priorität 1 (höchste)
Task B mit Priorität 2
Task C mit Priorität 3 (niedrigste)

dann wird IMMER Task A laufen, ausser, durch einen Interrupt wird Task B 
oder C aufgerufen, oder Task A gibt die CPU von sich aus frei. Ist das 
korrekt?

Wenn dem so ist, dann wäre das nämlich ganz schlecht für mich. Ich hatte 
mir eher erhofft, dass jeder Task mal dran kommt, und halt einfach der 
mit der höchsten Priorität am längsten darf.
Eine konkrete Anwendung davon wäre z.B.: Task A muss irgendwas 
berechnen, Task B soll einen TCP-Stack bedienen, und wenn dann mal ein 
bisschen CPU-Zeit übrig ist, kommt Task C dran, der das User Interface 
macht, also irgendwelche Lampen ein- und aus schaltet und Taster 
abfragt. Aber wenn uC/OS wirklich so funktioniert, wie ich es 
mittlerweile befürchte, dann wird das so nicht funktionieren, oder?
Was eigentlich sehr schade ist, denn das uC/OS sieht in meinem ungeübten 
Auge sehr schlank und effizient aus, der C-Code ist sehr sauber, die 
Variablen haben gute Namen, sodass man sie wieder erkennt (und nicht 
einfach i, wie in vielen anderen Codes) und es hat sogar Kommentare.

Gibt es vielleicht noch andere RTOSe, die man als Bastler oder Student 
kostenlos (oder meinetwegen zu einem vernünftigen Preis) kriegt, und die 
meinen Ansprüchen gerecht werden? :-)

von 123 (Gast)


Lesenswert?

da wirst du pech haben.

die meisten Betriebsysteme arbeiten nach dem schema.

Die Task mit der höchsten Priorität bekommt solange rechenzeit, bis sie 
auf irgend was warten muss. oder sie unterbricht sich selber für eine 
gewise zeit. das was windows macht ist richtig aufwendig, und für 
echtzeit anwendungen nicht wirklich brauchbar.

Auch dien Prioritätseinteilung ist ggf etwas "daneben". z.B. eine gui 
sollte zügig reagieren. je nach dem wie komplex sie ausfällt. nicht erst 
nach sekunden.

Taster werden über interrupts behändelt, oder ein task, die über den IRQ 
aktiviert wird. oder über eine Zeitlich gesteuerte task abgefragt. die 
z.B. alle 100ms anläuft und alle taster abklappert. entweder duch einen 
sleep oder eine timer realisier wird.

Die task für die Darstellung läuft dann los, wenn es was zu tun gibt. an 
sonsten wartet sie darauf, das ihr jemand sagt, das sie es tun soll. 
Semaphoren, Messages, Eventflags, ... oder auch zeitgestuert. (z.B. 
timer oder sleep)

tcp steck. ist eigentlich auch wie oben. ein teil wird im irq laufen, 
der server soket z.B. in einer eigenen task. Die läuft nur dann, wenn 
jemand was von deinem device will. amspmstem wartet er darauf das der 
IRQ zuschlägt.

und deine berechnungen wurde ich in der niedrigsten task ebene 
abgehandelt, nur dann wenn keiner sonst was rechnen braucht und das dann 
in einer endlos schleife ohne sleep.

Input     IRQ / IRQ + Task Prio 15 / oder über Timer alle z.B. 100ms
Output    Task prio 20 synchronisiert über eventgroup oder änlich /
          oder timer gesteuert ( synchronisation nicht vergessen )
Netzerk   IRQ + Task Prio 10
Berechnung Task Prio 30


gruss.

von 900ss (900ss)


Lesenswert?

Adelbert schrieb:
> Hallo 900sss,
> sorry, so war das nicht gemeint, mit dem Vorkauen.

Kein Thema, ich hatte ein wenig den Eindruck: statt lesen vorkauen 
lassen :-)

> verstehen. Bisher habe ich immer mit dem Mainloop mit Interrupts
> gearbeitet, aber das ist mühsam und manchmal kann das sehr
> unübersichtlich werden,

Ja das funktioniert auch sehr gut.

> deshalb erhoffe ich mir mit einem RTOS ein paar
> Vereinfachungen.

Du mußt dann aber umdenken und vor allem verstehen, wie ein RTOS 
funktioniert.

> (Ausserdem ist es viel toller, wenn man behaupten kann,
> dass auf dem selber gebauten Board ein RTOS läuft ;-)).

Stimmt! :-))
Aber wenn es nicht richtig läuft, dann lieber ohne RTOS aber mit 
Funktion.

> Kann es sein, dass bei uC/OS IMMER der Task mit der höchsten Priorität
> die CPU belegt? wenn ich also 3 Tasks habe:

Ja. Das hast du richtig verstanden und ist bei fast allen 
Echtzeit-Kernen so. Fast alle benutzen das "pre-emptive" Multitasking, 
so auch uc/OS-II.
Die andere bekanntere Form ist das kooperative Multitasking. Es gibt 
noch mehr Verfahren, z.B. noch Round-Robin. Manche Kerne können 
Pre-emptive und Round-Robin gleichzeitig, uc/OS-II aber nicht (ich 
glaube uc/OS-III ja). Beim Round-Robin bekomt jede Task eine feste 
Rechenzeit zugewiesen (Z.B. 10ms) und dann wird auf die nächste Task 
umgeschaltet. Wenn die Kernel beides können, dann wird zwischen Tasks, 
die die gleiche Priorität haben, mittels Round-Robin umgeschaltet.

>
> Task A mit Priorität 1 (höchste)
> Task B mit Priorität 2
> Task C mit Priorität 3 (niedrigste)
>
> dann wird IMMER Task A laufen, ausser, durch einen Interrupt wird Task B
> oder C aufgerufen, oder Task A gibt die CPU von sich aus frei. Ist das
> korrekt?

Stimmt fast, je nachdem wie du Task-A programmiert hast.


> Wenn dem so ist, dann wäre das nämlich ganz schlecht für mich.

Nein, du mußt das entsprechend programmieren. Die Task A z.B. soll 
rechnen. Sie muß ja die Inputs dafür bekommen. Wenn diese z.B. aus einer 
Queue kommen, dann wird nach dem Ende einer Berechnung wieder 
"QU_getValue" (Kernelaufruf) aufgerufen. Wenn jetzt keine Werte in der 
Queue stehen, dann wird die Task vom Scheduler stillgelegt und die 
anderen Tasks kommen dran. Sie dir mal die Task-States bei uc/OS-II an. 
Ich weiß aber nicht, ob das im Ref-Manual steht.

> Gibt es vielleicht noch andere RTOSe, die man als Bastler oder Student
> kostenlos (oder meinetwegen zu einem vernünftigen Preis) kriegt, und die
> meinen Ansprüchen gerecht werden? :-)

uc/OS-II sollte absolut deinen Ansprüchen gerecht werden, davon bin ich 
überzeugt :-)

Aber es gibt z.B. noch FreeRTOS oder ChibiOS undnoch viele andere.

Aber du hast dich in uc/OS-II schon ein wenig eingearbeitet, warum da 
jetzt aufhören und beim nächsten Kernel feststellen, dass der genauso 
arbeiten (pre-emptiv).

Ich würde mich informieren, wie man mit so einem RTOS arbeitet. Hab 
gerade keine passende Stelle aber frage mal Google.

900ss

von Adelbert (Gast)


Lesenswert?

Hallo 900sss,
JUHU, ES LEBT! Ich lasse erfolgreich 2 LEDs auf meinem Board blinken. 
Ich habe 2 Tasks kreiert, jeder schaltet seine LED ein bzw. aus und wird 
dann mittels

OSTimeDly

stillgelegt, sodass auf den anderen Task umgeschaltet werden kann. (Ein 
verdammter aufwand, nur um 2 lächerliche LEDs blinken zu lassen ;-)))

Ansich ist das ganz toll, aber wie gesagt würde mir das, was du unter 
"Round Robin" beschrieben hast noch ein bisschen besser gefallen. Klar 
werde ich jetzt nicht unbedingt mit uC/OS aufhören wollen, ich möchte es 
schon gerne verwenden, aber wenn es den Round Robin auch noch 
beherrschen würde, wäre es perfekt. Kann das denn z.B. FreeRTOS oder 
ChibiOS? Oder gibt es das gar nicht fürm solche Embedded-Anwendungen?

Jedenfalls, es macht mir in der Zwischenzeit den Eindruck, dass es 
funktioniert, aber ich bin mir nicht 100%ig sicher, ob ich alles richtig 
implementiert habe. Kannst du evtl. mal einen Blick in meinen Code 
werden, wenn ich ihn hochlade? (Darf ich das überhaupt, wenn da uC/OS 
mit dabei ist? :-/ )

Wie würde ich es jetzt machen, wenn ich z.B. Task A habe, welcher 
irgendwelches Zeugs berechnet, und Task B, der den TCP-Stack laufen 
lässt. Wenn beide grade nichts zu tun haben, sollen sie die CPU an den 
Task C abgeben, der das ganze dann visualisiert. Wie würde man das 
machen? Einfach überall einen OSTimeDly einzufügen, wird wohl kaum die 
richtige Lösung sein ;-) Ausserdem ist es schwierig, abzuschätzen, was 
für ein Delay sinnvoll ist. Müsste man das mittels eines Timers machen, 
der den Task C alle paar 10ms "aufweckt"?

Was müsste ich tun, wenn ich ein paar Aufgaben habe, die eigentlich 
gleich wichtig sind? Nehmen wir an, ich habe 2 serielle Schnittstellen. 
Auf der einen kommen Daten rein, die sollen von der CPU gespeichert 
werden, und ein paar Dinge sollen modifiziert werden, und danach sollen 
die daten über die 2. Schnittstelle weiter gesendet werden. Dann sollte 
das senden doch eigentlich dieselbe Priorität haben, wie das empfangen, 
denn die Daten sollen ja auch innerhalb einer gewissen Zeit raus. In 
uC/OS darf aber ein Task nicht dieselbe Priorität haben, wie ein 
anderer! Wie könnte man das Problem lösen? In einem solchen Fall wäre 
Round Robin genau das richtige. Eigentlich würde das "ideale RTOS" wohl 
nach dem Schema vorgehen:

Tasks mit der höheren Priorität laufen zuerst
Tasks mit der selben Priorität bekommen nach dem Round Robin-Verfahren 
eine feste CPU-Zeit zugeteilt.

Dann könne ich Task A und B mit gleicher Prio laufen lassen, die 
wechseln sich dann schön gleichmässig ab. Und dazwischen kommt dann 
irgendwann mal noch Task C dran, der eine niedrigere Priorität hat.

von 123 (Gast)


Lesenswert?

moin

RS232 In, wird im IRQ behandelt, und in einem grossen loop buffer 
abgespeichert. Wenn daten anliegen, wird z.B. ein Eventflag gesetzt, um 
z.B. eine Task aufzuwecken.

Die Verarbeitungstask ruft in RS232Read auf. hier wird z.b auf das 
eventflag von IRQ gewartet. und erst dann die angefordete anzahl von 
characters auf dem loop buffer copiert.

Danach folgt in der task die verarbeitung. die irgend wann mal ein 
RS232Write aufruft. die funktion configurert z.B. den dma zum versenden, 
schaltet den DataSendCompleat IRQ scharf, und wartet ggf darauf, das die 
dtaen komplett versendet worden sind. bzw vor dem senden, damit es zu 
keinen kollisionen kommt. ggf aufpassen mit den sendpuffern.


sollte die "RS232" noch zusätzlich irgendwelche Protokoll aufwand 
berücksichtigen, würde ich 2 weiter task drüberlegen. eine fürs senden, 
eine fürs empfangen. Prioritätsmässich würde ich senden höher legen als 
empfangen. denn wenn ich vor lauter empfangen nicht mehr senden kann ist 
auch nichts gewonnen. umgekehrt, läuft der empfangspuffer über, was ich 
erkennen kann.

Weiter läuft die Kommunikation meist über queues oder ähnliches. die 
laufen dann irgend wann mal voll, so das die verarbeitende task entweder 
fehler beim eintragen bekommt, oder suspendiert wird, bis die queue 
wiede platz bekommt.


Eine Receive Task sollte nicht als loop check for input implementiert 
werden. hier gibt es schönere lösungsmöglichkeiten über Interrupts

gruss

von 900ss (900ss)


Lesenswert?

Adelbert schrieb:
> Hallo 900sss,
> JUHU, ES LEBT! Ich lasse erfolgreich 2 LEDs auf meinem Board blinken.
> Ich habe 2 Tasks kreiert, jeder schaltet seine LED ein bzw. aus und wird
> dann mittels

Na dann.... Applaus, freut mich. :-)

>
> OSTimeDly
>
> stillgelegt, sodass auf den anderen Task umgeschaltet werden kann. (Ein
> verdammter aufwand, nur um 2 lächerliche LEDs blinken zu lassen ;-)))

Ja ja, aber es ist gut so anzufangen. Wenn du noch ein kompliziertes 
Programm hättest, dann wüßtest du kaum noch, wo du die Fehler suchen 
solltest.

>
> Ansich ist das ganz toll, aber wie gesagt würde mir das, was du unter
> "Round Robin" beschrieben hast noch ein bisschen besser gefallen.

Das liegt nur daran, dass du das Prinzip eines solchen Schedulers 
(pre-emptiv) noch nicht ganz verstanden hast. Nicht umsonst wird dieses 
Verfahren so häufig eingesetzt.

> Kann das denn z.B. FreeRTOS oder
> ChibiOS? Oder gibt es das gar nicht fürm solche Embedded-Anwendungen?

Ähhh die haben eine Homepage mit ganz viel Doku :-) Aus dem Kopf weiß 
ich das auch nicht, müßte erst nachlesen.

Aber nochmal ganz kurz zur Taskumschaltung. Der Kernel schaltet die 
Tasks nicht nur bei "OSTimeDly" um, weil die eine Task dann "schlafen" 
geht. Der Scheduler prüft auch bei jedem Timertick (IRQ), ob er Tasks 
umschalten muß. Weiter prüft er bei fast jedem Kernelaufruf 
(API-Aufruf), ob eine höher priorisierte Task wieder "ready" ist.

Ein kleines einfaches Beispiel, es ist nicht sinnvoll so ;-)
Es dient nur der Verdeutlichung würde aber trotzdem so funktionieren.
Nehmen wir an, du hast eine Task, die wartet auf Zeichen, die über den 
UART empfangen werden. Die Kette sieht so aus:

UART --> UartRxIrq --> | QueueRx | --> UartRxTask --> | QueueData | -->
....  weiter geht es unten

Der UART empf. ein Zeichen, das lößt einen Empfangsinterrupt aus, diese
(UartRxIrq) Routine ließt das Zeichen aus dem UART und packt es mittels
OSQPost() in eine Queue (QueueRx). Die Task UartRxTask holt diese
mittels OSQPend() wieder ab und verarbeitet sie. Die Task UartRxTask
bleibt solange in der Funktion OSQPend(), bis etwas in die Queue
geschrieben wird. Und was macht der Kernel solange mit dieser Task?
Richtig, er legt sie schlafen und erst wenn ein Zeichen in der Queue
ist, wird diese Task wieder geweckt. Also ähnlich, als wenn du die Task
mit OSTimeDly schlafen legst.
Und wenn diese Task jetzt die höchte Priorität hat, dann macht das
nicht, solange keine Zeichen in der Queue sind schläft die sie jetzt und
die anderen nieder priorisierten Tasks können arbeiten.
Es ist so, dass der Kernel immer bei einem Betriebssystem-API-Aufruf die
Task umschalten kann, wenn in dem Moment eine höher priorisierte "ready"
ist. Es sei denn du hast es explizit verboten.

Und weiter von oben:

.... UartTxTask --> | QueueTx | --> UartTxIrq --> UART

Die UartTxTask wartet mittels OSQPend() auf die Daten in "QueueData".
Solange da nichts drin ist, schläft auch diese Task. Erst wenn Daten da
sind, werden sie mittels OSQPost() in die QueueTx geschoben und
NUR das erste Zeichen in den Uart geschrieben zum senden. Der
UART-TX-Ready Interrupt holt sich jetzt die anderen Daten aus der Queue
UartTxIrq bis er keine mehr findet, dann wird nichts mehr in den UART
geschrieben und es war der letzte IRQ. Das ist jetzt etwas vereinfacht
geschildert. Was passiert z.B. wenn die UartTxTask Daten in QueueTx
packt und das Senden über UartTxIrq noch aktiv ist, weil die Queue nicht
leer war? Dann darf UartTxTask nicht das erste Zeichen in den UART
packen (das macht ja schon der UartTxIrq in dem Moment), dann gäbe es 
Datensalat. Das wird dann mit einem Flag gesteuert.
Solange UartTxIrq aktiv ist, wird ein Flag gesetzt welches UartTxTask
darüber informiert, dass der Sender-IRQ noch aktiv ist, er nicht extra
angestoßen werden muß mit dem ersten Zeichen, welches UartTxTask senden
möchte.

Hmmm... Schwierig das in Text zu fassen, hoffe es ist einigermaßen
verständlich.

Ich denke du solltest einfach damit rumspielen. Geht ja nix kaputt :-)
Und immer schön das Ref-Manual zu den verwendeten Funktionen lesen. Da 
wird es recht gut erklärt, sogar ob ein Context-Switch passiert (hab ich 
jedenfalls gerade gesehen an einer Stelle).

Es gibt zur Erklärung auch noch einen Artikel:
http://www.mikrocontroller.net/articles/Multitasking

Deinen Source ansehen brauche ich nicht, solange er funktioniert. 
Außerdem ist in dem ersten von mir geposteten Dokument ausreichend 
beschrieben, wie man alles testet :-) Das kannst du ja mal mit deinem 
Kernel machen, dann wirst du schon sehen, ob du alles richtig gemacht 
hast.

Uff ist das lang geworden. Solange es hilft....
900ss

von Adelbert (Gast)


Lesenswert?

Hey 900sss,
wow, vielen Dank für deine Ausführungen! Super :-) Ich habe mittlerweile 
noch ein bisschen mehr experimentiert, und nun lasse ich auch mittels 
zusätzlicher Interrupts LEDs blinken - via Messages, die an andere Tasks 
versandt werden usw. Wenn man da erst mal so ein bisschen in so ein RTOS 
rein sieht, dann merkt man erst, wie genial das ist! Total faszinierend, 
wie der Scheduler immer rausfindet, wo es noch was zu tun gibt und die 
Tasks das dann erledigen. Genau sowas wollte ich eigentlich!

Frage: Wenn ich Task a mit einer hohen Priorität habe, und Task B mit 
einer niedrigen. B wartet jetzt mittels OSQPend(), bis was im UART rein 
kommt. Wird dann Task B geweckt, wenn was rein kommt, oder wird so lange 
am Task A weiter gerechnet, bis dieser sich schlafen legt oder was 
anderes tun muss?
Umgekehrt ist der Fall natürlich klar, denn wenn B die höhere Priorität 
hätte, dann würde natürlich dieser Task weiterrechnen.

Jetzt wo es läuft, ist es echt toll, dieses uC/OS :-) Das erste RTOS, 
das ich zum Laufen gekriegt habe. Beim TNKernel (welcher präemptiv ist 
und nach dem Round Robin arbeitet, also genau das was ich wollte) habe 
ich es auch nach vielen Versuchen nicht geschafft. uC/OS geht aber jetzt 
super :D danke für deine Hilfe!

von 123 (Gast)


Lesenswert?

Es wird solange task A ausgeführt, bis diese sich durch einen 
Systemaurfuf selber unterbicht und in den zustand waiting versetzt wird. 
Warten auf Events, auslesen eines elementes einer Queue, die lehr ist, 
..., ... oder eine höher priorisierte Task in den Ready state wechselt. 
Dann kommt diese task C zum zuge.

Task B bleibt im Ready state bis sie die task mit der höchsten priorität 
ist, die im Ready state ist.

die task mit der niedrigsten priorität ist die idel loop, in der nicht 
benötigte rechenzeit verblasen wird.

gruss

und noch viel spass mit micrium

von 900ss (900ss)


Angehängte Dateien:

Lesenswert?

Adelbert schrieb:
> Hey 900sss,
> wow, vielen Dank für deine Ausführungen! Super :-)

Freit mich, wenn es hilft.
> .... Total faszinierend,
> wie der Scheduler immer rausfindet, wo es noch was zu tun gibt und die
> Tasks das dann erledigen. Genau sowas wollte ich eigentlich!

Sach ich doch, pre-empives Multitasking ist schon gut, birgt aber auch 
Fallen, da der Programmablauf sehr schwer nachzuvollziehen ist.

> kommt. Wird dann Task B geweckt, wenn was rein kommt, oder wird so lange
> am Task A weiter gerechnet, bis dieser sich schlafen legt oder was
> anderes tun muss?

Überlege selber. Wenn ein Zeichen in der Queue landet und Task B in den 
Ready state wechselt, dann hast du zwei Tasks, die laufen könnten, A und 
B. Welche nimmt der Scheduler?

> Umgekehrt ist der Fall natürlich klar, denn wenn B die höhere Priorität
> hätte, dann würde natürlich dieser Task weiterrechnen.

Richtig.

Wenn du jetzt überlegt hast, dann wirst du wissen, welche Task oben dran 
kommt (A oder B). Also im Prinzip wird immer die Task mit der höchsten 
Priorität ausgeführt, wenn mehrere "ready" sind. Es gibt aber Ausnahmen, 
das Stichwort heißt "Priority inversion". Dieses ist wichtig da es sonst 
zu dead-locks kommen kann. Und uc/OS beherrscht das auch. Frag mal 
Google. Suche solange, bis du einen Beitrag findest, der das gut 
erklärt. Oder im Anhang auch zwei PDFs dazu.


> Jetzt wo es läuft, ist es echt toll, dieses uC/OS :-)

Sag ich doch :-)

> Das erste RTOS, das ich zum Laufen gekriegt habe.

Ja, man ist "stolz wie Oskar" ;-)

> Beim TNKernel (welcher präemptiv ist
> und nach dem Round Robin arbeitet, also genau das was ich wollte) habe
> ich es auch nach vielen Versuchen nicht geschafft.

TNKernel hab ich vor uc/OS probiert und er funktionierte auch. Aber zu 
uc/OS-II hatte ich mehr Infos, deshalb hab ich das auch noch probiert 
und bin dort geblieben.

> super :D danke für deine Hilfe!

Bitte schön, fröhliches Basteln.

900ss

von Adelbert (Gast)


Lesenswert?

123,
auch dir möchte ich auch mal noch herzlich für deine Ausführungen
danken. Ihr beide habt mir echt weiter geholfen :-)

900sss,
noch eine allerletzte Frage.
Ich habe jetzt folgendes probiert. Ich initialisiere den UART, natürlich
mit Interrupts. Nun habe ich eine Funktion print_string gebastelt:

void print_string(char *str)
{
  char err;
  while(*str)
  {
    U0THR = *str;
    OSMboxPend(UART_Mbox, 0, &err);
    str++;
  }
}

die Interrupt-Routine sieht natürlich dann so aus:

void uartinterrupt(void)
{
  if(U0IIR & BIT_01) /* tx register empty */
  {
    OSMboxPost(UART_Mbox, (void*)0xdeadbeef);
  }
}

Wenn also ein Zeichen in den Sendepuffer geschrieben worden ist, dann
wird auf eine Message gewartet (und der Task dadurch natürlich de facto
schlafen gelegt).
Wenn das Zeichen gesendet worden ist, dann wird die Interruptroutine
aufgerufen, welche eine (hier sehr sinnvolle....) Message an den Task
sendet - und diesen dadurch natürlich wieder zum Leben erweckt. Darauf
hin wird das nächste Zeichen gesendet usw. usf.
Es funktioniert auch wunderbar zum Senden, wenn ich allerdings den
selben Mechanismus einbaue, um was zu Empfangen - dann stürzt mir der
Rechner ab, sobald ich auch nur ein einzelnes Zeichen sende. Er bleibt
interessanterweise bei der Funktion

OSIntExit

hängen.
Mach ich was falsch, hab ich zu wenig Stack, oder macht man das mit dem
UART gar nicht so, wie ich es anpacke? Im Sample-Project von Micrium
wird das zwar auch so gemacht. Ein Keyboard-Task checkt, ob eine Taste
gedrückt wurde. Wenn nicht, wird er mittels OSTimeDly unterbrochen.
Wurde allerdings eine Taste gedrückt, dann wird mittels

OSMboxPost(AppKbdIFMbox, (INT32U)key);

der Keycode an den User Interface Task gesandt, der das dann auf einem
Display anzeigt (oder irgendwie so - ich kenne die Hardware, für die
dieses Example geschrieben wurde, leider nicht).

von 900ss (900ss)


Lesenswert?

Adelbert schrieb:
> Mach ich was falsch,

Wahrscheinlich ;-)

> hab ich zu wenig Stack,

Das kannst du besser probieren, als ich. Oder soll ich deinen Stack zum 
probieren vegrößern? ;-)

> oder macht man das mit dem
> UART gar nicht so, wie ich es anpacke?

Es ist eine Möglichkeit, sehe da erstmal kein Problem. Nur das die 
Tasks, die print... aufrufen, solange hängenbleiben, bis der String 
gesendet ist. Das ist ein Nachteil. Aber wenn man damit leben kann...

Du solltest deinen Sourcecode posten, so wie du ihn verwendest, keine 
Auschnitte oder abgetippt. Poste alle deine C Files ALS ANHANG.
Das uc/OS selber kannst du weglassen.

Was heißt denn, er bleibt hängen? Stürzt ab, aber woher hast du die 
Info, dass das in der IntExit Routine passiert?

So kann man da nichts zu sagen. Du must etwas mehr Infos geben.

900ss

von 123 (Gast)


Lesenswert?

Moin

das mit dem uart kann man so machen. nur warscheinlich etwas langsam. 
Aber zur übung und zum lernen sicher gut.

Ich würde das senden des Strings imr IRQ machen. Mittels einer Semaphore 
würde ich verhindern, das 2 sende aufträge gleichzeitig auf die Uart 
zugreifen.

zu deinem Problem.

verwendest du 2 unterschiedliche MessageQueus? jeweils eine für jdede 
Task?
1. für die Send task, die 2. für die Receive Task? wenn nicht, weist du 
nicht, welche task aktiviert wird.


Man könnte für das einlesen eine Semaphore verwenden.
Beim Recieive würde ich dann einen Loopp buffer anlegen, in dem der IRQ 
daten ablegt. beim überlauf, ein Fehlerflag / eventflag setzen. Wenn 
daten in den puffer kopiert worden sind, wird für jedes Character ein 
OSSemPost auslösen

Die ReadString liest aus dem Ringpuffer jeweils nur ein Character. Vor 
dem auslesen wird ein OSSemPend aufgerufen.

Zur implementierung bitte einen Read pointer verwenden, der nur von der 
ReadString funktion verwendet wird, und einen Write pointer, der nur vom 
irq verwendet wird. ansonsten sind "Critical Sections" notwendig da die 
gleiche variable vom IRQ und einer Task verändert werden kann.

Sicher nicht die schnellste möglichkeit.

von Adelbert (Gast)


Lesenswert?

900sss,
den Sourcecode habe ich mittlerweile wieder gelöscht, weil ich mich so 
geärgert hab, weil er nicht funktioniert :-/ ich muss versuchen, ihn 
wiederherzustellen.
Eigentlich wollte ich die Verwendung von einem speziellen write buffer 
vermeiden. Ich möchte gerne eine print-Funktion, welche ohne unnötige 
Buffers auskommt, sodass man einen String senden kann, ohne Daten herum 
zu kopieren. Geht das?

von 123 (Gast)


Lesenswert?

Sicher.

Wenn du sicherstellen kanst / sicher stellt, dass der send puffer auf 
den du beim senden verwendest nicht wieder überschrieben wird.

z.B. so.

die Print Funktion bekommt einen Pointer auf die zu sendenden Strin 
übergeben. Diese startet das senden. und wartet auf das ende mit hilfe 
einer Semaphore.

Der IRQ send buffer empty kopiert dann immer das nächste character in 
den das send register, bis der komplette string verschickt wurde.

Wenn ales verschickt wurde setzt der IRQ die Semaphore und die 
blockierte task wieder zu aktivieren.


ob du vor dem starten des Senden oder nach dem starten des senden 
wartetst, oder auf beides oder garnicht, hängt vom Anwendungsfall / 
Anforderungen ab. willst du Synchron oder asynchron arbeiten.

Vor dem Start des Sendens verhindert, das während ein String verschikt 
wird ein zweite senden gestartet wird. z.B. wenn eine 2 tasks was senden 
will. oder wenn eine task so schnell hintereinander etwas senden will, 
das die RS232 nicht mehr hinterher kommt. der 2. Send wird bereits 
gestartet bevor der erste beendet wurde.

Nach dem Senden, ist vom prinzip her das gleiche, wie wenn die task das 
senden im polling mode selber übernehmen würde. nur würde sie die ganze 
zeit den zustand des UARTS überprüfen und keiner anderen task die 
möglichkeit geben die rechenzeit sinvoller zu nutzen.
Die task weis nach dem verlassen der Funktion, das der String über die 
Leitung geganben ist. und komplett verschickt wurde. nicht nur das das 
senden begonnen hat und demnächst irgend wann mal beendet wird.


beim empfangen das gleiche.

willst du synchron oder asynchron lesen.

den von mir angesprochenen ringpuffer brauchst du nur, wenn du daten, 
die vor dem Receive aufruf eingelaufen sind empfangen willst. wenn 
nicht, kannst du die daten auch in den puffer speichern, dem du der 
funktion  übergeben hast. Daten die vor dem aufruf eingetroffen sind 
liegen dann ggf noch im Register oder im fifo.

gruss

von 900ss (900ss)


Lesenswert?

Adelbert schrieb:
> 900sss,
> den Sourcecode habe ich mittlerweile wieder gelöscht, weil ich mich so
> geärgert hab, weil er nicht funktioniert :-/

Da wär ich arbeitslos, wenn ich löschen würde, was nicht gleich 
funktioniert. An manchen Fehlern sucht man Wochen, wenn sie schwer bis 
garnicht reproduzierbar sind, also einfach zufällig mal auftreten.
Ist zum Glück recht selten.

Da schmeißt er die Flinte gleich in's Korn ;-)

> Eigentlich wollte ich die Verwendung von einem speziellen write buffer
> vermeiden. Ich möchte gerne eine print-Funktion, welche ohne unnötige
> Buffers auskommt, sodass man einen String senden kann, ohne Daten herum
> zu kopieren. Geht das?

Klar, das hattest du ja schon und sah auch gut aus. Zumindest die 
Sendefunktion.

Du hast doch sicher einen JLink oder ähnliches dann kannst du doch auch 
sehr schön debuggen und den Fehler finden. Man muß sich nur Gedanken 
machen, wie man an das Problem rangeht und vor allem wie muß der Code 
funktionieren, nicht nur "so ungefähr".

900ss

von Adelbert (Gast)


Lesenswert?

Hihi, normalerweise lösche ich ja auch nicht alles, was nicht 
funktioniert ;-) Nur diesmal hab ich mich ein bisschen geärgert.

Ich muss es nochmal versuchen, das mache ich dann heute Abend und gebe 
wieder hier Bescheid - jetzt sitze ich grade in der FH und kann nicht so 
gut basteln.

Gruss

von Adelbert (Gast)


Lesenswert?

900sss,
so ich habe es erfolgreich zum Laufen gekriegt. Wie ich das Problem nun 
gelöst hab:
1
char old, new;
2
OS_EVENT *mbox;
3
OS_EVENT *uartmbox;
4
char* uartstr;
5
6
void timer_tick(void)
7
{
8
  T1IR = 0xFF;
9
  old = new;
10
  new = (FIO0PIN & BIT_05);
11
  if(new && !old)
12
    OSMboxPost(mbox, (void*)1);
13
}
14
15
void test_task(void* arg)
16
{
17
  char err;
18
  inituart();
19
  while(1)
20
  {
21
    OSMboxPend(mbox, 0, &err);
22
    print("key pressed\n\r");
23
  }
24
}
25
26
void inituart(void)
27
{
28
  PCLKSEL0 |= BIT_06; /* uart0 clock = 72 MHz */
29
  PINSEL0 |= (BIT_04 |BIT_06); /* enable RXD0 and TXD0 */
30
  U0LCR = BIT_07; /* divisor latch enable */
31
  U0DLM = 0;
32
  U0DLL = 23; /* 115200 */
33
  U0FDR = 0xA7;
34
  U0LCR = (BIT_01 | BIT_00); /* 8 bits, no parity */
35
  U0FCR = (BIT_02 | BIT_01 | BIT_00); /* fifo enable & reset */
36
  U0IER = (BIT_00 | BIT_01); /* enable interrupts */
37
  VICVectPriority6 = 0;
38
  VICVectAddr6 = (long int)uartint;
39
  VICIntEnable = BIT_06;
40
  uartmbox = OSMboxCreate((void*)0x12345678);
41
}
42
43
void uartint(void)
44
{
45
  long int intstatus = U0IIR; /* get the interrupt source */
46
  if(intstatus & BIT_02) /* receive data available */
47
  {
48
  }
49
  else if(intstatus & BIT_01) /* transmit holding register empty */
50
  {
51
    uartstr++;
52
    if(*uartstr != 0)
53
    {
54
      U0THR = *uartstr;
55
    }
56
    else
57
    {
58
      OSMboxPost(uartmbox, (void*)0x12345678);
59
    }
60
  }
61
}
62
63
void print(char* str)
64
{
65
  char err;
66
  OSMboxPend(uartmbox, 0, &err);
67
  uartstr = str;
68
  U0THR = *str;
69
}

So, was macht der Code?
der ausgeführte Task ist natürlich der test_task. Periodisch wird die 
Funktion timer_tick aufgerufen, welche einen Taster abfragt. Wurde 
dieser gedrückt (Erkennung der steigenden Flanke), dann wird eine 
Message an den test_task gesendet, welcher nur auf diese eine Message 
gewartet hat. Darauf hin führt der test_task nämlich die Funktion print 
auf. Diese prüft, ob momentan evtl. noch ein String gesendet wird. Wenn 
nicht, dann wird der Stringpointer uartstr entsprechend gesetzt, und das 
erste Byte ins Senderegister geschrieben. Die Funktion print ist dann 
fertig, der test_task kann sofort weiter rechnen. Ist das erste Byte des 
Strings dann gesendet, dann wird der uartint aufgerufen. Dieser prüft, 
ob das Senderegister leer ist. Ist dies der Fall, und das nächste Byte 
im String ist nicht eine Null, dann wird weiter gesendet. Ist es jedoch 
eine Null, der String ist also zu Ende, dann wird eine Message 
platziert. Und genau die Anwesendheit dieser Message prüft die 
print-Funktion zu beginn. Ist der String also noch nicht zuende gesendet 
worden, und man ruft trotzdem print auf, dann versetzt diese Funktion 
den aktuellen Task so langen in den "Schlafzustand", bis der String 
fertig gesendet wurde.

Na, klappt das so? Bei mir kommt jedenfalls der Text richtig im Terminal 
an :-)

Schöne Grüsse

von Duhi J. (gasi)


Lesenswert?

Kennt von Euch jemand Firmen die Auftargsarbeiten µC/OSII durchführen 
bzw. bereits Produkte realisiert haben?

Benötige da einige Kontakte

Liebe Grüße

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
Noch kein Account? Hier anmelden.