El manejo del tiempo es la forma de medir esperas, intervalos y tareas periódicas sin romper el flujo del programa.
En un microcontrolador, el tiempo es un recurso tan importante como la memoria RAM. Una espera mal planteada puede bloquear una tarea o desperdiciar CPU.
A menudo necesitamos hacer tres cosas relacionadas con el tiempo:
- Esperar: “No hagas nada durante 1 segundo”.
- Medir: “¿Cuánto tiempo ha tardado este sensor en responder?”.
- Programar: “Ejecuta esta tarea cada 5 minutos, pase lo que pase”.
En Arduino solemos recurrir a delay() y millis(). En nanoFramework disponemos de herramientas heredadas de .NET para expresar cada necesidad con claridad.
En esta entrada aprenderemos a usar Thread.Sleep, la clase Stopwatch y los Timers.
Pausar un hilo con Thread.Sleep
Ya hemos usado Thread.Sleep(1000) en ejemplos anteriores, pero es importante entender qué hace realmente “bajo el capó”.
Thread.Sleep suspende el hilo actual y permite que el planificador ejecute otros hilos mientras dura la espera.
Le dice al sistema operativo (RTOS): “Oye, no tengo nada que hacer durante los próximos 1000ms. Despiértame luego. Mientras tanto, usa la CPU para otras tareas (Wi-Fi, Garbage Collector, otros hilos)”.
using System.Threading;
// Pausa de 5 segundos
Thread.Sleep(5000);
// Dormir para siempre (útil para el final del Main)
Thread.Sleep(Timeout.Infinite);¡Cuidado con bloquear el sistema!
Nunca uses un bucle vacío while(Wait) { } para esperar. Eso pone la CPU al 100% y calienta el chip. Usa siempre Thread.Sleep o eventos (AutoResetEvent) para sincronizar.
Medir intervalos con Stopwatch
Si quieres medir cuánto tarda una función, DateTime.UtcNow no es la mejor opción para intervalos cortos.
Para esto existe la clase Stopwatch en el espacio de nombres System.Diagnostics. Es un cronómetro de alta resolución.
using System;
using System.Diagnostics;
using System.Threading;
namespace TimingEjemplo
{
public class Program
{
public static void Main()
{
// Instanciamos el cronómetro
Stopwatch cronometro = new Stopwatch();
Debug.WriteLine("Iniciando operación compleja...");
// Arrancamos el cronómetro
cronometro.Start();
// Simulamos una tarea pesada (ej: leer un archivo, cálculo matemático)
RealizarTareaPesada();
// Paramos el cronómetro
cronometro.Stop();
// Leemos el tiempo transcurrido
Debug.WriteLine($"La tarea tardó: {cronometro.ElapsedMilliseconds} ms");
// Si queremos reutilizarlo, debemos resetearlo
cronometro.Reset();
}
static void RealizarTareaPesada()
{
Thread.Sleep(1234); // Simulamos retardo
}
}
}Stopwatch es extremadamente útil para optimizar código. Si tu bucle principal va lento, pon cronómetros alrededor de tus funciones y descubre cuál es el cuello de botella.
Programar tareas periódicas con Timer
Hasta ahora, para hacer parpadear un LED poníamos un while(true) con Sleep. Si queremos que parpadee mientras el programa principal hace otras cosas, necesitamos un temporizador.
No podemos atrapar el flujo en un bucle infinito. Necesitamos un Timer.
Un Timer ejecuta un método (Callback) cada cierto tiempo, en un hilo separado.
using System;
using System.Threading;
using System.Diagnostics;
namespace TimerEjemplo
{
public class Program
{
private static int _contador = 0;
public static void Main()
{
Debug.WriteLine("Iniciando Timer...");
// Creamos el Timer
// Parámetros:
// 1. Callback: El método a ejecutar (TareaPeriodica)
// 2. State: Objeto para pasar datos (null si no se usa)
// 3. DueTime: Cuánto esperar para arrancar la primera vez (1000ms)
// 4. Period: Cada cuánto repetir (2000ms)
Timer miTimer = new Timer(TareaPeriodica, null, 1000, 2000);
Debug.WriteLine("El Timer trabaja en segundo plano. El Main sigue libre.");
// Simulamos que el Main hace otra cosa
int i = 0;
while (true)
{
Debug.WriteLine($"Main Thread trabajando... {i++}");
Thread.Sleep(5000);
}
}
// Este método será llamado cada 2 segundos por el Timer
private static void TareaPeriodica(object state)
{
_contador++;
Debug.WriteLine($"[Timer] Ejecución nº {_contador}. Hora: {DateTime.UtcNow}");
// Aquí podrías leer un sensor, enviar un ping, etc.
}
}
}Detener o cambiar el temporizador
Si en algún momento queremos parar el temporizador o cambiar su frecuencia, usamos el método Change.
// Detener el timer (Periodo infinito)
miTimer.Change(Timeout.Infinite, Timeout.Infinite);
// Reiniciar para que dispare inmediatamente y luego cada 500ms
miTimer.Change(0, 500);Nota sobre Hilos:
El método del Timer se ejecuta en un hilo del ThreadPool. Ten cuidado si accedes a variables compartidas con el hilo principal; asegúrate de que tu código es Thread-Safe (seguro para hilos) usando lock si es necesario.
¿Y qué pasa con la fecha y hora real?
Quizás te preguntes: “Vale, sé medir 5 minutos, pero ¿cómo sé que hoy es 24 de Octubre a las 18:00?”
Los microcontroladores como el ESP32 tienen un RTC (Real Time Clock) interno que cuenta el tiempo. Sin embargo, al encenderse, siempre empiezan en una fecha por defecto (año 2017 o similar) porque no tienen una pila de botón para guardar la hora al apagarse.
Para obtener la fecha real (“Wall Clock Time”), necesitamos sincronizarnos con Internet usando NTP (Network Time Protocol). Pero eso, amigos, lo veremos en el bloque de Conectividad Wi-Fi.