#include <Arduino_FreeRTOS.h>
#include <queue.h>
#include <semphr.h> // Für Software-Timer und Debounce-Semaphore
#include <timers.h>
// --- Hardware-Definitionen ---
#define LED1_PIN 9
#define LED2_PIN 10
#define LED3_PIN 11
#define BUTTON_PIN 2 // Taster an Digital Pin 2 (für Interrupt)
// --- Task Tags und Pin-Definitionen für Tracing ---
// PINS 5 und 6 werden für das Tracing verwendet
#define TRACE_SM_PIN 5
#define TRACE_BUTTON_PIN 6
// --- FreeRTOS Konfiguration (Wird normalerweise in FreeRTOSConfig.h gemacht) ---
// Stellen Sie sicher, dass configUSE_APPLICATION_TASK_TAG auf 1 gesetzt ist
// Wenn Sie die Arduino_FreeRTOS Library verwenden, ist es oft schon auf 1 gesetzt.
// Wenn nicht, muss es in der FreeRTOSConfig.h aktiviert werden.
// #define configUSE_APPLICATION_TASK_TAG 1
// --- Zustand-Definitionen (One-Hot) ---
typedef enum {
STATE_0 = (1 << 0), // LED1 an (001b)
STATE_1 = (1 << 1), // LED2 an (010b)
STATE_2 = (1 << 2), // LED3 an (100b)
STATE_3 = (1 << 3) // Alle LEDs aus (000b - optionaler Zustand, One-Hot wird hier erweitert)
} State_t;
// --- Event-Definitionen ---
typedef enum {
EVENT_NEXT_STATE, // Kurzer Druck: Nächster Zustand
EVENT_PREVIOUS_STATE, // Langer Druck: Vorheriger Zustand
EVENT_NONE
} Event_t;
// --- Globale Variablen und Handles ---
State_t currentState = STATE_0;
QueueHandle_t xStateQueue; // Queue für den Zustand-Übergang
// Software-Timer für die Unterscheidung von Kurz- und Langdruck
TimerHandle_t xLongPressTimer;
// Semaphore für Debounce
SemaphoreHandle_t xDebounceSemaphore;
// --- Funktions-Prototypen ---
void vTaskStatemachine( void *pvParameters );
void vTaskButtonHandler( void *pvParameters );
static void vLongPressTimerCallback( TimerHandle_t xTimer );
void button_ISR();
// --- Benutzerdefiniertes Trace-Makro (in Ihrem Code oder einer Trace-Header-Datei) ---
// Definiert in der Bibliothek, aber hier zur Veranschaulichung
// Schaltet den Pin des aktiven Tasks ein.
#define traceTASK_SWITCHED_IN() \
do { \
if (pxCurrentTCB->pxTaskTag != NULL) { \
digitalWrite((uint8_t)pxCurrentTCB->pxTaskTag, HIGH); \
} \
} while (0)
#define traceTASK_SWITCHED_OUT() \
do { \
if (pxCurrentTCB->pxTaskTag != NULL) { \
digitalWrite((uint8_t)pxCurrentTCB->pxTaskTag, LOW); \
} \
} while (0)
// --- Arduino setup() ---
void setup() {
Serial.begin(115200);
// Pin-Konfiguration
pinMode(LED1_PIN, OUTPUT);
pinMode(LED2_PIN, OUTPUT);
pinMode(LED3_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP); // Taster gegen GND
pinMode(TRACE_SM_PIN, OUTPUT);
pinMode(TRACE_BUTTON_PIN, OUTPUT);
digitalWrite(TRACE_SM_PIN, LOW);
digitalWrite(TRACE_BUTTON_PIN, LOW);
// Erstellen der Queue für Events
xStateQueue = xQueueCreate(1, sizeof(Event_t));
// Erstellen des Debounce-Semaphores (Binäres Semaphore)
xDebounceSemaphore = xSemaphoreCreateBinary();
// Erstellen des Software-Timers für Langdruck (500ms)
xLongPressTimer = xTimerCreate(
"LongPressTimer", // Name
pdMS_TO_TICKS(500), // Periodendauer in Ticks (500ms)
pdFALSE, // Auto Reload aus
(void *)0, // ID
vLongPressTimerCallback // Callback-Funktion
);
// Anlegen der Interrupt-Routine
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), button_ISR, FALLING);
// Anlegen der Tasks
xTaskCreate(vTaskStatemachine, "SM_Task", 128, NULL, 2, NULL);
xTaskCreate(vTaskButtonHandler, "Button_Task", 128, NULL, 3, NULL); // Höhere Priorität für schnelles Taster-Handling
// Scheduler starten (wird bei Arduino-FreeRTOS oft automatisch gemacht, aber explizit ist besser)
// vTaskStartScheduler(); // Wenn die Library dies nicht automatisch tut
}
// --- Arduino loop() (wird nicht verwendet, da der Scheduler läuft) ---
void loop() {
// Bleibt leer, da FreeRTOS Tasks die Kontrolle übernehmen
}
// --- Software-Timer-Callback für Langdruck-Erkennung ---
// Wird aufgerufen, wenn der Timer abläuft (nach 500ms) und der Taster noch gedrückt ist.
static void vLongPressTimerCallback( TimerHandle_t xTimer ) {
// Wenn der Taster nach Timer-Ablauf immer noch gedrückt ist (LOW), ist es ein Langdruck
if (digitalRead(BUTTON_PIN) == LOW) {
Event_t event = EVENT_PREVIOUS_STATE;
xQueueSend(xStateQueue, &event, 0); // Event an Statemachine senden (mit 0 Wartezeit)
Serial.println("LANGDRUCK erkannt: ZURUECK");
}
}
// --- Interrupt Service Routine (ISR) ---
// Wird beim Drücken des Tasters aufgerufen (FALLING Edge)
void button_ISR() {
// Startet den Timer für Langdruck-Erkennung
// Wenn der Timer bereits läuft, wird er zurückgesetzt.
xTimerStartFromISR(xLongPressTimer, NULL);
// Das Debounce-Semaphore freigeben, um den Button-Task zu wecken.
xSemaphoreGiveFromISR(xDebounceSemaphore, NULL);
}
// --- Task 2: Button Handler (Zustandsübergangs-Ereignisse erzeugen) ---
void vTaskButtonHandler( void *pvParameters ) {
// Task Tag setzen: Pin-Nummer für Tracing
vTaskSetApplicationTaskTag(NULL, (void *) TRACE_BUTTON_PIN);
// Für Debounce-Verzögerung
const TickType_t xDebounceDelay = pdMS_TO_TICKS(50);
Event_t event;
for (;;) {
// Auf Semaphore warten, das von der ISR freigegeben wird
if (xSemaphoreTake(xDebounceSemaphore, portMAX_DELAY) == pdTRUE) {
// Debouncing (kurze Pause, damit das Prellen vorbei ist)
vTaskDelay(xDebounceDelay);
// Taster-Zustand prüfen (immer noch gedrückt?)
if (digitalRead(BUTTON_PIN) == LOW) {
// Taster ist gedrückt (wird vom Timer weiter behandelt)
// Nun auf das Loslassen des Tasters warten
while (digitalRead(BUTTON_PIN) == LOW) {
vTaskDelay(1); // Kurze Wartezeit von 1 Tick
}
// Taster losgelassen: Langdruck-Timer stoppen
if (xTimerIsTimerActive(xLongPressTimer) == pdTRUE) {
xTimerStop(xLongPressTimer, 0);
// Wenn der Timer gestoppt wurde, bevor er ablief, war es ein KURZDRUCK
// Wichtig: Timer muss gestoppt werden, BEVOR der Callback ausgeführt wird.
event = EVENT_NEXT_STATE;
xQueueSend(xStateQueue, &event, 0); // Event an Statemachine senden
Serial.println("KURZDRUCK erkannt: WEITER");
}
}
}
}
}
// --- Task 1: Statemachine (LED-Steuerung) ---
void vTaskStatemachine( void *pvParameters ) {
// Task Tag setzen: Pin-Nummer für Tracing
vTaskSetApplicationTaskTag(NULL, (void *) TRACE_SM_PIN);
Event_t receivedEvent;
for (;;) {
// Auf ein Event in der Queue warten (Blockieren, bis ein Event eintrifft)
if (xQueueReceive(xStateQueue, &receivedEvent, portMAX_DELAY) == pdPASS) {
// Zustand-Übergangslogik
switch (receivedEvent) {
case EVENT_NEXT_STATE:
// Zustand: Nächster
switch (currentState) {
case STATE_0: currentState = STATE_1; break;
case STATE_1: currentState = STATE_2; break;
case STATE_2: currentState = STATE_3; break;
case STATE_3: currentState = STATE_0; break;
default: currentState = STATE_0; break;
}
break;
case EVENT_PREVIOUS_STATE:
// Zustand: Vorheriger (Zyklisch)
switch (currentState) {
case STATE_0: currentState = STATE_3; break;
case STATE_1: currentState = STATE_0; break;
case STATE_2: currentState = STATE_1; break;
case STATE_3: currentState = STATE_2; break;
default: currentState = STATE_0; break;
}
break;
default:
// Ignoriere unbekannte Events
break;
}
// --- One-Hot-Aktionslogik (Zustand-Ausgabe) ---
// Setzt die LEDs basierend auf dem neuen One-Hot-Zustand.
digitalWrite(LED1_PIN, (currentState & STATE_0) ? HIGH : LOW);
digitalWrite(LED2_PIN, (currentState & STATE_1) ? HIGH : LOW);
digitalWrite(LED3_PIN, (currentState & STATE_2) ? HIGH : LOW);
Serial.print("Neuer Zustand (Bitmaske): ");
Serial.println(currentState, BIN); // Ausgabe des Bitmusters
}
}
}