В предыдущей статье “Первый шаг к микроконтроллерам: введение в цифровые схемы” вы познакомились с логическими микросхемами на примере серии 155 и собранных на них простых конструкций. Но чтобы сделать более или менее сложную схему, потребуется много таких микросхем. Для внесения изменений в логику работы придётся перепроектировать и перепаивать схему, а то и создавать её заново.

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

Вокруг вас много «умных» вещей со встроенными микроконтроллерами. Это электрические чайники, стиральные машины и кондиционеры, детские игрушки, устройства умного дома. 

Для подключения внешних устройств у микроконтроллеров имеются линии ввода-вывода общего назначения General-Purpose Input/Output (GPIO). GPIO обычно используют для цифровых сигналов. В некоторых микроконтроллерах есть встроенные АЦП и ЦАП для работы с аналоговыми сигналами, но их точности иногда недостаточно — тогда применяют внешние модули. 

К порту GPIO можно подключать как простые устройства — кнопки и светодиоды, таки и сложные: дисплеи и радиомодули. Некоторые из них можно соединить с GPIO напрямую, для других нужны дополнительные модули. Например, с помощью реле можно управлять мощными нагрузками, а через специальные интерфейсные модули — различными электродвигателями. 

Но возможности современных микроконтроллеров не ограничиваются только обработкой сигналов. С их помощью обмениваются данными по сети, используя Wi-Fi и Bluetooth, создают веб-серверы и точки доступа (хот-споты) для подключения мобильного телефона к сети Wi-Fi вашего устройства. Через Wi-Fi, созданный в рамках хот-спота микроконтроллера, вы можете управлять устройством независимо от наличия интернета.

В этой статье я покажу, как работать с кнопками и светодиодами на отладочной плате ESP32-S3-DevKitC-1-N16R8. Вы узнаете, как писать программы в Arduino IDE, загружать их в микроконтроллер и проверять результат на макетной плате. Также приведу пример использования прерывания, ШИМ, распиновку платы микроконтроллера ESP32-S3-DevKitC-1-N16R8.

Каждый пример построен на простой схеме, которую легко собрать самостоятельно, так что если захотите повторить на практике, то сразу получите первый опыт работы с ESP32 от простого включения светодиода до управления им кнопками.

Выбор микроконтроллера для первых экспериментов

Сегодня среди самых распространённых платформ микроконтроллеров можно назвать Arduino, ESP32, nRF52840, Raspberry Pi Pico и STM32. Они отличаются количеством ядер и рабочей частотой процессора, объемом ОЗУ, наличием встроенных модулей беспроводной связи, количеством контактов GPIO и другими возможностями.

Начнём с экосистемы Arduino, которая не нуждается в особом представлении (рис. 1). 

Рис. 1. Плата Arduino UNO
Рис. 1. Плата Arduino UNO

Классическая UNO R3 с 8-битным микроконтроллером ATmega328P работает на частоте 16 МГц и оснащена 2 КБ оперативной памяти (ОЗУ). 

Платы UNO R4 Minima и компактная Nano R4 построены на 32-битном микропроцессоре Renesas RA4M1 (48 МГц, 32 КБ ОЗУ), но в них также нет беспроводной связи. Исключение — плата UNO R4 WiFi: в ней есть Wi-Fi и BLE, но там это реализовано на отдельном микроконтроллере ESP32-S3. В любом случае платформа Arduino вполне пригодна для обучения и простой автоматики. 

Специально для платформы Arduino разработано большое количество плат расширения (Shields), которые можно соединять друг с другом в виде этажерки. Слева на рис. 2 на Arduino смонтирована плата расширения, предназначенная для подключения сети Ethernet и SD-карты памяти, а справа — модуль для подключения к Wi-Fi.

Рис. 2. Платы расширения для подключения сети и SD-карты памяти
Рис. 2. Платы расширения для подключения сети и SD-карты памяти

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

На рис. 3 показана отладочная плата NUCLEO-F401RE, удобная для макетирования устройств.

Рис. 3. Плата NUCLEO-F401RE
Рис. 3. Плата NUCLEO-F401RE

Главное достоинство платформы nRF52840 — очень малое энергопотребление. Однако на этой платформе нет Wi-Fi, но есть более экономичные с точки зрения потребления энергии BLE, Thread и Zigbee. Также у неё не очень высокие производительность и дружелюбность к новичкам.

Raspberry Pi Pico — это недорогая плата на микроконтроллере RP2040 (двухъядерный ARM Cortex-M0+ до 133 МГц, 264 КБ ОЗУ) от Raspberry Pi. Её можно программировать на MicroPython, C/C++ и через Arduino IDE. У версии Pico W добавлены Wi-Fi и Bluetooth.

Для этой статьи я выбрал платформу ESP32 китайской компании Espressif Systems. На ней имеются встроенные Wi-Fi и Bluetooth (BLE), хороший запас мощности и богатая периферия. Для ESP32 можно создавать программное обеспечение (ПО) с использованием простой среды Arduino IDE, хотя есть и профессиональные инструменты разработки. 

На базе ESP32 созданы не только недорогие и малогабаритные модули, но и мощные со встроенными экранами и даже с модулями дальней радиосвязи LoRa. И всё это при относительно невысокой стоимости плат.

На рис. 4 слева показана отладочная плата микроконтроллера ESP32-S3-DevKitC-1-N16R8, а справа — малогабаритная плата ESP32-S3 Super Mini. 

Рис. 4. Две платы микроконтроллеров на базе ESP32
Рис. 4. Две платы микроконтроллеров на базе ESP32

На плате ESP32-S3-DevKitC-1-N16R8 установлено 512 КБ встроенной SRAM, 16 Мбайт флеш-памяти и 8 Мбайт памяти PSRAM. 

Содержимое флеш-памяти сохраняется при отключении питания, и сюда записывается прошивка (программа). Псевдостатическая память Pseudo-Static RAM (PSRAM) не сохраняет свое содержимое, если выключить питание платы микроконтроллера. Она медленнее встроенной памяти SRAM. Также имеется RTC-память небольшого объёма, содержимое которой сохраняется в режиме глубокого сна, когда плата микроконтроллера потребляет очень мало электроэнергии. Подробнее об этом можно прочитать в разделе «Memory Types» документации.

В микроконтроллере два ядра процессора, работающие с частотой до 240 МГц, имеется Wi-Fi 4, Bluetooth 5 LE и антенна Wi-Fi. 

Там же есть очень полезный на начальном этапе обучения RGB-светодиод, два разъёма USB, кнопки сброса RST и загрузки BOOT.

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

Что касается миниатюрной платы ESP32-S3 Super Mini, то несмотря на свой размер у неё неплохие возможности. На этой платформе два ядра Xtensa LX7, работающие с частотой до 240 МГц, Wi-Fi 4 и Bluetooth 5 LE. Также там есть 512 КБ SRAM и разъём USB, выводы GPIO. Плата может быть использована в умном доме, для обработки звука и изображений, а также в устройствах с дисплеем.

На рис. 5 показаны платформы Waveshare ESP32-S3-LCD-1.47 и Heltec HTIT-WB32LAF.

Рис. 5. Платформы ESP32 с экраном и модулем LoRa
Рис. 5. Платформы ESP32 с экраном и модулем LoRa

Компактная плата ESP32-S3-LCD-1.47 оборудована цветным дисплеем, двухядерным процессором Xtensa LX7 с рабочей частотой до 240 МГц. На плате есть Wi-Fi 4 и Bluetooth 5 LE с антенной на плате, 16 МБ флеш-памяти и 8 МБ PSRAM.

Экран размером 1,47" с разрешением 172×320 и 262 тыс. цветов подходит не только для тестирования, но и для создания графических интерфейсов.

На плате есть слот для карт TF (Micro-SD), разъём USB и кнопки RST и BOOT.

Справа на рис. 5 показана плата Heltec HTIT-WB32LAF. Она интересна прежде всего тем, что в её составе есть LoRa-трансивер SX1262, с помощью которого можно передавать данные на километры (хотя и с небольшой скоростью). На плате также имеется OLED-дисплей размером 0,96" с разрешением 128×64, разъёмы для солнечной панели, схема заряда и разъём для аккумулятора Li-Po.

В статье «Top 10 Microcontrollers in 2026: A Comprehensive Comparison Guide» вы найдёте сравнение характеристик современных популярных микроконтроллеров.

Разница между микроконтроллерами и микрокомпьютерами

Помимо микроконтроллеров, для работы с оборудованием используют и микрокомпьютеры. В качестве примера на рис. 6 показаны микрокомпьютеры Raspberry Pi 3 Model B+ и Orange Pi 3B. 

Рис. 6. Микрокомпьютеры Raspberry Pi 3 Model B+ и Orange Pi 3B
Рис. 6. Микрокомпьютеры Raspberry Pi 3 Model B+ и Orange Pi 3B

Микрокомпьютеры также оборудованы интерфейсом GPIO, позволяющим подключать внешние устройства, однако у них другая область применения по сравнению с микроконтроллерами, совершенно другая стоимость и энергопотребление.

Микроконтроллеры

Микроконтроллеры — это целая вычислительная система в одной микросхеме: процессор, память, периферия. Они выполняют программу в виде прошивки, либо без операционной системы, либо под управлением операционной системы реального времени RTOS. У ESP32 относительно небольшое (100-300 мА), а в режиме сна очень малое потребление энергии. На микроконтроллере программа стартует за доли секунды.

Микроконтроллеры устанавливаются в датчиках, узлах IoT, управляют двигателями, реле и бытовой техникой. Они применяются в автомобильной и промышленной электронике, то есть везде, где важны точные тайминги, экономичность и надёжность. 

Микрокомпьютеры

Что касается микрокомпьютеров, таких как Raspberry Pi и Orange Pi, то они представляют собой полноценные компьютеры на одной плате. Их часто называют одноплатными компьютерами Single-Board Computer (SBC).

Как правило, в микрокомпьютерах используются многоядерные процессоры ARM с рабочей частотой порядка 1-2,5 ГГц, и память объёмом от 512 МБ до 16 ГБ ОЗУ. Они работают под управлением ОС, например, Linux или Android. На плате есть разъёмы USB, Ethernet, HDMI и интерфейсы для подключения камер.

Микрокомпьютеры используют как домашние серверы и шлюзы, медиацентры, киоски, для компьютерного зрения и систем искусственного интеллекта (AI), для прототипирования сложных систем и как «мозг», координирующий несколько микроконтроллеров.

На микрокомпьютере можно запускать любые программы, от веб-серверов и баз данных до Docker, но время реакции не гарантировано. При этом энергопотребление микрокомпьютера составляет порядка 2-15 Вт. Перед выключением необходимо корректно завершать работу ОС, иначе можно повредить файловую систему на SD-карте.

По стоимости микроконтроллер во много раз дешевле микрокомпьютера. Если микроконтроллер можно приобрести за несколько сотен рублей, то стоимость микрокомпьютера может составлять от нескольких тысяч до десятков тысяч рублей. Очень сильно на стоимость микрокомпьютера влияет объем оперативной памяти. Кроме того, для микрокомпьютера требуется приобретение корпуса, SD-карты памяти, блока питания и вентилятора для охлаждения.

В сложных проектах эти устройства часто объединяют: за работу с «железом» в реальном времени отвечает микроконтроллер, а за сложную логику, сеть и нейросети — микрокомпьютер.

Платформа ESP32-S3-N16R8

В статье используется плата ESP32-S3-DevKitC-1-N16R8 (рис. 7).

Рис. 7. Платформа ESP32-S3-DevKitC-1-N16R8
Рис. 7. Платформа ESP32-S3-DevKitC-1-N16R8

На плате микроконтроллера есть два разъёма USB, один из которых подписан снизу платы как COM, а второй — как USB. Первый из них подключен через мост USB-UART типа CH343P и предназначен для загрузки прошивки в плату микроконтроллера, а также для работы отладчика кода. Также в зависимости от производителя платы может использоваться мост CP2102N.

Разъём с надписью USB подключён напрямую к контактам GPIO19 и GPIO20. Он может применяться как виртуальный COM-порт, как интерфейс отладки USB-JTAG, а также для эмуляции USB-устройств (клавиатура, мышь, геймпад).

Подключите плату контроллера через разъём с надписью COM к порту USB компьютера. После этого запустится прошивка, которая поставляется в составе этого контроллера. Она будет периодически изменять цвет светодиода RGB (рис. 8).

Рис. 8. Первое включение ESP32-S3-DevKitC-1-N16R8
Рис. 8. Первое включение ESP32-S3-DevKitC-1-N16R8

На плате есть ещё три светодиода, показанных на рис. 8. Нижний из них (красный) служит индикатором включения питания. Один из двух оставшихся мигает при передаче данных от компьютера в ESP32 через разъём COM (например, при загрузке прошивки), а другой — при отправке данных из ESP32 в компьютер (например, при передаче данных отладки).

Кнопка RST перезапускает чип и заново запускает программу. Кнопка BOOT подключена к контакту GPIO0. При удержании во время сброса или подачи питания она переводит ESP32-S3 в режим загрузчика для прошивки. В обычной работе её можно использовать как пользовательскую кнопку.

Что касается строки «N16R8» в названии платы, то она означает наличие 16 МБ флеш-памяти (N16) и 8 МБ памяти PSRAM (R8).

Пока мы не будем ничего подключать к плате контроллера. Вместо этого установим среду разработки ПО Arduino IDE, а решением «железных» вопросов займёмся позже.

Установка Arduino IDE на Windows 11

Для установки среды разработки Arduino IDE зайдите на сайт arduino.cc и щёлкнув кнопку Download, скачайте установочный файл Arduino IDE для Windows. Для Arduino IDE версии 2.3.10 этот файл называется arduino-ide_2.3.10_Windows_64bit.exe. Его можно также загрузить на сайте одного из магазинов, торгующем микроконтроллерами.

После загрузки запустите файл на выполнение. Пройдите все шаги установки, соглашаясь на установку драйверов при появлении соответствующих запросов. Подробно эта процедура описана в статье «Как установить arduino ide на windows».

Программы для Arduino IDE пишутся на языке C++, поэтому вам пригодится его знание. Вы можете начать со знания основ и готовых примеров, а язык освоите по ходу дела. Для изучения C++ можно воспользоваться сайтом LearnCpp.com (на английском языке) или учебником «Уроки С++» (на русском языке). 

Языку C++ посвящено много книг, например, книга Стивена Прата «Язык программирования C++. Лекции и упражнения». Также вам будет полезна документация на английском языке «Welcome to ESP32 Arduino Core’s documentation».

Запуск первой программы

После подключения платы контроллера к порту USB компьютера с Microsoft Windows 11 определите, какой COM-порт был выделен нашему контроллеру. Для этого запустите Диспетчер устройств (рис. 9) и раскройте ветвь Порты (COM и LPT).

Рис. 9. Диспетчер устройств
Рис. 9. Диспетчер устройств

В названии порта должен быть мост USB-UART типа CH343 или CP2102. У меня контроллеру был выделен порт COM4. Название порта потребуется при создании программы для ESP32 в Arduino IDE.

Если порт определился, запустите Arduino IDE. Выберите из меню Tools строку Port, а затем укажите в меню Serial Port название порта, к которому подключен ESP32 (рис. 10).

Рис. 10. Выбор порта, к которому подключена плата микроконтроллера
Рис. 10. Выбор порта, к которому подключена плата микроконтроллера

В моём случае это порт COM4, но у вас он может быть другой.

Далее выберите из меню File строку Preferences и введите в поле Additional boards manager URLs такую строку:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.jsonh

Сохраните изменения, щёлкнув кнопку OK.

На следующем шаге нужно указать тип микроконтроллера. Для этого выберите в меню Tools строку Board и щёлкните Boards manager. Введите в поиске строку «esp Espressif» и найдите пакет esp32 от Espressif Systems (рис. 11).

Рис. 11. Найден пакет esp32 by Espressif Systems
Рис. 11. Найден пакет esp32 by Espressif Systems

Щёлкните кнопку INSTALL и дождитесь завершения установки.

Далее для выбора платы выберите в меню Tools строку Board и выберите из ветви esp32 строку ESP32S3 Dev Module (рис. 12).

Рис. 12. Выбор платы
Рис. 12. Выбор платы

Если вы всё сделали правильно, то в панели инструментов будет показана выбранная плата. В строке состояния также будет показан выбранный COM-порт (рис. 13).

Рис. 13. Выбрана плата ESP32S3 Dev Module и порт COM4
Рис. 13. Выбрана плата ESP32S3 Dev Module и порт COM4

Для платы N16R8 в меню Tools нужно выбрать Flash Size: 16MB и PSRAM: OPI PSRAM, иначе память PSRAM работать не будет. Для мигания светодиодом это не критично.

Ещё одно замечание касается подключения платы к компьютеру через порт, обозначенный на плате как USB. Для отладки через Serial нужно установить USB CDC On Boot: Enabled. Если отладка идёт через разъём, обозначенный как COM, опцию нужно оставить выключенной.

Про отладку я расскажу в одной из следующих статей серии про микроконтроллеры.

Создание скетча

Создайте на своём компьютере каталог с названием RGB-Blink и сохраните в нём файл с именем RGB-Blink.ino:

#define RGB_PIN 48

void setup() {
  randomSeed(esp_random()); // аппаратный генератор случайных чисел
}

void loop() {
  uint8_t r, g, b;
  do {
    r = random(0, 256);
    g = random(0, 256);
    b = random(0, 256);
  } while (r + g + b < 100); // отсекаем слишком тёмные цвета

  // Яркость уменьшена в 4 раза, светодиод очень яркий
  neopixelWrite(RGB_PIN, r / 4, g / 4, b / 4);

  delay(1000);
}

Вы можете скачать этот файл из моего репозитория на GitHub.

Откройте подготовленный файл в Arduino IDE (рис. 14).

Рис. 14. Запуск программы RGB-Blink.ino
Рис. 14. Запуск программы RGB-Blink.ino

Для компиляции программы, загрузки её кода в плату микроконтроллера и запуска щёлкните кнопку Upload со стрелкой, выделенной на рис. 14.

В панели Output, расположенной в нижней части окна, появятся сообщения о ходе компиляции и загрузки (рис. 15).

Рис. 15. Сообщения в процессе компиляции и загрузки
Рис. 15. Сообщения в процессе компиляции и загрузки

Здесь же будут показаны и сообщения об ошибках, если они возникнут в ходе компиляции или загрузки.

Когда загрузка скомпилированной программы будет завершена, вы увидите сообщение о выполнении сброса (рис. 16).

Рис. 16. Выполнен сброс микроконтроллера и запуск программы
Рис. 16. Выполнен сброс микроконтроллера и запуск программы

В этот момент плата будет сброшена (как по кнопке RST), а только что загруженная программа начнёт свою работу.

Как работает программа RGB-Blink.ino

Программа RGB-Blink.ino каждую секунду зажигает случайным цветом RGB-светодиод, расположенный на плате микроконтроллера.

В начале программы задан номер пина, к которому подключен светодиод:

#define RGB_PIN 48

На официальной плате ESP32-S3-DevKitC-1 светодиод подключён к GPIO48 (ревизия 1.0) или к GPIO38 (ревизия 1.1). На клонах и других платах на базе ESP32-S3 подключение может отличаться, поэтому его следует уточнить по документации производителя.

Макрос RGB_BUILTIN избавляет от привязки к номеру пина.

Здесь используется не обычный трёхцветный светодиод, а со встроенным контроллером, типа WS2812 или совместимый (он же «NeoPixel»). Не углубляясь в детали, замечу, что для управления таким светодиодом достаточно одной линии GPIO.

В программах для ESP32 обычно определены две функции — setup и loop. Функция setup получает управление при запуске программы и выполняет необходимые инициализирующие действия. Далее вызывается функция loop, играющая роль основного цикла программы.

Сразу после запуска выполняется инициализация генератора случайных чисел функцией randomSeed:

void setup() {
  randomSeed(esp_random());
}

Далее функция loop для определения текущего цвета RGB-светодиода вызывает функцию random, возвращающую случайное число в диапазоне от 0 до 255:

uint8_t r, g, b;
do {
  r = random(0, 256);
  g = random(0, 256);
  b = random(0, 256);
} while (r + g + b < 100);

Функция random не включает верхнее значение второго параметра, поэтому здесь указано число 256, а не 255.

В условии while отбрасываются значения со слишком низкой яркостью.

Отображение цвета на светодиоде выполняет функция neopixelWrite:

neopixelWrite(RGB_PIN, r / 4, g / 4, b / 4);

Она отправляет на светодиод яркости красного, зелёного и синего компонентов. Компоненты цвета делятся на 4, так как встроенный светодиод светит слишком ярко. Деление на 4 оставляет примерно 25% от максимальной яркости.

Перед повтором цикла функция loop выполняет задержку на одну секунду:

delay(1000);

Функция esp_random возвращает 32-битное значение аппаратного генератора ESP32. В этом скетче оно используется функцией randomSeed для инициализации генератора псевдослучайных чисел Arduino. После этого функция random формирует последовательность чисел, используемых для выбора цветов. 

Поэтому после перезагрузки цвета идут в другом порядке. Аппаратный генератор выдаёт полноценно случайные числа только при работающем радиоблоке. Но для данного примера, где числа используются только для выбора цвета светодиода, это несущественно.

Подключаем кнопки

Ранее в статье “Первый шаг к микроконтроллерам: введение в цифровые схемы” я приводил примеры схем на микросхемах цифровой логики с двумя кнопками, реализующих функции 2И-НЕ, 2И, а также исключающее ИЛИ. В этих схемах выполнялась подтяжка входов микросхем к питанию с помощью резисторов. В схемах со счётчиками и сдвиговым регистром для борьбы с дребезгом контактов использовался конденсатор.

Теперь, когда в нашем распоряжении имеется плата с микроконтроллером, мы можем упростить все эти схемы. Микроконтроллер на платформе ESP32-S3-DevKitC-1-N16R8 может выполнять подтяжку большинства входов GPIO к питанию без внешнего резистора. Что же касается борьбы с дребезгом контактов кнопок, то её можно выполнить на программном уровне.

Соберите на макетной плате схему с платой микроконтроллера и двумя кнопками (рис. 17).

Рис. 17. Управление RGB-светодиодом с помощью кнопок
Рис. 17. Управление RGB-светодиодом с помощью кнопок

Плата ESP32-S3-DevKitC-1-N16R8 очень широкая, поэтому я соединил вместе две макетные платы, чтобы был доступ ко всем контактам. Если макетные платы не удаётся соединить вместе, убедитесь, что они одного и того же типа из одной поставки.

Кнопки подключены к контактам GPIO4 и GPIO5, а также к земле (к контакту GND платы микроконтроллера). Подтягивающие к питанию резисторы устанавливать не нужно.

Программа esp32s3_onboard_rgb_polling

Программа esp32s3_onboard_rgb_polling.ino управляет цветом RGB-светодиода, расположенного на плате модуля ESP32-S3-DevKitC-1-N16R8.

К выводам GPIO4 и GPIO5 подключены две кнопки. Если нажать на первую из них, вывод GPIO4 будет подключен к земле и светодиод загорится синим цветом. Аналогично, вторая кнопка, подключенная к GPIO5 и земле, зажигает светодиод зелёным цветом. И, наконец, если нажать обе кнопки одновременно, цвет изменится на голубой.

Контакты GPIO, к которым подключены кнопки, задаются в программе следующим образом:

const int BTN_BLUE  = 4;
const int BTN_GREEN = 5;

При инициализации функция setup задаёт режим подтяжки к питанию для GPIO4 и GPIO5, а также выключает RGB-светодиод с помощью функции setLed, если он горел ранее:

void setup() {
  pinMode(BTN_BLUE,  INPUT_PULLUP);
  pinMode(BTN_GREEN, INPUT_PULLUP);
  setLed(false, false);
}

Функция setLed задаёт яркость отдельных компонент цвета:

void setLed(bool green, bool blue) {
  neopixelWrite(LED_PIN, 0, green ? BRIGHT : 0, blue ? BRIGHT : 0);
}

Вся работа по управлению цветом светодиода выполняется в цикле loop:

void loop() {
  uint8_t raw = 0;
  if (digitalRead(BTN_BLUE)  == LOW) raw |= 0b01;
  if (digitalRead(BTN_GREEN) == LOW) raw |= 0b10;

  if (raw != lastRaw) {
    lastRaw = raw;
    lastChange = millis();
  }

  if (raw != stable && (millis() - lastChange) > DEBOUNCE_MS) {
    stable = raw;
    setLed(stable & 0b10, stable & 0b01);
  }

  delay(1);
}

Эта функция считывает состояние кнопок, определяет, что состояние перестало «дребезжать», и обновляет светодиод. Она вызывается в бесконечном цикле, примерно раз в миллисекунду.

В начале цикла состояние двух кнопок упаковывается в два бита переменной raw. При этом бит 0 соответствует синей кнопке, а бит 1 — зелёной.

Обе кнопки подтянуты к питанию в функции setup, поэтому нажатой считается кнопка с уровнем логического нуля LOW. Получается четыре возможных значения (в бинарном коде):

  • 0b00 — кнопки не нажаты;

  • 0b01 — нажата синяя кнопка;

  • 0b10 — нажата зелёная кнопка;

  • 0b11 — нажаты обе кнопки.

Если текущее «сырое» состояние кнопок отличается от прошлого замера, значит произошли изменения. Кнопку нажали, отпустили или контакт дребезжит.

Новое значение запоминается в переменной lastRaw, а в lastChange записывается текущее время в миллисекундах. Каждый раз фиксируется время последнего изменения. Пока контакты «звенят» и состояние скачет, lastChange постоянно обновляется:

if (raw != lastRaw) {
  lastRaw = raw;
  lastChange = millis();
}

Далее проверяется два условия — текущее состояние отличается от последнего подтверждённого, то есть что обновлять, и с последнего изменения прошло больше 30 мс, значит, дребезг закончился. После этого светодиод зажигается нужным цветом. 

Выражение stable & 0b10 выделяет бит зелёной кнопки, а stable & 0b01 — бит синей кнопки:

if (raw != stable && (millis() - lastChange) > DEBOUNCE_MS) {
  stable = raw;
  setLed(stable & 0b10, stable & 0b01);
}

Обработка прерывания

При обработке сигналов от оборудования часто применяются так называемые прерывания. Получив сигнал от аппаратуры (например, кнопки), процессор приостанавливает выполнение основной программы и передаёт управление специальной функции — обработчику прерывания Interrupt Service Routine (ISR).

Обработчик прерывания должен быстро выполнить какие-то действия, чтобы процессор мог быстро вернуться к выполнению основной программы.

В программе esp32s3_onboard_rgb_interrupt.ino обработка нажатий кнопок выполняется с помощью прерываний, что исключает необходимость поллинга (опроса в цикле, polling) состояния кнопок.

На этапе инициализации функция setup подключает обработчики прерывания onButtonChange к нашим кнопкам:

void setup() {
  pinMode(BTN_BLUE,  INPUT_PULLUP);
  pinMode(BTN_GREEN, INPUT_PULLUP);
  setLed(false, false);

  attachInterrupt(digitalPinToInterrupt(BTN_BLUE),  onButtonChange, CHANGE);
  attachInterrupt(digitalPinToInterrupt(BTN_GREEN), onButtonChange, CHANGE);
}

При любом изменении уровней на пинах, к которым подключены кнопки, основной цикл loop прерывается, и вызывается функция обработки прерываний onButtonChange:

void IRAM_ATTR onButtonChange() {
  lastEdge = millis();
  changed = true;
}

Прерывание срабатывает и при нажатии, и при отпускании кнопки, потому что в attachInterrupt задан режим CHANGE.

Функция onButtonChange выполняет минимальные действия. Она запоминает время события в переменной lastEdge и устанавливает флаг changed.

Атрибут IRAM_ATTR размещает функцию обработки прерываний в оперативной памяти (IRAM), а не в памяти FLASH. Дело в том, что на ESP32 во время записи в память FLASH кэш может быть отключён, и код из FLASH в этот момент не сможет быть выполнен.

Для обеих кнопок используется один и тот же обработчик прерываний. Какая именно кнопка вызвала прерывание, определяется позже в функции loop:

void loop() {
  if (changed && (millis() - lastEdge) > DEBOUNCE_MS) {
    changed = false;
    bool blue  = (digitalRead(BTN_BLUE)  == LOW);
    bool green = (digitalRead(BTN_GREEN) == LOW);
    setLed(green, blue);
  }
}

Здесь проверяется условие, что было изменение, и с момента последнего фронта прошло более 30 мс:

const uint32_t DEBOUNCE_MS = 30;
…
changed && (millis() - lastEdge) > DEBOUNCE_MS

Если условие выполнено, то сбрасывается флаг changed, после чего считывается актуальное состояние обеих кнопок. Результат считывания передаётся функции setLed, зажигающей светодиод нужным цветом.

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

Обратите внимание на переменные lastEdge и changed — их записывает обработчик прерывания, а читает основной цикл loop. Поэтому они объявлены с модификатором volatile:

volatile uint32_t lastEdge = 0;
volatile bool changed = false;

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

Без volatile компилятор вправе держать значение в регистре. Например, в цикле while, ожидающем флаг, основной код может никогда не увидеть изменения.

Модификатор volatile гарантирует только то, что значение будет читаться из памяти, но не делает атомарными составные действия. 

Между проверкой и сбросом changed может сработать прерывание. Здесь это практически безвредно. Если прерывание сработает между проверкой и сбросом, флаг будет потерян, но состояние кнопок читается уже после сброса, а значит, учтётся и этот фронт. Если же прерывание сработает после сброса, флаг будет установлен снова, и событие обработается на следующем проходе loop.

Подключаем светодиоды к GPIO

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

Подключите светодиоды через резисторы сопротивлением 300 Ом к выводам GPIO6 и GPIO7 (рис. 18).

Рис. 18. Добавляем светодиоды
Рис. 18. Добавляем светодиоды

Длинные ножки светодиодов (анод) подключите к резисторам, а короткие — к земле (вывод GND платы микроконтроллера).

Программа button2led.ino

Довольно простая программа button2led.ino позволяет управлять светодиодами с помощью кнопок.

При инициализации включается подтяжка контактов, к которым подключены кнопки. Кроме этого, контакты, соединённые со светодиодами, переключаются в режим вывода данных:

const int BTN1_PIN = 4;
const int BTN2_PIN = 5;
const int LED1_PIN = 6;
const int LED2_PIN = 7;
void setup() {
  pinMode(BTN1_PIN, INPUT_PULLUP);
  pinMode(BTN2_PIN, INPUT_PULLUP);
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
}

Внутри цикла loop программа с помощью digitalRead определяет состояние кнопок, и, если они нажаты, включает соответствующий светодиод, вызывая digitalWrite:

void loop() {
  digitalWrite(LED1_PIN, digitalRead(BTN1_PIN) == LOW);
  digitalWrite(LED2_PIN, digitalRead(BTN2_PIN) == LOW);
}

Здесь нет кода для устранения дребезга контактов, так как его влияние будет незаметно для глаз.

Программа esp32s3_2leds.ino

Программа esp32s3_2leds.ino плавно изменяет яркость двух светодиодов в противофазе. Когда один светодиод разгорается, другой гаснет, и наоборот (рис. 19).

Рис. 19. Качели светодиодов
Рис. 19. Качели светодиодов

Для регулировки яркости свечения светодиодов используется широтно-импульсная модуляция (ШИМ).

На этапе инициализации вызов функций ledcAttach привязывает пин к ШИМ-каналу с заданной частотой и разрядностью:

const int LED1_PIN = 6;
const int LED2_PIN = 7;
const uint32_t PWM_FREQ = 5000;   // 5 кГц, мерцания не видно
const uint8_t  PWM_BITS = 8;      // 0..255
…
void setup() {
  ledcAttach(LED1_PIN, PWM_FREQ, PWM_BITS);
  ledcAttach(LED2_PIN, PWM_FREQ, PWM_BITS);
}

Изменением яркости занимается цикл loop. В начале каждого прохода берётся остаток от деления millis на 3000 мс, то есть текущая позиция внутри трёхсекундного цикла. Эта позиция переводится в фазу от 0 до 2π и от неё вычисляется синус. 

Значение синуса лежит в диапазоне от -1 до 1. Следующее выражение сдвигает и сжимает его до диапазона от 0 до 1:

float b1 = (sinf(phase) + 1.0f) / 2.0f;

Так вычисляется яркость первого светодиода. Яркость второго считается так:

float b2 = 1.0f - b1;

За счёт этого и получается противофаза.

Далее обе величины яркости обрабатываются функцией gamma8, учитывающей нелинейное восприятие яркости глазом и возвращающей число от 0 до 255. Результат передаётся в ledcWrite, после чего обновляется скважность ШИМ на каждом пине.

В конце цикла выполняется задержка на пять миллисекунд:

const uint32_t PERIOD_MS = 3000;  // полный цикл «вдох-выдох»
uint8_t gamma8(float x) {         // x: 0.0..1.0
  return (uint8_t)(powf(x, 2.2f)  255.0f + 0.5f);
}
void loop() {
  uint32_t t = millis() % PERIOD_MS;
  float phase = (float)t / PERIOD_MS  2.0f * PI;

  // Синус превращаем из диапазона -1..1 в 0..1
  float b1 = (sinf(phase) + 1.0f) / 2.0f;
  float b2 = 1.0f - b1;           // противофаза

  ledcWrite(LED1_PIN, gamma8(b1));
  ledcWrite(LED2_PIN, gamma8(b2));
  delay(5); 
}

Выводы GPIO на ESP32-S3-DevKitC-1-N16R8

При использовании микроконтроллеров для автоматизации, важно разобраться с тем, как можно и как нельзя использовать пины GPIO. На рис. 20 показана распиновка GPIO для микроконтроллера ESP32-S3-DevKitC-1-N16R8.

Рис. 20. Распиновка GPIO для ESP32-S3-DevKitC-1-N16R8
Рис. 20. Распиновка GPIO для ESP32-S3-DevKitC-1-N16R8

Вы также можете найти распиновку для всего семейства на этой странице. Рисунок называется ESP32-S3-DevKitC-1 Pin Layout.

Программа, загруженная в память микроконтроллера, теоретически может адресовать любые пины GPIO. Однако, чтобы не возникли проблемы, нужно осторожно относиться к выбору пинов для подключения устройств. 

Без проблем можно использовать пины GPIO1, 2, 4–18, 21, 38–42 и 47. Они обозначены на рис. 20 зелёным цветом. Номера пинов совпадают с обозначением на плате микроконтроллера.

Пины, которые использовать нельзя (обозначены красным цветом или отсутствуют на рис. Х):

  • GPIO33–37 — используются PSRAM модуля R8 и недоступны.

  • GPIO26–32 — не выведены наружу и подключены к SPI FLASH и PSRAM. 

  • GPIO22–25 — у ESP32-S3 таких выводов нет физически.

Также не рекомендуется использовать пины GPIO0, GPIO3, GPIO45, GPIO46, GPIO43, GPIO44, GPIO19 и GPIO20:

  • GPIO0 — кнопка BOOT, используется для конфигурирования в момент старта микроконтроллера.

  • GPIO3 — выбор источника JTAG для отладки, конфигурирование при старте.

  • GPIO45 — выбор напряжения для SPI Flash.

  • GPIO46 — вывод журнала ROM-загрузчика.

  • GPIO43, GPIO44 — консоль через USB-UART.

  • GPIO19, GPIO20 — подключены к разъёму USB платы микроконтроллера.

Контакт GPIO48 (GPIO38 для платы версии 1.1) используется для управления RGB-светодиодом, размещённым на плате.

Подробнее о «железе» микроконтроллеров и пинах читайте в документации «ESP32-S3-DevKitC-1 v1.0 User Guide».

На все пины можно подавать напряжение не выше 3,3 В. Сигналы 5 В необходимо подавать через делитель или преобразователь уровней.

Максимальный ток, протекающий через пин, не должен превышать 20 мА (абсолютный максимум составляет около 40 мА, но это предельный, а не рабочий режим). Поэтому светодиоды подключают через резистор 220–330 Ом.

Периферия в ESP32-S3 раздаётся через GPIO-матрицу, поэтому I2C, SPI, UART, I2S и ШИМ (LEDC) можно назначить практически на любой пин GPIO. Стандартное назначение такое:

  • I2C: SDA — GPIO8, SCL — GPIO9

  • SPI: CS — GPIO10, MOSI — GPIO11, SCK — GPIO12, MISO — GPIO13

На плате ESP32-S3-DevKitC-1-N16R8 есть такая периферия:

  • Два АЦП: ADC1 (GPIO1–10) и ADC2 (GPIO11–20). Wi-Fi-драйвер периодически захватывает ADC2, из-за чего чтение может не удасться, поэтому датчики лучше подключать к ADC1.

  • Сенсорные входы (Touch) подключены к GPIO1–14.

  • GPIO15 и GPIO16 — выводы для часового кварца 32 кГц (XTAL_32K_P/N). На официальной плате ESP32-S3-DevKitC-1 кварц не установлен (смотрите принципиальную схему на сайте Espressif), поэтому эти пины свободны и работают как обычные GPIO.

  • GPIO39–42 — JTAG-выводы MTCK, MTDO, MTDI и MTMS. Если вы не подключаете внешний JTAG-отладчик, их можно использовать как обычные GPIO.

Что дальше

За рамками этой вводной статьи остались многие важные темы, которые я разберу в будущих материалах об ESP32: подключение к плате самой разной периферии, работа в сети Wi-Fi, отладка программ и многое другое.

В следующей статье мы перейдём от светодиодов к реальной нагрузке: посмотрим, как управлять через GPIO лампами, электродвигателями и реле. Выход микроконтроллера рассчитан на небольшой ток, поэтому такие устройства подключают через транзисторные ключи и драйверы. Я расскажу, как их выбрать и подключить, и покажу примеры программ для управления нагрузкой.

Оставайтесь на связи, и вы сможете войти в мир разработки встраиваемых устройств, изучая всё на практике и собирая разные конструкции.

Автор @AlexandreFrolov


НЛО прилетело и оставило здесь промокод для читателей нашего блога:
-15% на заказ нового VDS — HABRFIRSTVDS.

Положение об акции

Комментарии (0)