A MicroPython timer is a hardware or software peripheral that triggers a specific callback function or interrupt after a precisely measured interval, independent of the main execution loop. When you replace a blocking time.sleep() with a machine.Timer, you change your circuit's behavior from a sequential, frozen state to an event-driven architecture. This frees the main CPU to handle WiFi stacks, UART parsing, or display refreshes while the timing runs in the background.
Beginners commonly confuse machine.Timer with hardware PWM or simple delays. A timer interrupt does not toggle a GPIO pin directly like PWM does; it fires a software callback. It is also frequently confused with time.sleep(), which halts the entire processor thread. ESP32-WROOM-32 tick resolution: 1µs, meaning your software callbacks are highly precise, provided you respect interrupt context rules.
Hardware vs. Virtual Timers on the ESP32 and RP2040
Not all timers are created equal under the hood. The underlying C implementation of MicroPython maps the machine.Timer API differently depending on the silicon you are using. Understanding this distinction prevents mysterious crashes when you port code from a Raspberry Pi Pico to an ESP32.
| Feature | ESP32 (FreeRTOS Virtual Timers) | RP2040 / Pico (Hardware Alarms) |
|---|---|---|
| Underlying Mechanism | Software timers managed by the FreeRTOS daemon | Dedicated hardware alarm peripherals (4 total) |
| Available Instances | Virtually unlimited (constrained by RAM) | Strictly limited to 4 hardware alarms |
| Jitter / Precision | ~1ms jitter (depends on RTOS task scheduling) | Sub-microsecond precision (tied to hardware clock) |
| Best Use Case | Sensor polling, UI debouncing, watchdog feeding | Strict PID control loops, precise pulse generation |
On the ESP32, when you initialize a timer via the MicroPython machine.Timer documentation, you are actually creating a virtual software timer. The ESP32 has four physical hardware timers, but MicroPython reserves those for the PWM and RMT (Remote Control) peripherals. On the RP2040, you are interacting directly with the silicon's hardware alarms. For 95% of IoT and hobbyist projects, the ESP32's virtual timers are more than precise enough.
Worked Numeric Example: 500ms Sensor Polling on an ESP32
Let us look at a real-world scenario: reading a BME280 temperature and humidity sensor over I2C every 500ms and sending the data over MQTT.
An I2C read sequence for a BME280, combined with floating-point compensation math in MicroPython, takes approximately 14ms to execute on an ESP32-WROOM-32 running at 160MHz.
If you use a
while True: loop with time.sleep(0.5), the CPU is completely blocked for 500ms. During this half-second, the WiFi radio might receive TCP acknowledgments, but the main thread cannot process them. This leads to buffer overflows, dropped MQTT packets, and eventual WiFi disconnects.
Now, apply a machine.Timer with a 500ms period. The timer fires an interrupt every 500ms. The callback executes the 14ms I2C read.
The Math:
Callback execution time: 14ms
Timer period: 500ms
CPU Duty Cycle for this task: (14 / 500) * 100 = 2.8%
The main thread now has 486ms of completely free execution time per cycle to handle the WiFi stack, parse incoming serial commands, or update an OLED display. According to the MicroPython ESP32 Quick Reference, yielding the main thread to the RTOS is mandatory for stable WiFi operation.
from machine import Timer, I2C, Pin
import bme280
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
sensor = bme280.BME280(i2c=i2c)
def read_sensor_callback(t):
# 14ms execution time
temp, hum, pres = sensor.values
print(f"Temp: {temp}")
# Initialize a 500ms periodic virtual timer
tim = Timer(0)
tim.init(period=500, mode=Timer.PERIODIC, callback=read_sensor_callback)
while True:
# Main loop is free to handle network tasks
pass
Where You Meet Timers in Practice
You will reach for machine.Timer constantly when moving from simple blink-sketches to robust embedded firmware. Here is where they earn their keep on the workbench:
- Switch Debouncing: Mechanical switches bounce for 5ms to 50ms. Instead of blocking the CPU with a delay to wait out the bounce, you trigger a
Timer.ONE_SHOTfor 50ms on the first pin-change interrupt. The callback reads the pin state only after the bouncing has settled. - PID Control Loops: Motor controllers and thermal regulators require math to be calculated at a strict, unvarying interval (e.g., exactly every 10ms). Timers guarantee the derivative and integral terms do not accumulate errors due to loop jitter.
- Watchdog Feeding: If your main loop hangs, the hardware watchdog resets the board. A periodic timer running in the background can act as an independent monitor, feeding the watchdog only if the main thread updates a shared state variable, proving the system is actually healthy.
- Software PWM / Dimming: While hardware PWM is better, you can use high-frequency timers (e.g., 1kHz) to manually toggle GPIO pins for basic LED dimming or buzzer tones when hardware PWM channels are exhausted.
Decision Tree: Choosing Your Timer Mode and Architecture
Do not guess which timer configuration to use. Follow this decision path to arrive at the correct setup for your specific hardware constraint.
| Condition / Requirement | Timer Choice | Concrete Pick |
|---|---|---|
| Need sub-microsecond precision or direct pin toggling without CPU intervention | Do NOT use machine.Timer |
Use machine.PWM or the RMT peripheral. |
| Need a single, non-blocking delay (e.g., turn off a relay 5 seconds after a button press) | Software One-Shot | Timer(mode=Timer.ONE_SHOT, period=5000) |
| Need continuous sensor polling or telemetry beaconing | Software Periodic | Timer(mode=Timer.PERIODIC, period=1000) |
| Callback requires heavy processing (I2C reads, string formatting, network calls) | Scheduled Deferral | Use micropython.schedule() inside the ISR. |
machine.Timer in PERIODIC mode paired with micropython.schedule(). This covers 95% of use cases, prevents memory allocation crashes, and keeps the WiFi stack stable.
Interrupt Context Rules and Common Confusions
The most common way builders brick their MicroPython firmware is by violating Interrupt Service Routine (ISR) rules. When a timer fires, it halts the main program and jumps to your callback. This callback runs in an interrupt context.
According to the MicroPython ISR Rules documentation, you cannot allocate memory inside an interrupt. If your callback creates a new string, appends to a list, or initializes an object, the garbage collector might be busy, causing a hard crash or a silent reboot.
The Fix: Use micropython.schedule(). This allows the timer interrupt to queue a function to run in the main thread at the next available safe moment, completely bypassing the memory allocation restriction.
import micropython
from machine import Timer
# Pre-allocate a global buffer to avoid memory allocation in the ISR
buffer = bytearray(10)
def heavy_task(data):
# This runs in the main thread, safe to use I2C, print, and allocate
print(f"Processing: {data}")
def timer_isr(t):
# This runs in interrupt context. NO memory allocation allowed.
# We schedule the heavy task to run safely in the main loop.
micropython.schedule(heavy_task, buffer)
tim = Timer(0)
tim.init(period=1000, mode=Timer.PERIODIC, callback=timer_isr)
Frequently Asked Questions
Can I use a MicroPython timer to generate a 50Hz servo signal?
No. While a timer fires at 50Hz (every 20ms), the software interrupt latency and jitter (often 1-3ms on the ESP32) will cause the servo to jitter violently. For servos, always use the dedicated machine.PWM class, which uses hardware timers to generate the pulse without CPU intervention.
What happens if my timer callback takes longer than the timer period?
If you set a 100ms periodic timer, but your callback takes 150ms to execute, the RTOS will queue the next interrupt. Your callbacks will begin to stack up, eventually causing a watchdog reset or a stack overflow crash. Always ensure your ISR execution time is at least 20% shorter than the timer period.
Do timers consume battery power on a sleeping ESP32?
Standard machine.Timer instances require the CPU and RTOS to remain awake. If you put the ESP32 into light or deep sleep, virtual timers stop. For waking from deep sleep, you must use the hardware Real Time Clock (RTC) via machine.RTC or an external hardware interrupt on a GPIO pin.






