Time management is the way to measure waits, intervals, and periodic tasks without breaking the program flow.
In a microcontroller, time is as important a resource as RAM. A poorly planned wait can block a task or waste CPU.
We often need to do three things related to time:
- Wait: “Do nothing for 1 second.”
- Measure: “How long did it take for this sensor to respond?”
- Schedule: “Execute this task every 5 minutes, no matter what.”
In Arduino, we usually resort to delay() and millis(). In nanoFramework, we have tools inherited from .NET to express each need clearly.
In this post, we will learn to use Thread.Sleep, the Stopwatch class, and Timers.
Pausing a Thread with Thread.Sleep
We have already used Thread.Sleep(1000) in previous examples, but it’s important to understand what it really does “under the hood.”
Thread.Sleep suspends the current thread and allows the scheduler to execute other threads while the wait lasts.
It tells the operating system (RTOS): “Hey, I have nothing to do for the next 1000ms. Wake me up later. In the meantime, use the CPU for other tasks (Wi-Fi, Garbage Collector, other threads).”
using System.Threading;
// Pause for 5 seconds
Thread.Sleep(5000);
// Sleep forever (useful at the end of Main)
Thread.Sleep(Timeout.Infinite);Be careful not to block the system!
Never use an empty loop while(Wait) { } to wait. That puts the CPU at 100% and heats up the chip. Always use Thread.Sleep or events (AutoResetEvent) for synchronization.
Measuring Intervals with Stopwatch
If you want to measure how long a function takes, DateTime.UtcNow is not the best option for short intervals.
For this, we have the Stopwatch class in the System.Diagnostics namespace. It’s a high-resolution stopwatch.
using System;
using System.Diagnostics;
using System.Threading;
namespace TimingEjemplo
{
public class Program
{
public static void Main()
{
// Instantiate the stopwatch
Stopwatch stopwatch = new Stopwatch();
Debug.WriteLine("Starting complex operation...");
// Start the stopwatch
stopwatch.Start();
// Simulate a heavy task (e.g., reading a file, mathematical calculation)
PerformHeavyTask();
// Stop the stopwatch
stopwatch.Stop();
// Read the elapsed time
Debug.WriteLine($"The task took: {stopwatch.ElapsedMilliseconds} ms");
// If we want to reuse it, we must reset it
stopwatch.Reset();
}
static void PerformHeavyTask()
{
Thread.Sleep(1234); // Simulate delay
}
}
}Stopwatch is extremely useful for optimizing code. If your main loop is slow, place stopwatches around your functions and discover the bottleneck.
Scheduling Periodic Tasks with Timer
So far, to blink an LED we used a while(true) loop with Sleep. If we want it to blink while the main program does other things, we need a timer.
We cannot trap the flow in an infinite loop. We need a Timer.
A Timer executes a method (Callback) at set intervals, on a separate thread.
using System;
using System.Threading;
using System.Diagnostics;
namespace TimerEjemplo
{
public class Program
{
private static int _counter = 0;
public static void Main()
{
Debug.WriteLine("Starting Timer...");
// Create the Timer
// Parameters:
// 1. Callback: The method to execute (PeriodicTask)
// 2. State: Object to pass data (null if not used)
// 3. DueTime: How long to wait before the first execution (1000ms)
// 4. Period: How often to repeat (2000ms)
Timer myTimer = new Timer(PeriodicTask, null, 1000, 2000);
Debug.WriteLine("The Timer works in the background. The Main thread remains free.");
// Simulate the Main thread doing something else
int i = 0;
while (true)
{
Debug.WriteLine($"Main Thread working... {i++}");
Thread.Sleep(5000);
}
}
// This method will be called every 2 seconds by the Timer
private static void PeriodicTask(object state)
{
_counter++;
Debug.WriteLine($"[Timer] Execution no. {_counter}. Time: {DateTime.UtcNow}");
// Here you could read a sensor, send a ping, etc.
}
}
}Stopping or Changing the Timer
If at any point we want to stop the timer or change its frequency, we use the Change method.
// Stop the timer (Infinite period)
myTimer.Change(Timeout.Infinite, Timeout.Infinite);
// Restart to fire immediately and then every 500ms
myTimer.Change(0, 500);Note on Threads:
The Timer’s method runs on a ThreadPool thread. Be careful if you access variables shared with the main thread; ensure your code is Thread-Safe using lock if necessary.
What About Real Date and Time?
You might wonder: “Okay, I know how to measure 5 minutes, but how do I know that today is October 24th at 6:00 PM?”
Microcontrollers like the ESP32 have an internal RTC (Real Time Clock) that counts time. However, when powered on, they always start at a default date (year 2017 or similar) because they don’t have a backup battery to retain the time when powered off.
To get the real date (“Wall Clock Time”), we need to synchronize with the Internet using NTP (Network Time Protocol). But that, my friends, we will cover in the Wi-Fi Connectivity module.