657 lines
20 KiB
Typst
657 lines
20 KiB
Typst
#import "@preview/ilm:2.1.0": *
|
|
|
|
#let link-text(body) = text(blue, body)
|
|
|
|
#set text(lang: "de")
|
|
|
|
#show: ilm.with(
|
|
title: [Ausarbeitung\ Embedded Systems Architecture],
|
|
authors: "Konstantin Veltmann",
|
|
date: datetime(year: 2026, month: 07, day: 03),
|
|
date-format: "[day padding:zero]. [month repr:short]. [year repr:full]",
|
|
figure-index: (
|
|
enabled: true,
|
|
title: "Abbildungsverzeichnis",
|
|
),
|
|
preface: [
|
|
Hiermit versichere ich, dass ich die vorliegende Arbeit selbstständig verfasst und keine anderen als die angegebenen Quellen und Hilfsmittel benutzt habe. Ich versichere, dass ich alle wörtlich oder sinngemäß aus anderen Werken übernommenen Aussagen als solche gekennzeichnet habe. Dies gilt explizit auch für die Verwendung von text- oder codegenerierenden KI-Werkzeugen. Die eingereichte Arbeit ist weder vollständig noch in wesentlichen Teilen Gegenstand eines anderen Prüfungsverfahrens gewesen. Ich habe zur Kenntnis genommen, dass die Arbeit einer elektronischen Plagiatsprüfung unterzogen werden kann.
|
|
|
|
#v(2em)
|
|
#image("./assets/Unterschrift.png", height: 1.5cm)
|
|
#line(angle: 0deg, length: 6cm)
|
|
|
|
#v(5em)
|
|
Der Quelltext dieser Arbeit sowie Beispielcode und das vollständige Quellenverzeichnis sind unter #link-text([https://git.veltko.de/Weckyy702/ausarbeitung-embedded-systems-architecture]) aufzufinden und unter der #link("https://unlicense.org", link-text([Unlicense])) lizensiert.
|
|
],
|
|
)
|
|
|
|
= Aufgabe a) - Polling Zustandsautomat
|
|
|
|
#figure(
|
|
image("./assets/fsm.png"),
|
|
caption: [Modifizierter Zustandsautomat],
|
|
)
|
|
|
|
Dieser Automat verhält sich wie in der letzten Aufgabe. Für Aufgabe b) müssen die direkten Tastenabfragen (`getButton(...)`) mit Anfragen an die entsprechenden Module ersetzt werden:
|
|
- `getButton(I2CSCHEDULER_STOCK3, 1) => stock_get_oeffnen()`
|
|
- `getButton(I2CSCHEDULER_STOCK0, 0) => ruf_get_schliessen()`
|
|
|
|
= Aufgabe b) - Betriebssystem
|
|
|
|
== Prioritäten
|
|
|
|
Für die Taskprioritäten wurde folgendes Überlegt:
|
|
|
|
- Die Tastentasks sind Sicherheitsrelevant und müssen auf ISRs reagieren. Deshalb sollten sie die höchsten Prioritäten haben
|
|
- Der I²C-Scheduler muss mit (externer) Hardware reagieren und hat daher die nächsthöchste Priorität
|
|
- Die Tür-Tasks müssen ebenfalls mit Hardware interagieren und regelmäßig die Motorposition überprüfen
|
|
- Ebenso wie die LED-Tasks
|
|
- Da der Timeout-Tasks die Ruf-FSM freigeben soll, sollte er höhere Priorität haben als diese
|
|
- Die Stockwerks- und Ruf-FSMs sind als nächstes dran
|
|
- Die Tür-FSM hat die niedrigste Priorität, da sie von den aktuellsten Daten der anderen FSMs abhängig ist
|
|
|
|
Daraus ergeben sich folgende Prioritäten:
|
|
|
|
#table(
|
|
columns: (1fr, auto),
|
|
table.header([Task], [Priorität]),
|
|
[ `task_UNTEN_END_1` ], [ 1 ],
|
|
[ `task_OBEN_END_2` ], [ 2 ],
|
|
[ `task_STOCK1_EXAKT_3` ], [ 3 ],
|
|
[ `task_STOCK1_OBEN_4` ], [ 4 ],
|
|
[ `task_STOCK2_UNTEN_5` ], [ 5 ],
|
|
[ `task_STOCK2_EXAKT_6` ], [ 6 ],
|
|
[ `task_STOCK2_OBEN_7` ], [ 7 ],
|
|
[ `task_STOCK3_UNTEN_8` ], [ 8 ],
|
|
[ `task_STOCK3_EXAKT_9` ], [ 9 ],
|
|
[ `task_STOCK3_OBEN_10` ], [ 10 ],
|
|
[ `task_STOCK4_UNTEN_11` ], [ 11 ],
|
|
[ `task_STOCK4_EXAKT_12` ], [ 12 ],
|
|
[ `task_I2CSCHEDULER_13` ], [ 13 ],
|
|
[ `task_TUER_KABINE_14` ], [ 14 ],
|
|
[ `task_TUER_AUSSEN1_15` ], [ 15 ],
|
|
[ `task_TUER_AUSSEN234_16` ], [ 16 ],
|
|
[ `task_LED_KABINE_17` ], [ 17 ],
|
|
[ `task_LED_AUSSEN1_18` ], [ 18 ],
|
|
[ `task_RUF_TIMEOUT_19` ], [ 19 ],
|
|
[ `task_FSM_STOCK_20` ], [ 20 ],
|
|
[ `task_FSM_RUF_21` ], [ 21 ],
|
|
[ `task_FSM_TUER_22` ], [ 22 ],
|
|
)
|
|
|
|
== Positionstaster
|
|
|
|
Das Drücken der Positionstaster soll über ISRs festgestellt werden. Pro Taster wird eine ISR registriert, die einen Task startet, der ein Event an die #link(<chap-stock_fsm>, [Stockwerks-FSM]) sendet.
|
|
|
|
Eine alternative Implementation könnte ein Event direkt aus der ISR an die FSM senden, um den Scheduler zu entlasten. Eventuell wurde diese Variante nicht gewählt, da in ISRs kein dynamischer Speicher für das Senden des Events angelegt werden kann.
|
|
|
|
=== ISR
|
|
|
|
Für jeden Taster wird eine ISR wie folgt registriert. Für die Entprellung wird davon ausgegangen, dass es eine monoton steigende globale Zeitquelle mit 1µs Auflösung gibt, deren aktueller Wert mit `uint32_t clock_get_us(void)` auch in ISRs abgefragt werden kann.
|
|
|
|
```c
|
|
ISR(isr_UNTEN_END) {
|
|
static uint32_t last_press=0;
|
|
|
|
uint32_t now = clock_get_us();
|
|
if((now - last_press) > 500) {
|
|
last_press = now;
|
|
TaskActivateISR(task_UNTEN_END_1);
|
|
}
|
|
}
|
|
```
|
|
|
|
=== Tasks
|
|
|
|
Für jeden Taster wird ein Task wie folgt angelegt:
|
|
|
|
```c
|
|
TASKDECLARE(task_UNTEN_END_1, 1, 256, FALSE);
|
|
|
|
TASK(task_UNTEN_END_1) {
|
|
QueueSend_stock((stock_event_t) { .stock = STOCK_UNTEN_END });
|
|
TaskTerminate();
|
|
}
|
|
```
|
|
|
|
Das Event `stock_event_t` ist in #ref(<chap-stock_fsm>) definiert.
|
|
|
|
== I2CScheduler
|
|
|
|
Zum zyklischen Aufrufen des I²C-Tasks wird ein Counteralarm genutzt, der den Task alle 10ms startet. Sollte der Task in den 10ms bis zum nächsten Auslösen des Alarms nicht durchgelaufen sein, wird er nicht zweimal laufen.
|
|
Der Task läuft also _höchstens_ alle 10ms.
|
|
|
|
Die Initialisierung des Counteralarms wird in #ref(<chap-init>) besprochen.
|
|
|
|
Es wird angenommen, dass die in der Aufgabenstellung genannte Funktion `I2CSCHEDULER_DISPLAY` mit jedem Taskzyklus aufgerufen werden soll.
|
|
|
|
```c
|
|
TASKDECLARE(task_I2CSCHEDULER_13, 13, 256, TRUE);
|
|
ALARMDECLARE(alarm_I2C_CYCLE);
|
|
|
|
TASK(task_I2CSCHEDULER_13) {
|
|
|
|
I2CSCHEDULER_DISPLAY();
|
|
|
|
if(I2CSCHEDULER_NEOPIXEL_GET_KEY(I2C_STOCK_0, I2C_KEY_1)) {
|
|
QueueSend_ruf((ruf_event_t){ .type = RUF_EVENT_BUTTON, .stock = STOCK0, .direction = DIRECTION_UP });
|
|
}
|
|
|
|
// Weitere Tasten...
|
|
|
|
if(I2CSCHEDULER_NEOPIXEL_GET_KEY(I2C_STOCK_3, I2C_KEY_0)) {
|
|
QueueSend_ruf((ruf_event_t){ .type = RUF_EVENT_BUTTON, .stock = STOCK3, .direction = DIRECTION_DOWN });
|
|
}
|
|
|
|
TaskTerminate();
|
|
}
|
|
```
|
|
|
|
== Ruf-Automat <chap-ruf_fsm>
|
|
|
|
Diese Eventgesteuerte FSM ist für das Behandeln von Fahrgastwünschen zuständig und gibt der #link(<chap-tür_fsm>, [Tür-FSM]) das Schließsignal, wenn sich lange genug in einem Stockwerk aufgehalten wurde.
|
|
|
|
=== Event-Queue <chap-event_queue>
|
|
|
|
Die Event-Queue wird wie folgt definiert:
|
|
|
|
```c
|
|
typedef enum {
|
|
RUF_EVENT_BUTTON,
|
|
RUF_EVENT_TIMEOUT,
|
|
RUF_EVENT_GEOEFFNET,
|
|
// ...
|
|
} ruf_event_e;
|
|
|
|
typedef struct {
|
|
ruf_event_e type;
|
|
union {
|
|
struct {
|
|
i2cscheduler_stock_e stock;
|
|
direction_e direction;
|
|
};
|
|
};
|
|
} ruf_event_t;
|
|
|
|
VLDECLARE(vl_FSM_RUF_QUEUE, ruf_event_t);
|
|
SEMAPHOREDECLARE(sem_FSM_RUF_QUEUE_LENGTH, 0);
|
|
SEMAPHOREDECLARE(sem_FSM_RUF_QUEUE_TX_MUTEX, 1);
|
|
|
|
void QueueSend_ruf(ruf_event_t event) {
|
|
int reenable = IsrGetAndDisable();
|
|
SemaphoreDown(sem_FSM_RUF_QUEUE_TX_MUTEX);
|
|
vlAddBack(vl_FSM_RUF_QUEUE, &event);
|
|
SemaphoreUp(sem_FSM_RUF_QUEUE_TX_MUTEX);
|
|
IsrSet(reenable);
|
|
|
|
SemaphoreUp(sem_FSM_RUF_QUEUE);
|
|
}
|
|
|
|
void QueueReceive_ruf(ruf_event_t* e) {
|
|
SemaphoreDown(sem_FSM_RUF_QUEUE);
|
|
vlGetFront(vl_FSM_RUF_QUEUE, e);
|
|
}
|
|
```
|
|
|
|
Die Semaphore `sem_FSM_RUF_QUEUE_LENGTH` repräsentiert die Anzahl an unbearbeiteten Events in der Queue. Der consumer-Task ruft `SemaphoreDown` auf, um zu warten, bis mindestens ein Element vorliegt. \
|
|
Da mehrere Tasks in die Queue senden könnten, stellt `QueueSend_ruf` einen kritischen Bereich dar, der durch `sem_FSM_RUF_QUEUE_TX_MUTEX` geschützt wird. `QueueReceive_ruf` muss nicht geschützt werden, da davon auszugehen ist, dass `vlAddBack` nicht mit `vlGetFront` interferriert und es nur einen Empfängertask gibt.
|
|
|
|
=== Datenstruktur
|
|
|
|
Der Zustand der FSM wird in folgender Datenstruktur repräsentiert:
|
|
|
|
```c
|
|
typedef enum {
|
|
RUF_STATE_WAIT_OPEN
|
|
RUF_STATE_OEFFNEN,
|
|
// ...
|
|
} ruf_state_e;
|
|
|
|
struct {
|
|
ruf_state_e state;
|
|
bool schliessen : 1;
|
|
// ...
|
|
} fsm_ruf;
|
|
```
|
|
|
|
=== Externe Schnittstelle <chap-ruf_interface>
|
|
|
|
Die Schnittstelle für die Ruf-FSM sieht wie folgt aus. Obwohl (lesend) auf globale Variablen zugegriffen wird, ist kein kritischer Bereich notwendig, da nur ein Prozess gleichzeitig laufen kann, wodurch sich Lesen und Schreiben nicht überlappen und diese Funktionen nicht aus ISRs aufgerufen werden. Dasselbe gilt für die meisten getter-Funktionen.
|
|
|
|
```c
|
|
int ruf_get_schliessen(void) {
|
|
return fsm_ruf.schliessen;
|
|
}
|
|
|
|
ruf_state_e ruf_get_state(void) {
|
|
return fsm_ruf.state;
|
|
}
|
|
```
|
|
|
|
=== Task
|
|
|
|
Die Ruf-FSM wartet ständig auf neue Events und verarbeitet sie wie folgt:
|
|
|
|
```c
|
|
ALARMDECLARE(alarm_FSM_RUF_TIMEOUT);
|
|
TASKDECLARE(task_FSM_RUF_21, 21, 256, TRUE);
|
|
|
|
TASK(task_FSM_RUF_21) {
|
|
ruf_event_t event;
|
|
for(;;) {
|
|
//Auf das nächste Event warten
|
|
QueueReceive_ruf(&event);
|
|
//Event behandeln
|
|
switch(fsm_ruf.state) {
|
|
case RUF_STATE_WAIT_OPEN:
|
|
if(event.type != RUF_EVENT_TIMEOUT) {
|
|
/*TODO: was ist mit Eingaben während des Timeouts?*/
|
|
break;
|
|
}
|
|
/* Status für fsm_tuer setzen */
|
|
fsm_ruf.schliessen = true;
|
|
// ...
|
|
break;
|
|
// ....
|
|
case RUF_STATE_OEFFNEN:
|
|
if(event.type == RUF_EVENT_GEOEFFNET) {
|
|
AlarmSetTask(alarm_FSM_RUF_TIMEOUT, 5000, FALSE, task_RUF_TIMEOUT_19);
|
|
fsm_ruf.state = RUF_STATE_WAIT_OPEN;
|
|
}
|
|
// ...
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
=== Timeout
|
|
|
|
Der Ruf-Timeout Task ist wie folgt implementiert:
|
|
|
|
```c
|
|
TASKDECLARE(task_RUF_TIMEOUT_19, 19, 256, FALSE);
|
|
|
|
TASK(task_RUF_TIMEOUT_19) {
|
|
QueueSend_ruf((ruf_event_t) { .type = RUF_EVENT_TIMEOUT });
|
|
TaskTerminate();
|
|
}
|
|
```
|
|
|
|
== Tür-Automat <chap-tür_fsm>
|
|
|
|
Dieser Automat ist für die (synchrone) Kontrolle der Innen- und Außentüren zuständig. Er ist als polling FSM implementiert, die alle 20ms aufgerufen wird.
|
|
Da nicht spezifiziert ist, wie die "korrekte" Außentür zu bestimmen ist, wird davon ausgegangen, dass es eine Funktion `tuer_t* get_aussen_tuer(void)` gibt, die die korrekte Außentür für das momentane Stockwerk bestimmt.
|
|
|
|
=== Datenstruktur
|
|
|
|
Der Zustand der Tür-FSM wird in folgender Datenstruktur gehalten:
|
|
|
|
```c
|
|
typedef enum {
|
|
FSM_TUER_STATE_INIT = 0,
|
|
FSM_TUER_STATE_OEFFNEN1,
|
|
FSM_TUER_STATE_OEFFNEN1A,
|
|
FSM_TUER_STATE_OEFFNEN1B,
|
|
FSM_TUER_STATE_OFFEN,
|
|
FSM_TUER_STATE_SCHLIESSEN1,
|
|
FSM_TUER_STATE_SCHLIESSEN1A,
|
|
FSM_TUER_STATE_SCHLIESSEN1B,
|
|
FSM_TUER_STATE_GESCHLOSSEN,
|
|
FSM_TUER_STATE_EINGEKLEMMT,
|
|
} fsm_tuer_state_e;
|
|
|
|
struct {
|
|
fsm_tuer_state_e state;
|
|
} fsm_tuer;
|
|
```
|
|
|
|
=== Tasks
|
|
Da der Code im Task genau dann ausgeführt wird, wenn der Counteralarm auslöst, hat die FSM implizit ein `Zyklus`-Event erhalten.
|
|
|
|
```c
|
|
ALARMDECLARE(alarm_FSM_TUER_CYCLE);
|
|
TASKDECLARE(task_FSM_TUER_22, 22, 256, TRUE);
|
|
|
|
TASK(task_FSM_TUER_22) {
|
|
|
|
tuer_t* tuer_aussen = get_aussen_tuer();;
|
|
|
|
switch(fsm_tuer.state) {
|
|
case FSM_TUER_STATE_INIT:
|
|
if(stock_get_oeffnen()) {
|
|
fsm_tuer.state = FSM_TUER_STATE_OEFFNEN1;
|
|
tuer_set_cmd(tuer_kabine, TUER_CMD_OEFFNEN);
|
|
tuer_set_cmd(tuer_aussen, TUER_CMD_OEFFNEN);
|
|
}
|
|
break;
|
|
case FSM_TUER_STATE_OEFFNEN1:
|
|
if(tuer_getState(tuer_aussen) == TUER_GEOEFFNET) {
|
|
fsm_tuer.state = FSM_TUER_STATE_OEFFNEN1A;
|
|
} else if(tuer_getState(tuer_kabine) == TUER_GEOEFFNET) {
|
|
fsm_tuer.state = FSM_TUER_STATE_OEFFNEN1B;
|
|
}
|
|
break;
|
|
case FSM_TUER_STATE_OEFFNEN1A:
|
|
if(tuer_getState(tuer_kabine) == TUER_GEOEFFNET) {
|
|
QueueSend_ruf((ruf_event_t){.type = RUF_EVENT_GEOEFFNET});
|
|
fsm_tuer.state = FSM_TUER_STATE_GEOEFFNET;
|
|
}
|
|
break;
|
|
case FSM_TUER_STATE_OEFFNEN1B:
|
|
if(tuer_getState(tuer_aussen) == TUER_GEOEFFNET) {
|
|
fsm_tuer.state = FSM_TUER_STATE_GEOEFFNET;
|
|
}
|
|
break;
|
|
case FSM_TUER_STATE_OFFEN:
|
|
if(ruf_get_schliessen) {
|
|
fsm_tuer.state = FSM_TUER_STATE_SCHLIESSEN1;
|
|
tuer_set_cmd(tuer_kabine, TUER_CMD_SCHLIESSEN);
|
|
tuer_set_cmd(tuer_aussen, TUER_CMD_SCHLIESSEN);
|
|
}
|
|
break;
|
|
case FSM_TUER_STATE_SCHLIESSEN1:
|
|
/* Eingeklemmte Türen werden zuerst überprüft, da sie sicherheitsrelevant sind */
|
|
if(tuer_getState(tuer_aussen) == TUER_EINGEKLEMMT) {
|
|
fsm_tuer.state = FSM_TUER_STATE_EINGEKLEMMT;
|
|
tuer_set_cmd(tuer_kabine, TUER_CMD_OEFFNEN);
|
|
tuer_set_cmd(tuer_aussen, TUER_CMD_OEFFNEN);
|
|
} else if(tuer_getState(tuer_kabine) == TUER_EINGEKLEMMT) {
|
|
fsm_tuer.state = FSM_TUER_STATE_EINGEKLEMMT;
|
|
tuer_set_cmd(tuer_kabine, TUER_CMD_OEFFNEN);
|
|
tuer_set_cmd(tuer_aussen, TUER_CMD_OEFFNEN);
|
|
} else if(tuer_getState(tuer_aussen) == TUER_GESCHLOSSEN) {
|
|
fsm_tuer.state = FSM_TUER_STATE_SCHLIESSEN1A;
|
|
} else if(tuer_getState(tuer_kabine) == TUER_GESCHLOSSEN) {
|
|
fsm_tuer.state = FSM_TUER_STATE_SCHLIESSEN1B;
|
|
}
|
|
break;
|
|
case FSM_TUER_STATE_SCHLIESSEN1A:
|
|
if(tuer_getState(tuer_kabine) == TUER_EINGEKLEMMT) {
|
|
fsm_tuer.state = FSM_TUER_STATE_EINGEKLEMMT;
|
|
tuer_set_cmd(tuer_kabine, TUER_CMD_OEFFNEN);
|
|
tuer_set_cmd(tuer_aussen, TUER_CMD_OEFFNEN);
|
|
} else if(tuer_getState(tuer_kabine) == TUER_GESCHLOSSEN) {
|
|
fsm_tuer.state = FSM_TUER_STATE_GESCHLOSSEN;
|
|
}
|
|
break;
|
|
case FSM_TUER_STATE_SCHLIESSEN1B:
|
|
if(tuer_getState(tuer_aussen) == TUER_EINGEKLEMMT) {
|
|
fsm_tuer.state = FSM_TUER_STATE_EINGEKLEMMT;
|
|
tuer_set_cmd(tuer_kabine, TUER_CMD_OEFFNEN);
|
|
tuer_set_cmd(tuer_aussen, TUER_CMD_OEFFNEN);
|
|
} else if(tuer_getState(tuer_aussen) == TUER_GESCHLOSSEN) {
|
|
fsm_tuer.state = FSM_TUER_STATE_GESCHLOSSEN;
|
|
}
|
|
break;
|
|
case FSM_TUER_STATE_GESCHLOSSEN:
|
|
if(stock_get_oeffnen()) {
|
|
fsm_tuer.state = FSM_TUER_STATE_OEFFNEN1;
|
|
tuer_set_cmd(tuer_kabine, TUER_CMD_OEFFNEN);
|
|
tuer_set_cmd(tuer_aussen, TUER_CMD_OEFFNEN);
|
|
}
|
|
break;
|
|
case FSM_TUER_STATE_EINGEKLEMMT:
|
|
if(tuer_getState(tuer_aussen) == TUER_OFFEN) {
|
|
fsm_tuer.state = FSM_TUER_STATE_OEFFNEN1A;
|
|
} else if(tuer_getState(tuer_kabine) == TUER_OFFEN) {
|
|
fsm_tuer.state = FSM_TUER_STATE_OEFFNEN1B;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
```
|
|
|
|
== Stockwerks-Automat <chap-stock_fsm>
|
|
|
|
=== Event-Queue
|
|
|
|
Die Event-Queue für den Stockwerks-Automaten ist analog zu der in #ref(<chap-event_queue>) besprochenen implementiert.
|
|
Das Event ist wie folgt definiert:
|
|
|
|
```c
|
|
typedef enum {
|
|
STOCK_UNTEN_END,
|
|
// ...
|
|
} stock_e;
|
|
|
|
typedef strut {
|
|
stock_e stock;
|
|
} stock_event_t;
|
|
|
|
void QueueReceive_stock(stock_event_t* e);
|
|
void QueueSend_stock(stock_event_t e);
|
|
```
|
|
|
|
=== Datenstruktur
|
|
|
|
Der Zustand der FSM wird in folgender Datenstruktur gehalten.
|
|
|
|
```c
|
|
typedef enum {
|
|
// ...
|
|
} stock_state_e;
|
|
|
|
struct {
|
|
stock_state_e state;
|
|
bool oeffnen : 1;
|
|
} fsm_stock;
|
|
```
|
|
|
|
=== Externe Schnittstelle
|
|
|
|
Zur Kommunikation mit der polling FSM werden folgende Funktionen bereitgestellt. Es gelten die gleichen Bedenken zur Synchronisierung wie in #ref(<chap-ruf_interface>).
|
|
|
|
```c
|
|
int stock_get_oeffnen(void) {
|
|
return fsm_stock.oeffnen;
|
|
}
|
|
```
|
|
|
|
=== Tasks
|
|
|
|
```c
|
|
TASKDECLARE(task_FSM_STOCK_20, 20, 256, TRUE);
|
|
|
|
TASK(task_FSM_STOCK_20) {
|
|
stock_event_t event;
|
|
for(;;) {
|
|
QueueReceive_stock(&event);
|
|
|
|
switch(fsm_stock.state) {
|
|
// ...
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
== Türen <chap-türen>
|
|
|
|
Die Türen (`tuer_kabine`, `tuer_aussen` und `tuer_aussen234`) werden jeweils durch eine Datenstruktur `tuer_t` und einen zugehörigen Task repräsentiert. Außerdem werden für die pollenden FSMs getter-Methoden bereitgestellt.
|
|
|
|
=== Datenstrukturen
|
|
|
|
Der Zustand der Tueren wird in folgender Datenstruktur festgehalten:
|
|
|
|
```c
|
|
|
|
typedef enum {
|
|
TUER_GESCHLOSSEN,
|
|
TUER_OEFFNEN,
|
|
TUER_OFFEN,
|
|
TUER_SCHLIESSEN,
|
|
TUER_EINGEKLEMMT,
|
|
} tuer_state_e;
|
|
|
|
typedef enum {
|
|
TUER_CMD_NONE,
|
|
TUER_CMD_OEFFNEN,
|
|
TUER_CMD_SCHLIESSEN,
|
|
} tuer_cmd_e;
|
|
|
|
typedef struct {
|
|
tuer_state_e state;
|
|
tuer_cmd_e command;
|
|
// ...
|
|
} tuer_t;
|
|
|
|
tuer_t tuer_kabine, tuer_aussen1, tuer_assen234;
|
|
```
|
|
|
|
=== Externe Schnittstellen
|
|
|
|
Damit andere Module den Zustand der Türen abfragen können, werden folgende Methoden bereitgestellt:
|
|
|
|
```c
|
|
void tuer_set_cmd(tuer_t* me, tuer_cmd_e cmd) {
|
|
me->command = cmd;
|
|
}
|
|
|
|
tuer_state_e tuer_getState(tuer_t* me) {
|
|
return me->state;
|
|
}
|
|
```
|
|
|
|
=== Tasks
|
|
|
|
Hier wird beispielsweise nur der Code für die Kabinentür angezeigt. Die Außentüren führen identischen Code in jeweils eigenen Tasks mit eigenen Counteralarmen aus.
|
|
|
|
```c
|
|
TASKDECLARE(task_TUER_KABINE_14, 14, 256, TRUE);
|
|
ALARMDECLARE(alarm_TUER_KABINE_CYCLE);
|
|
|
|
TASK(task_TUER_KABINE_14) {
|
|
|
|
switch(tuer_kabine.state) {
|
|
case TUER_OEFFNEN:
|
|
if(/*tür offen*/) {
|
|
}
|
|
break;
|
|
// ...
|
|
}
|
|
|
|
TaskTerminate();
|
|
}
|
|
```
|
|
|
|
== LEDs
|
|
|
|
Jede (physische) Tür hat eine LED, die den Zustand der Tür anzeigt. Das Blinken wird durch folgenden Zustandsautomaten realisiert:
|
|
|
|
#figure(
|
|
image("./assets/led_fsm.png"),
|
|
caption: [LED-Zustandsautomat],
|
|
)
|
|
|
|
Wie bei der Tür-FSM wird das `Zyklus`-Event implizit bei jedem Aufrufen des Tasks empfangen.
|
|
|
|
=== Datenstruktur
|
|
|
|
```c
|
|
typedef enum {
|
|
LED_STATE_IDLE,
|
|
LED_STATE_BLINK,
|
|
} led_state_e;
|
|
|
|
struct led_fsm {
|
|
led_state_e state;
|
|
uint32_t total_blink_time_ms;
|
|
uint32_t max_blink_time_ms;
|
|
uint32_t blink_cycle_time_ms;
|
|
// Pin-Nummern etc. ...
|
|
} led_kabine, led_aussen1;
|
|
```
|
|
|
|
=== Schnittstelle
|
|
|
|
Da mehrere Schreib-Operationen auf den geteilten globalen LED-FSMs ausgeführt werden, muss sie durch eine Semaphore geschützt werden.
|
|
|
|
```c
|
|
SEMAPHOREDECLARE(sem_LED_KABINE_MUTEX, 1);
|
|
|
|
void led_start_blink(struct led_state* me, uint32_t cycle_time_ms, uint32_t blinkt_time_ms) {
|
|
SemaphoreDown(sem_LED_KABINE_MUTEX);
|
|
me->blink_cycle_time_ms = cycle_time_ms;
|
|
me->max_blink_time_ms = blink_time_ms;
|
|
SemaphoreUp(sem_LED_KABINE_MUTEX);
|
|
TaskActivate(task_LED_KABINE_17);
|
|
}
|
|
```
|
|
|
|
=== Tasks
|
|
|
|
Für jede der LEDs wird ein Task wie folgt erzeugt.
|
|
|
|
```c
|
|
TASKDECLARE(task_LED_KABINE_17, 17, 256, TRUE);
|
|
ALARMDECLARE(alarm_LED_KABINE);
|
|
|
|
TASK(task_LED_KABINE_17) {
|
|
SemaphoreDown(sem_LED_START_MUTEX);
|
|
switch(led_kabine.state) {
|
|
case LED_STATE_IDLE:
|
|
led_kabine.total_blink_time_ms = 0;
|
|
AlarmSetTask(alarm_LED_KABINE, led_kabine.blink_cycle_time_ms, TRUE, task_LED_KABINE_17);
|
|
// set PIO
|
|
|
|
led_kabine.state = LED_STATE_BLINK;
|
|
break;
|
|
case LED_STATE_BLINK:
|
|
if(led_kabine.total_blink_time_ms >= led_kabine.max_blink_time_ms) {
|
|
// clear PIO
|
|
led_kabine.state = LED_STATE_IDLE;
|
|
AlarmCancel(alarm_LED_KABINE);
|
|
break;
|
|
}
|
|
led_kabine.total_blink_ms += led_kabine.blink_cycle_time_ms;
|
|
// toggle PIO
|
|
break;
|
|
}
|
|
SemaphoreUp(sem_LED_START_MUTEX);
|
|
TaskTerminate();
|
|
}
|
|
```
|
|
|
|
== Initialisierung <chap-init>
|
|
|
|
Die initialisierung der Ressourcen und Tasks wird durch einen hochprioren `init`-Task wie folgt vorgenommen:
|
|
|
|
```
|
|
TASKDECLARE(task_INIT_0, 0, 256, TRUE);
|
|
|
|
TASK(task_INIT_0) {
|
|
|
|
// evtl. weitere Initialisierung für Peripherie ...
|
|
|
|
AlarmSetTask(alarm_I2C_CYCLE, 10, TRUE, task_i2c_scheduler_13);
|
|
AlarmSetTask(alarm_FSM_TUER_CYCLE, 20, TRUE, task_FSM_TUER_22);
|
|
AlarmSetTask(alarm_TUER_KABINE_CYCLE, 15, TRUE, task_TUER_KABINE_14);
|
|
AlarmSetTask(alarm_TUER_AUSSEN1_CYCLE, 15, TRUE, task_TUER_AUSSEN1_15);
|
|
AlarmSetTask(alarm_TUER_AUSSEN234_CYCLE, 15, TRUE, task_TUER_AUSSEN234_16);
|
|
|
|
TaskTerminate();
|
|
}
|
|
```
|
|
|
|
= Aufgabe c) - Scheduling-Diagramm
|
|
|
|
Aus Gründen der Übersichtlichkeit wird der `Blocked`-Zustand nicht explizit dargestellt, sondern durch ein leeres Feld. Die Zustände #text(yellow)[Ready] und #text(green)[Running] werden als farbige Boxen dargestellt; #text(red)[ISRs] in rot.
|
|
Aktiviert ein Task einen anderen Task durch `TaskActivate`, zeigt ein gestrichelter Pfeil vom aktivierenden Task zum aktivierten Task.
|
|
|
|
#figure(
|
|
image("./assets/scheduling_diagram.png"),
|
|
caption: [Timing-Diagramm],
|
|
)
|
|
|
|
Für dieses Diagramm wird folgender (unrealistischer) Ablauf angenommen:
|
|
1. Bei $t = 2"ms"$ löst der Stock 3-Taster aus.
|
|
2. Dadurch wird de Stock-FSM (wieder) aktivert und setzt ihre `oeffnen`-Flag.
|
|
3. Die TÜr-FSM pollt die Stock-FSM und signalisiert der Kabinen- und Außentür, sich zu öffnen.
|
|
4. Die Türen aktivieren ihre jeweiligen LED-Tasks und starten die Motoren.
|
|
5. Beim nächsten Zyklus der TÜr-Regler sind die TÜren bereits geöffnet.
|
|
6. Die Tür-FSM sendet das "Geöffnet"-Event and die Ruf-FSM, die das Timeout startet
|
|
7. Das Ruf-Timeout endet bei $t = 46"ms"$ und aktiviert baldmöglichst die Ruf-FSM
|
|
8. Bei $t = 45.5"ms"$ wird eine der Neokeys gedrückt.
|
|
9. Der I²C-Scheduler sendet beim nächsten Zyklus eine Event an die Ruf-FSM, die ihre `schliessen`-Flag setzt.
|
|
10. Bei $t=64"ms"$ weist die Tür-FSM die Türen zum Schließen an.
|