Forum: Mikrocontroller und Digitale Elektronik Neues RTOS für AVR Mikrocontroller (AvRtos)


von Harald B. (harald_b69)


Lesenswert?

Hallo liebe AVR Gemeinde.
Da die aktuell verfügbaren Real Time Operating Systems (RTOS) für AVR's 
nicht ganz meinen Erwartungen entsprach, habe ich mich daran gemacht 
selbst ein RTOS zu schreiben.
Das Ergebnis ist AvRtos. Es wurde in C geschrieben um die Lesbarkeit 
zu verbessern und so evtl. Fehler schneller finden zu können. Das 
gesamte RTOS besteht lediglich aus 2 Dateien, was das Einbinden in ein 
bestehendes Projekt vereinfachen soll. Entwickelt wurde AvRtos auf 
einem ATmega32 und benötigt dabei folgende System Ressourcen:

  - 1 kByte Flash für Code
  - 11 Byte RAM pro Task Control Block
  - 5  Byte RAM pro Semaphor Control Block
  - 20 Byte RAM pro Queue Control Block

Zu beachten ist jedoch, dass pro Task jeweils ca. 100-150 Byte für den 
Stack zur Verfügung gestellt werden muss. Dies hängt jedoch vom 
jeweiligen System ab.
Für die Task Synchronisierung stehen Semaphoren und Queues zur 
Verfügung, was in den meisten Fällen ausreicht.

Ich habe bei Sourceforge ein Projekt angelegt. Dort kann AvRtos auch 
heruntergeladen werden.
https://sourceforge.net/projects/avrtos/

Unter folgendem Link ist die aktuelle Doxygen Dokumentation zu finden.
http://avrtos.sourceforge.net

Über Kritik oder weitere Anregungen würde ich mich natürlich sehr 
freuen.

von Peter S. (psavr)


Lesenswert?

Vielen Dank. Sieht gut aus, aufgeräumter und übersichtlicher Code, gute 
Beschreibung! Ich werde es gerne bei Gelegenheit ausprobieren.

Eine Kleinigkeit die mir aufgefallen ist:
1
/* Type definition for a queue */
2
typedef struct _Queue
3
{
4
   uint8_t* queueBuffer;   /*< Pointer to the queue buffer */
5
   uint16_t msgSize;       /*< Size of one message */
6
   uint16_t msgCount;      /*< Number of messages currently put to the queue */
7
   uint16_t msgMax;        /*< Number of maximal messages that can be put to the queue */
8
   uint16_t insertPointer; /*< Pointer to handle message insertion */
9
   uint16_t removePointer; /*< Pointer to handle removing messages */
10
   tElement getElement;    /*< Element to handle the queue task list */
11
   tElement putElement;    /*< Element to handle the queue task list */
12
} tQueue;

Ist es wirklich notwendig, dass die Variabeln in diesem strukt uint16_t 
sind, oder würde uint8_t nicht auch reichen? Ist effizienter auf einem 8 
Bitter, zudem hat man sicher Atomic-Zugriffe.

MfG peter

: Bearbeitet durch User
von Harald B. (harald_b69)


Lesenswert?

Hallo Peter,

es ist tatsächlich nicht notwendig, dass bei der Queue als Datentyp 
uint16_t verwendet wird. Danke für die Anmerkung.

Ich werde den Datentyp auf uint8_t ändern und eine neue Version 
hochladen.

Die Anzahl an Nachrichten, die an die Queue gesendet werden können, 
reduziert sich somit auf maximal 255 und die Nachrichtengröße wird 
ebenfalls auf 255 Byte reduziert. Dies ist jedoch auf einem 8-Bitter 
mehr als genug.

Es hat ausserdem den Vorteil, dass der Bedarf an RAM für einen Queue 
Kontrollblock auf 13 Byte reduziert wird und ein optimierter Zugriff auf 
die Daten der Queue stattfindet.

MfG Harald

von FiatLux (Gast)


Lesenswert?

Wie viele Takte werden für einen Taskwechsel maximal benötigt? In der 
Dokumentation steht es nicht drin oder ich habe es überlesen.

von (prx) A. K. (prx)


Lesenswert?

Harald B. schrieb:
> Für die Task Synchronisierung stehen Semaphoren und Queues zur
> Verfügung, was in den meisten Fällen ausreicht.

Ich vermisse die Möglichkeit, Semaphoren und Queues in Interrupts zu 
verwenden. Nicht wartend, natürlich, aber zur Signalisierung. IMHO ist 
das in Programmen mit einem RTOS eigentlich immer erforderlich. Im 
existierenden Code ist das schon aufgrund der verwendeten Methode der 
Interrupt-Sperre nicht möglich.

Siehe <util/atomic.h> ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
statt ENTER/EXIT_CRITICAL.

von (prx) A. K. (prx)


Lesenswert?

Es schiene mir sinnvoll, den direkt auf die Timer-Hardware bezogenenen 
Teil auszulagern. Nicht alle AVRs sind gleich gestrickt und vielleicht 
will jemand das ja an einen anderen Timer hängen.

Also den Timer-Hook _kernelScheduleTick als Teil des API exportieren und 
den Teil mit TIM0 in ein separates Files legen. Die Bits vom 
Timer-Register kann man übrigens auch symbolisch angeben. Das ist 
deutlich sicherer als numerisch (TCCR0 = 0x08 | 0x03).

: Bearbeitet durch User
von Harald B. (harald_b69)


Lesenswert?

Wieviele Takte für einen Taskwechsel benötigt werden kann ich aktuell 
nicht sagen, da ich es bis jetzt noch nicht ermittelt habe. Ich werde 
dies noch nachholen und in die Doku aufnehmen.

Die Möglichkeit Semaphoren aus einem Interrupt heraus zu setzen ist 
gegeben. Allerdings muss hier darauf geachtet werden, dass dies die 
letzte Aktion innerhalb der ISR ist. Der entsprechende Code für den 
Usart könnte z.B. wie folgt aussehen:
1
ISR(USART_RXC_vect)
2
{
3
   uint8_t taskWoken = FALSE;
4
5
   ...
6
7
   /* Check if the frame end was reached */
8
   if( lastCharacter == '\r' )
9
   {
10
      ...
11
12
      /* All data received, wake up and inform calling task by setting the semaphore  */
13
      taskWoken = TRUE;
14
   }
15
16
   if( taskWoken )
17
   {
18
      semaphoreSet( RxReadySemaphore );
19
   }
20
}

Allerding ist das Senden von Nachrichten an eine Queue tatsächlich nicht 
möglich. Der interne Aufbau einer Queue lässt dies schon nicht zu. Ist 
die Queue bereits voll während der entsprechende Interrupt ausgeführt 
wird, würde die Nachricht verloren gehen.
Queues sollten lediglich zur Synchronisierung und zum Datenaustausch 
zwischen Tasks eingesetzt werden.

Zur Synchronisierung von Interrupts und Tasks können wie oben 
beschrieben Semaphoren eingesetzt werden.

Die Initialisierung des Timer Interrupts sowie die Macros für den 
Context-Switch in eine separate Datei auszulagern habe ich auch schon 
überlegt. Allerdings war mein Bestreben, das gesamte RTOS in einer Datei 
zu belassen :-).
Um einen anderen Timer (z.B. TIMER1) als System-Tick zu verwenden, 
müssen die entsprechenden Anpassungen aktuell in der Datei AvRtos.c 
vorgenommen werden. Dies habe ich in der Doxygen Doku beschrieben.

Vielen Dank für die konstruktive Kritik. Ich werde die Anmerkungen 
beachten und in die neue Version mit aufnehmen.

MfG Harald

von (prx) A. K. (prx)


Lesenswert?

Harald B. schrieb:
> Um einen anderen Timer (z.B. TIMER1) als System-Tick zu verwenden,
> müssen die entsprechenden Anpassungen aktuell in der Datei AvRtos.c
> vorgenommen werden. Dies habe ich in der Doxygen Doku beschrieben.

Wobei man dann aber diese Anpassung mit einem Update des Kerncodes 
nochmal machen muss.

von (prx) A. K. (prx)


Lesenswert?

Harald B. schrieb:
> Zur Synchronisierung von Interrupts und Tasks können wie oben
> beschrieben Semaphoren eingesetzt werden.

Wobei hier aber Task-Switches ausschliesslich als Teil des Timer-Ticks 
ausgeführt werden. Als Reaktionszeit eines preemptives RTOS ist das doch 
arg zäh. Bei einem Zeitverhalten in dieser Grössenordnung kann man auch 
kooperativ arbeiten und dem Programmierer damit einige Arbeit ersparen.

Um das mit kurzen Reaktionszeiten nutzen zu können könnte der Timer-Tick 
in seine 2 Teile getrennt werden. Der zweite Teil liesse sich dann in 
anderen ISRs verwenden.

von (prx) A. K. (prx)


Lesenswert?

A. K. schrieb:
> Wobei man dann aber diese Anpassung mit einem Update des Kerncodes
> nochmal machen muss.

PS: Weshab man üblicherweise jene Files, die zur Anpassung vorgesehen 
sind, von der Deklaration des APIs trennt. Das betrifft also auch das .h 
File.

von Harald B. (harald_b69)


Lesenswert?

Ja, das ist allerdings richtig.

Hmmm... Es ist unter Umständen tatsächlich sinnvoller von der 1-File 
Strategie weg zu gehen und die Hardware abhängigen Teile in ein 
separates File auszulagern.

Ich sehe schon... Die nächste Version kommt bald :-).

von Harald B. (harald_b69)


Lesenswert?

Ich habe mir eure Kritik und Anregungen zu Herzen genommen und eine neue 
verbesserte Version erstellt.

Diese kann unter https://sourceforge.net/projects/avrtos/ 
heruntergeldaen werden.

Die Hardware abhängigen Teile des Codes habe ich nun in ein 2. File 
ausgelagert. Bitte darauf achten, dass die Initialisierung des Timer 
Interrupt nun in der Datei AvRtosHw.c zu finden ist.

Ausserdem können nun alle OS-Funktionen auch innerhalb einer 
Interrupt-Routine aufgefufen werden.
Am Ende der Interrupt-Routine wird dann ein Context-Switch ausgeführt, 
falls eine Task lauffähig geworden ist.
Um dies zu erreichen musste das Macro ISR() neu definiert werden. Also 
bitte darauf achten, dass alle Interrupt-Funktionen mit Hilfe dieses 
Macros definiert werden.

Falls Ihr weitere Anregungen habt, stehe ich euch gerne zur Verfügung.

MfG Harald

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.