esp32_timg
TIMG0 and TIMG1: four 64-bit general-purpose timers.
The kernel’s timer::once/every ride the 1 ms scheduler tick, so nothing
sub-millisecond is expressible and every period is quantised to the tick
and to scheduler load. These are the hardware underneath a timer that has
to be accurate: a 16-bit prescaler off the 80 MHz APB clock, a 64-bit
counter, and an alarm that fires an interrupt.
Two groups, two timers each. Each timer is wholly independent — its own divider, counter, alarm and interrupt source.
This is not the tick
Section titled “This is not the tick”The scheduler tick is the Xtensa core’s own CCOMPARE, in arch-xtensa, and
stays that way. A tick that lived in a peripheral would stop when that
peripheral was gated off, and the tick is the one clock the kernel cannot
do without. These are for everything else.
Register facts
Section titled “Register facts”Offsets and bit positions from esp-idf soc/timer_group_reg.h. T0 sits at
the group base and T1 is 0x24 further on, so one set of offsets serves
both.
| Register | Offset | Notes |
|---|---|---|
CONFIG |
0x00 |
EN 31, INCREASE 30, AUTORELOAD 29, DIVIDER [28:13], LEVEL_INT_EN 11, ALARM_EN 10 |
LO / HI |
0x04 / 0x08 |
the counter, only valid after a latch |
UPDATE |
0x0c |
write anything to latch the counter into LO/HI |
ALARMLO / ALARMHI |
0x10 / 0x14 |
when to fire |
LOADLO / LOADHI |
0x18 / 0x1c |
value a reload installs |
LOAD |
0x20 |
write anything to install it |
Group-wide, not per timer: INT_ENA 0x98, INT_RAW 0x9c,
INT_ST 0xa0, INT_CLR 0xa4, with T0 in bit 0 and T1 in bit 1.
Modules
Section titled “Modules”lact— The LAC timer — a timer group’s fifth counter, and a programmable alarm.
Structs
Section titled “Structs”struct Timg { /* private fields */ }One general-purpose timer.
enum Group { Timg0, Timg1,}Which timer group.
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
enum Mode { OneShot, Periodic,}What happens when the alarm fires.
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
enum Timer { T0, T1,}Which timer within a group.
Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq
TimerError
Section titled “TimerError”enum TimerError { UnsupportedResolution, UnsupportedPeriod,}Why a timer could not be configured.
Implements: Clone, Copy, Debug, Eq, From, PartialEq, StructuralPartialEq
Functions
Section titled “Functions”clear_interrupt
Section titled “clear_interrupt”unsafe fn clear_interrupt(group: Group, timer: Timer)Acknowledge a timer’s alarm without holding a [Timg].
For a top-half, which cannot take a lock and so cannot reach a shared handle. The registers are the state; nothing else is needed to clear one bit.
In IRAM, because a top-half that registered as IRAM-safe has to be able to acknowledge its alarm during a flash operation. One register write, and leaving it in flash would make an otherwise-correct IRAM handler stop the core on its very first instruction that mattered.
Safety
Section titled “Safety”Writes the group’s interrupt-clear register.
divider_for
Section titled “divider_for”fn divider_for(resolution_hz: u32) -> Result<u32, TimerError>Prescaler for a requested counter frequency.
The field is 16 bits and the hardware treats 0 as 65536, so the encodable range is 2..=65536 — esp-idf asserts the same lower bound. A divider of 1 is not “no division”; it is rejected.
References: TimerError
unsafe fn rearm(group: Group, timer: Timer)Re-arm a timer’s alarm without holding a [Timg].
The companion to [clear_interrupt], and needed in the same place: a
periodic alarm’s handler has to put ALARM_EN back every time, and a
top-half cannot reach a shared handle.
In IRAM, and for the same reason as [clear_interrupt]: a periodic
alarm whose handler runs during a flash operation has to put ALARM_EN
back, and it cannot reach out to flash to do it.
Safety
Section titled “Safety”Read-modify-writes the timer’s config register.