Краткое описание микроконтроллеров ESP32 и модулей на их основе по-русски
29.01.21
Введение
Довольно давно присматривался к микроконтроллерам (МК) от конторы «Espressif Systems». Несколько лет назад их камни ESP8266 уже взбудоражили радиолюбительский сегмент, и это было немудрено – всего за сотню рублей пользователю предлагался модуль, содержащий на борту полноценный Wi-Fi, а также SPI, UART, I2S и АЦП. Немаловажным здесь являлось то, что ты покупал именно готовый модуль с камнем и всем полагающимся обвесом (включая WiFi антенну), поэтому тебе не нужно было думать, как изготовить печатную плату для чипа в мелком корпусе, как его потом туда впаять, как развести высокочастотную часть и т.д. Нет, ты получал уже собранное изделие со здоровенными контактными площадками, изготовить под которые печатку не составляло никакого труда (да и просто припаять к ним провода было тоже несложно). Однако ESP8266 как-то прошли мимо меня стороной – и времени для их освоения особо не было, и со всеми необходимыми задачами вполне справлялись старые ламповые AVR-ки. Но тут «Espressif Systems» выпустили следующее поколение своих камней (ESP32), и к ним я уже не смог остаться равнодушным. В новые микроконтроллеры китайцы запихали вообще всё, что только можно: Wi-Fi, Bluetooth (в том числе BLE), четыре 64-битных таймера, два многоканальных АЦП, два ЦАПа, драйвер сенсорных кнопок, интерфейсы SPI, I2C, I2S, UART (да не по одной штуке), контроллеры Enternet’а, CAN’а, SD-карт, ИК-датчиков, многоканальные формирователи ШИМ-сигналов для управления двигателями и светодиодами, аппаратные ускорители алгоритмов шифрования AES, SHA, RSA и ECC, и даже датчик Холла (датчик Холла, Карл!). Плюс ко всему всё это, как и раньше, можно купить в виде готового экранированного модуля, стоимость которого лежит в районе 250р. (что не сильно дороже 328-й Меги в корпусе TQFP). То есть ESP32 – это не те же самые AVR-ки, только пошустрее и подешевле, а вообще абсолютно другой уровень камня, имеющий на порядки больше фарша и работающий на сотнях мегагерц. Оставаться в стороне от такого чуда мне не хотелось, поэтому пришлось изучать даташыты.
В данной заметке я хотел бы перечислить поподробнее, что́ есть на борту микроконтроллеров ESP32 и какими возможностями они обладают, дабы понимать, куда можно приспособить данные камни, а где их ресурсов будет недостаточно. При этом нужно помнить, что на самом деле большинство любителей интересует не сам китайский чудо-чип, а модули, созданные на его базе, поэтому правильнее было бы рассматривать характеристики именно модулей. Однако чаще всего параметры микроконтроллера и готовой платы с ним будут совпадать, поскольку камень является «сердцем» подобного изделия. Поэтому для начала я предлагаю всё же разобраться, какие вообще чипы могут нам встретиться в модулях ESP32 и какие они имеют характеристики. Ну а уж пото́м можно будет перейти к конкретным Wi-Fi-модулям и сравнить, насколько их возможности отличаются от возможностей «голых» МК ESP32.
Микроконтроллеры, применяемые в модулях ESP32
Любой модуль ESP32 – это печатная плата, на которую установлен микроконтроллер ESP с необходимым обвесом, тактовый генератор, ВЧ-тракт и FLASH-память для записи прошивки:
(в некоторых модулях сюда может добавиться еще и дополнительное ОЗУ). Поэтому для того, чтобы представлять себе возможности готовых модулей, неплохо бы знать характеристики камней, которые туда устанавливаются. До начала 2020г. все модули типа «ESP32» создавались на базе трех чипов, отличающихся друг от друга лишь количеством ядер процессора, производительностью и максимальной тактовой частотой:
• ESP32-D0WDQ6: 2 ядра / 600MIPS / 240МГц;
• ESP32-D0WD 2 ядра / 600MIPS / 240МГц;
• ESP32-S0WD 1 ядро / 200MIPS / 160МГц.
Однако, в январе 2020г. появились микроконтроллеры «ECO V3», у которых были исправлены некоторые баги, встречающиеся в более ранних версиях камней. И в настоящее время (январь 2021г.) количество моделей чипов, которые могут нам встретиться в модулях типа «ESP32», выросло до пяти, дополнившись кирпичами
• ESP32-D0WDQ6-V3: 2 ядра / 600MIPS / 240МГц;
• ESP32-D0WD-V3 2 ядра / 600MIPS / 240МГц.
(в названиях микроконтроллеров версии «ECO V3» в конце присутствует индекс «-V3»).
И, вроде бы, пять моделей камней – это уже́ как-то до хера. Однако пугаться такого длинного перечня не нужно, ибо его можно ужать всего до двух позиций. Начнем с того, что микроконтроллеры версии «ECO V3» почти ничем не отличаются от своих предшественников («ECO V1»). Это всё те же камни, в тех же корпусах, с той же цоколевкой и с той же периферией, только с устраненными косяками. Посмотрите полный список дополнений и исправленных багов в ECO V3 – ничего революционного там нет. Поэтому для радиолюбительства вполне допустимо считать камни версии «ECO V3» и «ECO V1» равнозначными, т.е. две последних модели в нашем списке можно вычеркнуть (они просто превратятся в своих предшественников). В итоге мы вернулись к первоначальному перечню из трех позиций, и вот здесь все три модели уже́ имеют существенные отличия. Однако, камень ESP32-D0WDQ6 отличается от собратьев «лишь» более могучим корпусом (QFN 6x6mm вместо QFN 5x5mm) и другим расположением выводов. Понятно, что для разработчика печатной платы под «голый» чип ESP32 эти отличия являются критически важными, однако, для пользователя готового модуля это абсолютно не принципиально (на габаритах и расположении выводов именно модуля данный факт никак не сказывается). Таким образом, можно считать, что
модули «ESP32» созданы на базе всего двух типов микроконтроллеров:
Модель МК |
Кол-во ядер |
Максимальная тактовая частота |
Максимальная производительность |
ESP32-D0WDx |
2 |
240МГц |
600MIPS |
ESP32-S0WD |
1 |
160МГц |
200MIPS |
отличающихся исключительно количеством ядер, максимальной тактовой частотой и, как следствие, производительностью.
Всё остальное у интересующих нас камней абсолютно одинаковое. Функциональный состав данных чипов показан на рисунке ниже (слева):
И вы только посмотрите, насколько цифровой фарш у ESP32 богаче того же ESP8266 (который, как бы, совсем убогим тоже не назовешь)! Один только Bluetooth чего сто́ит, а ведь есть еще контроллеры Ethernet, CAN, сенсорных кнопок и т.д. Со слов производителя, такое разнообразие цифровых узлов стало возможным исключительно благодаря переходу на более козы́рную технологию 40нм. А еще данный переход позволил снизить общее энергопотребление чипа, что весьма актуально, ведь камни ESP32 изначально затачивались для работы в устройствах с батарейным или аккумуляторным питанием. В свете этого одноядерный чип ESP32-S0WD выглядит уже́ не так убого – пусть он и не такой шустрый, как камни с двумя ядрами, зато в ряде случаев энергопотребление у него может быть ниже на 20%. Поэтому в тех приложениях, где основное внимание уделяется энергосбережению, а быстродействие особо не нужно, рекомендуется использовать именно ESP32-S0WD.
Как следует из последнего рисунка, микроконтроллеры ESP32 состоят из высокочастотной (выделена голубым) и цифровой частей. При том в цифровой части есть свои большие куски:
• основной процессор;
• встроенная память;
• контроллеры беспроводных интерфейсов (Wi-Fi и Bluetooth);
• цифровая периферия (UART, Ethernet, SPI, I2C и т.д.);
• модуль RTC, предназначенный для работы камня в режимах с низким энергопотреблением, общего управления питанием, а также включающий в себя всю аналоговую периферию («медленные» контроллеры АЦП, ЦАП, драйвер сенсорных датчиков, осциллятор на 32кГц);
• аппаратные блоки шифрования данных.
Если расписать эти куски более подробно, то в сумме на борту чипов ESP32-D0WDx и ESP32-S0WD присутствует:
• процессор Xtensa LX6 (32бит);
• малопотребляющий сопроцессор ULP;
• встроенная память: основное ОЗУ (520кбайт), RTC-ОЗУ (16кбайт) и еще по-мелочи;
• возможность подключения внешней памяти (FLASH и SRAM);
• контроллер прямого доступа к памяти (DMA) для 13 периферийных устройств;
• встроенный генератор на 8МГц;
• встроенный генератор на 150кГц с низким энергопотреблением;
• возможность подключения внешнего кварцевого резонатора на 2МГц…40МГц;
• возможность подключения внешнего кварцевого резонатора на 32,768кГц для RTC;
• четыре 64-битных таймера общего назначения с 16-битным делителем;
• один дополнительный 48-битный таймер в модуле RTC;
• три сторожевых таймера (в тои числе сторожевой таймер модуля RTC);
• 34 порта ввода-вывода;
• возможность подключения большей части периферии к любому порту ввода-вывода;
• Wi-Fi (802.11 b/g/n);
• Bluetooth (v4.2 BR/EDR, а также BLE);
• АЦП с разрядностью 12 бит (число каналов – до 18);
• 2 ЦАП’а с разрядностью 8 бит;
• 10 каналов для подключения емкостных датчиков (например, сенсорных кнопок);
• 3x UART;
• 4x SPI;
• 2x I2C;
• 2x I2S;
• 1x ведущий (Host) контроллер SD/eMMC/SDIO;
• 1x ведомый (Slave) контроллер SDIO/SPI;
• 1x контроллер Ethernet MAC с поддержкой IEEE 1588;
• 1x контроллер CAN 2.0;
• порт для подключения ИК-датчиков;
• 6 каналов ШИМ для управления электродвигателями (при помощи H-моста);
• 16 каналов ШИМ для управления яркостью светодиодов (или для других целей);
• датчик Холла;
• аппаратные ускорители AES, SHA, RSA и ECC.
Согласитесь, перечень довольно внушителен, и в нем разберешься не сразу. Поэтому для облегчения знакомства с семейством ESP32 коротенько разберем перечисленные цифровые узлы. При этом постараемся рассмотреть их более подробно, чем это обычно делается в Интернете (типа «Вот есть UART/SPI/I2C…» и всё). Но с другой стороны, пересказывать даташыты целиком здесь тоже не имеет никакого смысла, ибо получится долго и нудно. Поэтому будем выдергивать из разных источников только самое главное и сводить всё в один документ. Этим убьется сразу два зайца – с одной стороны, читатель более тесно познакомится с чипами ESP32, а с другой – вся информация будет находиться в одном(!) месте. Второй заяц может показаться вам незначительным, но вот лично меня дико бесит выковыривать инфу из десятка даташытов, когда всё можно было бы свести в один файл. К тому же, в случае ESP32 ситуация осложняется тем, что этот камень является чисто китайским изобретением, а это обычно означает, что чип будет дешевым, но документация на него покажется сущим адом для разработчика – информация по какому-либо вопросу может быть запросто раскидана по десяти-пятнадцати местам одного документа (в лучших китайских традициях), а о чем-то вы вообще из официального даташыта никогда не узнаете. Поэтому для полноты картины приходится не только прочесывать всю документацию целиком (которая еще и разбита на несколько файлов), но и шерстить кучу статей/форумов в Интернете. Попробуйте, например, детально разобраться с тем, что происходит внутри камня в различных энергосберегающих режимах, используя исключительно официальные доки – у меня это не получилось. В связи с этим, сведение всей важной информации по отдельным узлам ESP32 в одно место, на мой взгляд, является крайне актуальной задачей. Поэтому приступим к работе, и начнем мы сразу с самого главного.
Процессор
Каждый чип ESP32 построен по Гарвардской архитектуре на базе 32-битного процессора Xtensa LX6. Данный проц обеспечивает поддержку таких достаточно лакомых фишек, как:
• 16/24 битный набор инструкций, обеспечивающий высокую «плотность» программного кода;
• вычисления с плавающей точкой;
• «DSP-инструкции», включающие в себя такие козы́рные операции, как 32-битное умножение, 32-битное деление, 40-битное умножение с накоплением;
• поддержка 52 прерываний от 71 различных периферийных источников (по 26 прерываний на каждое ядро);
• возможность отладки по «JTAG».
Все периферийные устройства и память чипа (как внутренняя, так и внешняя), висят на шине данных и/или шине команд основного процессора. В двухъядерных версиях проца адресное пространство одного ядра практически полностью совпадает с адресным пространством второго ядра, т.е. является симметричным. Отметим, что я́дра процессора называются «PRO_CPU» и «APP_CPU» (сокращения от «Protocol» и «Application») и, вообще говоря, не являются абсолютно равнозначными. Однако в большинстве случаев оба ядра́ можно считать взаимозаменяемыми.
Система тактирования
В микроконтроллерах ESP32 основной процессор, система RTC и периферия могут тактироваться от разных источников тактового сигнала. В качестве оных могут выступать:
• внешний кварцевый резонатор на 2МГц…40МГц (сигнал «XTL_CLK»). Подключается к выводам «XTAL_P» и «XTAL_N» чипа. Обратите внимание на то, что для нормальной работы Wi-Fi и Bluetooth частота кварца должна быть равна строго 40МГц±10ppm;
• внешний кварцевый резонатор на 32,768кГц (сигнал «XTL32K_CLK»). Подключается к выводам «32X_P» и «32X_N» чипа;
• внутренний генератор на 8МГц с возможностью подстройки/калибровки (сигнал «RTC8M_CLK»). Присутствует на борту ESP32 с рождения;
• внутренний генератор на 150кГц с низким энергопотреблением и возможностью подстройки/калибровки (сигнал «RTC_CLK»). Присутствует на борту ESP32 с рождения;
• внутренний генератор с ФАПЧ, формирующий синхронизирующие импульсы с малым джиттером для периферийных устройств шины I2S (сигнал «APLL_CLK»). Частота синхроимпульсов составляет 16МГц…128МГц. Присутствует на борту ESP32 с рождения;
Это, так сказать, «самостоятельные» источники, являющиеся полноценными генераторами тактового сигнала (т.е. формирующими данный сигнал «из ничего»). Есть еще умножитель и делитель, которые увеличивают/уменьшают частоту импульсов, подаваемых на их вход:
• умножитель частоты на базе генератора с ФАПЧ (сигнал «PLL_CLK»). Формирует высокочастотные импульсы с частотой 320МГц или 480МГц из сигнала с внешнего кварца (который на 2…40МГц);
• делитель частоты внутреннего генератора 8МГц на 256 (сигнал «RTC8M_D256_CLK»). Формирует НЧ импульсы с частотой 31,25кГц.
Перечисленные семь сигналов являются базовыми. Тактовые импульсы для конкретного узла камня формируются из них при помощи операций деления (либо базовые импульсы подаются на узел тупо напрямую):
Выбор источника тактирования, а также коэффициента деления частоты базового сигнала производится при помощи соответствующих регистров и констант. Для всех узлов микроконтроллера есть возможность выбора тактовой частоты как минимум из двух источников. Вообще, данная тема весьма интересна (по крайней мере, для меня), и тут есть еще много чего сказать. Однако подробное рассмотрение механизма тактирования камней ESP32 требует как минимум отдельной заметки, для данной же обзорной статьи приведенных общих сведений, думаю, вполне достаточно.
Прерывания
В микропроцессорной технике прерыванием называется событие, нарушающее последовательное исполнение команд камнем и заставляющее его перепрыгнуть на другой кусок программы. Источником такого события может быть либо внутренняя периферия чипа, либо внешние сигналы, а о его наступлении процессор узнаёт через линии запроса на прерывание (ЛЗП). Прерывания позволяют основному процу быстро реагировать на события, происходящие внутри камня или во внешнем мире, поэтому чем больше прерываний понимает кирпич, тем обычно лучше для пользователя, и, на мой взгляд, с данной темой у ESP32 всё более чем хорошо. Однако, в китайских камнях система прерываний устроена несколько иначе, чем это было в AVR и STM, поэтому для лучшего понимания имеет смысл рассмотреть ее более подробно.
Начнем с того, что рассматриваемые микроконтроллеры имеют аж 71 линию запроса на прерывание – это является прямым следствием богатства их внутреннего мира. Любой периферийный модуль (включая таймеры и порты ввода/вывода) имеет хотя бы одну собственную линию запроса, и теоретически может достучаться по ней до процессора. Вот только проц в состоянии обработать не более 26 прерываний от периферии (на каждое ядро), что, безусловно, сначала может показаться лютым убожеством. Однако, в случае ESP32 даже этого количества вполне достаточно. Дело в том, что в рассматриваемых камнях перечень прерываний, на которые сможет реагировать процессор, не является незыблемым, как это было в AVR и STM32 – здесь пользователь формирует данный список собственноручно. При этом каждое из 26 процессорных прерываний может быть соединено практически с любым узлом внутренней периферии (включая таймеры и GPIO) при помощи соответствующей ЛЗП. Более того – ESP32 позволяет вешать на одно прерывание процессора линии запроса от нескольких периферийных устройств, и тогда любое из них сможет растормошить камень. В связи с этим мне кажется, что 26 прерываний на одно ядро – это более чем достаточно, поскольку в реальных проектах обычно не используются прерывания от вообще всей периферии, ну а если такое и потребуется, то можно будет группировать несколько ЛЗП в один узел.
За то, какие именно периферийные линии запроса будут подключены к процессорным прерываниям, в ESP32 отвечает специальная матрица (желто-зеленая херня на рисунке ниже):
Физически данная матрица, судя по всему, представляет из себя хитрожопый многоканальный коммутатор, который имеет 71 вход и две группы по 32 выхода (26+6), да при этом еще умеет соединять свои входные и выходные линии в любой произвольной комбинации. Верхняя по схеме группа выходов (выделена желтым цветом) отвечает за формирование процессорных прерываний для ядра «PRO_CPU», нижняя (зеленая) – за прерывания для ядра «APP_CPU». Чтобы подключить определенную линию запроса на прерывание к процессору (т.е. сделать так, чтобы соответствующая периферия могла пинать проц), коммутатор просто должен соединить вход, на который приходит эта ЛЗП, с одним из своих выходов. То, к какому именно выходу будет подключен каждый входной сигнал коммутатора, определяется содержимым специальных регистров конфигурации. Ну и еще матрица прерываний в ESP32 имеет пару дополнительных бонусов: во-первых, она поддерживает работу с маскированием, а во-вторых – позволяет напрямую прочитать текущее состояние всех периферийных ЛЗП при помощи регистров статуса.
Все источники прерываний от периферии, доступные в ESP32, перечислены в таблице ниже:
По центру данной таблицы (столбец «Peripheral Interrupt Source => Name») располагается перечень периферийных устройств, которые могут генерить запрос на прерывание. Нетрудно видеть, что сюда входит:
• вся «основная» цифровая периферия: WiFi, Bluetooth, 2xSDIO Slave, SDIO Host, 4xSPI, 2xI2S, 3xUART, Ethernet, 4xMCPWM (почему 4, а не 2, я пока не понял), LED-PWM, CAN, контроллер ИК-датчиков, 2xI2C, DMA-контроллеры UART и SPI;
• прочая цифровая периферия: контроллер однократно программируемой памяти (eFuse), аппаратный ускоритель RSA, устройство управления памятью (MMU), устройство защиты памяти (MPU), кэш (CACHE);
• периферия RTC-системы (RTC_CORE; видимо, именно по этой линии дают о себе знать АЦП, ЦАП, контроллер емкостных датчиков и т.д.);
• все таймеры общего назначения, сторожевые таймеры и счетчики импульсов (PCNT);
• порты ввода/вывода (GPIO), использующиеся для формирования внешних прерываний;
• прочее,
т.е. вообще вся периферия, которая известна рядовому пользователю. Отметим, что некоторые названия источников прерываний в данном столбце оканчивается на «_NMI» (Non-Maskable Interrupt) – судя по всему, это значит, что прерывание является немаскируемым (см. далее). Всё, что находится слева от центрального столбца «Name», относится к ядру «PRO_CPU», а всё, что справа – к ядру «APP_CPU». Цифры от 0 до 68 являются условными порядковыми номерами источников прерываний (где они используются, я так и не нашел). Обратите внимание на то, что прерывания, формируемые при помощи портов ввода/вывода (стро́ки с номерами 22 и 23 в таблице), могут быть подключены только к одному ядру процессора*. Таким образом, каждому ядру можно поставить в соответствие только 69 линий запроса на прерывание из имеющихся 71.
* – здесь имеется ввиду, что прерывание от каждого отдельного GPIO (например, от GPIO25) может быть подключено только к одному ядру. Прерывания от любого другого порта можно направить на другое ядро (а можно и на то же самое). Иными словами, каждому GPIO может быть поставлено в соответствии либо «PRO_CPU», либо «APP_CPU», однако, на подключение других портов это никак не повлияет.
Далее в столбцах «Status Register» идут регистры статуса
▪ «PRO_INTR_STATUS_REG_0_REG»
▪ «PRO_INTR_STATUS_REG_1_REG»
▪ «PRO_INTR_STATUS_REG_2_REG»
и
▪ «APP_INTR_STATUS_REG_0_REG»
▪ «APP_INTR_STATUS_REG_1_REG»
▪ «APP_INTR_STATUS_REG_2_REG»,
которые позволяют узнать состояние сигналов на входах коммутаторов «PRO_CPU» и «APP_CPU» соответственно (см. предпоследнюю картинку). Данные регистры доступны только для чтения, соответствие их битов номерам источников прерываний указано в таблице. При этом в документации нигде ничего не сказано насчет состояния этих битов, но я думаю, что если на какой-либо линии висит запрос на прерывание, соответствующий бит в регистре статуса будет равен единице.
Ну и самое важное, что есть в рассматриваемой таблице – это регистры конфигурации матрицы прерываний (столбцы «Peripheral Interrupt Configuration Register»). Именно их содержимое определяет, к какому выходу коммутаторов «PRO_CPU» и «APP_CPU» будет подключена та или иная периферийная линия запроса на прерывание. У каждой ЛЗП есть свой собственный конфигурационный регистр, в который записывается номер выхода, к которому необходимо ее подключить. Причем, поскольку линии запроса практически всей периферии (за исключением GPIO) могут быть подключены как к ядру «PRO_CPU», так и к ядру «APP_CPU», каждая ЛЗП имеет целых два регистра конфигурации:
▪ «PRO_(название_прерывания)_MAP_REG»
и
▪ «APP_(название_прерывания)_MAP_REG».
Регистры первой группы отвечают за то, какие прерывания будут пинать ядро «PRO_CPU», а регистры второй группы – за ядро «APP_CPU». Отметим, что один и тот же источник прерывания может быть сопоставлен сразу обоим ядрам.
Номера́ выходов, которые записываются в регистры конфигурации – это, по сути, номера́ прерываний процессора. Напомню, что ядрам камня доступно по 32 прерывания, каждое из которых имеет номер в соответствии с данной таблицей:
И если в конфигурационный регистр «PRO_x_MAP_REG» (или «APP_x_MAP_REG») записать номер из данной таблицы, у которого в столбце «Category» значится «Peripheral», то процессор начнет реагировать на прерывание от соответствующего источника. В противном случае (т.е. если «Category» = «Internal») данный источник считается отключенным от проца (почему – объясню чуть ниже). Кроме того, имеется возможность объединения на одном прерывании нескольких ЛЗП – в этом случае процессор будет реагировать на каждое из них. Для этого необходимо в конфигурационные регистры разных источников записать один и тот же номер (правда, нужно помнить, что обработка такого «объединенного» прерывания занимает больше времени). Обратите внимание на то, что после сброса МК во всех регистрах конфигурации «PRO_x_MAP_REG» и «APP_x_MAP_REG» содержится значение «16», так что по умолчанию все периферийные прерывания от процессора отключены. Всего на каждое ядро приходится по 69 конфигурационных регистров (по количеству входов соответствующих коммутаторов).
Очевидно, что таблица прерываний у каждого ядра будет своя. Конечно, структура у этих таблиц будет одинаковой (суммарно 32 прерывания, из них 26 периферийных и 6 внутренних), но соответствие этих прерываний периферийным ЛЗП может быть уникально для каждого проекта. Кстати, по поводу внутренних прерываний. Каждое ядро умеет не только реагировать на прерывания от периферии, но и само генерить их. Источниками прерываний в ядре являются внутренние таймеры, некий монитор производительности (Performance Monitor) и сама программа (т.н. программные прерывания). Всего эти источники формируют шесть внутренних прерываний с номерами 6, 7, 11, 15, 16 и 29 (см. таблицу выше), которые не могут быть вынесены за пределы данного ядра и служат исключительно для его собственных нужд. В связи с этим, коммутаторы «PRO_CPU» и «APP_CPU» то ли вообще не имеют выходов с вышеуказанными номерами, то ли они просто не подключены к соответствующим ядрам. Но в любом случае – если подключить какой-либо вход коммутатора к выходу 6, 7, 11, 15, 16 или 29, этот вход физически не сможет добраться до процессора, поэтому соответствующую линию запроса на прерывание можно считать отключенной. При этом конкретный номер из шести указанных не играет абсолютно никакого значения – для выключения ЛПЗ можно использовать любой из них. Ну и теперь, думаю, понятно, почему на функциональной схеме, приведенной выше, процессорные прерывания объединены в группы по 26 и по 6. Кстати, на данной схеме у «шин», относящихся к регистрам, наклонная линия с цифрой – это не разрядность шины, а просто указатель на то, сколько регистров содержится в этой группе.
Обратите внимание на то, что в таблице прерываний указана не только категория каждого прерывания (периферийное/внутреннее), но и его тип (столбец «Type»), а также приоритет (столбец «Priority Level»). И надо сказать, что в соответствующей главе официальной документации (глава 3 «Interrupt Matrix») про тип и приоритет прерываний ничего не сказано. Однако, данные моменты являются общими для микропроцессорной техники, поэтому кой-чего про них сказать можно. Как видно из последней таблицы, запросы на прерывание в камнях ESP32 могут формироваться как по уровню соответствующего периферийного сигнала («Level-Triggered Interrupts»), так и по его фронту («Edge-Triggered»). В первом случае запрос на прерывание будет генерироваться всё время, пока соответствующая ЛЗП активна. При этом даже если процессор обработает данный запрос, но линия после этого останется в активном состоянии, система сразу же сформирует новое прерывание. Во втором случае запрос на прерывание формируется всего один раз – в момент перехода ЛЗП из неактивного состояние в активное (т.е. в момент формирования фронта сигнала). После этого запрос висит до тех пор, пока не будет обработан или принудительно сброшен. Новый запрос на прерывание может сформировать только новый переход соответствующей ЛЗП из неактивного состояния в активное (при этом очевидно, что для этого сначала она должна стать неактивной). Отличное сравнение этих двух типов прерываний есть здесь (отмечу, что аналогия с детьми просто прекрасна). Обратите внимание на то, что несколько ЛЗП могут объединяться на одном процессорном прерывании только в том случае, когда оно имеет тип «Level-Triggered» – иначе один или несколько запросов могут быть тупо потеряны.
Особняком в последней таблице стоит прерывание с номером 14. Данное прерывание имеет тип «NMI», и вообще говоря, в компьютерной технике эта аббревиатура обозначает немаскируемое прерывание (Non-Maskable Interrupt). А под оными понимаются прерывания, обработку которых невозможно запретить при помощи регистров маскирования. Однако, в документации на ESP32 написано обратное (см. пункт 3.3.4. на стр. 39): при поступлении специального сигнала («PRO_CPU NMI Interrupt Mask» или «APP_CPU NMI Interrupt Mask», см. схему выше) камень будет временно игнорировать все источники прерываний, привязанные в последней таблице к номеру 14, т.е. заявленные как немаскируемые. Иными словами, в случае необходимости ESP32 начнет игнорировать немаскируемые прерывания, хотя, по идее, должно бы быть наоборот. Данная странность, конечно, заслуживает самого пристального внимания, однако, подробно разбираться с механизмом маскирования в обзорной статье, на мой взгляд, рановато – пока достаточно будет просто запомнить, что в ESP32 данный механизм в том или ином виде есть.
В завершение данного пункта хотелось бы сделать несколько замечаний. Во-первых, внимательно проверяйте имена регистров в предпоследней таблице (это которая огромная)! Там есть опечатки. Например, в таблице сказано, что прерываниям от портов ввода/вывода соответствуют конфигурационные регистры «PRO_GPIO_INTERRUPT_PRO_MAP_REG» и «APP_GPIO_INTERRUPT_APP_MAP_REG». На самом же деле данные регистры называются «PRO_GPIO_INTERRUPT_MAP_REG» и «APP_GPIO_INTERRUPT_MAP_REG» соответственно. Регистры статуса ядра PRO_CPU в таблице названы «PRO_INTR_STATUS_REG_0»…«PRO_INTR_STATUS_REG_2», а на самом деле они «PRO_INTR_STATUS_REG_0_REG»…«PRO_INTR_STATUS_REG_2_REG» (для ядра APP_CPU это тоже справедливо). В общем, по мелочи косяки в таблице есть, и про это нужно помнить. Во-вторых, нигде не сказано, имеет ли каждое ядро доступ только к своим регистрам статуса (см. столбцы «Status Register» в предпоследней таблице) или оно может прочитать и «соседские» регистры. Ну и в-третьих, запрос на прерывание от определенного периферийного узла в общем случае может спровоцировать далеко не одно событие. Например, в соответствии с предпоследней таблицей, от каждого модуля UART идет всего по одной линии запроса на прерывание: «UART_INTR», «UART1_INTR» и «UART2_INTR». Однако, перевести каждую из них в активное состояние может аж 19 событий:
причем, у каждого модуля UART эти события свои собственные. Поэтому первым делом обработчик прерывания от UART’а должен выяснить – а какое из перечисленных событий вызвало запрос на прерывание. Отметим, что этот подход несколько отличается от тех же AVR-ок, где каждое важное событие имеет собственную ЛЗП (например, у каждого UART’а есть аж три линии: «Rx Complete», «Data Register Empty» и «Tx Complete»). Но с другой стороны, т.к. ESP32 может работать на сотнях мегагерц, думаю, процесс выяснения источника прерывания в каждом случае не займет много времени.
Сопроцессор ULP и режимы энергопотребления
Помимо основного процессора на борту камней ESP32 присутствует сопроцессор ULP (Ultra-Low-Power co-processor). Данный процессор имеет крайне низкое энергопотребление, и в режиме глубокой спячки (Deep-Sleep Mode) именно он рулит поведением микроконтроллера. Основной же процессор, по сравнению с ULP жрущий энергию как не в себя, при этом отключается. Напомним, что в рассматриваемых микроконтроллерах энергопотреблению вообще уделено достаточно большое внимание, поскольку камни ESP32 были специально разработаны для мобильных устройств, носимой электроники и приложений «Интернета вещей» (IoT), где питание дывайса чаще всего осуществляется при помощи батареек или аккумулятора. Поэтому нет ничего удивительного в том, что с точки зрения энергосбережения ESP32 имеет аж шесть режимов работы, включая режим полнейшей отключки, из которой камень не сможет вытащить ничто, кроме сигнала «ENABLE». Далее пробежимся по каждому из этих режимов, причем, разберем их достаточно подробно, ибо в официальной документации по данному поводу царит такая каша, что просто диву даешься, что происходит в башке у составителей. К тому же, даташыт содержит откровенные ошибки и недомолвки, о которых ниже будет сказано.
• Active mode (активный режим): основной режим работы микроконтроллеров ESP32, в котором активны все его составляющие – основной процессор, элементы ВЧ-тракта, память, периферия и т.д. При этом энергопотребление чипа, естественно, становится совершенно конским, и может достигать 240мА (в пике – до 1А). Однако, с такой прожорливостью приходится мириться, ибо только в активном режиме камень может передавать и принимать данные по беспроводным интерфейсам, а также ожидать их прихода (т.е. «слушать»). Имейте ввиду, что из-за активности 2,4ГГц-овых радиоузлов основной процессор в режиме «Active mode» может тактироваться только от внешнего кварца на 40МГц (XTAL), либо от умножителя частоты на базе генератора с ФАПЧ (80/160/240МГц) – в противном случае будет наблюдаться рассогласование частот. Поэтому все пользовательские настройки тактовой частоты проца в данном режиме игнорируются.
• Modem-sleep mode: в данном режиме существенное снижение потребления достигается за счет выключения ВЧ-тракта микроконтроллера, а также генераторов частоты Wi-Fi и Bluetooth. Остальные узлы камня работают как обычно, при этом учитываются пользовательские настройки тактовой частоты основного процессора. Возникновение любого события, которое способно вывести микроконтроллер из спящего режима, будет способствовать немедленному пробуждению чипа. Потребление в режиме «Modem-sleep mode» зависит от частоты работы камня, количества ядер процессора и используемой периферии, однако, оно в любом случае будет составлять от нескольких миллиампер до нескольких десятков миллиампер.
Следует отметить, что если для работы программы требуется поддержка Wi-Fi/Bluetooth-соединения, микроконтроллер будет периодически включать всю радио-приблуду, т.е. кратковременно переходить из режима «Modem-sleep mode» в активный режим работы. Такое поведение называется «Association sleep pattern» (не знаю, как можно адекватно перевести данное словосочетание), и оно, естественно, влечет за собой увеличение потребления чипа. При этом для того, чтобы максимально сэкономить энергию, но не потерять Wi-Fi/Bluetooth-соединение, используются маяки DTIM (DTIM beacon mechanism). В интервале между двумя маяками камень будет отключать всю радио-приблуду, и лишь перед приходом очередного маяка она будет включена. Межмаяковый интервал определяется используемым роутером и обычно составляет 100мс…1сек. Также следует помнить, что в режим «Modem-sleep mode» чип может перейти лишь в том случае, когда он подключен к роутеру или точке доступа как станция (Station mode).
• Light-sleep mode (режим легкого сна): в данном режиме дополнительное снижение потребления (по сравнению с «Modem-sleep mode») достигается за счет выключения высокочастотных генераторов тактовых импульсов, которые жрут достаточно много энергии. Это касается обоих генераторов с ФАПЧ, а также генератора, связанного с внешним кварцем XTAL (это который на 2МГц…40МГц)*. Кроме того, в рассматриваемом режиме работы прекращается подача тактовых импульсов на основной процессор, память и на всю цифровую периферию, что способствует дополнительному снижению потребления (такой прием называется «Clock gating»). Обратите внимание на то, что хотя эти узлы в результате и не смогут дальше нормально функционировать, но все хранящиеся в них данные сохранятся, поскольку питание с цифровой части микроконтроллера пока не снимается. Поэтому говорят, что в режиме легкого сна работа основного процессора, памяти и цифровой периферии приостановлена, и после пробуждения камень сможет продолжить свою деятельность с того места, где его вогнали в спячку (ибо вся необходимая информация останется сохранена). Потребление микроконтроллера в рассматриваемом режиме составляет примерно 800мкА.
Таким образом, в «Light-sleep mode» остаются активными только внутренние генераторы тактовых импульсов, внешний генератор на 32,768кГц, а также вся система RTC (RTC-память, RTC-периферия и сопроцессор ULP). Кроме того, если для работы программы требуется поддержка Wi-Fi/Bluetooth-соединения, микроконтроллер будет периодически включать всю радио-приблуду (см. «Association sleep pattern» выше)**. Вывести камень в активный режим способно любое «пробуждающее» событие, однако, выход из спячки будет длиться дольше, чем в случае «Modem-sleep mode»***. Обратите внимание на то, что в режиме легкого сна сопроцессор ULP и сенсорный контроллер имеют возможность периодически мониторить состояние панели с емкостными датчиками (например, с сенсорными кнопками), и при обнаружении касания к ней они могут разбудить кирпич.
* - в официальной документации говорится, что в режиме легкого сна отключается также и внутренний генератор на 8МГц, однако, при этом пропадет возможность тактирования сопроцессора ULP, так что, на мой взгляд, это явный глюк китайских даташытов.
** - об этом в официальной документации нет ни слова.
*** – конкретное время пробуждения микроконтроллера, по идее, должно бы быть указано в официальной документации, однако, там для всех(!) режимов указано только «less than 1ms», что является глупостью (да и враньём).
• Deep-sleep mode (режим глубокого сна): в данном режиме дополнительное снижение энергопотребления достигается за счет отключения питания от всей цифровой части микроконтроллера (основной процессор, память, цифровая периферия). Активными остаются только внутренние генераторы тактовых импульсов, внешний генератор на 32,768кГц*, а также система RTC (RTC-память, RTC-периферия и сопроцессор ULP). Обратите внимание на то, что из-за снятия питания с цифровой части камня вся информация, хранящаяся в соответствующих узлах микроконтроллера, в частности, в ОЗУ и в основном процессоре, будет безвозвратно потеряна (в отличие от режима легкого сна). Единственное место, где данные останутся целыми – это RTC-память. Поэтому перед переходом в режим глубокого сна микроконтроллер сохраняет данные о Wi-Fi/Bluetooth-соединении именно в ней (это необходимо сделать, чтобы после пробуждения быстрее восстановить связь). Отметим, что хранить данные в RTC-памяти может и сам пользователь – для этого они должны лежать в глобальных переменных с атрибутом «RTC_DATA_ATTR» (например RTC_DATA_ATTR int bootCount = 5;). Только следует помнить о том, что эта память намного меньше, чем «обычное» ОЗУ ESP32 – объем RTC FAST Memory составляет всего 8кбайт против 520кбайт обычной оперативки. Кроме того, в RTC модуле есть восемь 32-битных регистров общего назначения («RTC_CNTL_STORE0_REG» - «RTC_CNTL_STORE7_REG», см. далее), которые всегда находятся под питанием, поэтому их содержимое не может быть испорчено никакими энергосберегающими режимами. Эти регистры тоже могут быть использованы для хранения любой информации.
Поскольку в режиме «Deep-sleep mode» основной процессор обесточен, работой микроконтроллера целиком рулит сопроцессор ULP, программа для которого хранится в области RTC-памяти, называемой «RTC SLOW Memory». Основная задача сопроцессора – пасти́ состояние различных датчиков/таймеров, изменять содержание регистров модуля RTC, а в случае необходимости – разбудить всю остальную систему. В качестве источников пробуждения может выступать таймер, емкостные датчики, внешние прерывания или любая комбинация этих источников. Отметим, что выход из режима глубокого сна длится еще дольше, чем из режима «Light-sleep mode». Потребление микроконтроллера в данном режиме зависит от «сценария» работы сопроцессора ULP и может составлять от 10мкА до 150мкА.
Режим глубокого сна рекомендуется для приложений Wi-Fi/Bluetooth с низким энергопотреблением, которые редко подключаются к сети. Кроме того, сопроцессор ULP имеет собственную систему команд, а также доступ к четырем 16-битным регистрам общего назначения, к памяти «RTC SLOW Memory» и почти к каждому узлу модуля RTC (АЦП, таймер, порты ввода-вывода и т.д.). В итоге несложные программы, которые не требуют доступа к беспроводным интерфейсам и куче цифровой периферии, могут быть вообще реализованы исключительно на сопроцессоре. Прелесть такого подхода состоит в том, что мы можем работать в энергосберегающем режиме «Deep-sleep mode» вообще на протяжении всей программы. Конечно, при этом останется невостребованной практически вся начинка ESP32, но чем черт не шутит – возможно, данный сценарий работы когда-нибудь и пригодится.
* – в официальной документации говорится, что в режиме глубокого сна отключается также и внутренний генератор на 8МГц. Однако, при этом пропадет возможность тактирования сопроцессора ULP, играющего решающую роль в данном энергосберегающем режиме. Так что это, на мой взгляд, явный глюк китайских даташытов.
• Hibernation mode (режим гибернации): в этом режиме для еще большей экономии энергии отключаются сопроцессор ULP, внутренний генератор на 8МГц и RTC-периферия. Остается активным лишь таймер RTC, работающий на низкой частоте, и некоторые порты ввода-вывода, привязанные к RTC-модулю, поэтому вывести микроконтроллер из режима гибернации можно только при помощи этих портов и таймера. Обратите внимание на то, что выход из рассматриваемого режима длится еще дольше, чем из режима глубокого сна. Отметим также, что при впадении в гибернацию данные будут стерты как из «обычного» ОЗУ (это которое 520кбайт), так и из всей RTC-памяти. В связи с этим, для хранения какой-либо информации у нас остаются только вечно бодрые регистры «RTC_CNTL_STORE0_REG»...«RTC_CNTL_STORE7_REG» (8 штук по 32 бита). Потребление чипа в данном режиме составляет 5мкА, поэтому режим гибернации, как и режим глубокого сна, рекомендуется для приложений Wi-Fi/Bluetooth с низким энергопотреблением, которые редко подключаются к сети.
• Power off (отключение): для входа в данный режим на вывод «CHIP_PU» («EN») необходимо подать низкий уровень, при этом питание чипа будет отключено. В данном режиме микроконтроллер потребляет не более 1мкА. Вывести камень из полной отключки может только высокий уровень напряжения, поданный на вывод «CHIP_PU».
После сброса микроконтроллер ESP32 начнет работать в активном режиме. Однако, если нет необходимости постоянно поддерживать работу основного процессора (например, при ожидании внешнего события), можно вогнать чип в один из предустановленных режимов с низким потреблением, описанных выше. При этом лучше выбирать режим, который будет оптимально сочетать энергопотребление, длительность выхода из сна и доступные источники пробуждения. Примерное потребление камня в сберегающих режимах можно узнать из данной таблицы:
Обратите внимание, что каждый из предустановленных режимов можно дополнительно оптимизировать и адаптировать к любому приложению.
Память
Два основных типа памяти, используемых в микроконтроллерах ESP32 – это FLASH и SRAM (ОЗУ). Флэш традиционно используется для хранения прошивки (программы) микроконтроллера, а в ОЗУ хранятся данные, создаваемые и изменяемые в процессе работы этой прошивки. При этом рассматриваемые камни имеют довольно неплохой объем встроенного ОЗУ (по сравнению с теми же ESP8266) и при этом еще позволяют навешивать внешнюю оперативку. С другой стороны, своего флэша у чипов семейства ESP32 нет (кроме ESP32-D2WD и ESP32-U4WDH), поэтому для хранения прошивки им приходится использовать внешние микросхемы FLASH-памяти. Это не очень хорошо, поскольку применение навесного флэша ведет к дополнительным затратам и потере скорости исполнения программы. Однако, в готовые модули FLASH-память уже́ встроена, а частота работы камня может составлять сотни мегагерц, поэтому для пользователей именно модулей ESP32 обозначенные минусы, скорее всего, будут не особо критичны. Таким образом, можно сказать, что с точки зрения радиолюбителя с памятью у микроконтроллеров ESP32 дела обстоят весьма неплохо.
Вследствие использования Гарвардской архитектуры, любой чип ESP32 имеет отдельную шину команд или инструкций (Instruction Bus) и отдельную шину данных (Data Bus). По первой из них процессор вытаскивает из памяти команды, которые ему необходимо выполнить, по второй – различные данные, которые ему понадобятся при выполнении этих команд. При этом рассматриваемые камни поддерживают технологию XiP, т.е. выборка инструкций для процессора в большинстве случаев идет непосредственно из FLASH-памяти. Однако данный процесс является довольно медленным и для кусков программы, которые критичны ко времени исполнения (т.е. должны выполняться как можно шустрее), совершенно неприемлем. Поэтому микроконтроллеры ESP32 позволяют копировать части прошивки в ОЗУ, чтобы в дальнейшем процессор мог брать команды прямо из оперативки (это занимает гораздо меньше времени, чем работа с FLASH). Кроме того, размещение куска программы в ОЗУ требуется в том случае, когда этот кусок работает с флэшем. Таким образом, прошивка микроконтроллера может лежать не только во FLASH-памяти, но и в некоторых областях SRAM, поэтому шина команд процессора имеет доступ как ко флэшу, так и к оперативке. Аналогичным образом и шина данных не ограничивается доступом только лишь к ОЗУ – пользователь волен размещать любые константы или их массивы во FLASH-памяти, и при этом процессор без труда сможет до них достучаться.
Отметим, что внутренняя память микроконтроллеров ESP32 включает в себя не только традиционное основное ОЗУ – там есть и некоторые специфические области. Полный перечень встроенных мозгов рассматриваемых кирпичей будет таким:
• основное ОЗУ (SRAM; 520 кбайт). Тактируется от того же сигнала, что и основной процессор, поэтому может быть прочитано за один такт, что, наряду с неплохим объемом, безусловно, является плюсом. Минус же основного ОЗУ заключается в том, что в режимах глубокого сна и гибернации его содержимое безвозвратно пропадает (см. выше). Несмотря на это, в «обычных» режимах работы микроконтроллера львиная часть данных будет храниться именно в рассматриваемой области внутренней памяти ESP32.
Основное ОЗУ разбито на три части, причем, здесь всё не так просто, как в тех же AVR-ках, ибо ESP32 позволяет хранить в оперативке не только данные, но и команды для процессора. Особенности каждого куска основного ОЗУ приведены ниже:
▪ «SRAM0» (размер 192 кбайт). К области «SRAM0» имеет доступ только шина инструкций, поэтому размещать в ней лучше исключительно команды. (Справедливости ради нужно отметить, что последние версии ESP-IDF позволяют хранить в области «SRAM0» и данные, но доступ к ним займет существенно больше времени, чем для других областей основного ОЗУ, плюс при этом отпадает возможность работы в многоядерном режиме). Важной особенностью «SRAM0» является то, что кэш для процессора выделяется именно из данной области ОЗУ, на что уходит 64 кбайт памяти (по 32 кбайт на каждое ядро). Оставшиеся 128 кбайт всегда могут быть прочитаны и записаны любым ядром проца. Отметим, что если кэш в проекте не используется, то отводимые под него 64 кбайта можно пустить на «обычные» ну́жды. Обратите также внимание на то, что вектор прерываний тоже всегда располагается в области «SRAM0», сразу после кэша процессора.
▪ «SRAM1» (размер 128 кбайт). Сюда имеет доступ как шина команд, так и шина данных, поэтому в области «SRAM1» теоретически можно размещать и инструкции для процессора, и обычные данные. На практике же эту область чаще всего используют для хранения данных, поскольку приложениям обычно не хватает памяти именно для них.
▪ «SRAM2» (размер 200 кбайт). До этой области может достучаться только шина данных, поэтому здесь размещаются исключительно данные приложений. Обычно «SRAM2» используется для хранения информации в совокупности с областью «SRAM1». Таким образом получаем, что под данные пользователю доступно лишь 328 кбайт из всего объема внутреннего ОЗУ (520 кбайт). Повторимся, что последние версии ESP-IDF позволяют использовать для этих целей также и область «SRAM0», но доступ к этому куску памяти занимает значительно больше времени.
• дополнительное ОЗУ в модуле RTC (SRAM; 16 кбайт). Это область внутренней памяти ESP32, задуманная как временный склад важной информации при переводе микроконтроллера в режим глубокого сна (Deep-Sleep Mode). Дело в том, что в данном режиме содержание основного ОЗУ камня безвозвратно пропадает, а системе обычно необходимо иметь на руках какие-то исходные данные, чтобы суметь продолжить работу после пробуждения. Вот для сохранения подобных данных и служит дополнительная память модуля RTC. Кроме того, часть этой памяти может быть отведена под программу для сопроцессора ULP.
Отметим, что RTC-память и главный процессор тактируются от разных сигналов, поэтому доступ к ячейкам этой памяти может быть более медленным, чем к обычному ОЗУ. Более низкая скорость работы и крайне ограниченный объем – плата за то, что память RTC модуля может работать в режиме глубокого сна. При этом она состоит из двух частей, довольно сильно отличающихся друг от друга:
▪ «RTC FAST Memory» (размер 8 кбайт). Доступ к данной памяти имеет как шина данных, так и шина команд. Основное назначение быстрой RTC-памяти – хранение в режиме глубокого сна данных, которые потребуются системе после пробуждения (например, для быстрого восстановления Wi-Fi/Bluetooth-соединения). В обычном режиме работы микроконтроллера данная память может быть использована как часть основного ОЗУ (т.е. как обычная оперативка). Для тактирования RTC FAST Memory используется сигнал «ABP_CLK», а это значит, что доступ к памяти будет осуществляться либо на частоте работы основного процессора, либо в два раза ниже. При этом следует иметь ввиду, что достучаться до быстрой RTC-памяти может только ядро «PRO_CPU». Для ядра «APP_CPU» попытка обратиться к соответствующим адресам ни к чему не приведет.
▪ «RTC SLOW Memory» (размер 8 кбайт). Доступ к данной памяти имеет и шина данных, и шина команд. Как и в случае быстрой RTC-памяти, здесь можно хранить данные, которые потребуются системе после выхода из режима глубокого сна. Однако у медленной памяти есть одна особенность – именно в ней должна храниться программа для сопроцессора ULP, а также данные, создаваемые и изменяемые в процессе работы этой программы. В обычном режиме работы микроконтроллера область RTC SLOW Memory может быть использована как часть основного ОЗУ (т.е. как обычная оперативка). Правда, следует помнить, что для тактирования медленной памяти используется сигнал «FAST_CLK» (не пытайтесь понять, просто запомните данный факт), поэтому доступ к ней будет осуществляться на частоте существенно меньшей частоты́ работы основного процессора (обычно – раз в 10 ниже). Но зато к памяти RTC SLOW Memory имеют полноценный доступ оба ядра основного процессора, так что нет худа без добра.
• регистры восстановления (8 х 32 бита). Это 8 регистров общего назначения (РОН), размер каждого из которых составляет 32 бита. Задуманы как временный склад важной информации при переводе микроконтроллера в режим гибернации (Hibernation Mode). Необходимость введения специальных регистров восстановления в ESP32 связана с тем, что после перехода камня в режим гибернации вся информация стирается не только из основного ОЗУ чипа (как в режиме глубокого сна), но и из RTC-памяти, поскольку с нее тоже снимается питание. В этом случае какие-либо данные могут быть сохранены исключительно в восстанавливающих регистрах, поскольку они остаются запитаны всегда, и информация из них не пропадает ни в одном режиме работы чипа (ну, кроме «Power-Off»). Таким образом, при использовании гибернации необходимо тщательно продумывать процесс пробуждения микроконтроллера, поскольку для хранения «восстановительной» информации у нас остается всего восемь ячеек по 4 байта каждая. Отметим, что в остальных режимах работы камня (в том числе и в Deep-sleep mode) рассматриваемые регистры могут использоваться как обычные РОН.
• постоянная память (ROM; 448 кбайт). Это ПЗУ, в котором хранится функциональная прошивка камня, т.е. загрузчик (Bootloader) и данные, необходимые для нормальной работы ядер процессора. По сути, постоянная память обслуживает исключительно внутреннюю кухню микроконтроллера – помогает правильно залить прошивку в него, а также заставляет проц предсказуемо и корректно выполнять команды программы. В итоге получаем, что ROM – это крайне важный кусок ESP32, ибо без него микроконтроллер вообще работать не сможет, но при этом он же является самой «неинтересной» частью внутренней памяти ESP32, поскольку однократно прошивается на заводе и не может как-либо изменяться пользователем. Отметим, что после снятия питания информация в постоянной памяти ESP32 не стирается (как, в принципе, и у любого другого ПЗУ).
Отметим, что постоянная память разбита на две сильно неравные части: «ROM0» размером 384 кбайт и «ROM1» размером 64 кбайт. Доступ к первой области ПЗУ имеет только шина команд, ко второй – только шина данных.
• однократно программируемая память (eFuse или OTP; 1024 бит). Каждый камень ESP32 имеет 33 системных параметра, значения которых так или иначе влияют на его работу. В качестве примера подобных параметров можно привести MAC-адрес чипа, напряжение питания на выводе VDD_SDIO (+1,8В или +3,3В), статус JTAG (включен/выключен), точную частоту 8МГц-вого генератора тактовых импульсов, параметры, отвечающие за шифрование FLASH-памяти и тому подобные. И вот для записи значений системных параметров как раз служит однократно программируемая память, состоящая из 1024 ячеек типа «eFuse».
По сути, рассматривая память – это обычное программируемое ПЗУ без возможности его перезаписи (наподобие наших микросхем 556РТ4 и подобных). Каждая ячейка «eFuse» – это пережигаемый бит, и если вы его запрограммировали в единицу, то он уже́ никогда не сможет быть сброшен в нуль. Фактически, eFuse – это аналог фузов у микроконтроллеров AVR, только в ESP32 их нельзя многократно перешивать. Поэтому при программировании eFuse надо быть крайне внимательным. Отметим, что информация, записанная в однократно программируемой памяти чипа, после снятия питания не стирается (как и у любого другого ППЗУ).
Обратите внимание на то, что из имеющихся 1024 ячеек памяти «eFuse» 256 ячеек используются системой для хранения MAC-адреса микросхемы и непосредственной конфигурации ее аппаратной части. Оставшиеся 768 бит могут использоваться приложением пользователя. Типовой пример использования ячеек «eFuse» – запись параметров шифрования FLASH-памяти, а также идентификатора чипа.
Это то, что касается внутренней памяти микроконтроллеров ESP32. Однако, как было сказано выше, при необходимости она может быть расширена за счет внешних FLASH- и SRAM-микросхем, имеющих интерфейс SPI. При этом для увеличения скорости обмена информацией в камни ESP32 встроена поддержка режима Quad SPI (QSPI), позволяющего считывать за один такт не один бит данных, а сразу четыре. Обратите внимание на то, что к шине SPI рассматриваемых микроконтроллеров одновременно могут быть подключены как FLASH-чипы, так и SRAM-чипы:
Следует отметить, что доступ к внешней памяти может осуществляться исключительно через высокоскоростной кэш и устройство управления памятью (MMU). В связи с этим, при работе с ESP32 без кэша обычно обойтись не удается, ибо практически все камни данного семейства используют для хранения прошивки именно внешнюю память. Таким образом, хоть рассматриваемые микроконтроллеры и позволяют не отводить часть области «SRAM0» под кэш, чаще всего воспользоваться такой возможностью не получается.
В качестве внешней памяти к микроконтроллерам ESP32 можно прикрутить:
• до 16 Мбайт FLASH;
• до 8 Мбайт ОЗУ (SRAM),
однако, здесь не всё так просто. В таблице ниже показано «максимальное» распределение внешней памяти рассматриваемых камней:
Отсюда можно вынести несколько важных моментов. Во-первых, FLASH-память в случае ESP32 доступна исключительно для чтения, т.е. записать что-либо в нее микроконтроллер не может в принципе. Во-вторых, из максимальных 16 мегабайт флэша шине команд доступно лишь 11512 кбайт (т.е. 11 Мбайт + 248 кбайт), остальное пространство отдано шине данных. Это означает, что непосредственно на хранение инструкций камня (по большей части и составляющих его прошивку) у нас имеется чуть больше 11 Мбайт памяти. Кроме того, производитель заостряет внимание на том, что если под область команд выделяется больше 3320 кбайт (т.е. 3 Мбайт + 248 кбайт), производительность кэша будет снижена, что тоже, как бы, не особо радует. Ну а в-третьих, в таблице прямо сказано, что микроконтроллеры ESP32 могут адресовать не более 4 Мбайт внешнего ОЗУ. Поэтому хоть рассматриваемые камни и понимают внешний SRAM объемом до 8 Мбайт, одновременно работать они могут лишь в одной его половине.
Конечно, при работе с внешней памятью у ESP32 есть не только недостатки, но и приятные бонусы. Одним из основных, на мой взгляд, является поддержка аппаратного шифрования содержимого FLASH-памяти на базе стандарта AES. Это позволяет пользователю надежно защитить прошивку камня и константы от несанкционированного доступа. Кроме того, при работе с внешней памятью (как с FLASH, так и со SRAM) шина данных поддерживает 8-битное, 16-битное и 32-битное чтение информации. Ну и еще одной полезной фичей является возможность подключения к шине QSPI сразу нескольких чипов памяти (при этом, напомню, можно совмещать разные типы мозгов).
Контроллер DMA
Как и большинство современных микроконтроллеров, все чипы семейства ESP32 поддерживают работу с прямым доступом к памяти (или, по-русски, Direct Memory Access, сокращенно DMA). Данный узел позволяет некоторым периферийным устройствам (UART, SPI, I2C, Ethernet и т.д.) работать с ОЗУ камня напрямую, т.е. без участия процессора. Это помогает, с одной стороны, разгрузить проц, а с другой – ускорить процесс обмена данными между памятью и периферией, поэтому в ряде случаев грамотное использование DMA позволяет существенно увеличить скорость работы программы.
Чтобы понять пользу, приносимую механизмом прямого доступа к памяти (ПДП), рассмотрим те же самые AVR-ки (Mega и Tiny). Взаимодействие периферийных устройств с ОЗУ в них выглядело так. Если, например, UARTу требовалось выплюнуть наружу один байт, лежащий в оперативной памяти камня, то ядру (т.е. центральному процессору) приходилось сначала копировать этот байт в один из своих регистров, затем переносить содержимое этого регистра в регистр данных UART, и только после этого начиналась отправка данных во внешний мир. При этом процесс переноса информации занимал увеличенное время, поскольку выполнялась операция копирования данных в промежуточный регистр, да еще и проц при этом не мог выполнять никаких других действий. Контроллер же DMA позволяет реализовать «идеальный» случай – UART сам может залезть в нужное место ОЗУ и напрямую вытянуть себе оттуда всю необходимую информацию, а главный процессор при этом может заниматься своими делами и не отвлекаться на всякую вспомогательную херню. Отметим, что аналогичным образом контроллер DMA может работать не только с отдельными ячейками памяти (каковыми, по сути, и являются регистры периферийных устройств), но и с целыми кусками ОЗУ, что в реальных задачах экономит уйму времени центрального процессора.
В микроконтроллерах ESP32 прямой доступ к памяти имеют 13 периферийных устройств:
Нетрудно видеть, что в этот список входит вся высокоскоростная периферия данных камней, а вот у медленной (АЦП, ЦАП, I2C…) и у таймеров прямого доступа к памяти нет. Это вполне ожидаемо, т.к. главный процессор будет максимально отвлекать работа именно на высокой скорости. При этом, насколько я смог понять из чудесной китайской документации на рассматриваемый чип, каждое периферийное «семейство» ESP32 (UART, SPI, I2S, SD, EMAC и RADIO) имеет свой собственный контроллер DMA. Отметим, что у разных «семейств» работа с контроллером DMA может довольно сильно отличаться (например, настроить данный контроллер для UART и для EMAC – не одно и то же). Однако, при этом есть и некоторые общие моменты, о которых имеет смысл сказать отдельно.
Контроллер DMA любого периферийного «семейства» в ESP32 имеет возможность работы как в полудуплексном режиме, так и в полном дуплексе. Кроме того, поддерживается пакетная передача информации, в том числе и с 4-х байтной длительностью одного «такта». Каждый контроллер DMA умеет генерить ряд прерываний, которые сообщают ядру камня о состоянии процесса обмена данными или о его завершении. Объем ОЗУ, до которого может напрямую достучаться любое из вышеупомянутых «семейств», составляет 328 кбайт (это области «SRAM1» и «SRAM2», см. выше), причем, пользователь имеет возможность самостоятельно задать размер копируемого куска памяти в байтах. Отметим, что при работе с оперативкой контроллеры DMA используют те же адреса, что и центральный процессор, когда он общается с областями «SRAM1» и «SRAM2» по шине данных. Однако, физически прямой доступ к памяти в ESP32 реализован отдельно от основного проца – под DMA выделена собственная шина, называемая AHB (одна из разновидностей AMBA) и имеющая сравнительно высокую пропускную способность за счет возможности пакетной передачи данных.
Ну и еще один момент. В документации производителя сказано (см. пункт 7.3 на стр. 119), что механизм работы всех контроллеров DMA в ESP32 одинаков. Однако, описание самого́ механизма настолько мутное и лаконичное, что просто диву даешься. Ситуация осложняется еще и тем, что примеров работы ESP32 с прямым доступом к памяти не особо-то найдешь (во всяком случае, мне не попадались). Поэтому даже после пары дней изучения данной темы у меня осталось немало вопросов по поводу DMA. В связи с этим всё, что изложено далее в этом пункте, нуждается в дополнительной проверке, и по ходу моей дальнейшей работы уже́ с конкретным железом, возможно, будет корректироваться.
Насколько я понял, суть работы контроллеров DMA в рассматриваемых камнях такова. В само́м контроллере содержится перечень дескрипторов, совокупность которых полностью описывает область ОЗУ, к которой периферия хочет получить прямой доступ. Каждый дескриптор может описать только «небольшой» кусок памяти размером не более 4 кбайт, поэтому для работы с бо́льшими объемами оперативки нам потребуется несколько дескрипторов (теоретически доступный максимум, напомню, составляет 328 кбайт). В ESP32 каждый дескриптор содержит адрес начала соответствующего «небольшого» куска ОЗУ и его размер, а также ссылку на следующий дескриптор, который содержит аналогичную информацию, но только о другой «небольшой» области оперативки. В итоге, пройдясь по всем имеющимся дескрипторам, контроллер DMA полностью обслужит пространство памяти, к которому хочет обратиться соответствующая периферия в данный момент:
Дескрипторы делятся на два вида: входные (in_link) и выходные (out_link). Входные дескрипторы содержат информацию об областях памяти, в которые контроллер DMA будет класть данные, приходящие извне, а выходные – об областях, из которых информация будет выплевываться наружу. Совокупность дескрипторов образует так называемый связный список (Linked List), т.е. структуру данных, каждый элемент которой содержит не только «полезную» информацию, но и ссылку на следующий элемент. Если нам требуется что-либо записать в память, то нам понадобится сформировать входной связный список (т.е. список из входных дескрипторов), а если нужно что-то из нее вытащить – выходной список. Таким образом, контроллер DMA может одновременно содержать максимум два связных списка (и то – если идет работа в полнодуплексном режиме).
Структура входных и выходных дескрипторов абсолютно одинакова. Каждый дескриптор состоит из трех двойных слов (под двойным словом здесь и далее подразумевается 4 байта или 32 бита):
Первое слово (DW0) состоит из пяти полей, причем поле «Reserved» не используется, но в него нельзя записывать единицы. Смысл остальных полей слова DW0 такой:
• «Owner»: размер поля составляет 1 бит. Поле отвечает за то, кто будет иметь доступ к области памяти, описываемой данным дескриптором. Если «Owner»=1, то область будет доступна для контроллера DMA, в противном случае работать с этим куском памяти сможет только основной процессор. Судя по всему, в любом связном списке значение поля «Owner» будет одинаковым для всех дескрипторов, входящих в состав этого списка, т.к. в даташыте прямо так и написано: «The allowed operator of the buffer corresponding to the current linked list». А вот зачем это поле вообще нужно, я так и не понял. Возможно, процессор тоже лазает в память при помощи связных списков, и бит «Owner» помогает отличить списки DMA от списков проца. Но в любом случае – в документации про это прямо не сказано, а гадать – дело неблагодарное;
• «EOF» (End Of File): размер поля составляет 1 бит. Значение «EOF» станет равно 1 только при выполнении последнего дескриптора из связного списка, во всех остальных случаях «EOF»=0. Таким образом, данное поле сигнализирует о том, что работа контроллера DMA с памятью завершается, т.к. маркирует последнюю «небольшую» часть ОЗУ из всей требуемой области памяти;
• «Size»: размер поля составляет 12 бит. Поле указывает размер куска ОЗУ, с которым будет работать контроллер DMA в течение текущего дескриптора. Обратите внимание на то, что поле «Size» имеет отношение не к отдельным байтам, а к адресам памяти микроконтроллера (по сути, оно говорит, сколько адресов займет требуемый кусок памяти). В связи с этим его значение должно быть выровнено по двойным словам, т.е. быть кратно числам 0x0, 0x4, 0x8 и 0xC (0, 4, 8, 12, 16 и т.д.). Судя по всему, в любом связном списке значение поля «Size» будет одинаковым для всех дескрипторов, входящих в состав этого списка, т.к. в даташыте прямо так и написано: «The size of the buffer corresponding to the current linked list»;
• «Length»: размер поля составляет 12 бит. Поле указывает количество байтов, которое должно быть передано в кусок ОЗУ, описываемый дескриптором (ну, или вытащено из этого куска). В том случае, когда идет прием информации (т.е. когда она пишется в память) и значение поля «Length» меньше указанного размера «небольшой» области (т.е. «Size»), контроллер DMA не будет резервировать «лишнее» пространство в оперативке, что позволяет оптимально использовать ОЗУ даже при передаче произвольного количества байтов данных.
Второе слово (DW1) полностью состоит из поля «Buffer Address Pointer» (т.е. размер этого поля составляет 32 бита). Именно здесь указывается адрес начала «небольшой» области ОЗУ, с которым будет работать контроллер DMA. Обратите внимание на то, что рассматриваемое поле имеет отношение не к отдельным байтам, а к адресам памяти микроконтроллера, поэтому его значение должно быть выровнено по двойным словам, т.е. быть кратно числам 0x0, 0x4, 0x8 и 0xC (0, 4, 8, 12, 16 и т.д.).
Ну и третье слово (DW2) полностью состоит из поля «Next Descriptor Address». В данном поле указывается адрес следующего дескриптора в связном списке. Иными словами, после выполнения текущего дескриптора контроллер DMA должен будет перейти к дескриптору, расположенному по адресу, указанному в слове DW2. Отметим, что если текущий дескриптор является последним в связном списке (т.е. бит «EOF» равен 1), то значение «Next Descriptor Address» будет равно нулю.
Обратите внимание на то, что ограничение на максимальный объем «небольшого» куска ОЗУ (4 кбайт) связано с размером поля «Size». Поскольку его размер составляет всего 12 бит, при помощи этого поля можно зарезервировать лишь 212=4096 однобайтовых ячеек памяти, т.е. 4 кбайт оперативки. А вот размер поля «Buffer Address Pointer» взят с избытком, т.к. позволяет адресовать до 232=4 Гбайт памяти, что в случае работы с ESP32, мягко говоря, многовато.
Вот такая вот, казалось бы, нехитрая схема работы у механизма DMA. Однако, повторюсь, описана она настолько коряво, что даже после многократного прочтения официальной документации и часов поисков в Интернете всё равно по теме прямого доступа к памяти в ESP32 остается довольно много вопросов. Например, в даташыте не написано про то, где именно располагаются дескрипторы контроллеров DMA. Поэтому мне не очень понятно – зачем для их хранения использовать именно связные списки, а не традиционные массивы? Если они располагаются в ОЗУ, тогда всё ясно – таким образом просто экономится оперативка, ведь под связный перечень не нужно заранее резервировать определенное адресное пространство. Скорее всего, оно так и есть, но ведь можно же было про это написа́ть. Также мне не совсем понятен смысл поля «Size». Вроде бы ESP32 умеет подгонять размер области ОЗУ, описываемой дескриптором, под параметр «Length» (см. чуть выше). Более того, это подается как полезная фича, поскольку она позволяет экономить оперативку – SRAM не резервируется под данные, которых заведомо не будет. Зачем тогда в каждом дескрипторе прописывать фиксированное значение «Size» – неясно. Но самое главное – нигде не сказано, каким образом можно достучаться до полей слов DW0, DW1 и DW2 (да и сами эти слова встречаются во всей документации всего один раз, в пункте 7.3.2). Правда, поверхностное изучение регистров UART, SPI, I2S и EMAC говорит, что, скорее всего, всё необходимое для работы соответствующих контроллеров DMA делается через них, а DW0...DW2 относятся исключительно к внутренней кухне камней ESP32, скрытой от пользователя. Но тут уже́ надо копать сильно глубже и проверять догадки в железе. Для вводной же заметки приведенных сведений, думаю, вполне достаточно.
Таймеры общего назначения (ТОН)
В состав микроконтроллеров ESP32 входят четыре таймера общего назначения («General Purpose Timer»), разрядность которых составляет 64 бита. Любой из них может быть запрограммирован как на последовательное увеличение своего значения, так и на его уменьшение, т.е. работать либо как повышающий, либо как понижающий счетчик. Текущее состояние каждого таймера (т.е. число, до которого он успел досчитать) хранится в соответствующем 64-битном счетном регистре, который может быть прочитан пользователем в любой момент времени*. Кроме того, каждый ТОН имеет свой собственный 16-битный предделитель, значение которого устанавливается пользователем и может лежать в диапазоне от 2 до 65535. Включение/выключение счета производится при помощи отдельного бита в соответствующем регистре конфигурации, что позволяет не дергать постоянно предделитель таймера (как это было в тех же AVR-ках, где останов любого счетчика автоматически затирал текущее состояние коэффициента деления тактовых импульсов). При этом выключение таймера не приводит к стиранию содержимого его счетного регистра – оно останется неизменным до следующего включения счетчика или до его перезагрузки.
* - на самом деле механизм чтения текущего состояния таймеров общего назначения несколько сложнее, но для обзорной статьи такого упрощения будет вполне достаточно.
Каждый таймер общего назначения имеет свой собственный 64-битный регистр сравнения (т.н. регистр тревоги; «Alarm Register»), способный вызывать перезагрузку таймера и/или прерывание от него. Такие события могут возникнуть в тот момент, когда значение счетного регистра ТОН совпадёт со значением его регистра сравнения. Кроме того, «тревога» сработает и в том случае, когда регистр сравнения инициализирован «слишком поздно», и счетчик уже́ миновал соответствующее значение. Например, если у нас активирован режим увеличения счётного регистра, перезагрузка таймера и/или прерывание от него произойдут, когда текущее значение счетчика будет больше значения, загруженного в его регистр сравнения. Если же мы работаем в режиме уменьшения счёта, то соответствующие события возникнут, когда текущее значение таймера будет меньше значения, загруженного в тревожный регистр. Отметим, что в обоих случаях сигнал тревоги будет сформирован сразу же после загрузки требуемого значения в соответствующий регистр сравнения.
Обратите внимание на то, что в ESP32 перезагрузкой таймера называется не тупой сброс в нуль, а перенос в его счетный регистр значения, которое находится во вспомогательных регистрах «LOAD_HI» и «LOAD_LOW» (здесь удобнее и понятнее всего называть их именно так, хотя реальные имена этих регистров несколько другие). Таким образом, после перезагрузки в регистре счета ТОН может лежать вообще любое число из диапазона 0…264-1. Отметим, что камни ESP32 поддерживают два вида перезагрузки – принудительную и автоматическую. Принудительно перенести содержимое вспомогательных регистров в регистр счета пользователь может программно в любой момент времени – для этого необходимо записать произвольное (т.е. вот вообще какое угодно) значение в регистр «TIMGn_Tx_LOAD_REG». Если же включена автоматическая перезагрузка таймера, то счетный регистр ТОН будет проинициализирован значениями из «LOAD_HI» и «LOAD_LOW» только при возникновении тревоги. Данную функцию можно включать и выключать при помощи отдельного бита в соответствующем регистре конфигурации счетчика. Имейте ввиду, что если автоматическая перезагрузка таймера выключена, то значение его счетного регистра будет продолжать увеличиваться или уменьшаться даже после возникновения сигнала тревоги (т.е. не будет принудительно изменено).
В завершение данного пункта прикинем, с какими временны́ми интервалами позволяют работать таймеры общего назначения в ESP32. Понятно, что 64-битный счетный регистр позволяет каждому счетчику досчитать до значения 264-1= 18 446 744 073 709 551 615 (это, вроде бы, 18 с лишним квинтиллионов, но в любом случае – цифра просто астрономическая). Очевидно, что минимальный интервал времени, который может «понять» таймер, будет равен минимально возможному периоду его тактовых импульсов, ну а максимальный – максимально возможному периоду (с учетом всех делителей), умноженному на страшное число, приведенное выше. Поэтому для решения задачи нам осталось только разобраться с тем, откуда тактируются таймеры общего назначения в ESP32.
В рассматриваемых микроконтроллерах таймеры общего назначения тактируются от сигнала «APB_CLK» (см. выше), который, в свою очередь, формируется из тактовых импульсов основного процессора:
И в соответствии с этой таблицей, частота импульсов «APB_CLK» может быть разной – зависит она от того, от какого источника тактируется главный проц. При этом нужно понимать, что основным режимом работы микроконтроллера у львиной доли приложений будет являться активный режим, а в нем главный процессор чаще всего тактируется от сигнала «PLL_CLK» (т.е. от умножителя с ФАПЧ). Таким образом, получаем, что частоту импульсов на входе таймеров ESP32 в большинстве случаев можно считать равной 80МГц (см. таблицу), а их период, соответственно, принять за 1/80МГц=12,5нс. Далее необходимо вспомнить, что на входе каждого ТОНа у нас висит еще и предделитель, который может делить частоту сигнала «APB_CLK» на число в пределах от 2 до 65535. И таким образом, получаем, что период импульсов на входе непосредственно счетчика составит от 2×12,5нс=25нс до 65535×12,5нс=819,1875мкс. В итоге можно прогнозировать, что в большинстве приложений при помощи таймеров общего назначения мы можем сформировать временны́е интервалы, лежащие в диапазоне от
1 × 25нс = 25нс
до
18 446 744 073 709 551 615 × 819,1875мкс = 15 111 342 160 881 943 секунд = 479 177 516 лет,
что, мягко говоря, впечатляет, особенно на фоне AVR-ок с их 16-битными счетчиками. В утешение последних можно было бы сказать, что в ESP32 таймеры общего назначения не могут считать импульсы, приходящие на один из внешних выводов камня, а также не имеют функций захвата и формирования аппаратного ШИМ сигнала. Но этого, увы, не скажешь: под генерацию ШИМа у рассматриваемых камней выделен аж целый самостоятельный модуль на 16 каналов. Вдобавок к этому, есть дополнительный формирователь ШИМ, специально заточенный под управление двигателями, и поддерживающий функцию захвата. Для подсчета же внешних импульсов существует отдельный блок «PULSE_CNT», включающий в себя 8 (восемь, Карл!) двухканальных счетчиков, которые могут формировать уйму разных событий. Так что, на мой взгляд, с точки зрения таймеров общего назначения у ESP32 всё в полном порядке, ибо они занимаются именно тем, чем и должны – формированием временны́х интервалов. Ну а все дополнительные функции в этих камнях просто переложены на специализированную периферию, которая справляется с ними гораздо лучше, чем сами таймеры.
Таймер модуля RTC
Помимо таймеров общего назначения в состав ESP32 входит один дополнительный таймер, имеющий крайне урезанную функциональность и счетчик с разрядностью «всего» 48 бит. Располагается этот таймер в модуле RTC и используется по большей части для пробуждения основного процессора камня в назначенное время, а также для периодического тормошения ULP-сопроцессора (см. выше) и контроллера сенсорных кнопок.
Насколько я понял, таймер RTC-модуля – это просто 48-битный счетчик, отсчитывающий в цикле значения от 0 до 248-1= 281 474 976 710 655. Важной особенностью данного счетчика является то, что пользователь не может как-либо повлиять на его состояние – счетный регистр RTC-таймера доступен только для чтения. Но зато этот таймер будет считать импульсы в любом режиме работы микроконтроллера – сбросить его может только снятие питания с чипа (а также, видимо, вход в режим «Power Off»). Поэтому немудрено, что основной задачей дополнительного таймера является пробуждение различных узлов камня.
Для тактирования RTC-таймера используется сигнал «SLOW_CLK», (см. выше) частота которого по умолчанию составляет 150кГц, а период – 6,667мкс. Таким образом, счетный регистр дополнительного таймера будет переполняться и сбрасываться каждые
(248-1) × 6,667мкс ≈ 1 876 593 670 секунд = 521 276 часов = 21 719 дней = 59,5 лет,
т.е. на нашем веку переполниться он точно не успеет. При этом следует помнить, что в качестве «SLOW_CLK» могут также использоваться сигналы «XTL32K_CLK» и «RTC8M_D256_CLK», частота которых составляет 32,768кГц и 31,25кГц соответственно. Очевидно, что при работе RTC-счетчика с данными сигналами временно́й интервал между его переполнениями будет еще больше.
Сторожевые таймеры
Как и любой современный микроконтроллер, ESP32 имеет средства для восстановления работоспособности системы даже в случае возникновения различных программных сбоев. Данные средства традиционно представлены сторожевыми таймерами («Watchdog Timer» или WDT), которых на борту рассматриваемых камней присутствует аж три штуки. Два сторожевых таймера расположены в основной системе* чипа и называются «Main system WDT» («MWDT0» и «MWDT1»). Один таймер находится в модуле RTC и называется «RWDT».
* - в документации на ESP32 основной системой называется совокупность процессора, основной памяти и всей периферии (т.е. это весь камень за вычетом системы RTC).
Отметим, что функциональность сторожевых таймеров у ESP32 довольно богата. Обычно данный узел тупо формирует сброс системы по истечении определенного интервала времени. То есть традиционно WDT умеет работать только с одним временны́м промежутком и действие, совершаемое им после этого, может быть лишь одно – это сброс всей системы. В рассматриваемых камнях всё несколько сложнее: здесь каждый сторожевой таймер является четырехступенчатым счетчиком, который по завершении каждой ступени может не только сбрасывать определенные узлы камня (не обязательно всю систему), но и формировать прерывания.
Алгоритм работы любого WDT в ESP32 будет таким. Сначала таймер отсчитывает временно́й интервал (этап) №0, после чего может совершить определенное действие (сброс или прерывание). Затем отсчитывается интервал №1 и тоже совершается какое-либо действие. То же самое повторяется для этапов №2 и №3, после чего происходит переход к интервалу №0 и так далее в цикле. Максимальная длительность интервалов №0…№3 составляет 4 294 967 296 тактовых импульсов, т.е. счетчики WDT являются 32-битными. Действия, совершаемые таймером по завершении интервалов №0…№3, могут быть такими:
• бездействие («OFF»). По истечении соответствующего этапа сторожевой таймер просто переходит на следующую ступень, не предпринимая никаких дополнительных действий;
• прерывание («INTERRUPT»). По истечении соответствующего этапа сторожевой таймер сформирует прерывание;
• перезапуск основного процессора («RESET CPU»). По истечении соответствующего этапа сторожевой таймер сбросит одно или оба ядра проца. Отметим, что таймер «MWDT0» может сбросить только ядро «PRO_CPU» (см. выше), а таймер – «MWDT1» только ядро «APP_CPU». Таймеру «RWDT» доступен перезапуск любого из двух ядер основного процессора (а также обоих ядер сразу);
• перезапуск основной системы («RESET SYSTEM»). По истечении соответствующего этапа сторожевой таймер сбросит всю основную систему (включая и себя самого́). Напомню, что в документации на ESP32 основной системой называется совокупность процессора, основной памяти и всей периферии. Обратите внимание на то, что система RTC в данный перечень не входит, поэтому в рассматриваемом случае она сброшена не будет;
• перезапуск всего микроконтроллера полностью. По истечении соответствующего этапа сторожевой таймер сбросит весь чип целиком (т.е. как основную систему, так и систему RTC). Данный вид действия доступен только таймеру «RWDT».
Отметим, что для каждого этапа пользователь может самостоятельно задать как длительность, так и действие, совершаемое сторожевым таймером по истечении соответствующего временно́го интервала. Поэтому все три WDT в ESP32 могут быть довольно гибко настроены (можно даже корректировать длительность импульса сброса). Кроме того, поскольку рассматриваемые узлы умеют не только сбрасывать камень, но и формировать прерывания, они могут быть использованы в качестве обычных таймеров общего назначения. Правда, размерность таких таймеров составит «всего» 32 бита, и на фоне 64 бит это может показаться недостаточным. Чтобы немного исправить данную ситуацию, можно для трех этапов в качестве завершающего события задать «бездействие», и тогда максимальное значение, до которого сможет дойти счетчик перед формированием прерывания, составит
4 × 4 294 967 296 = 17 179 869 184 тактовых импульсов.
Конфигурация каждого из сторожевых таймеров хранится сразу в нескольких регистрах. Пользователь может изменять любой из этих регистров, однако, все изменения вступят в силу лишь после специальной команды «инициализация WDT». После данной команды в таймер загрузятся значения сразу всех соответствующих конфигурационных регистров, затем его счетчик будет автоматически сброшен и таймер начнет работу с временно́го интервала №0. Отметим, что регистры конфигурации WDT в ESP32 имеют защиту от записи – для ее включения необходимо положить в специальный защитный регистр любое значение, отличное от 0x50D83AA1. После этого все конфигурационные регистры соответствующего таймера станут недоступны для каких-либо изменений до тех пор, пока в регистр зашиты вновь не будет записано 0x50D83AA1. Обратите внимание на то, что в рассматриваемых камнях на каждый сторожевой таймер выделен свой собственный защитный регистр.
Принцип тактирования сторожевых таймеров основной системы («MWDT0» и «MWDT1») полностью совпадает с оным у таймеров общего назначения. На вход данных WDT поступает сигнал «APB_CLK», который затем проходит через 16-битный предделитель. Значение его коэффициента деления может составлять от 2 до 65535 и задается оно отдельно для каждого таймера. После предделителя тактовый сигнал поступает на вход соответствующего сторожевого счетчика, поэтому WDT будет считать именно эти «деленные» импульсы. Если учесть, что в большинстве случаев частота последовательности «APB_CLK» составляет 80МГц, то период «деленных» импульсов будет лежать в диапазоне от
2 × (1/80МГц) = 25нс
до
65535 × (1/80МГц) = 819,1875мкс.
Поэтому длительность каждого этапа таймеров основной системы может составлять от
1 × 25нс = 25нс
до
4 294 967 296 × 819,1875мкс = 3 518 383,521792 секунд = 40,72 дней.
Отметим, что это, конечно, не так круто, как в случае таймеров общего назначения, но всё равно весьма впечатляет. Что же касается сторожевого таймера в системе RTC («RWDT»), то он тактируется от сигнала «RTC8M_D256_CLK» (см. выше), обычно имеющего частоту 31,25кГц. При этом период данных импульсов составляет ровно 1/31,25кГц=32мкс, поэтому длительность каждого этапа таймера «RWDT» будет лежать в диапазоне от
1 × 32мкс = 32мкс
до
4 294 967 296 × 32мкс = 137 438,953472 секунд = 1,59 дней,
поскольку никаких предделителей у него нет.
Ну и еще один интересный факт, касающийся сторожевых таймеров в ESP32. Дело в том, что в данных камнях таймеры «MWDT0» и «RWDT» используются для т.н. защиты при загрузке прошивки из FLASH-памяти. Механизм данной защиты состоит в следующем. Вместе с началом загрузки прошивки автоматически запускаются и вышеупомянутые таймеры. При этом таймер «MWDT0» настраивается так, чтобы по завершении этапа №0 он сбрасывал основную систему, а «RWDT» – весь микроконтроллер целиком. Таким образом, если процесс загрузки не завершится в течение заранее определенного интервала времени (какого именно – я не нашел), сторожевые таймеры перезагрузят систему. Такой подход помогает избежать проблем, связанных с кривой загрузкой данных из FLASH-памяти. Обратите внимание на то, что после успешной заливки прошивки необходимо сбросить биты типа «WDT_FLASHBOOT_MOD_EN» в регистрах конфигурации таймеров «MWDT0» и «RWDT». После этого данные таймеры могут быть настроены так, как необходимо пользователю.
Продолжение следует...