/*
* RTOS LESSON 1: TASKS & POLLING (Serial Version)
* Platform: ESP32 Dev Kit V1 (Standard)
* Hardware: Uses onboard BOOT button (GPIO 0)
*
* AI Use: Code converted and commented using Gemini
* AI Verification: Code base reviewed and confirmed by M. Borowczak
* Orignal Sources: M. Borowczak
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "esp_random.h"
#include "rom/ets_sys.h" // For busy-wait simulation
// --- HARDWARE CONFIGURATION ---
// GPIO 0 is usually the 'BOOT' button on Dev Kits
#define INPUT_BUTTON_PIN GPIO_NUM_0
// --- STACK SIZES ---
// 4096 bytes is generous; good for class safety to avoid overflows during prints
#define STACK_SIZE_STD 4096
// --- SHARED GLOBALS ---
// 'volatile' tells compiler these change unexpectedly (by other tasks/ISRs)
volatile int g_latest_sensor_value = 0;
volatile float g_latest_mean = 0.0;
volatile int g_button_press_count = 0;
volatile char g_network_status[10] = "IDLE";
// Data History (Simulating heavy memory usage)
// 10,000 ints * 4 bytes = ~40KB RAM
#define WIN_SIZE 10000
static int data_history[WIN_SIZE];
static int data_index = 0;
// --- TASK 1: SENSOR (Simulates Blocking Hardware) ---
void task_sensor(void *pvParameters) {
while(1) {
// ets_delay_us is a BUSY WAIT (blocks the CPU core).
// This simulates a sensor that takes time to read without yielding.
ets_delay_us(150000); // 150ms blocking
g_latest_sensor_value = (int)(esp_random() % 100);
// Yield to other tasks for 10ms
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// --- TASK 2: MATH (Simulates CPU Heavy Calculation) ---
void task_math(void *pvParameters) {
while(1) {
// 1. Update history buffer
int current_val = g_latest_sensor_value;
data_index = (data_index + 1) % WIN_SIZE;
data_history[data_index] = current_val;
// 2. Calculate Mean (CPU intensive loop)
double sum = 0.0;
for(int i = 0; i < WIN_SIZE; i++) {
sum += data_history[i];
}
g_latest_mean = (float)(sum / WIN_SIZE);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// --- TASK 3: NETWORK (Simulates Latency) ---
void task_network(void *pvParameters) {
while(1) {
// 30% chance of a "bad connection"
if ((esp_random() % 10) < 3) {
strcpy((char*)g_network_status, "RETRY");
printf(">> [NET] Connection poor... Retrying (2s block)...\n");
vTaskDelay(pdMS_TO_TICKS(2000)); // Simulates network timeout
} else {
strcpy((char*)g_network_status, "SENT ");
vTaskDelay(pdMS_TO_TICKS(200));
}
}
}
// --- TASK 4: INPUT (Polling Method) ---
void task_input(void *pvParameters) {
// Config Button Pin
gpio_reset_pin(INPUT_BUTTON_PIN);
gpio_set_direction(INPUT_BUTTON_PIN, GPIO_MODE_INPUT);
gpio_set_pull_mode(INPUT_BUTTON_PIN, GPIO_PULLUP_ONLY);
while(1) {
// Poll: Check pin state repeatedly
if (gpio_get_level(INPUT_BUTTON_PIN) == 0) {
vTaskDelay(pdMS_TO_TICKS(50)); // Simple Debounce
// Confirm press after debounce
if (gpio_get_level(INPUT_BUTTON_PIN) == 0) {
g_button_press_count++;
int64_t detection_time = esp_timer_get_time() / 1000;
// Print Immediate Feedback
printf("\n!!! [INPUT] BUTTON PRESSED !!!\n");
printf("Time: %lld ms | Count: %d\n\n", detection_time, g_button_press_count);
// Wait here until button is released (blocking polling)
while(gpio_get_level(INPUT_BUTTON_PIN) == 0) {
vTaskDelay(pdMS_TO_TICKS(10));
}
}
}
// Check button every 10ms
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// --- TASK 5: MONITOR (Replaces Display) ---
// Prints system status to Serial Console every second
void task_serial_monitor(void *pvParameters) {
while(1) {
printf("[MONITOR] Net: %s | Btn: %d | Val: %d | Mean: %.2f\n",
g_network_status,
g_button_press_count,
g_latest_sensor_value,
g_latest_mean);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// --- MAIN SETUP ---
void app_main(void)
{
// Safety delay to allow serial monitor to open
vTaskDelay(pdMS_TO_TICKS(2000));
// Disable buffering for instant printf output
setvbuf(stdout, NULL, _IONBF, 0);
printf("\n--- RTOS TASKS DEMO (SERIAL) ---\n");
printf("1. Hold the BOOT button on your board.\n");
printf("2. Watch the [MONITOR] lines update.\n");
printf("3. Watch [NET] simulate lag occasionally.\n\n");
// CREATE TASKS
// Priority: 1 (Low) to 5 (High)
// Highest priority to catch button presses immediately
xTaskCreate(task_input, "INPUT", STACK_SIZE_STD, NULL, 5, NULL);
// Middle priority for logic
xTaskCreate(task_sensor, "SENSE", STACK_SIZE_STD, NULL, 3, NULL);
xTaskCreate(task_network, "NET", STACK_SIZE_STD, NULL, 3, NULL);
// Lowest priority for background math and printing status
xTaskCreate(task_math, "MATH", STACK_SIZE_STD, NULL, 1, NULL);
xTaskCreate(task_serial_monitor, "MONITOR", STACK_SIZE_STD, NULL, 1, NULL);
}