5. Echtzeit Betriebssystem¶
RTOS = Realtime Operating System
Multitasking ohne RTOS
Siehe den Artikel von Bill Earl: Multi-tasking the Arduino.
Motivation
https://gitlab.informatik.hs-augsburg.de/es2/es2-nucl476/taskswitch
https://gitlab.informatik.hs-augsburg.de/es2/es2-nucl476/shared-data-problem
5.1. CMSIS RTOS API¶
Dieser Abschnitt dient als Übersicht zum CMSIS RTOS API. Dieses API wird für RTOS-Beispiele der Cube-Bibliothek verwendet. Die darunterliegende Implementierung ist mit FreeRTOS gemacht. Fast der komplette Abschnitt wurde aus der Keil CMSIS RTOS Doku entnommen, siehe [1]. Wer das Thema auch noch in einem Buch nachlesen möchte, kann in [MARTIN] Kap. 9 oder [YIUDG] Kap. 19 schauen.
[1] https://www.keil.com/pack/doc/CMSIS/RTOS/html/index.html
Nur ein “API”, Implementierung z.B. durch FreeRTOS oder Keil RTX.
CMSIS RTOS Dokumentation: http://www.keil.com/pack/doc/CMSIS/RTOS/html/index.html
CMSIS RTOS API
5.1.1. Liste der CMSIS RTOS API Funktionen¶
Funktion Aus ISR aufrufbar Kategorie
---------------------|-------------------|-----------------
osThreadCreate nein Task Management
osThreadGetId nein
osThreadSetPriority nein
osThreadTerminate nein
osThreadYield nein
osSignalClear nein ITC: Signale
osSignalSet ja
osSignalWait nein
osSemaphoreCreate nein ITC: Semaphore
osSemaphoreDelete nein
osSemaphoreWait nein
osSemaphoreRelease ja
osMutexCreate nein ITC: Mutex
osMutexDelete nein
osMutexWait nein
osMutexRelease nein
osMailAlloc ja ITC: Mail
osMailCreate nein
osMailFree ja
osMailGet ja
osMailPut ja
osMessageCreate nein ITC: Message Queue
osMessageGet ja
osMessagePut ja
osPoolCreate nein Memory Pool
osPoolAlloc ja
osPoolFree ja
osTimerCreate nein Timer
osTimerDelete nein
osTimerStart nein
osTimerStop nein
osDelay nein Delay
osKernelSysTick nein
Inter-Thread Communication (ITC) and Resource Sharing
Signal Events Synchronize threads using signals.
Message Queue Exchange messages between threads in a FIFO-like operation.
Memory Pool Manage thread-safe fixed-size blocks of dynamic memory.
Mail Queue Exchange data between threads using a queue of memory blocks.
Mutexes Synchronize resource access using Mutual Exclusion (Mutex).
Semaphores Access shared resources simultaneously from different threads.
5.1.2. Threads¶
Threads als enum-Typen
typedef enum
{
THREAD_1 = 0,
THREAD_2
} Thread_TypeDef;
osThreadDef
Interessante Frage: Was ist osThreadDef eigentlich genaugenommen?
Argumente: name, thread, priority, instances, stack
Beispiel
osThreadDef(THREAD_1, LED_Thread1, osPriorityNormal, 0, configMINIMAL_STACK_SIZE);
osThreadCreate
Aus [1]:
Remove the thread function from the active thread list. If the thread is currently RUNNING the execution will stop.
Startet die Ausführung eines Threads
Gegenteil: osThreadTerminate()
Argumente: Ptr to thread def, ptr to args
osThreadId LEDThread1Handle;
LEDThread1Handle = osThreadCreate(osThread(THREAD_1), NULL);
osKernelStart();
Kernel starten.
Thread functions
static void LED_Thread1(void const *argument);
...
static void LED_Thread1(void const *argument)
{
...
}
osThreadSuspend
Suspend thread1
osThreadSuspend(thread_id);
Suspend own thread
osThreadSuspend(NULL); // osThreadYield(void)
osThreadResume
osThreadResume(thread_id);
5.1.3. Signal¶
osSignalSet
Aus [1]:
Set the signal flags of an active thread. This function may be used also within interrupt service routines.
Returns previous signal flags of the specified thread or 0x80000000 in case of incorrect parameters.
Ein Signal ist ein diesem Fall ein 32-Bit Integer (int32_t).
osSignalSet( LED1_ThreadId, BIT_0 );
osSignalClear
Clear the signal flags of an active thread.
signals = osSignalClear (thread_id, 0x01);
osSignalWait
Aus [1]:
Suspend the execution of the current RUNNING thread until all specified signal flags with the parameter signals are set. When the parameter signals is 0 the current RUNNING thread is suspended until any signal is set. When these signal flags are already set, the function returns instantly. Otherwise the thread is put into the state WAITING. Signal flags that are reported as event are automatically cleared.
The argument millisec specifies how long the system waits for the specified signal flags. While the system waits the thread calling this function is put into the state WAITING. The timeout value can have the following values:
when millisec is 0, the function returns instantly.
when millisec is set to osWaitForever the function will wait for an infinite time until a specified signal is set.
all other values specify a time in millisecond for a timeout.
for (;;)
{
event = osSignalWait( BIT_1 | BIT_2, osWaitForever);
if (event.value.signals == (BIT_1 | BIT_2))
{
BSP_LED_Toggle(LED2);
...
Es gibt auch event.status
:
if (evt.status == osEventSignal) {
// handle event status
...
5.1.4. Semaphore¶
Macro osSemaphoreDef
. SEM
steht für die neu zu definierende
Semaphore.
osSemaphoreDef(SEM);
osSemaphoreCreate
Argumente: Ptr auf SemaphoreDef, Anzahl Resourcen
Aufgabe: Was ist osSemaphoreId
? (Tags verwenden)
osSemaphoreId osSemaphore;
...
osSemaphore = osSemaphoreCreate(osSemaphore(SEM) , 1);
...
// Semaphore z.B. als Argument an osThreadCreate uebergeben
SemThread1Handle = osThreadCreate(osThread(SEM_Thread1), (void *) osSemaphore);
osSemaphoreWait
osSemaphoreWait(sema, millis);
Aus [1]:
Wait until a Semaphore token becomes available. When no Semaphore token is available, the function waits for the time specified with the parameter millisec.
The argument millisec specifies how long the system waits for a Semaphore token to become available. While the system waits the thread that is calling this function is put into the state WAITING. The millisec timeout can have the following values:
when millisec is 0, the function returns instantly.
when millisec is set to osWaitForever the function will wait for an infinite time until the Semaphore token becomes available.
all other values specify a time in millisecond for a timeout.
The return value indicates the number of available tokens (the semaphore count value). If 0 is returned, then no semaphore was available. The value -1 is returned in case of incorrect parameters.
osSemaphoreId semaphore;
...
if (osSemaphoreWait(semaphore , 100) == osOK)
...
osSemaphoreRelease
Release a Semaphore token. This increments the count of available semaphore tokens.
osSemaphoreRelease(semaphore);
5.1.5. Mutex¶
Aus [1]:
Synchronize resource access using Mutual Exclusion (Mutex).
Mutual exclusion (widely known as Mutex) is used in various operating systems for resource management. Many resources in a microcontroller device can be used repeatedly, but only by one thread at a time (for example communication channels, memory, and files). Mutexes are used to protect access to a shared resource. A mutex is created and then passed between the threads (they can acquire and release the mutex).
A mutex is a special version of a semaphore. Like the semaphore, it is a container for tokens. But instead of being able to have multiple tokens, a mutex can only carry one (representing the resource). Thus, a mutex token is binary and bounded. The advantage of a mutex is that it introduces thread ownership. When a thread acquires a mutex and becomes its owner, subsequent mutex acquires from that thread will succeed immediately without any latency. Thus, mutex acquires/releases can be nested.
#define mutexTWO_TICK_DELAY ((uint32_t) 2)
static osMutexId os_mutex_id;
osMutexDef(os_mutex);
osMutexCreate
Create and initialize a Mutex object.
os_mutex_id = osMutexCreate(osMutex(os_mutex));
osMutexWait
Aus [1]:
Wait until a Mutex becomes available. If no other thread has obtained the Mutex, the function instantly returns and blocks the mutex object.
The argument millisec specifies how long the system waits for a mutex. While the system waits the thread that is calling this function is put into the state WAITING. The millisec timeout can have the following values:
when millisec is 0, the function returns instantly.
when millisec is set to osWaitForever the function will wait for an infinite time until the mutex becomes available.
all other values specify a time in millisecond for a timeout.
if (osMutexWait(os_mutex_id, mutexTWO_TICK_DELAY) != osOK)
{
...
osMutexRelease
Aus [1]:
Release a Mutex that was obtained with osMutexWait. Other threads that currently wait for the same mutex will be now put into the state READY.
if (osMutexRelease(os_mutex_id) != osOK)
{
BSP_LED_Toggle(LED3);
}
osMutexDelete
Aus [1]:
Delete a Mutex object. The function releases internal memory obtained for Mutex handling. After this call the mutex_id is no longer valid and cannot be used. The Mutex may be created again using the function osMutexCreate.
if (mutex_id != NULL) {
status = osMutexDelete(mutex_id);
if (status != osOK) {
...
5.1.6. Mail Queue¶
Aus [1]:
A mail queue resembles a Message Queue, but the data that is being transferred consists of memory blocks that need to be allocated (before putting data in) and freed (after taking data out). The mail queue uses a Memory Pool to create formatted memory blocks and passes pointers to these blocks in a message queue. This allows the data to stay in an allocated memory block while only a pointer is moved between the separate threads. This is an advantage over messages that can transfer only a 32-bit value or a pointer. Using the mail queue functions, you can control, send, receive, or wait for mail.
osMailQId mailId;
#define MAIL_SIZE (uint32_t) 1
...
typedef struct // Mail object structure
{
uint32_t var1; // var1 is a uint32_t
uint32_t var2; // var2 is a uint32_t
uint8_t var3; // var3 is a uint8_t
} Amail_TypeDef;
...
// Define Mail Queue (Macro)
osMailQDef(mail, MAIL_SIZE, Amail_TypeDef);
...
// Create Mail Queue
mailId = osMailCreate(osMailQ(mail), NULL);
osMailAlloc
Aus [1]:
Allocate a memory block from the mail queue that is filled with the mail information.
The argument queue_id specifies a mail queue identifier that is obtain with osMailCreate.
The argument millisec specifies how long the system waits for a mail slot to become available. While the system waits the thread calling this function is put into the state WAITING. The millisec timeout can have the following values:
when millisec is 0, the function returns instantly.
when millisec is set to osWaitForever the function will wait for an infinite time until a mail slot can be allocated.
all other values specify a time in millisecond for a timeout.
pTMail = osMailAlloc(mailId, osWaitForever);
pTMail->var1 = ProducerValue1;
...
osMailPut
Aus [1]:
Put the memory block specified with mail into the mail queue specified by queue.
Status and Error Codes
osOK: the message is put into the queue.
osErrorValue: mail was previously not allocated as memory slot.
osErrorParameter: a parameter is invalid or outside of a permitted range.
if (osMailPut(mailId, pTMail) != osOK) /* Send Mail */
osMailGet
Aus [1]:
Suspend the execution of the current RUNNING thread until a mail arrives. When a mail is already in the queue, the function returns instantly with the mail information.
The argument millisec specifies how long the system waits for a mail to arrive. While the system waits the thread that is calling this function is put into the state WAITING. The millisec timeout can have the following values:
when millisec is 0, the function returns instantly.
when millisec is set to osWaitForever the function will wait for an infinite time until a mail arrives.
all other values specify a time in millisecond for a timeout.
event = osMailGet(mailId, osWaitForever); /* wait for mail */
if (event.status == osEventMail)
{
pRMail = event.value.p;
pRMail->var1 ...
...
osMailFree
Aus [1]:
Free the memory block specified by mail and return it to the mail queue.
osMailFree(mailId, pRMail); /* free memory allocated for mail */
5.1.7. Message Queue¶
Code Beispiel siehe www.keil.com/pack/doc/CMSIS/RTOS/html/group__CMSIS__RTOS__Message.html
Aus [1]:
Message passing is another basic communication model between threads. In the message passing model, one thread sends data explicitly, while another thread receives it. The operation is more like some kind of I/O rather than a direct access to information to be shared. In CMSIS-RTOS, this mechanism is called s message queue. The data is passed from one thread to another in a FIFO-like operation. Using message queue functions, you can control, send, receive, or wait for messages. The data to be passed can be of integer or pointer type:
osMessageCreate
#define QUEUE_SIZE 4
osMessageQId msg_id;
typedef struct {
float voltage;
float current;
int counter;
} T_MEAS;
osPoolDef(mpool, QUEUE_SIZE, T_MEAS); // Define memory pool
osPoolId mpool;
osMessageQDef(msg, QUEUE_SIZE, &T_MEAS);
msg_id = osMessageCreate(osMessageQ(msg), NULL);
...
osMessagePut
Aus [1]:
Put the message info in a message queue specified by queue_id.
When the message queue is full, the system retries for a specified time with millisec. While the system retries the thread that is calling this function is put into the state WAITING. The millisec timeout can have the following values:
when millisec is 0, the function returns instantly.
when millisec is set to osWaitForever the function will wait for an infinite time until a message queue slot becomes available.
all other values specify a time in millisecond for a timeout.
T_MEAS *mptr;
mptr = osPoolAlloc(mpool); // also osPoolcreate() ...
...
osMessagePut(msg_id, (uint32_t)mptr, osWaitForever);
osMessageGet
T_MEAS *rptr;
osEvent evt;
evt = osMessageGet(msg_id, osWaitForever); // wait for message
if (evt.status == osEventMessage) {
rptr = evt.value.p;
...
5.1.8. Memory Pool¶
Code Beispiel siehe https://www.keil.com/pack/doc/CMSIS/RTOS/html/group__CMSIS__RTOS__PoolMgmt.html
osPoolCreate
typedef struct {
uint32_t length;
uint32_t width;
uint32_t height;
uint32_t weight;
} properties_t;
osPoolDef (object_pool, 10, properties_t); // Declare memory pool
osPoolId (object_pool_id); // Memory pool ID
object_pool_id = osPoolCreate(osPool(object_pool));
osPoolAlloc
properties_t *object_data;
object_data = (properties_t *) osPoolAlloc(object_pool_id);
object_data->length = 100;
object_data->width = 10;
object_data->height = 23;
object_data->weight = 1000;
osPoolFree
status = osPoolFree (MemPool_Id, object_data);
if (status==osOK) {
// handle status code
...
}
5.1.9. Time, Timer¶
osKernelSysTick()
uint32_t osKernelSysTick(void)
osTimerCreate
Aus [1]:
Create a one-shot or periodic timer and associate it with a callback function argument. The timer is in stopped until it is started with osTimerStart.
void Timer1_Callback (void const *arg);
osTimerDef (Timer1, Timer1_Callback);
exec1 = 1;
id1 = osTimerCreate (osTimer(Timer1), osTimerOnce, &exec1);
osTimerStart
timerDelay = 1000;
status = osTimerStart(id, timerDelay);
osTimerStop
status = osTimerStop(id1);
if (status != osOK) {
...
osTimerDelete
Stop and delete timer.
status = osTimerDelete(id);
if (status != osOK) {
...
5.1.10. Delays¶
osDelay(millis)
Führt zu einem Suspend der Task bis Delay abgelaufen ist.
Aus [1]:
Wait for a specified time period in millisec.
The millisec value specifies the number of timer ticks and is therefore an upper bound. The exact time delay depends on the actual time elapsed since the last timer tick.
For a value of 1, the system waits until the next timer tick occurs. That means that the actual time delay may be up to one timer tick less.
5.2. Beispielprogramme¶
Die Beispielprogramme für Nucleo L476 sind auf gitlab:
https://gitlab.informatik.hs-augsburg.de/es2/es2-nucl476/cube-demos
Alle Beispiele sind mit CMSIS-RTOS v1 API!
Damit Sie die Beispiele besser studieren können, navigieren Sie wieder mit dem Tags Mechanismus in Vim.
FreeRTOS_EXTI
FreeRTOS_LowPower
FreeRTOS_Mail
FreeRTOS_Mutexes
FreeRTOS_Queues
FreeRTOS_Semaphore
FreeRTOS_Semaphore3
FreeRTOS_SemaphoreFromISR
FreeRTOS_Signal
FreeRTOS_SignalFromISR
FreeRTOS_ThreadCreation
FreeRTOS_ThreadCreation2
FreeRTOS_Timers
Veranschaulichung durch “Bildchen”
5.3. FreeRTOS¶
Multitasking mit RTOS
FreeRTOS als freies Projekt von Richard Barry seit 15 Jahren
Kleines Echtzeit-Betriebssystem, das viele verschiedene Entwicklungsumgebungen und CPU Architekturen unterstützt. Siehe dazu im FreeRTOS Quelltext das Verzeichnis:
FreeRTOSv10.1.1/FreeRTOS/Source/portable/
Von Amazon 2017 aufgekauft, deshalb nun “Amazon FreeRTOS” im Bereich der “Amazon Web Services” (AWS).
https://aws.amazon.com/blogs/opensource/announcing-freertos-kernel-v10/
MIT Lizenz
Quelltext von FreeRTOS in Cube Bibliothek
Middlewares/Third_Party/FreeRTOS/
CMSIS RTOS “Wrapper” um FreeRTOS
Siehe im Verzeichnis
Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS/
Dateien
cmsis_os.c
undcmsis_os.h
.
Variablennamen
Präfixe
u
- unsignedp
- pointeruc
- uint8_tv
- voidpc
- pointer to charpv
- pointer to chars
- int16_tl
- int32_tx
- BaseType_t (“bester” Integer Datentyp auf Architektur)
Tasks
[BARRY], Kap. 3
Zustandsdiagramm
Im SUSPENDED Zustand ist die Task nicht für den Scheduler verfügbar.
Aus dem BLOCKED Zustand kommt man nur durch
Zeitliche Events, oder
Synchronisations Events
Task Timing
Task Priorität
0 ist die niedrigste Priorität
Bereich: 0 … configMAX_PRIORITIES-1
Tasks anlegen und löschen
xTaskCreate()
vTaskDelete()
vTaskStartScheduler()
Beispiel:
/* Args: Ptr to C fkt, descriptive name, stack depth, task parameter void*,
priority, ptr to task handle
Returns PASS or FAIL. */
xTaskCreate( prvQueueReceiveTask, "Rx", configMINIMAL_STACK_SIZE, NULL,
mainQUEUE_RECEIVE_TASK_PRIORITY, NULL );
...
/* sample task */
prvQueueReceiveTask(void *pvParameters);
Tasks steuern
vTaskDelay()
vTaskDelayUntil()
vTaskSuspend()
vTaskResume()
vTaskResumeFromISR()
Timer
([BARRY], Kap. 5)
“One-shot” und “Periodic timers”
Zustände: Dormant, Running
API:
xTimerCreate()
xTimerDelete()
xTimerStart()
xTimerDelete()
xTimerStop()
xTimerReset()
xTimerChangePeriod()
Callback Funktion: void ATimerCallback( TimerHandle_t xTimer );
Beispiel für STM32F100 mit Timer, Queue und zwei Tasks:
https://www.freertos.org/FreeRTOS-for-Cortex-M3-STM32-STM32F100-Discovery.html
Synchronisationsmittel
queues ([BARRY] Kap. 4 - Queues) - Mailboxen (Sonderfall einer Queue der Länge 1, [BARRY], Kap. 4.7, S. 144)
binary semaphores ([BARRY], Kap. 6 - Interrupt Management)
counting semaphores
mutexes ([BARRY], Kap. 7 - Resource Management)
recursive mutexes
event groups ([BARRY], Kap. 8 - Event Groups)
direct to task notifications ([BARRY], Kap. 9 - Task Notifications)
gatekeeper task ([BARRY], Kap. 7 - Resource Management)
Semaphore
([BARRY], Kap. 6 - Interrupt Management)
vSemaphoreCreateBinary (Queue der Länge 1)
vSemaphoreCreateCounting
vSemaphoreCreateMutex
vSemaphoreTake (synonym: Take, P, decrement, down)
vSemaphoreGive (synonym: Give, V, incr, up)
vSemaphoreGiveFromISR
Siehe Abb. in [BARRY], S. 194 (Synchronisieren einer Task mit einem Interrupt über eine binäre Semaphore).
Queue Management
([BARRY], Kap. 4 - Queues)
Queues sind FIFO buffer.
Daten werden in die Queue kopiert.
Können von mehreren Tasks aus angesprochen werden.
Blockierzeit beim Lesen und beim Schreiben einstellbar.
Mehrere Schreiber können blockieren, wenn die Queue voll ist. Wenn Platz frei ist, wird nur ein Schreiber aktiviert.
Falls mehrere Leser blockieren, dann wird nur ein Leser bei Datenempfang aktiviert.
Queues können gruppiert werden (“queue sets”).
API
xQueueCreate
xQueueSend
xQueueReceive
Mutex
([BARRY], Kap. 7 - Resource Management) * mutex = MUTual EXclusion
Spezielle Form einer binären Semaphore um auf eine Resource zuzugreifen, die von mehreren Tasks geteilt wird.
Beispiel: vPrintString()
xSemaphoreCreateMutex()
Abb. [BARRY], S. 245
Prioritätsinversion ([BARRY], S. 250)
Rekursive Mutexe
Gatekeeper Tasks
Gegenseitiger Ausschluss ohne Prioritätsinversion oder Deadlock
Dem Gatekeeper “gehört” die Resource
“Klienten” des Gatekeeper senden Nachricht über Queue
vPrintString() jetzt mit Gatekeeper
Signale
kommen in FreeRTOS eigentlich nicht vor, sondern in CMSIS RTOS (siehe [YIUDG], Kap. 19)
API:
osSignalSet()
,osSignalWait()
,osSignalClear()
Siehe Beispiele
es2-nucl476/cube-demos/FreeRTOS_Signal()
es2-nucl476/cube-demos/FreeRTOS_SignalFromISR()
Heap Management
[BARRY], Kap. 2
pvPortMalloc()
vPortFree()
Verschiedene Strategien:
heap1: deterministisch (kein free())
heap2: best fit (nicht deterministisch)
heap3: stdlib malloc() und free()
heap4: first fit algo
API
5.4. Literatur zum RTOS Kapitel¶
FreeRTOS
Mastering the FreeRTOS Real Time Kernel, 2016.
https://hhoegl.informatik.hs-augsburg.de/es2/prog/rtos/Mastering-FreeRTOS-2016.pdf
The FreeRTOS distribution
Heap Memory Management
Task Management
Queue Management
Software Timer Management
Interrupt Management
Resource Management
Event Groups
Task Notifications
Low Power Support
Developer support
Real Time Application Design Tutorial. Using FreeRTOS in small embedded systems.
https://www.freertos.org/tutorial
lokale Kopie: https://hhoegl.informatik.hs-augsburg.de/es2/prog/rtos/freertos-designguide/freertos-designtipps.html
Kap. 16 (Real-Time Operating Systems), S. 217-235.
https://hhoegl.informatik.hs-augsburg.de/es2/Buecher/Brown-Discovering-the-STM32-2016/book.pdf
Developing Applications on STM32Cube with RTOS, 26 Seiten. Erklärt auch kurz die FreeRTOS Beispiele (mit CMSIS-RTOS API!) in der CubeF4 Bibliothek.
https://hhoegl.informatik.hs-augsburg.de/es2/prog/rtos/UM1722-Cube-FreeRTOS.pdf
The Architecture of Open-Source Applications (Volume II), FreeRTOS
Allgemein zu empfehlen sind auch alle Standardwerke zu Betriebssystemen: Tanenbaum, Silberschatz, Stallings …
Tanenbaum, Modern Operating Systems, Kap. “Processes and Threads”.
Silberschatz, Operating System Concepts, Kap. 6 “Synchronization”.
Stallings, Operating Systems. Internals and Design Principles, Kap. 5 “Concurrency: Mutual Exclusion and Synchronization”.
Verweise auf die allgemeinen Literaturangaben Sect. 7.
- [YIUDG], Kap. 19 (Using Embedded Operating Systems), S. 605-645. Behandelt
CMSIS-RTOS, nicht FreeRTOS.
https://hhoegl.informatik.hs-augsburg.de/es2/Buecher/DGCM3andCM4-3e-ch19.pdf
- [MARTIN], Kap. 6 (Developing with CMSIS RTOS), S. 165-216 (erste Auflage
von 2013).
[WHITE], Kap. 5 (Managing the Flow of Activity)
[NOVIELLO], Kap. 23 (Running FreeRTOS), S. 635-703.