// SJF-ESP32: mismo firmware para la placa fisica y Wokwi.
// El algoritmo administra actividades; no sustituye el kernel de FreeRTOS.
#include <stdint.h>
#include <stddef.h>
// Declaraciones para el preprocesador de archivos .ino de Arduino.
struct AcumuladorLatencia;
struct Informe;
enum class ResultadoPaso;
enum class Politica { FCFS, SJF, RR, PRIORIDADES };
constexpr Politica ALGORITMO = Politica::SJF;
constexpr const char *NOMBRE_ALGORITMO = "SJF-ESP32";
constexpr uint64_t QUANTUM_US = 2000;
constexpr int NUM_TAREAS = 4;
enum class Estado { DORMIDA, LISTA, EJECUTANDO, BLOQUEADA };
struct Trabajo {
int id = 0;
int prioridad = 1;
Estado estado = Estado::DORMIDA;
uint64_t periodoUs = 0, proximaUs = 0, numero = 0;
uint64_t llegadaUs = 0, inicioUs = 0, listoDesdeUs = 0;
uint64_t bloqueadoDesdeUs = 0, ordenCola = 0;
uint64_t ejecucionUs = 0, esperaUs = 0, bloqueoUs = 0;
uint64_t turnoUs = 0, estimadoUs = 1;
bool iniciado = false;
int paso = 0;
bool bandera = false;
};
// Seleccion pura: tambien se prueba fuera de la ESP32 con un reloj controlado.
int elegirListo(Politica politica, const Trabajo *trabajos, int cantidad) {
int mejor = -1;
for (int i = 0; i < cantidad; ++i) {
const Trabajo &t = trabajos[i];
if (t.estado != Estado::LISTA) continue;
if (mejor < 0) { mejor = i; continue; }
const Trabajo &m = trabajos[mejor];
bool elegir = false;
if (politica == Politica::SJF && t.estimadoUs != m.estimadoUs)
elegir = t.estimadoUs < m.estimadoUs;
else if (politica == Politica::PRIORIDADES && t.prioridad != m.prioridad)
elegir = t.prioridad > m.prioridad;
else if (politica == Politica::SJF || politica == Politica::PRIORIDADES)
elegir = t.llegadaUs != m.llegadaUs ? t.llegadaUs < m.llegadaUs : t.id < m.id;
else
elegir = t.ordenCola != m.ordenCola ? t.ordenCola < m.ordenCola : t.id < m.id;
if (elegir) mejor = i;
}
return mejor;
}
bool debeDesalojar(Politica politica, const Trabajo &actual,
const Trabajo *trabajos, int cantidad) {
int siguiente = elegirListo(politica, trabajos, cantidad);
if (siguiente < 0) return false;
if (politica == Politica::RR) return actual.turnoUs >= QUANTUM_US;
if (politica == Politica::PRIORIDADES)
return trabajos[siguiente].prioridad > actual.prioridad;
return false;
}
void ponerListo(Trabajo &t, uint64_t ahora, uint64_t &orden) {
if (t.estado == Estado::BLOQUEADA) t.bloqueoUs += ahora - t.bloqueadoDesdeUs;
t.estado = Estado::LISTA;
t.listoDesdeUs = ahora;
t.ordenCola = ++orden;
}
void comenzarTramo(Trabajo &t, uint64_t ahora) {
t.esperaUs += ahora - t.listoDesdeUs;
if (!t.iniciado) { t.iniciado = true; t.inicioUs = ahora; }
t.turnoUs = 0;
t.estado = Estado::EJECUTANDO;
}
#ifndef PLANIFICACION_HOST_TEST
#include <Arduino.h>
#include <atomic>
#include <esp_timer.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
constexpr int LED_VERDE = 25, LED_ROJO = 26, LED_AMARILLO = 33;
constexpr int BOTON = 32, TRIG = 18, ECHO = 19, BUZZER = 23;
constexpr unsigned int FRECUENCIA_BUZZER_HZ = 2000;
constexpr float UMBRAL_CM = 5.0f;
constexpr uint64_t TIMEOUT_ECO_US = 30000;
constexpr uint64_t INFORME_US = 10000000;
const char *NOMBRES[NUM_TAREAS] = { "Indicador", "Sensor", "Alarma", "Boton" };
Trabajo tareas[NUM_TAREAS];
uint64_t ordenCola = 0, origenUs = 0, ultimoInformeUs = 0, sesion = 1;
uint64_t cambios = 0, desalojos = 0, ocupacionUs = 0, registrosPerdidos = 0;
uint64_t mayorPasoUs = 0, excesosQuantum = 0;
int ejecutando = -1, ultimoDespachado = -1;
bool calibrando = ALGORITMO == Politica::SJF;
bool calibrada[NUM_TAREAS] = {};
std::atomic<bool> reinicioSolicitado(false);
bool drenandoParaReinicio = false;
TaskHandle_t manejadorPlanificador = nullptr;
// Estos datos del circuito solo los modifica el planificador (nucleo 1).
float distanciaCm = -1.0f;
bool alarmaHabilitada = true, buzzerEncendido = false, verdeEncendido = false;
bool lecturaBotonAnterior = HIGH, botonEstable = HIGH;
uint64_t cambioBotonUs = 0, condicionCercanaUs = 0, habilitadaDesdeUs = 0;
bool condicionCercana = false, ecoValido = false;
uint32_t duracionEcoUs = 0;
uint64_t instanteEcoUs = 0, vencimientoEcoUs = 0;
// Interrupciones: solo capturan tiempos; nunca imprimen ni activan el buzzer.
portMUX_TYPE muxCapturas = portMUX_INITIALIZER_UNLOCKED;
volatile bool ecoArmado = false, ecoSubio = false, ecoCompleto = false;
volatile int64_t subidaEcoUs = 0, bajadaEcoUs = 0, pulsacionFisicaUs = 0;
uint64_t relojUs() { return static_cast<uint64_t>(esp_timer_get_time()); }
void IRAM_ATTR interrupcionEco() {
const int nivel = digitalRead(ECHO);
const int64_t ahora = esp_timer_get_time();
bool notificar = false;
portENTER_CRITICAL_ISR(&muxCapturas);
if (ecoArmado) {
if (nivel == HIGH && !ecoSubio) { ecoSubio = true; subidaEcoUs = ahora; }
else if (nivel == LOW && ecoSubio) {
bajadaEcoUs = ahora;
ecoCompleto = true;
ecoArmado = false;
notificar = true;
}
}
portEXIT_CRITICAL_ISR(&muxCapturas);
if (notificar && manejadorPlanificador != nullptr) {
BaseType_t despertar = pdFALSE;
vTaskNotifyGiveFromISR(manejadorPlanificador, &despertar);
if (despertar) portYIELD_FROM_ISR();
}
}
void IRAM_ATTR interrupcionBoton() {
if (digitalRead(BOTON) != LOW) return;
portENTER_CRITICAL_ISR(&muxCapturas);
if (pulsacionFisicaUs == 0) pulsacionFisicaUs = esp_timer_get_time();
portEXIT_CRITICAL_ISR(&muxCapturas);
}
struct AcumuladorLatencia {
uint64_t cantidad = 0, sumaUs = 0, maximoUs = 0;
};
AcumuladorLatencia latenciaBoton, latenciaAlarma;
void agregarLatencia(AcumuladorLatencia &a, uint64_t valor) {
++a.cantidad; a.sumaUs += valor;
if (valor > a.maximoUs) a.maximoUs = valor;
}
struct Estadistica {
uint64_t completadas = 0, omitidas = 0;
uint64_t ejecucionUs = 0, esperaUs = 0, bloqueoUs = 0;
uint64_t retornoUs = 0, respuestaUs = 0;
uint64_t ultimoInicioUs = 0, intervalos = 0, sumaIntervalosUs = 0;
uint64_t minimoIntervaloUs = UINT64_MAX, maximoIntervaloUs = 0, jitterMaximoUs = 0;
};
Estadistica estadisticas[NUM_TAREAS];
// Copias enviadas a la consola: el logger nunca lee estructuras en modificacion.
struct Registro {
uint64_t sesion, numero, llegadaUs, inicioUs, finalUs;
uint64_t ejecucionUs, esperaUs, bloqueoUs;
int id;
};
struct Informe {
bool reinicio;
bool calibracionTerminada;
uint64_t sesion, duracionUs, cambios, desalojos, ocupacionUs, perdidos;
uint64_t pasoMaximoUs, excesosQuantum;
float distancia;
bool habilitada, sonando;
Estadistica tareas[NUM_TAREAS];
AcumuladorLatencia boton, alarma;
};
QueueHandle_t colaRegistros = nullptr, colaInformes = nullptr;
void enviarInforme(bool reinicio = false, bool finCalibracion = false) {
Informe i{};
i.reinicio = reinicio; i.calibracionTerminada = finCalibracion;
i.sesion = sesion; i.duracionUs = relojUs() - origenUs;
i.cambios = cambios; i.desalojos = desalojos; i.ocupacionUs = ocupacionUs;
i.perdidos = registrosPerdidos; i.pasoMaximoUs = mayorPasoUs;
i.excesosQuantum = excesosQuantum;
i.distancia = distanciaCm; i.habilitada = alarmaHabilitada; i.sonando = buzzerEncendido;
for (int n = 0; n < NUM_TAREAS; ++n) i.tareas[n] = estadisticas[n];
i.boton = latenciaBoton; i.alarma = latenciaAlarma;
if (xQueueSend(colaInformes, &i, 0) != pdTRUE) ++registrosPerdidos;
}
void iniciarEstadisticas(uint64_t ahora, bool finCalibracion) {
origenUs = ultimoInformeUs = ahora;
ordenCola = cambios = desalojos = ocupacionUs = registrosPerdidos = 0;
mayorPasoUs = excesosQuantum = 0;
ultimoDespachado = -1;
latenciaBoton = AcumuladorLatencia{};
latenciaAlarma = AcumuladorLatencia{};
xQueueReset(colaRegistros);
for (int i = 0; i < NUM_TAREAS; ++i) {
estadisticas[i] = Estadistica{};
tareas[i].numero = 0;
tareas[i].proximaUs = ahora;
}
// No se cambian LEDs, distancia, habilitacion ni estado del sonido.
enviarInforme(true, finCalibracion);
}
void liberarActivaciones(uint64_t ahora) {
if (drenandoParaReinicio) return;
// Procesar vencimientos en orden temporal, no por posicion del arreglo.
for (;;) {
int siguiente = -1;
for (int i = 0; i < NUM_TAREAS; ++i) {
if (calibrando && (calibrada[i] || tareas[i].numero != 0)) continue;
if (tareas[i].proximaUs <= ahora &&
(siguiente < 0 || tareas[i].proximaUs < tareas[siguiente].proximaUs)) siguiente = i;
}
if (siguiente < 0) break;
Trabajo &t = tareas[siguiente];
const uint64_t vencimientos = (ahora - t.proximaUs) / t.periodoUs + 1;
t.proximaUs += vencimientos * t.periodoUs;
if (t.estado != Estado::DORMIDA) {
if (!calibrando) estadisticas[siguiente].omitidas += vencimientos;
continue;
}
if (!calibrando && vencimientos > 1) estadisticas[siguiente].omitidas += vencimientos - 1;
++t.numero;
t.llegadaUs = ahora; t.inicioUs = 0; t.iniciado = false;
t.ejecucionUs = t.esperaUs = t.bloqueoUs = t.turnoUs = 0;
t.paso = 0; t.bandera = false;
ponerListo(t, ahora, ordenCola);
}
}
void revisarEco(uint64_t ahora) {
Trabajo &t = tareas[1];
if (t.estado != Estado::BLOQUEADA) return;
bool completo;
int64_t subida, bajada;
portENTER_CRITICAL(&muxCapturas);
completo = ecoCompleto;
subida = subidaEcoUs; bajada = bajadaEcoUs;
if (ahora >= vencimientoEcoUs) ecoArmado = false;
portEXIT_CRITICAL(&muxCapturas);
if (!completo && ahora < vencimientoEcoUs) return;
ecoValido = completo && bajada >= subida &&
static_cast<uint64_t>(bajada) <= vencimientoEcoUs;
duracionEcoUs = ecoValido ? static_cast<uint32_t>(bajada - subida) : 0;
instanteEcoUs = ecoValido ? static_cast<uint64_t>(bajada) : vencimientoEcoUs;
// El eco puede llegar durante el paso de disparo: evitar tiempos negativos.
uint64_t listoEn = instanteEcoUs > t.bloqueadoDesdeUs ? instanteEcoUs : t.bloqueadoDesdeUs;
ponerListo(t, listoEn, ordenCola);
}
enum class ResultadoPaso { CONTINUAR, TERMINADO, ESPERAR };
ResultadoPaso ejecutarPaso(Trabajo &t, uint64_t ahora) {
if (t.id == 0) {
if (t.paso++ == 0) { verdeEncendido = !verdeEncendido; return ResultadoPaso::CONTINUAR; }
digitalWrite(LED_VERDE, verdeEncendido);
return ResultadoPaso::TERMINADO;
}
if (t.id == 1) {
if (t.paso++ == 0) {
portENTER_CRITICAL(&muxCapturas);
ecoArmado = true; ecoSubio = false; ecoCompleto = false;
subidaEcoUs = bajadaEcoUs = 0;
portEXIT_CRITICAL(&muxCapturas);
vencimientoEcoUs = ahora + TIMEOUT_ECO_US;
digitalWrite(TRIG, LOW); delayMicroseconds(2);
digitalWrite(TRIG, HIGH); delayMicroseconds(10); digitalWrite(TRIG, LOW);
return ResultadoPaso::ESPERAR;
}
distanciaCm = ecoValido && duracionEcoUs > 0 ? duracionEcoUs * 0.0343f / 2.0f : -1.0f;
bool cercana = distanciaCm > 0 && distanciaCm <= UMBRAL_CM;
if (cercana && !condicionCercana) condicionCercanaUs = instanteEcoUs;
condicionCercana = cercana;
return ResultadoPaso::TERMINADO;
}
if (t.id == 2) {
if (t.paso++ == 0) {
t.bandera = alarmaHabilitada && distanciaCm > 0 && distanciaCm <= UMBRAL_CM;
return ResultadoPaso::CONTINUAR;
}
digitalWrite(LED_ROJO, t.bandera);
if (t.bandera != buzzerEncendido) {
if (t.bandera) {
uint64_t desde = condicionCercanaUs > habilitadaDesdeUs ? condicionCercanaUs : habilitadaDesdeUs;
if (!calibrando && ahora >= desde) agregarLatencia(latenciaAlarma, ahora - desde);
tone(BUZZER, FRECUENCIA_BUZZER_HZ);
} else noTone(BUZZER);
buzzerEncendido = t.bandera;
}
return ResultadoPaso::TERMINADO;
}
// T4: el antirrebote usa el reloj, sin delay(30) que retenga esta actividad.
bool lectura = digitalRead(BOTON);
if (lectura != lecturaBotonAnterior) {
lecturaBotonAnterior = lectura;
cambioBotonUs = ahora;
}
if (ahora - cambioBotonUs >= 30000) {
if (lectura != botonEstable) {
botonEstable = lectura;
if (botonEstable == LOW) {
alarmaHabilitada = !alarmaHabilitada;
if (alarmaHabilitada) habilitadaDesdeUs = ahora;
digitalWrite(LED_AMARILLO, alarmaHabilitada);
int64_t pulsacion;
portENTER_CRITICAL(&muxCapturas);
pulsacion = pulsacionFisicaUs;
portEXIT_CRITICAL(&muxCapturas);
if (!calibrando && pulsacion > 0 && ahora >= static_cast<uint64_t>(pulsacion))
agregarLatencia(latenciaBoton, ahora - static_cast<uint64_t>(pulsacion));
}
}
if (lectura == HIGH) {
portENTER_CRITICAL(&muxCapturas);
pulsacionFisicaUs = 0;
portEXIT_CRITICAL(&muxCapturas);
}
}
return ResultadoPaso::TERMINADO;
}
void terminarTrabajo(Trabajo &t, uint64_t ahora) {
t.estimadoUs = t.ejecucionUs > 0 ? t.ejecucionUs : 1;
if (calibrando) {
calibrada[t.id] = true;
} else {
Estadistica &s = estadisticas[t.id];
++s.completadas;
s.ejecucionUs += t.ejecucionUs; s.esperaUs += t.esperaUs;
s.bloqueoUs += t.bloqueoUs; s.retornoUs += ahora - t.llegadaUs;
s.respuestaUs += t.inicioUs - t.llegadaUs;
Registro r{sesion, t.numero, t.llegadaUs - origenUs, t.inicioUs - origenUs,
ahora - origenUs, t.ejecucionUs, t.esperaUs, t.bloqueoUs, t.id};
if (xQueueSend(colaRegistros, &r, 0) != pdTRUE) ++registrosPerdidos;
}
t.estado = Estado::DORMIDA;
ejecutando = -1;
}
void tareaPlanificador(void *) {
origenUs = ultimoInformeUs = relojUs();
for (int i = 0; i < NUM_TAREAS; ++i) tareas[i].proximaUs = origenUs;
habilitadaDesdeUs = cambioBotonUs = origenUs;
for (;;) {
if (reinicioSolicitado.exchange(false)) drenandoParaReinicio = true;
uint64_t ahora = relojUs();
revisarEco(ahora);
bool dormidas = true, finCalibracion = true;
for (int i = 0; i < NUM_TAREAS; ++i) {
dormidas = dormidas && tareas[i].estado == Estado::DORMIDA;
finCalibracion = finCalibracion && calibrada[i];
}
if (calibrando && finCalibracion && dormidas) {
calibrando = false;
iniciarEstadisticas(ahora, true);
}
if (drenandoParaReinicio && dormidas) {
drenandoParaReinicio = false;
++sesion;
iniciarEstadisticas(ahora, false);
}
liberarActivaciones(ahora);
// El ISR pudo registrar un eco durante la revision: refrescar antes de medir WT.
ahora = relojUs();
Politica efectiva = calibrando ? Politica::FCFS : ALGORITMO;
// Primero se admiten llegadas; despues se reencola un quantum vencido.
if (ejecutando >= 0 && debeDesalojar(efectiva, tareas[ejecutando], tareas, NUM_TAREAS)) {
ponerListo(tareas[ejecutando], ahora, ordenCola);
ejecutando = -1;
if (!calibrando) ++desalojos;
}
if (ejecutando < 0) {
ejecutando = elegirListo(efectiva, tareas, NUM_TAREAS);
if (ejecutando >= 0) {
Trabajo &t = tareas[ejecutando];
if (!calibrando && !t.iniciado) {
Estadistica &s = estadisticas[t.id];
if (s.ultimoInicioUs != 0) {
uint64_t intervalo = ahora - s.ultimoInicioUs;
++s.intervalos; s.sumaIntervalosUs += intervalo;
if (intervalo < s.minimoIntervaloUs) s.minimoIntervaloUs = intervalo;
if (intervalo > s.maximoIntervaloUs) s.maximoIntervaloUs = intervalo;
uint64_t jitter = intervalo > t.periodoUs ? intervalo - t.periodoUs : t.periodoUs - intervalo;
if (jitter > s.jitterMaximoUs) s.jitterMaximoUs = jitter;
}
s.ultimoInicioUs = ahora;
}
comenzarTramo(t, ahora);
if (!calibrando && ultimoDespachado >= 0 && ultimoDespachado != ejecutando) ++cambios;
ultimoDespachado = ejecutando;
}
}
if (ejecutando >= 0) {
Trabajo &t = tareas[ejecutando];
const uint64_t inicioPaso = relojUs();
ResultadoPaso resultado = ejecutarPaso(t, inicioPaso);
const uint64_t finPaso = relojUs();
const uint64_t costo = finPaso - inicioPaso;
t.ejecucionUs += costo; t.turnoUs += costo;
if (!calibrando) {
ocupacionUs += costo;
if (costo > mayorPasoUs) mayorPasoUs = costo;
if (ALGORITMO == Politica::RR && costo > QUANTUM_US) ++excesosQuantum;
}
if (resultado == ResultadoPaso::TERMINADO) terminarTrabajo(t, finPaso);
else if (resultado == ResultadoPaso::ESPERAR) {
t.estado = Estado::BLOQUEADA; t.bloqueadoDesdeUs = finPaso; ejecutando = -1;
}
}
ahora = relojUs();
if (!calibrando && ahora - ultimoInformeUs >= INFORME_US) {
enviarInforme(); ultimoInformeUs = ahora;
}
if (ejecutando < 0 && elegirListo(efectiva, tareas, NUM_TAREAS) < 0)
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1));
}
}
double promedioMs(uint64_t suma, uint64_t cantidad) {
return cantidad == 0 ? 0.0 : static_cast<double>(suma) / cantidad / 1000.0;
}
void imprimirInforme(const Informe &i) {
if (i.reinicio) {
Serial.printf("\n# %s; SESION=%llu; %s\n", NOMBRE_ALGORITMO,
(unsigned long long)i.sesion,
i.calibracionTerminada ? "calibracion SJF terminada, medicion iniciada" : "estadisticas reiniciadas");
return;
}
Serial.printf("\n# INFORME ACUMULADO %s; sesion=%llu; duracion=%.3f s\n",
NOMBRE_ALGORITMO, (unsigned long long)i.sesion, i.duracionUs / 1000000.0);
Serial.println("# tarea;completadas;omitidas;BT_ms;WT_ms;bloqueo_ms;TAT_ms;RT_ms;periodo_ms;min_ms;max_ms;jitter_max_ms");
uint64_t total = 0;
for (int n = 0; n < NUM_TAREAS; ++n) {
const Estadistica &s = i.tareas[n]; total += s.completadas;
Serial.printf("RES;T%d;%llu;%llu;%.6f;%.6f;%.6f;%.6f;%.6f;%.6f;%.6f;%.6f;%.6f\n",
n + 1, (unsigned long long)s.completadas, (unsigned long long)s.omitidas,
promedioMs(s.ejecucionUs, s.completadas), promedioMs(s.esperaUs, s.completadas),
promedioMs(s.bloqueoUs, s.completadas), promedioMs(s.retornoUs, s.completadas),
promedioMs(s.respuestaUs, s.completadas), promedioMs(s.sumaIntervalosUs, s.intervalos),
s.intervalos ? s.minimoIntervaloUs / 1000.0 : 0.0, s.maximoIntervaloUs / 1000.0,
s.jitterMaximoUs / 1000.0);
}
Serial.printf("# cambios=%llu; desalojos=%llu; throughput=%.3f activaciones/s\n",
(unsigned long long)i.cambios, (unsigned long long)i.desalojos,
i.duracionUs ? total * 1000000.0 / i.duracionUs : 0.0);
Serial.printf("# ocupacion_fragmentos=%.6f%% (NO es CPU total); paso_max=%.6f ms; pasos_mayores_quantum=%llu; registros_perdidos=%llu\n",
i.duracionUs ? i.ocupacionUs * 100.0 / i.duracionUs : 0.0,
i.pasoMaximoUs / 1000.0, (unsigned long long)i.excesosQuantum, (unsigned long long)i.perdidos);
Serial.printf("# boton: n=%llu; respuesta_media=%.3f ms; maxima=%.3f ms (incluye antirrebote)\n",
(unsigned long long)i.boton.cantidad, promedioMs(i.boton.sumaUs, i.boton.cantidad), i.boton.maximoUs / 1000.0);
Serial.printf("# alarma: n=%llu; latencia_media=%.3f ms; maxima=%.3f ms (condicion habilitada -> orden de salida)\n",
(unsigned long long)i.alarma.cantidad, promedioMs(i.alarma.sumaUs, i.alarma.cantidad), i.alarma.maximoUs / 1000.0);
Serial.printf("# circuito: distancia=%.2f cm; habilitada=%d; sonido=%d\n\n", i.distancia, i.habilitada, i.sonando);
}
void tareaConsola(void *) {
Serial.printf("\n# %s | actividades en nucleo 1; consola en nucleo 0 | 115200 baudios\n", NOMBRE_ALGORITMO);
Serial.println("# Misma fuente para ESP32 y Wokwi. R reinicia solo las estadisticas.");
Serial.println("# BT aproxima los fragmentos: puede incluir interrupciones internas; excluye espera del eco y salida serial.");
Serial.println("# Los cambios cuentan identificadores consecutivos distintos, sin contar IDLE.");
Serial.println("# RR/prioridades: apropiacion en puntos de control; no se reemplaza el kernel.");
if (ALGORITMO == Politica::RR) Serial.println("# quantum=2 ms; no se agrega carga artificial.");
if (ALGORITMO == Politica::SJF) Serial.println("# SJF: calibracion FCFS inicial; estimacion=ultima ejecucion observada.");
Serial.println("# ACT;sesion;tarea;numero;AT_ms;inicio_ms;CT_ms;BT_ms;WT_ms;bloqueo_ms;TAT_ms;RT_ms");
for (;;) {
while (Serial.available()) {
char c = Serial.read();
if (c == 'R' || c == 'r') reinicioSolicitado.store(true);
}
Informe informe;
while (xQueueReceive(colaInformes, &informe, 0) == pdTRUE) imprimirInforme(informe);
Registro r;
for (int n = 0; n < 12 && xQueueReceive(colaRegistros, &r, 0) == pdTRUE; ++n) {
Serial.printf("ACT;%llu;T%d;%llu;%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;%.3f;%.3f\n",
(unsigned long long)r.sesion, r.id + 1, (unsigned long long)r.numero,
r.llegadaUs / 1000.0, r.inicioUs / 1000.0, r.finalUs / 1000.0,
r.ejecucionUs / 1000.0, r.esperaUs / 1000.0, r.bloqueoUs / 1000.0,
(r.finalUs - r.llegadaUs) / 1000.0, (r.inicioUs - r.llegadaUs) / 1000.0);
}
vTaskDelay(pdMS_TO_TICKS(5));
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_VERDE, OUTPUT); pinMode(LED_ROJO, OUTPUT);
pinMode(LED_AMARILLO, OUTPUT); pinMode(BUZZER, OUTPUT);
pinMode(TRIG, OUTPUT); pinMode(ECHO, INPUT); pinMode(BOTON, INPUT_PULLUP);
digitalWrite(TRIG, LOW); digitalWrite(LED_VERDE, LOW);
digitalWrite(LED_ROJO, LOW); digitalWrite(BUZZER, LOW); digitalWrite(LED_AMARILLO, HIGH);
const uint64_t periodos[] = {500000, 100000, 100000, 20000};
const int prioridades[] = {1, 2, 4, 3};
for (int i = 0; i < NUM_TAREAS; ++i) {
tareas[i].id = i; tareas[i].periodoUs = periodos[i]; tareas[i].prioridad = prioridades[i];
}
colaRegistros = xQueueCreate(128, sizeof(Registro));
colaInformes = xQueueCreate(3, sizeof(Informe));
configASSERT(colaRegistros != nullptr && colaInformes != nullptr);
attachInterrupt(digitalPinToInterrupt(ECHO), interrupcionEco, CHANGE);
attachInterrupt(digitalPinToInterrupt(BOTON), interrupcionBoton, FALLING);
BaseType_t r = xTaskCreatePinnedToCore(tareaConsola, "Consola", 6144, nullptr, 1, nullptr, 0);
configASSERT(r == pdPASS);
r = xTaskCreatePinnedToCore(tareaPlanificador, "Planificador", 6144, nullptr, 2, &manejadorPlanificador, 1);
configASSERT(r == pdPASS);
}
void loop() {
// La actividad de Arduino no controla los componentes ni consume un ciclo ocupado.
vTaskDelay(pdMS_TO_TICKS(1000));
}
#endif