[ESP32S3, Undocumented] Управление аналоговым обвесом ESP32-S3


Аналоговые устройства в Espressif SoC’s

Аналоговые блоки внутри чипа (такие как VCO/PLL-генераторы, SAR ADC, усилители, температурные датчики и датчики напряжения) часто требуют совсем другой модели управления, чем обычная цифровая периферия с MMIO.

В отличие от регистровой логики, где значения просто читаются и пишутся по фиксированным адресам, аналоговые узлы живут в своих доменах питания, чувствительны к шуму и требуют аккуратно синхронизированной настройки (с внутренними задержками и калибровочными циклами).

Поэтому вместо прямого memory-mapped доступа используется отдельная управляющая шина, которая позволяет изолировать аналоговую часть от цифровой и, самое главное, унифицировать управление такими разными устройствами, как VCO, PLL, миксер, SAR ADC и схемы автокалибровки.


Внутренняя I2C-шина ESP32-S3 для управления аналоговыми блоками SoC

В микроконтроллере ESP32-S3 помимо привычных периферий существует скрытая внутренняя I2C-шина, недокументированная в официальных API и используемая самим чипом для управления аналоговыми и RF-блоками.

Эта шина не выведена наружу и обслуживается ROM-функциями вроде rom_i2c_readReg, выступая связующим слоем между цифровой логикой и аналоговым миром - такими модулями, как SAR ADC, PLL, Wi-Fi PHY, калибровочные цепи и датчики.

Через неё выполняются заводские и runtime-калибровки, настройка генераторов, измерение внутренних напряжений и температур, а также управление режимами питания, что делает её ключевым, но скрытым элементом архитектуры чипа.

Несмотря на название, эта шина не является классической I2C: у неё нет внешних линий SDA/SCL, а взаимодействие реализовано через MMIO-регистры и внутреннюю маршрутизацию.

Через эту шину CPU общается с 13 устройствами. Например, частота процессора выше 160 MHz устанавливается именно через эту шину - путём записи констант в регистры аналоговых блоков.

Ниже приведён список найденных на сегодняшний день устройств на этой шине. Это не официальный список и он, вероятно, не полный.


Таблица 1 - устройства на внутренней I2C-шине

Адрес Имя от Espressif Примечания
??? ? ?
0x6d DIG_REG
0x6c ? ?
0x6b Bias (CON, CPR, CGM) Калибровка и bias
0x6a BIAS Калибровка и bias
0x69 SAR_ADC (TSENS, RTC) ADC, измерение напряжений и температур
0x68 ? ?
0x67 BBTOP (Fi, Fq, Bi, Bq) Baseband top
0x66 BBPLL (AD1–AD3, PLL) Baseband PLL
0x65 CKGEN
0x64 RFRX (LNA, MX, VGA) Радио (приём), усилители и миксер
0x63 ? ?
0x62 FPLL (VCO, BIA)
0x61 BROWNOUT_DET, ULP Brownout детектор и Ultra Low Power CPU

Хост-контроллеры скрытой I2C-подобной шины

Шина представлена двумя хост-контроллерами: HOSTID0 и HOSTID1.

Адрес устройства однозначно определяет, каким хост-контроллером будет обслуживаться запрос.

Программист должен сам определить, какой контроллер использовать. Например, устройство с адресом 0x66 обслуживается контроллером #0 - попытка использовать контроллер #1 завершится неудачей.


Таблица 2 - соответствие адресов и хост-контроллеров

Адрес Хост-контроллер
0x61 0
0x62 1
0x63 1
0x64 1
0x65 0
0x66 0
0x67 1
0x68 0
0x69 0
0x6a 0
0x6b 1
0x6c 0
0x6d 0

Каждый хост-контроллер управляется через свой MMIO-регистр:

#define I2C_RTC_CMD_HOST0  0x6000e000  // Контроллер #0
#define I2C_RTC_CMD_HOST1  0x6000e004  // Контроллер #1

Регистры идентичны, поэтому далее рассматривается только HOST0.


Формат регистра I2C_RTC_CMD_HOST0

Регистр используется для:

  • отправки команд (Reset / Read / Write)
  • чтения статуса (BUSY)
//  31...27   26     25     24   23..16   15....8   7......0
//  RESERVED  EXEC   BUSY   WR    DATA    REG_NUM   I2C_ADDR
#define I2C_RTC_EXEC    (1UL<<26)  // Выполнить команду (W)
#define I2C_RTC_BUSY    (1UL<<25)  // Контроллер занят (R)
#define I2C_RTC_WR      (1UL<<24)  // 1 = запись, 0 = чтение (W)

Поля:

  • 0..7 - I2C адрес минус 0x61
  • 8..15 - номер регистра
  • 16..23 - данные
#define I2C_RTC_DATA_S  16
#define I2C_RTC_REG_S   8
#define I2C_RTC_ADDR_S  0

Алгоритм №1: чтение/запись регистра через шину

Предположим, что мы хотим прочитать регистр #7 устройства 0x66:

  1. Определяем HOSTID контроллера, отвечающего за устройство 0x66, по таблице (Табл. 2), в нашем случае это HOSTID0,
    значит адрес управляющего регистра - 0x6000e000

  2. Формируем команду для управляющего регистра I2C:

     uint32_t cmd = I2C_RTC_EXEC | (7 << 8) | 0x66; // Данных нет, только адрес и номер регистра, WR флаг не установлен (чтение)
  1. Записываем команду в регистр 0x6000e000
  2. Крутимся в цикле и ждем, пока не погаснет бит I2C_RTC_BUSY в управляющем регистре
  3. Читаем 32 бита из управляющего регистра и сдвигаем вправо на I2C_RTC_DATA_S бит и маскируем младший байт (data &= 0xff)

При записи, мы поступаем похожим образом, но теперь в команду надо добавить еще и данные:

     uint32_t cmd = I2C_RTC_EXEC | (data << 16) | (7 << 8) | 0x66;

А шаг #5 из алгоритма выше нам тут при записи и не нужен вовсе.

## Главная магия: специальные регистры I2C_RTC_READ_EN и I2C_RTC_MST0_EN (общие на оба хост-контроллера)

```c
#define I2C_RTC_READ_EN  0x6000e044    
#define I2C_RTC_MST0_EN  0x6000e048 

Перед каждой операцией по шине i2c выставляются определенные константы в регистры 0x6000e044 и 0x6000e048:
эти регистры задают каким блокам разрешено отвечать по шине i2c. Если константу не выставить, то ни запись ни чтение
успехом не завершатся.

Существующий код от Espressif всегда пишет регистр MST0_EN первым, а регистр READ_EN - вторым

Значения констант, которые пишутся в эти регистры зашиты в ROM ESP32-S3 и представляют из себя два массива
по 13 элементов каждый. Элемент 0 относится к устройству с адресом 0x62, элемент 1 - у устройству с адресом 0x63 и так далее,
всего 13 устройств. Забудем пока про устройство 0x61 - тут не используется и доступ к нему осуществляется немного иначе.

Перед каждой операцией необходимо записывать значения в оба регистра.


Константы из ROM

Таблица 3 - константы для регистра MST0_EN

// Константы для регистра MST0_EN. Пропатчено bootloaderом так, что каждая константа равна 0x1fe00
// Данный массив тут приведен лишь для истории. В нем оказался какой-то баг и Espressif заменила 
// константы ниже на одну константу 0x1fe00 (включить все модули)
//
uint32_t mst0_magic[13] = {
    [0] = 0x00008000,
    [1] = 0x00000080,
    [2] = 0x00000020,
    [3] = 0x00000010,
    [4] = 0x00002000,
    [5] = 0x00004000,
    [6] = 0x00000040,
    [7] = 0x00000800,
    [8] = 0x00010000,
    [9] = 0x00000200,
    [10]= 0x00000100,
    [11]= 0x00000000,
    [12]= 0x00000400,
};

Таблица 4 - константы для регистра READ_EN

// Константы для регистра READ_EN.
// Активный бит - нулевой
//
uint32_t read_magic[13] = {
    [0] = ~0x00400000,
    [1] = ~0x00000800,
    [2] = ~0x00000200,
    [3] = ~0x00000100,
    [4] = ~0x00010000,
    [5] = ~0x00020000,
    [6] = ~0x00000400,
    [7] = ~0x00008000,
    [8] = ~0x00040000,
    [9] = ~0x00002000,
    [10]= ~0x00001000,
    [11]= ~0x00000000,
    [12]= ~0x00004000,
};

Особенности

READ_EN

  • значения используются напрямую из ROM
  • биты инвертированы (0 = включено)
  • запись выполняется напрямую:
*READ_EN = read_magic[i2c_addr - 0x62];

MST0_EN

  • переопределяется bootloader’ом
  • всегда устанавливается в 0x1fe00
#define I2C_RTC_MST0_MAGIC_VAL  0x1fe00
#define I2C_RTC_MST0_MAGIC_MASK 0xfffe000f

*MST_EN = (*MST_EN & 0xfffe000f) | 0x1fe00;

Алгоритм работы с READ_EN и MST0_EN

ВАЖНО: Данная последовательность действий необходимая перед любой операцией чтения\записи по шине I2C:

  1. По I2C адресу устройства выбирается константа для регистра READ_EN (см. Табл. 4)
  2. Для регистра MST0_EN константа всегда одна и та же: 0x1fe00
  3. Записываем в MST0_EN константу найденную на шаге 2
  4. Записываем в READ_EN константу найденную на шаге 1
  5. Переходим к алгоритму “Алгоритм#1 чтения/записи регистра переферии через шину i2c”

Сброс (master reset) хост-контроллера

В ESP32-S3 ROM есть код, который выполняет master reset обоиъ хост-контроллеров следующим образом:

void regi2c_master_reset() {

  volatile uint32_t *r = (volatile uint32_t *)I2C_RTC_CMD_HOST0;

  for (int i = 0; i < 2; i++) {
    if (*r & I2C_RTC_EXEC) {
      *r = 0;
      while (*r & I2C_RTC_BUSY) {
        asm volatile ("memw" ::: "memory");
      }
    }
    r++;
  }
}

Судя по коду, сбрасывается контроллер, зависший в состоянии EXEC.

PS:
Продолжение следует. Будем читать и писать регистры и выводитьинтересные и не очень циферки.

PPS:
Данный папирус может содержать ошибки и опечатки. Сорян, исправим.
Когда в этом топике появится код работающий, тогда уж утвердим статус.
Все вышенаписанное - про ESP32-S3. На других процессорах все ровно то же самое, просто адреса регистров другие.
Информация получена анализом кода от Espressif: ESP32-S3 ROM, который, о чудо, доступен в виде .elf файла со всеми именами функций и глобальных переменных.

3 лайка

А почему в отвлеченных? Эту серию тем в Аппаратные надо!

Практическое применение всего этого хотелось бы понять и увидеть.

1 лайк

Скоро чонть напишу. Или не скоро.

Применение очень простое - управление 13-ю устройствами на шине :). Меня интересует, как перенастроить WiFi , чтобы превратить его в лабораторный генератор.

ЗЫЖ

Я - старый гвардеец, у нас был принят лозунг “information ought to be free“. Хочу дополнить esp32s3 Technical Reference Manual той информацией, которую разработчик утаил.

Спой

Зачем-то они еще повырезали всю инфу про Debug Assist Peripheral. Теперь в документации - все регистры reserved. Врод, как нету его. А он есть. Умеет за памятью следить и сохранять регистры при креше фатальном. Но засекретили.

лер

Еще забавный прикол обнаружился, но это ,скорее, про антиотладку. На ESP32 (а и подозреваю, даже на других принципиально архитектурах тоже прокатит) можно объехать на козе Debug Data Watchpoints. Аппаратный. Т.е. берете вы свой любимый JTAG/OpenOCD/GDB ставите аппаратную точку останова по чтению\записи данных, а она не срабатывает. В ESP32-S3 ROM зашита функция dma_memcpy(). Копирует из памяти в память с использованием DMA. Как результат - процессор не в курсе, что кто-то прочитал какую-то память и брейкпоинт не срабатывает

1 лайк

@vvb333007
Я бы посоветовал создать отдельную тему, куда поместить ссылки на все ваши изыскания по железу. Информация реально полезная, но раскидана по куче не связанных тем. Было бы классно если бы все ссылки были в одном месте

7 лайков

Я думаю, я просто размещу в каждой такой теме ссылку на next и prev :). Ну, или когда информации качественной наберется какая-то критическая масса, тогда запилю отдельный топик со ссылками.

Жалко, что нельзя редактировать оригинальный пост теперь. А еще я не знаю, как зачеркнутым написать что-то.

Ошибка вскрылась. В документе выше. Из-за ошибки в ROM.

Правильно так:

0..7 - I2C адрес минус 0x61

В коде у них ошибка. Они вычитают 0x62. Но я нашел функции, которые передают аргумент 0x61. После вычитания получается 0xff, и совершенно случайно, в результате, получаются нужные данные, чтобы устройство 0x61 работало. Не буду им об этом сообщать :frowning:

Спойлер

Я нашел один 0day очень многообещающий. Если ESP32 используется как точка доступа, а у вас есть клиентский password от нее, то вы можете вызвать remote code execution. Ну, это если предельно упрощать. Понятное дело, придется проявить смекалку, но все же.. У них совершенно тупой race cond в коде. Связанный с одновременной записью в некий список, я умолчу, какой. Там нет синхронизации и если время коннекта одного клиента совпадет со временем дисконнекта другого, то список будет порушен. В этом списке лежат коллбэки, которые , соответственно, можно перезаписать , чем надо.

Исправил.

1 лайк

Немного о контроле внутренних напряжений аналоговых блоков

Семь аналоговых устройств на шине I2C позволяют замерять напряжение на тестовых точках внутри SoC.

Каждое аналоговое устройство содержит один или несколько логических блоков.
Например, устройство с адресом 0x64 (RFRX) включает три блока:

  • LNA (Low Noise Amplifier)
  • VGA (Variable Gain Amplifier)
  • MX (mixer)

и на каждом блоке можно производить измерения в нескольких тестовых точках.


Тестовые точки

Каждый блок имеет 4 тестовых точки для измерения напряжений.

Эти напряжения:

  • проверяются при обычной работе
  • используются PHY для подстройки параметров

Цель — удерживать измеренные значения близко к эталонным, чтобы ничего не плыло. Гигагерцы же!


Эталонные значения

Эталонные напряжения:

  • сведены в таблицу
  • зашиты в ROM

Использование ADC

Измерения выполняются через ADC2 (внутреннее название — SAR2_ADC).
использовать второй ADC на чипах Espressif с WiFi нельзя - расползется вся автоподстройка WiFi/BT/BLE

Чип сам маршрутизирует сигнал до ADC от своих кишок.


Регистры управления (регистры аналоговых устройств на шине)

У каждого аналогового устройства есть один или несколько 8-битных регистров.

Они используются для:

  • включения режима измерения (ME — Measurement Enable)
  • выбора тестовой точки (TP — Test Point, 0..3)

Таблица устройств и регистров

Адрес  Устройство  Регистр  Битовые поля  Тип   Описание битов
                            номера битов
---------------------------------------------------------------------------
0x62   FPLL        8        ----32--      ME    2=FPLL_VCO, 3=FPLL_BIA
                   8        ------10      TP    TestPoint [0..3]

0x64   RFRX        0        ---432--      ME    2=VGA, 3=LNA, 4=MX
                   1        -----21-      TP    TestPoint [0..3]

0x65   CKGEN       0        7-------      ME    7=CKGEN_PK
                   2        ------10      TP    TestPoint [0..3]

0x66   BBPLL       10       76--3---      ME    3=PLL, 6=AD1, 7=AD2, 76=AD3
                   10       --54----      TP    TestPoint [0..3]

0x67   BTOP        0        --5432--      ME    2=Fi, 3=Fq, 4=Bi, 5=Bq
                   0        ------10      TP    TestPoint [0..3]

0x69   SAR_ADC     7        ----32--      ME    2=TSEN, 3=RTC
                            ------10      TP    TestPoint [0..3]

0x6A   BIAS        4        ------1-      ME    1=CON
                   2        ------10      ME    0=CPR, 1=CGM
                   7        ----32--      TP    TestPoint [0..3]

Усреднение измерений

Измерение выполняется многократно:

  • ROM-функция делает 4 замера и усредняет
  • вызывающая функция делает это 8 раз

Итого 32 измерения на один результат


Алгоритм измерения

  1. Принудительно устанавливается максимальное усиление приёма
  2. Конфигурируется SAR2_ADC (детали — позже)
  3. В регистре ME включается нужный блок (см. алгоритм записи в регистр I2C в начале темы)
  4. В регистре TP выбирается тестовая точка (0..3)
  5. Выполняется 32 чтения SAR2_ADC с последующим усреднением (каждое измерение — 10 бит)

Обработка результата

Полученное значение:

  • нормируется относительно референса
  • сравнивается с эталонным

Пример вывода напряжений и референсов в консоль (устройство Baseband Top, блок Fi ):

btop fi 0 Voltage: 092, Expected: 089, Absolute error: 3
btop fi 1 Voltage: 093, Expected: 092, Absolute error: 1
btop fi 2 Voltage: 091, Expected: 090, Absolute error: 1
btop fi 3 Voltage: 090, Expected: 090, Absolute error: 0

Динамика подстройки

Софт подгоняет параметры примерно раз в ~2 секунды.
при включении передачи происходит скачок
затем система автоматически возвращает значения к норме