/*
* SUPER LOOP DEMO - SERIAL MONITOR EDITION
* Platform: ESP32 Dev Kit V1
* Hardware: Uses onboard BOOT button (GPIO 0)
* AI Use: Code converted, expanded (to emphasize delay/issue), and commented using Gemini
* AI Verification: Code base reviewed and confirmed by M. Borowczak
* Orignal Sources: M. Borowczak
*
* KEY CONCEPTS:
* 1. The "Super Loop": Everything happens in sequence.
* 2. Blocking: If "NET" hangs, "INPUT" cannot run.
* 3. Visual Latency: The text bar shows how sluggish the loop is.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.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"
// --- HARDWARE CONFIG ---
// GPIO 0 is the 'BOOT' button on Dev Kits
#define INPUT_BUTTON_PIN GPIO_NUM_0
// --- DATA CONFIG ---
#define WIN_SIZE 10000
static int data_history[WIN_SIZE];
static int data_index = 0;
// --- HELPER: PRINT STATUS BAR ---
// Visualizes current activity and loop latency in the Serial Monitor
// Status: Which function is currently hogging the CPU
// Latency: How long the LAST loop took (visualized as a bar)
void print_status(const char* status, int64_t last_loop_ms) {
// Latency Bar: 1 '#' = approx 200ms
char bar[12];
int blocks = last_loop_ms / 200;
if (blocks > 10) blocks = 10;
// Build bar string "[####......]"
bar[0] = '[';
for(int i=1; i<=10; i++) {
if(i <= blocks) bar[i] = '#';
else bar[i] = '.';
}
bar[11] = ']';
bar[12] = '\0'; // Null terminate
// \r returns cursor to start of line (overwrite previous line if supported)
// or just print new lines for scrolling log style
printf("[ SYSTEM ] Status: %-8s | Last Loop: %4lld ms %s\n", status, last_loop_ms, bar);
}
// --- 1. SENSOR (Blocking 150ms) ---
int collect_sensor_data() {
// Simulates a sensor reading that halts the CPU
ets_delay_us(150000);
return (int)(esp_random() % 100);
}
// --- 2. MATH (CPU Hog) ---
void compute_statistics(float *mean_out, float *std_out, int window_size) {
double sum = 0.0;
// Pass 1: Mean
for(int i = 0; i < window_size; i++) {
int idx = (data_index - i + WIN_SIZE) % WIN_SIZE;
sum += data_history[idx];
}
double mean = sum / window_size;
// Pass 2: Std Dev
double sum_sq_diff = 0.0;
for(int i = 0; i < window_size; i++) {
int idx = (data_index - i + WIN_SIZE) % WIN_SIZE;
sum_sq_diff += pow((data_history[idx] - mean), 2);
}
*mean_out = (float)mean;
*std_out = (float)sqrt(sum_sq_diff / window_size);
}
// --- 3. NETWORK (Blocking Jitter) ---
void send_telemetry(int payload_size) {
int delay_ms = payload_size / 20;
// 30% chance of massive lag spike (simulating bad wifi)
if ((esp_random() % 10) < 3) {
printf(">> [NET] Packet Retry (1000ms delay)...\n");
// We explicitly print here to show the system hanging on "RETRY"
// In a real super loop, you wouldn't get updates during this freeze!
vTaskDelay(pdMS_TO_TICKS(1000));
}
vTaskDelay(pdMS_TO_TICKS(delay_ms));
}
// --- MAIN LOOP ---
void app_main(void)
{
// Disable buffering for instant printf output
setvbuf(stdout, NULL, _IONBF, 0);
// Init GPIO
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);
// Init Data
memset(data_history, 0, sizeof(data_history));
printf("\n--- SUPER LOOP DEMO (SERIAL) ---\n");
printf("1. Watch the 'Last Loop' time vary.\n");
printf("2. Try to press the BOOT button.\n");
printf("3. Notice how hard it is to get 'BTN PRESSED' to appear.\n\n");
vTaskDelay(pdMS_TO_TICKS(2000));
int64_t last_loop_duration = 0;
while (1) {
int64_t start_time = esp_timer_get_time();
// --- STEP 1: SENSOR ---
print_status("SENSING", last_loop_duration);
int raw_data = collect_sensor_data();
data_index = (data_index + 1) % WIN_SIZE;
data_history[data_index] = raw_data;
// --- STEP 2: MATH ---
print_status("MATH...", last_loop_duration);
float m, s;
// Run heavy math 5 times to simulate complex processing
for(int k=0; k<5; k++) {
compute_statistics(&m, &s, WIN_SIZE);
}
// --- STEP 3: NETWORK ---
print_status("NETWORK", last_loop_duration);
int payload_size = (esp_random() % 2 == 0) ? 100 : 3000;
send_telemetry(payload_size);
// --- STEP 4: INPUT CHECK ---
print_status("INPUT?", last_loop_duration);
// The Trap: We only check the button AFTER all the above blocking is done.
// If you pressed the button during "SENSING" or "MATH", this check misses it!
if (gpio_get_level(INPUT_BUTTON_PIN) == 0) {
printf("\n!!! BUTTON PRESSED !!!\n");
printf("!!! BUTTON PRESSED !!!\n");
printf("!!! BUTTON PRESSED !!!\n\n");
// Delay so the user sees the success message
vTaskDelay(pdMS_TO_TICKS(500));
}
// --- CALC PERFORMANCE ---
int64_t end_time = esp_timer_get_time();
last_loop_duration = (end_time - start_time) / 1000;
// Tiny yield to prevent watchdog trigger (optional in pure superloop but good practice)
vTaskDelay(1);
}
}