ESP32S3 fast 32bit and 64bit usec counters. Set/Get

vvb333007
Posts: 76
Joined: Wed Jul 31, 2024 5:53 am
Location: Thailand
Contact:

ESP32S3 fast 32bit and 64bit usec counters. Set/Get

Postby vvb333007 » Mon May 25, 2026 9:15 pm

Hello,

I'd like to share some of my findings which may be useful for others. These are specific to ESP32-S3.

Two alternatives to `esp_timer_get_time()`, with ability to set counter value.

This code is written fro Arduino framework fro simplicity but can be easily adopted for ESP-IDF.

MMIO registers used in the sketch are not documented in TRM but it is very likely that they will remain constants in future releases of ESP32-S3

Functions of interest: micros64(), setmicros64() and micros32()

Code: Select all

// ESP32-S3 Microsecond Counters: 32/64-bit, read/write
//
// Two ways to get a microsecond timestamp:
//
// 1. Very fast, but returns only the lower 32 bits
// 2. Full 64-bit timestamp, slightly slower
//
// For simplicity, this example uses the Arduino framework.
//
// Setting the microsecond counter MAY interfere with WiFi TSF logic.
// Or maybe not - I have not tested it.
//
// WiFi does not need to be initialized to use these counters.
//
// vvb333007@gmail.com
//
#include <Arduino.h>
#include <stdint.h>

// Command register:
#define WIFI_TSF_CMD_REG (volatile uint32_t *)((uintptr_t)0x6003500c)

// Commands:
#define WIFI_TSFC_LATCH 0x01 // Latch current counter#0 (STA TSF counter) value into MMIO regs 0x18 and 0x1C
#define WIFI_TSFC_SET   0x10 // Set current counter#0 value from regs 0x10 and 0x14

// Latched 64-bit counter value.
// After issuing the LATCH command, these registers contain
// a snapshot of the current microsecond counter.
//
#define WIFI_TSF_HIGH_REG (volatile uint32_t *)((uintptr_t)0x6003501c)
#define WIFI_TSF_LOW_REG  (volatile uint32_t *)((uintptr_t)0x60035018)

// Setter registers.
// Values written here are used when updating
// the current microsecond counter.
//
#define WIFI_TSF_SET_HIGH_REG (volatile uint32_t *)((uintptr_t)0x60035014)
#define WIFI_TSF_SET_LOW_REG  (volatile uint32_t *)((uintptr_t)0x60035010)

// Free-running 32-bit microsecond counter.
// The simplest and fastest option.
//
#define WIFI_MICROS_REG (volatile uint32_t *)((uintptr_t)0x60035000)

// Fastest possible micros() implementation
//
static inline uint32_t micros32() {
  return *WIFI_MICROS_REG;
}

// Full 64-bit microsecond timestamp.
//
// Create a snapshot, read two 32-bit values,
// and combine them into a 64-bit result.
//
uint64_t micros64() {

  uint32_t low, high;

  // Create snapshot
  *WIFI_TSF_CMD_REG |= WIFI_TSFC_LATCH;

  // Read latched values
  low = *WIFI_TSF_LOW_REG;
  high = *WIFI_TSF_HIGH_REG;

  // Release latch
  *WIFI_TSF_CMD_REG &= ~WIFI_TSFC_LATCH;

  // Build 64-bit result
  return (((uint64_t)high) << 32) | (uint64_t)low;
}

// Set the current microsecond counter value
//
void setmicros64(uint64_t val) {

  *WIFI_TSF_SET_LOW_REG = (uint32_t)(val & 0xffffffffUL);
  *WIFI_TSF_SET_HIGH_REG = (uint32_t)(val >> 32);

  *WIFI_TSF_CMD_REG |= WIFI_TSFC_SET;
}

// Demo
//
void setup() {

  Serial.begin(115200);
  Serial.printf("\r\n\r\nESP32-S3 microsecond counters demo:\r\n");
}

// 1. Display both 64-bit and 32-bit counters once per second
//    for a 10-second interval
//
// 2. Reset the 64-bit counter to zero
//
// 3. Repeat
//
void loop() {

  for (int i = 0; i < 10; i++) {
    Serial.printf("micros64 = %llu, micros32 = %lu\r\n", micros64(), micros32());
    delay(1000);
  }

  Serial.printf("Resetting 64-bit counter to zero, keeping 32-bit running...\r\n");

  setmicros64(0);
}
Typical output:

Code: Select all

micros64 = 51010996, micros32 = 51010997
micros64 = 52010996, micros32 = 52010997
micros64 = 53010996, micros32 = 53010997
micros64 = 54010996, micros32 = 54010997
micros64 = 55010996, micros32 = 55010997
micros64 = 56010996, micros32 = 56010997
Resetting 64-bit counter to zero, keeping 32-bit running...
micros64 = 3, micros32 = 57011019
micros64 = 999981, micros32 = 58010997
micros64 = 1999981, micros32 = 59010997
micros64 = 2999981, micros32 = 60010997
Last edited by vvb333007 on Tue May 26, 2026 12:57 pm, edited 1 time in total.
Thanks!
Slava.

vvb333007
Posts: 76
Joined: Wed Jul 31, 2024 5:53 am
Location: Thailand
Contact:

Re: ESP32S3 fast 32bit and 64bit usec counters. Set/Get

Postby vvb333007 » Tue May 26, 2026 12:55 pm

There are 4 (four) counters like that above but to use them you have to perform some initialization. Counter #0 (example above) works after boot thanks to ROM code. Once initialized, counters can be read or set by using constants 0x01 and 0x10 above shifted left by [0..3] to select required counter

Four counters:

Code: Select all

// ESP32-S3 Microsecond Counters: 32/64-bit, read/write
// 4 full 64bit usec counters (read/write) and one 32 bit freerunning counter
//
// Two ways to get a microsecond timestamp:
//
// 1. Very fast, but returns only the lower 32 bits
// 2. Full 64-bit timestamp, slightly slower
//
// For simplicity, this example uses the Arduino framework.
//
// Setting the microsecond counters MAY interfere with WiFi TSF logic.
// Or maybe not — I haven't tested it.
//
// WiFi does not need to be initialized to use these counters.
//
// vvb333007@gmail.com
//
#include <Arduino.h>
#include <stdint.h>

// Command Register:
//
#define WIFI_TSF_CMD_REG (volatile uint32_t *)((uintptr_t)0x6003500c)
#  define WIFI_TSFC_LATCH 0x01 // Latch current counter value into MMIO regs 0x18 and 0x1C
#  define WIFI_TSFC_SET   0x10 // Set current counter value from regs 0x10 and 0x14

// Counter Enable Registers (4 registers)
//
#define WIFI_TSF_EN_REG(_Counter) (volatile uint32_t *)((uintptr_t)(0x60035028 + _Counter * 0x0c))
#  define WIFI_TSFE_ENABLE 0x98000000  // Enable flags (TODO: leave only required flags)
 
// Latched 64-bit counter value.
// After issuing the LATCH command, these registers contain
// a snapshot of the current microsecond counter.
//
#define WIFI_TSF_HIGH_REG (volatile uint32_t *)((uintptr_t)0x6003501c)
#define WIFI_TSF_LOW_REG  (volatile uint32_t *)((uintptr_t)0x60035018)

// Setter registers.
// Values written here are used when updating
// the current microsecond counter.
//
#define WIFI_TSF_SET_HIGH_REG (volatile uint32_t *)((uintptr_t)0x60035014)
#define WIFI_TSF_SET_LOW_REG  (volatile uint32_t *)((uintptr_t)0x60035010)

// Free-running 32-bit microsecond counter.
// The simplest and fastest option.
//
#define WIFI_MICROS_REG (volatile uint32_t *)((uintptr_t)0x60035000)


// Fastest possible micros() implementation
//
static inline uint32_t micros32() {
  return *WIFI_MICROS_REG;
}

// Full 64-bit microsecond timestamp.
//
// Create a snapshot, read two 32-bit values,
// and combine them into a 64-bit result.
//
uint64_t micros64(uint8_t counter) {

  uint32_t low = 0, high = 0;

  if (counter < 4) {

    // Create snapshot
    *WIFI_TSF_CMD_REG |= (WIFI_TSFC_LATCH << counter);

    // Read latched values
    low = *WIFI_TSF_LOW_REG;
    high = *WIFI_TSF_HIGH_REG;

    // Release latch
    *WIFI_TSF_CMD_REG &= ~(WIFI_TSFC_LATCH  << counter);
  }

  // Build 64-bit result
  return (((uint64_t)high) << 32) | (uint64_t)low;
}

// Set the current microsecond counter value
//
void setmicros64(uint8_t counter, uint64_t val) {

  if (counter < 4) {
    *WIFI_TSF_SET_LOW_REG = (uint32_t)(val & 0xffffffffUL);
    *WIFI_TSF_SET_HIGH_REG = (uint32_t)(val >> 32);

    *WIFI_TSF_CMD_REG |= (WIFI_TSFC_SET  << counter);
  }
}

// Enable/Disable counters
void micros64en(uint8_t counter, bool en) {

  if (counter < 4) {
    if (en) {
      *WIFI_TSF_EN_REG(counter) |= WIFI_TSFE_ENABLE;
    } else {
      *WIFI_TSF_EN_REG(counter) &= ~WIFI_TSFE_ENABLE;
    }
    
  }
}


// Demo
//
void setup() {

  Serial.begin(115200);
  delay(300);
  Serial.printf("\r\n\r\nESP32-S3 microsecond counters demo:\r\n");
  //micros64en(0, true); // Works on its own from the start. May be it is a good idea to call enable on this one too
  micros64en(1, true);
  micros64en(2, true);
  micros64en(3, true);
}

// 1. Display both 64-bit and 32-bit counters once per second
//    for a 10-second interval
//
// 2. Reset the 64-bit counter to zero
//
// 3. Repeat
//
void loop() {

  for (int i = 0; i < 10; i++) {
    Serial.printf("micros64 = %llu / %llu / %llu / %llu\r\n", micros64(0), micros64(1), micros64(2), micros64(3) );
    delay(1000);
  }

  Serial.printf("Resetting 64-bit counters 1 and 3 to zero, keeping 32-bit running...\r\n");

  setmicros64(1, 0);
  setmicros64(3, 0);
}
Thanks!
Slava.

mikemoy
Posts: 692
Joined: Fri Jan 12, 2018 9:10 pm

Re: ESP32S3 fast 32bit and 64bit usec counters. Set/Get

Postby mikemoy » Tue Jun 16, 2026 3:15 am

Or this for IDF

Code: Select all

#include "esp_timer.h"

static esp_timer_handle_t periodic_timer;

void timer_callback(void* arg) {
    // Executed every 100,000 microseconds (0.1 seconds)
    // Toggle GPIO or post event to FreeRTOS here
}

void app_main() {
    esp_timer_create_args_t timer_args = {
        .callback = &timer_callback,
        .name = "periodic_timer"
    };
    esp_timer_create(&timer_args, &periodic_timer);
    esp_timer_start_periodic(periodic_timer, 100000); // 100,000 us
}   

vvb333007
Posts: 76
Joined: Wed Jul 31, 2024 5:53 am
Location: Thailand
Contact:

Re: ESP32S3 fast 32bit and 64bit usec counters. Set/Get

Postby vvb333007 » Sat Jul 25, 2026 3:28 am

The counters I talk about are not related to esp_timer_get_time().
Also , they do not raise interrupts. These counters are from WiFi block. They are used for TSF and they are not documented.

What they CAN do is to independently count microseconds, with ability to set counters values. So these counters can count 4 different time intervals.

If you just read them - then you can read all 4. If you are using WiFi AP or WIFI STA, then you can read/set 3 of them, while fourth can be only read. If you use AP+STA, then it is 2 counters that can be RW and two counters RO (unless you want to interfere with TSF mechanism)
Thanks!
Slava.

Who is online

Users browsing this forum: No registered users and 1 guest