[ESP32S3 Undocumented] Еще один счетчик микросекунд. 64 бита

Два способа читать микросекунды на ESP32-S3. Про 32-битный я уже рассказывал, ну а теперь пришел черед 64-битного

Довольно простой код, поэтому писанины не будет:

// Example for ESP32-S3: two ways to read 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.
//
//
#include <Arduino.h>
#include <stdint.h>

// Timer command register
#define WIFI_TSF_CMD_REG (volatile uint32_t *)((uintptr_t)0x6003500c)
#  define WIFI_TSFC_LATCH 1 // Latch current counter values into MMIO registers

// Latched 64-bit counter value
#define WIFI_TSF_HIGH_REG (volatile uint32_t *)((uintptr_t)0x6003501c)
#define WIFI_TSF_LOW_REG  (volatile uint32_t *)((uintptr_t)0x60035018)

// Free-running 32-bit microsecond counter
#define WIFI_MICROS_REG (volatile uint32_t *)((uintptr_t)0x60035000)

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

// Full 64-bit microsecond timestamp
// The logic is straightforward enough to not need much explanation
//
uint64_t micros64() {

  uint32_t low, high;

  // create snapshot
  *WIFI_TSF_CMD_REG |= WIFI_TSFC_LATCH;

  low = *WIFI_TSF_LOW_REG;
  high = *WIFI_TSF_HIGH_REG;

  *WIFI_TSF_CMD_REG &= ~WIFI_TSFC_LATCH;

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

void setup() {

  Serial.begin(115200);
}

void loop() {

  delay(1000);

  Serial.printf("%llu , %lu\r\n", micros64(), micros32());
}

Спасибо за инфо.
Запускается автоматически? Никаких настроек не требует?

Есть мысли, почему ссылка на него отсутствует в описании?

Работает сразу после включения, ничего дополнительно инициализировать не нужно - всю инициализацию необходимую делает ROM.

Не включили в документацию потому, что регистры относятся к недокументированному блоку WiFi. Все регистры недокументированы, относящиеся к WiFi.

Вот лишь часть регистров (их там около тысячи. может и больше), даже названия которых Espressif засекретил :). В документации на чип, в разделе Peripherals Memory Map есть штук 8 “Reserved Areas”. Они никакие не “reserved”. Они просто недокументированы.

Адрес блока  Название               Комментарий

60005000   FE2_REGS                 Radio Front End2. Около 120 регистров.
60006000   FE_REGS                  Radio Front End1
600060e0   PBUS                     Peripheral PHY Bus. За ней скрывается одиннадцать недокументированных устройств
60006148   ???                      I/Q calibration
6000e000   I2C_MST                  Internal I2C control bus (“REGI2C”)
                                    За ней скрывается 12 недокументированных устройств, у каждого в среднем 10
                                    регистров
60011000   BT_BASE_REG              BT
60011800   BT_AGC                   BT Gain
6001c000   AGC                      Gain
6001c100   ANT                      Antenna Configuration
6001c400   BB_TX                    BaseBand TX
6001c800   BRX                      Radio
6001cc00   NRX                      Radio
6001D000   BB_BASE                  BaseBand
6002A000   PERI_BACKUP              Backdoor DMA
60033000   WIFI_MAC_BASE
60034000   WIFI_MAC_BASE2
60035000   WIFI_MAC_BASE3           Таймеры, счетчики и прочая муть
600ce000   REG_ASSIST_DEBUG_BASE    Было в старой документации. В новой - убрали.

@vvb333007 Материал то интересный и ИКСклюзивный, может уже в одно место для удобности и славы :slightly_smiling_face: Кто бы намекнул бы администрации бы :roll_eyes:

Спойлер

А я и так все складываю в этот раздел и названия тем [ESP32S3 Undocumentded].

Продолжаем разговор.

Таймер 64битный оказался настолько хорош, что поддался не только чтению, но и установке.

// Счётчики микросекунд ESP32-S3: 32/64 бит, чтение/запись
//
// Есть два способа получить микросекундную метку времени:
//
// 1. Очень быстрый, но возвращает только младшие 32 бита
// 2. Полная 64-битная метка времени, немного медленнее
//
// Для простоты пример использует Arduino framework.
//
// Установка значения микросекундного счётчика МОЖЕТ вмешиваться
// в работу TSF логики WiFi. А может и нет — не проверял.
//
// Для использования этих счётчиков инициализация WiFi не требуется.
//
// vvb333007@gmail.com
//
#include <Arduino.h>
#include <stdint.h>

// Регистр команд:
#define WIFI_TSF_CMD_REG (volatile uint32_t *)((uintptr_t)0x6003500c)

// Команды:
#define WIFI_TSFC_LATCH 0x01 // Зафиксировать текущее значение в MMIO-регистрах 0x18 и 0x1C
#define WIFI_TSFC_SET   0x10 // Установить значение из регистров 0x10 и 0x14

// Зафиксированное 64-битное значение счётчика.
//
// После команды LATCH эти регистры содержат
// снимок текущего значения микросекундного счётчика.
//
#define WIFI_TSF_HIGH_REG (volatile uint32_t *)((uintptr_t)0x6003501c)
#define WIFI_TSF_LOW_REG  (volatile uint32_t *)((uintptr_t)0x60035018)

// Регистры установки значения.
//
// Значения, записанные сюда, используются
// для обновления текущего значения счётчика.
//
#define WIFI_TSF_SET_HIGH_REG (volatile uint32_t *)((uintptr_t)0x60035014)
#define WIFI_TSF_SET_LOW_REG  (volatile uint32_t *)((uintptr_t)0x60035010)

// Свободно работающий 32-битный счётчик микросекунд.
// Самый простой и быстрый вариант.
//
#define WIFI_MICROS_REG (volatile uint32_t *)((uintptr_t)0x60035000)

// Максимально быстрый вариант micros()
//
static inline uint32_t micros32() {
  return *WIFI_MICROS_REG;
}

// Полная 64-битная микросекундная метка времени.
//
// Создаём снимок, читаем два 32-битных значения
// и объединяем их в одно 64-битное.
//
uint64_t micros64() {

  uint32_t low, high;

  // Создать снимок
  *WIFI_TSF_CMD_REG |= WIFI_TSFC_LATCH;

  // Прочитать зафиксированные значения
  low = *WIFI_TSF_LOW_REG;
  high = *WIFI_TSF_HIGH_REG;

  // Снять фиксацию
  *WIFI_TSF_CMD_REG &= ~WIFI_TSFC_LATCH;

  // Собрать 64-битный результат
  return (((uint64_t)high) << 32) | (uint64_t)low;
}

// Установить текущее значение счётчика
//
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;
}

// Демонстрация
//
void setup() {

  Serial.begin(115200);
  Serial.printf("\r\n\r\nДемонстрация счётчиков микросекунд ESP32-S3:\r\n");
}

// 1. Раз в секунду выводить 64- и 32-битный счётчики
//    в течение 10 секунд
//
// 2. Сбросить 64-битный счётчик в ноль
//
// 3. Повторить
//
void loop() {

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

  Serial.printf("Сбрасываем 64-битный счётчик в ноль, 32-битный продолжаем...\r\n");

  setmicros64(0);
}

И, строго говоря, в коде ROM “вроде как” доступно аж четыре таких 64битных таймера с установкой\чтением и даже алармом. Но аларм нам не интересен, а вот остальные 3 счетчика оживить не удалось. Может они и не работают. Тащемта смысл в том, что константы 0x01 и 0x10 можно сдвигать влево на [0..3] бит, выбирая счетчик от 0 до 3. Но работает почему-то счетчик #0 , остальные читаются как нули.

Свидетели великого переполнения микрос/миллис перекрестились …

В смысле “чур меня” или “слава тебе ..”?

Ладно. Буду считать, что таймеры более-менее документированы. Это , на самом деле, кусок механизмов WiFi , а именно WIFI TSF.

Если счетчики просто читать, то все ок. А если писать, то теоретически может с чем-то интерферировать. Но у меня вам шпаргалка. Счетчик нумер 0 используется в режиме WIFI STA (т.е. клиент). Счетчик #1 - для точки доступа (SoftAP). А оставшиеся два - для MESH и еще чего-то. Так что всегда можно найти счетчик, который никем не занят.

Вот финальный скетч-демонстрашка, с описанием регистров. В оригинале я писал все на аншлийском, а потом переводил на русский ЧатомГПТ. Но сейчас мне это делать лень, тем более, что код выше написан по-русски, а код ниже - не особо то и отличается.

Колдунство с 4 счетчиками:

// 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 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)
#  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 all 64-bit counters once per second
//    for a 10-second interval
//
// 2. Reset the two of four 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);
}