Аналоговые устройства в 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 адрес минус 0x618..15- номер регистра16..23- данные
#define I2C_RTC_DATA_S 16
#define I2C_RTC_REG_S 8
#define I2C_RTC_ADDR_S 0
Алгоритм №1: чтение/запись регистра через шину
Предположим, что мы хотим прочитать регистр #7 устройства 0x66:
-
Определяем HOSTID контроллера, отвечающего за устройство 0x66, по таблице (Табл. 2), в нашем случае это HOSTID0,
значит адрес управляющего регистра - 0x6000e000 -
Формируем команду для управляющего регистра I2C:
uint32_t cmd = I2C_RTC_EXEC | (7 << 8) | 0x66; // Данных нет, только адрес и номер регистра, WR флаг не установлен (чтение)
- Записываем команду в регистр 0x6000e000
- Крутимся в цикле и ждем, пока не погаснет бит I2C_RTC_BUSY в управляющем регистре
- Читаем 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:
- По I2C адресу устройства выбирается константа для регистра READ_EN (см. Табл. 4)
- Для регистра MST0_EN константа всегда одна и та же: 0x1fe00
- Записываем в MST0_EN константу найденную на шаге 2
- Записываем в READ_EN константу найденную на шаге 1
- Переходим к алгоритму “Алгоритм#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 файла со всеми именами функций и глобальных переменных.