Lines
93.88 %
Functions
100 %
#![cfg_attr(docsrs, procmacros::doc_replace)]
//! # Timer Group (TIMG)
//!
//! ## Overview
//! The Timer Group (TIMG) peripherals contain one or more general-purpose
//! timers, plus one or more watchdog timers.
//! The general-purpose timers are based on a 16-bit pre-scaler and a 54-bit
//! auto-reload-capable up-down counter.
//! ## Configuration
//! The timers have configurable alarms, which are triggered when the internal
//! counter of the timers reaches a specific target value. The timers are
//! clocked using the APB clock source.
//! Typically, a general-purpose timer can be used in scenarios such as:
//! - Generate period alarms; trigger events periodically
//! - Generate one-shot alarms; trigger events once
//! - Free-running; fetching a high-resolution timestamp on demand
//! ## Examples
//! ### General-purpose Timer
//! ```rust, no_run
//! # {before_snippet}
//! use esp_hal::timer::{Timer, timg::TimerGroup};
//! let timg0 = TimerGroup::new(peripherals.TIMG0);
//! let timer0 = timg0.timer0;
//! // Get the current timestamp, in microseconds:
//! let now = timer0.now();
//! // Wait for timeout:
//! timer0.load_value(Duration::from_secs(1));
//! timer0.start();
//! while !timer0.is_interrupt_set() {
//! // Wait
//! }
//! timer0.clear_interrupt();
//! # {after_snippet}
//! ```
//! ### Watchdog Timer
//! use esp_hal::timer::{
//! Timer,
//! timg::{MwdtStage, TimerGroup},
//! };
//! let mut wdt = timg0.wdt;
//! wdt.set_timeout(MwdtStage::Stage0, Duration::from_millis(5_000));
//! wdt.enable();
//! loop {
//! wdt.feed();
//! # }
use core::marker::PhantomData;
use super::Error;
#[cfg(timergroup_timg1)]
use crate::peripherals::TIMG1;
#[cfg(soc_has_clock_node_timg_function_clock)]
use crate::soc::clocks::TimgFunctionClockConfig;
#[cfg(soc_has_clock_node_timg_wdt_clock)]
use crate::soc::clocks::TimgWdtClockConfig;
use crate::{
asynch::AtomicWaker,
interrupt::{self, InterruptConfigurable, InterruptHandler},
pac::timg0::RegisterBlock,
peripherals::{Interrupt, TIMG0},
private::Sealed,
soc::clocks,
system::PeripheralClockControl,
time::{Duration, Instant, Rate},
};
const NUM_TIMG: usize = 1 + cfg!(timergroup_timg1) as usize;
cfg_select! {
// We need no locks when a TIMG has a single timer, and we don't need locks for ESP32
// and S2 where the effective interrupt enable register (config) is not shared between
// the timers.
all(timergroup_timg_has_timer1, not(any(esp32, esp32s2))) => {
use esp_sync::RawMutex;
static INT_ENA_LOCK: [RawMutex; NUM_TIMG] = [const { RawMutex::new() }; NUM_TIMG];
}
_ => {}
#[procmacros::doc_replace(
"timers" => {
cfg(timergroup_timg_has_timer1) => "2 timers",
_ => "a general purpose timer",
)]
/// A timer group consisting of __timers__ and a watchdog timer.
pub struct TimerGroup<'d, T>
where
T: TimerGroupInstance + 'd,
{
_timer_group: PhantomData<T>,
/// Timer 0
pub timer0: Timer<'d>,
/// Timer 1
#[cfg(timergroup_timg_has_timer1)]
pub timer1: Timer<'d>,
/// Watchdog timer
pub wdt: Wdt<T>,
#[doc(hidden)]
pub trait TimerGroupInstance {
fn id() -> u8;
fn register_block() -> *const RegisterBlock;
fn clock_instance() -> clocks::TimgInstance;
fn peripheral() -> crate::system::Peripheral;
fn wdt_interrupt() -> Interrupt;
#[cfg(timergroup_timg0)]
impl TimerGroupInstance for TIMG0<'_> {
fn id() -> u8 {
0
#[inline(always)]
fn register_block() -> *const RegisterBlock {
Self::regs()
fn clock_instance() -> clocks::TimgInstance {
clocks::TimgInstance::Timg0
fn peripheral() -> crate::system::Peripheral {
crate::system::Peripheral::Timg0
fn wdt_interrupt() -> Interrupt {
Interrupt::TG0_WDT_LEVEL
impl TimerGroupInstance for crate::peripherals::TIMG1<'_> {
1
clocks::TimgInstance::Timg1
crate::system::Peripheral::Timg1
Interrupt::TG1_WDT_LEVEL
impl<'d, T> TimerGroup<'d, T>
/// Creates a new instance of [`TimerGroup`] in blocking mode.
pub fn new(_timer_group: T) -> Self {
// TODO: use PeripheralGuard
if PeripheralClockControl::enable(T::peripheral()) {
PeripheralClockControl::reset(T::peripheral());
} else {
// Refcount was more than 0. Decrement to avoid overflow because we don't handle
// dropping the driver.
PeripheralClockControl::disable(T::peripheral());
clocks::ClockTree::with(|clocks| {
let clock = T::clock_instance();
clock.configure_function_clock(clocks, TimgFunctionClockConfig::default());
clock.request_function_clock(clocks);
});
Self {
_timer_group: PhantomData,
timer0: Timer {
timer: TimerId::Timer0,
tg: T::id(),
register_block: T::register_block(),
_lifetime: PhantomData,
},
timer1: Timer {
timer: TimerId::Timer1,
wdt: Wdt::new(),
impl super::Timer for Timer<'_> {
fn start(&self) {
self.set_counter_active(false);
self.set_alarm_active(false);
self.reset_counter();
self.set_counter_decrementing(false);
self.set_counter_active(true);
self.set_alarm_active(true);
fn stop(&self) {
fn reset(&self) {
self.reset_counter()
fn is_running(&self) -> bool {
self.is_counter_active()
fn now(&self) -> Instant {
self.now()
fn load_value(&self, value: Duration) -> Result<(), Error> {
self.load_value(value)
fn enable_auto_reload(&self, auto_reload: bool) {
self.set_auto_reload(auto_reload)
fn enable_interrupt(&self, state: bool) {
self.set_interrupt_enabled(state);
fn clear_interrupt(&self) {
self.clear_interrupt()
fn is_interrupt_set(&self) -> bool {
self.is_interrupt_set()
fn async_interrupt_handler(&self) -> InterruptHandler {
match (self.timer_group(), self.timer_number()) {
(0, 0) => asynch::timg0_timer0_handler,
(0, 1) => asynch::timg0_timer1_handler,
(1, 0) => asynch::timg1_timer0_handler,
#[cfg(all(timergroup_timg_has_timer1, timergroup_timg1))]
(1, 1) => asynch::timg1_timer1_handler,
_ => unreachable!(),
fn peripheral_interrupt(&self) -> Interrupt {
(0, 0) => Interrupt::TG0_T0_LEVEL,
(0, 1) => Interrupt::TG0_T1_LEVEL,
(1, 0) => Interrupt::TG1_T0_LEVEL,
(1, 1) => Interrupt::TG1_T1_LEVEL,
fn set_interrupt_handler(&self, handler: InterruptHandler) {
self.set_interrupt_handler(handler)
fn waker(&self) -> &AtomicWaker {
asynch::waker(self)
/// A timer within a Timer Group.
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Timer<'d> {
register_block: *const RegisterBlock,
_lifetime: PhantomData<&'d mut ()>,
timer: TimerId,
tg: u8,
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TimerId {
Timer0,
Timer1,
impl Sealed for Timer<'_> {}
unsafe impl Send for Timer<'_> {}
/// Timer peripheral instance
impl Timer<'_> {
/// Unsafely clone this peripheral reference.
///
/// # Safety
/// The caller must ensure that only one instance of this type is used at a
/// time.
pub unsafe fn clone_unchecked(&self) -> Self {
register_block: self.register_block,
timer: self.timer,
tg: self.tg,
/// Creates a new peripheral reference with a shorter lifetime.
/// Use this method to keep working with the peripheral after dropping the
/// driver that consumes this.
/// See [Peripheral singleton] section for more information.
/// [Peripheral singleton]: crate#peripheral-singletons
pub fn reborrow(&mut self) -> Timer<'_> {
unsafe { self.clone_unchecked() }
pub(crate) fn set_interrupt_handler(&self, handler: InterruptHandler) {
let interrupt = match (self.timer_group(), self.timer_number()) {
for core in crate::system::Cpu::other() {
crate::interrupt::disable(core, interrupt);
interrupt::bind_handler(interrupt, handler);
fn register_block(&self) -> &RegisterBlock {
unsafe { &*self.register_block }
fn timer_group(&self) -> u8 {
self.tg
fn timer_number(&self) -> u8 {
self.timer as u8
fn t(&self) -> &crate::pac::timg0::T {
self.register_block().t(self.timer_number().into())
fn reset_counter(&self) {
let t = self.t();
t.loadlo().write(|w| unsafe { w.load_lo().bits(0) });
t.loadhi().write(|w| unsafe { w.load_hi().bits(0) });
t.load().write(|w| unsafe { w.load().bits(1) });
fn set_counter_active(&self, state: bool) {
self.t().config().modify(|_, w| w.en().bit(state));
fn is_counter_active(&self) -> bool {
self.t().config().read().en().bit_is_set()
fn set_counter_decrementing(&self, decrementing: bool) {
self.t()
.config()
.modify(|_, w| w.increase().bit(!decrementing));
fn set_auto_reload(&self, auto_reload: bool) {
.modify(|_, w| w.autoreload().bit(auto_reload));
fn set_alarm_active(&self, state: bool) {
self.t().config().modify(|_, w| w.alarm_en().bit(state));
fn source_frequency(&self) -> Rate {
soc_has_clock_node_timg_function_clock => {
let timg = match self.timer_group() {
0 => crate::soc::clocks::TimgInstance::Timg0,
#[cfg(soc_has_timg1)]
1 => crate::soc::clocks::TimgInstance::Timg1,
let hz = timg.function_clock_frequency();
_ => {
let hz = crate::soc::clocks::apb_clk_frequency();
Rate::from_hz(hz)
let clk_src = self.source_frequency();
let Some(ticks) = timeout_to_ticks(value, clk_src, self.divider()) else {
return Err(Error::InvalidTimeout);
// The counter is 54-bits wide, so we must ensure that the provided
// value is not too wide:
if (ticks & !0x3F_FFFF_FFFF_FFFF) != 0 {
let high = (ticks >> 32) as u32;
let low = (ticks & 0xFFFF_FFFF) as u32;
t.alarmlo().write(|w| unsafe { w.alarm_lo().bits(low) });
t.alarmhi().write(|w| unsafe { w.alarm_hi().bits(high) });
Ok(())
self.register_block()
.int_clr()
.write(|w| w.t(self.timer as _).clear_bit_by_one());
let periodic = self.t().config().read().autoreload().bit_is_set();
self.set_alarm_active(periodic);
t.update().write(|w| w.update().set_bit());
while t.update().read().update().bit_is_set() {
// Wait for the update to complete
let value_lo = t.lo().read().bits() as u64;
let value_hi = t.hi().read().bits() as u64;
let ticks = (value_hi << 32) | value_lo;
let micros = ticks_to_timeout(ticks, clk_src, self.divider());
Instant::from_ticks(micros)
fn divider(&self) -> u32 {
// From the ESP32 TRM, "11.2.1 16Â-bit Prescaler and Clock Selection":
//
// "The prescaler can divide the APB clock by a factor from 2 to 65536.
// Specifically, when TIMGn_Tx_DIVIDER is either 1 or 2, the clock divisor is 2;
// when TIMGn_Tx_DIVIDER is 0, the clock divisor is 65536. Any other value will
// cause the clock to be divided by exactly that value."
match t.config().read().divider().bits() {
0 => 65536,
1 | 2 => 2,
n => n as u32,
.int_raw()
.read()
.t(self.timer as _)
.bit_is_set()
fn set_interrupt_enabled(&self, state: bool) {
any(esp32, esp32s2) => {
// On ESP32 and S2, the `int_ena` register is ineffective - interrupts fire even
// without int_ena enabling them. We use level interrupts so that we have a status
// bit available.
self.t().config().modify(|_, w| w.level_int_en().bit(state));
timergroup_timg_has_timer1 => {
INT_ENA_LOCK[self.timer_group() as usize].lock(|| {
.int_ena()
.modify(|_, w| w.t(self.timer_number()).bit(state));
.modify(|_, w| w.t(0).bit(state));
fn ticks_to_timeout(ticks: u64, clock: Rate, divider: u32) -> u64 {
// 1_000_000 is used to get rid of `float` calculations
let period: u64 = 1_000_000 * 1_000_000 / (clock.as_hz() as u64 / divider as u64);
ticks * period / 1_000_000
fn timeout_to_ticks(timeout: Duration, clock: Rate, divider: u32) -> Option<u64> {
let micros = timeout.as_micros();
let ticks_per_sec = (clock.as_hz() / divider) as u64;
micros.checked_mul(ticks_per_sec).map(|n| n / 1_000_000)
/// Behavior of the MWDT stage if it times out.
#[allow(unused)]
#[derive(Debug, Clone, Copy)]
pub enum MwdtStageAction {
/// No effect on the system.
Off = 0,
/// Triggers an interrupt.
Interrupt = 1,
/// Resets the CPU core.
ResetCpu = 2,
/// Resets the main system, power management unit and RTC peripherals.
ResetSystem = 3,
/// MWDT stages.
/// Timer stages allow for a timer to have a series of different timeout values
/// and corresponding expiry action.
pub enum MwdtStage {
/// MWDT stage 0.
Stage0,
/// MWDT stage 1.
Stage1,
/// MWDT stage 2.
Stage2,
/// MWDT stage 3.
Stage3,
pub struct Wdt<TG> {
phantom: PhantomData<TG>,
/// Watchdog driver
impl<TG> Wdt<TG>
TG: TimerGroupInstance,
/// Creates a new instance of [`Wdt`].
pub fn new() -> Self {
let mut this = Self {
phantom: PhantomData,
this.set_write_protection(false);
TG::clock_instance().configure_wdt_clock(clocks, TimgWdtClockConfig::default())
this.set_write_protection(true);
this
/// Enables the watchdog timer instance.
pub fn enable(&mut self) {
// SAFETY: The `TG` instance being modified is owned by `self`, which is behind
// a mutable reference.
unsafe { self.set_wdt_enabled(true) };
/// Disables the watchdog timer instance.
pub fn disable(&mut self) {
unsafe { self.set_wdt_enabled(false) };
/// Forcibly enable or disable the watchdog timer
/// This bypasses the usual ownership rules for the peripheral, so users
/// must take care to ensure that no driver instance is active for the
/// timer.
pub unsafe fn set_wdt_enabled(&mut self, enabled: bool) {
let reg_block = unsafe { &*TG::register_block() };
self.set_write_protection(false);
if enabled {
clocks::ClockTree::with(|clocks| TG::clock_instance().request_wdt_clock(clocks));
reg_block
.wdtconfig0()
.modify(|_, w| w.wdt_en().bit(enabled));
reg_block.wdtconfig0().modify(|_, w| unsafe {
w.wdt_flashboot_mod_en().bit(false);
w.wdt_stg0().bits(MwdtStageAction::ResetSystem as u8);
w.wdt_cpu_reset_length().bits(7);
w.wdt_sys_reset_length().bits(7);
w.wdt_stg1().bits(MwdtStageAction::Off as u8);
w.wdt_stg2().bits(MwdtStageAction::Off as u8);
w.wdt_stg3().bits(MwdtStageAction::Off as u8)
#[cfg(timergroup_wdt_has_conf_update)]
.modify(|_, w| w.wdt_conf_update_en().set_bit());
if !enabled {
clocks::ClockTree::with(|clocks| TG::clock_instance().release_wdt_clock(clocks));
self.set_write_protection(true);
/// Feeds the watchdog timer.
pub fn feed(&mut self) {
reg_block.wdtfeed().write(|w| unsafe { w.bits(1) });
fn set_write_protection(&mut self, enable: bool) {
let wkey = if enable { 0u32 } else { 0x50D8_3AA1u32 };
.wdtwprotect()
.write(|w| unsafe { w.wdt_wkey().bits(wkey) });
/// Sets the timeout, in microseconds, of the watchdog timer.
pub fn set_timeout(&mut self, stage: MwdtStage, timeout: Duration) {
soc_has_clock_node_timg_wdt_clock => {
let clk_src = Rate::from_hz(TG::clock_instance().wdt_clock_frequency());
let clk_src = Rate::from_hz(clocks::apb_clk_frequency());
let timeout_ticks = timeout.as_micros() * clk_src.as_mhz() as u64;
let (prescaler, timeout) = if timeout_ticks > u32::MAX as u64 {
let prescaler = timeout_ticks
.div_ceil(u32::MAX as u64 + 1)
.min(u16::MAX as u64) as u16;
let timeout = timeout_ticks
.div_ceil(prescaler as u64)
.min(u32::MAX as u64);
(prescaler, timeout as u32)
(1, timeout_ticks as u32)
reg_block.wdtconfig1().write(|w| unsafe {
#[cfg(timergroup_timg_has_divcnt_rst)]
w.wdt_divcnt_rst().set_bit();
w.wdt_clk_prescale().bits(prescaler)
let config_register = match stage {
MwdtStage::Stage0 => reg_block.wdtconfig2(),
MwdtStage::Stage1 => reg_block.wdtconfig3(),
MwdtStage::Stage2 => reg_block.wdtconfig4(),
MwdtStage::Stage3 => reg_block.wdtconfig5(),
config_register.write(|w| unsafe { w.hold().bits(timeout) });
/// Sets the stage action of the MWDT for a specific stage.
/// Modifies MWDT behavior only if a custom bootloader with the following modifications is
/// used:
/// - `ESP_TASK_WDT_EN` parameter **disabled**
/// - `ESP_INT_WDT` parameter **disabled**
pub fn set_stage_action(&mut self, stage: MwdtStage, action: MwdtStageAction) {
match stage {
MwdtStage::Stage0 => w.wdt_stg0().bits(action as u8),
MwdtStage::Stage1 => w.wdt_stg1().bits(action as u8),
MwdtStage::Stage2 => w.wdt_stg2().bits(action as u8),
MwdtStage::Stage3 => w.wdt_stg3().bits(action as u8),
impl<TG> crate::private::Sealed for Wdt<TG> where TG: TimerGroupInstance {}
impl<TG> InterruptConfigurable for Wdt<TG>
fn set_interrupt_handler(&mut self, handler: interrupt::InterruptHandler) {
let interrupt = TG::wdt_interrupt();
impl<TG> Default for Wdt<TG>
fn default() -> Self {
Self::new()
// Async functionality of the timer groups.
mod asynch {
use procmacros::handler;
use super::*;
use crate::asynch::AtomicWaker;
const NUM_WAKERS: usize = {
let timer_per_group = 1 + cfg!(timergroup_timg_has_timer1) as usize;
NUM_TIMG * timer_per_group
static WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS];
pub(super) fn waker(timer: &Timer<'_>) -> &'static AtomicWaker {
let index = (timer.timer_number() << 1) | timer.timer_group();
&WAKERS[index as usize]
#[inline]
fn handle_irq(timer: Timer<'_>) {
timer.set_interrupt_enabled(false);
waker(&timer).wake();
// INT_ENA means that when the interrupt occurs, it will show up in the
// INT_ST. Clearing INT_ENA that it won't show up on INT_ST but if
// interrupt is already there, it won't clear it - that's why we need to
// clear the INT_CLR as well.
#[handler]
pub(crate) fn timg0_timer0_handler() {
handle_irq(Timer {
register_block: TIMG0::regs(),
tg: 0,
pub(crate) fn timg1_timer0_handler() {
register_block: TIMG1::regs(),
tg: 1,
pub(crate) fn timg0_timer1_handler() {
#[cfg(all(timergroup_timg1, timergroup_timg_has_timer1))]
pub(crate) fn timg1_timer1_handler() {
/// Event Task Matrix
#[cfg(etm_driver_supported)]
pub mod etm {
use crate::etm::{EtmEvent, EtmTask};
/// Event Task Matrix event for a timer.
pub struct Event {
id: u8,
/// Event Task Matrix task for a timer.
pub struct Task {
impl EtmEvent for Event {
fn id(&self) -> u8 {
self.id
impl Sealed for Event {}
impl EtmTask for Task {
impl Sealed for Task {}
/// General purpose timer ETM events.
pub trait Events {
/// ETM event triggered on alarm
fn on_alarm(&self) -> Event;
/// General purpose timer ETM tasks
pub trait Tasks {
/// ETM task to start the counter
fn cnt_start(&self) -> Task;
/// ETM task to stop the counter
fn cnt_stop(&self) -> Task;
/// ETM task to reload the counter
fn cnt_reload(&self) -> Task;
/// ETM task to capture the counter
fn cnt_cap(&self) -> Task;
/// ETM task to load the counter with the value stored when the last
/// `now()` was called
fn alarm_start(&self) -> Task;
impl Events for Timer<'_> {
fn on_alarm(&self) -> Event {
Event {
id: 48 + self.timer_group(),
impl Tasks for Timer<'_> {
fn cnt_start(&self) -> Task {
Task {
id: 88 + self.timer_group(),
fn alarm_start(&self) -> Task {
id: 90 + self.timer_group(),
fn cnt_stop(&self) -> Task {
id: 92 + self.timer_group(),
fn cnt_reload(&self) -> Task {
id: 94 + self.timer_group(),
fn cnt_cap(&self) -> Task {
id: 96 + self.timer_group(),