Ночник в акриловом корпусе довёл меня до тач-пинов. Кнопку нужно было выводить наружу: сверлить панель, вклеивать тактовую, герметизировать щель — и через полгода кнопка всё равно заедала от пыли. Второй вариант с ёмкостным модулем TTP223 работал, но сидел на соплях из двухстороннего скотча, и его периодически уводило от чувствительности, когда корпус стоял на металлической полке. К третьей итерации я наконец прочитал даташит на сам чип и обнаружил, что ESP32 умеет тач прямо из коробки.
Внутри кристалла живут десять каналов ёмкостного ввода, T0-T9, и к каждому можно прикрутить кусок фольги, пружину от кнопки или просто медную пластинку — получится сенсор без единой движущейся части. В этом уроке разберём, какие GPIO соответствуют каналам и какие из них трогать опасно, почему показания touchRead() при касании падают, а не растут, как ставить порог не на глазок, а через калибровку по фону, и почему чужие примеры с константным порогом глючат у каждого второго. Скетч в конце — это рабочая тач-кнопка с перекалибровкой с клавиатуры, я использую её как заготовку для всех сенсорных панелей.
Как ESP32 чувствует палец: T0-T9 и порог
Периферия тача меряет время заряда ёмкости пина: пальцуешь электрод — суммарная ёмкость растёт, время заряда растёт, а число, которое возвращает touchRead(), становится МЕНЬШЕ. Это главное, о что спотыкаются все, кто пришёл с TTP223: у ESP32 касание — это падение значения, сравнение делается «v < порог», и порог берётся как доля от «сухого» фона. Фон зависит от длины провода, толщины корпуса и даже влажности воздуха, поэтому честная схема такая: в setup() снимаем эталон, в loop() сравниваем с его половиной, по команде с Serial перекалибровываем.
Каналы раскиданы по GPIO неравномерно, и тут две мины. T1 — это GPIO0, то есть пин кнопки BOOT: повесить на него электрод можно, но во время прошивки палец на сенсоре уводит плату в режим загрузки. T5 — GPIO12, strapping-пин: уровень на нём при подаче питания выбирает напряжение flash, и ёмкость фольги иногда мешает плате стартовать. Рабочие лошадки — T0 (GPIO4), T6-T9 (GPIO14, 27, 33, 32): свободные, далеко от strapping, и к ним удобно вести провода. Полную таблицу соответствия собрал ниже, а про то, какие пины вообще опасны, я подробно писал в уроке про распиновку DevKit.
// Тач-кнопка на фольге: T0 = GPIO4, светодиод D2 DevKit V1.const int PIN_TOUCH = T0; // макрос T0 равен 4const int PIN_LED = 2; // встроенный синий на DevKit V1 int fon = 0; // "сухое" значение, сняли в setupbool ledOn = false;unsigned long tLast = 0; void setup() { Serial.begin(115200); delay(300); fon = touchRead(PIN_TOUCH); // эталон без касания Serial.print("fon: "); Serial.println(fon); pinMode(PIN_LED, OUTPUT);} void loop() { int v = touchRead(PIN_TOUCH); // при касании v ПАДАЕТ bool touch = v < fon / 2; // просел вдвое - это касание if (touch && millis() - tLast > 300) { // пауза против дребезга tLast = millis(); ledOn = !ledOn; digitalWrite(PIN_LED, ledOn); Serial.print("v="); Serial.print(v); Serial.print(" -> led "); Serial.println(ledOn); } // 'k' в мониторе порта: перекалибровать фон на новом месте if (Serial.available() && Serial.read() == 'k') { fon = touchRead(PIN_TOUCH); Serial.print("novyj fon: "); Serial.println(fon); }}Разбор по строкам. kalibr снимается один раз в setup(), когда палец далеко от электрода, и дальше служит эталоном: порог «fon / 2» срабатывает, когда значение просело вдвое, — такая пропорция держится и на фольге под 2 мм акрила, и на голом проводе. Задержка 300 мс на millis() гасит дребезг пальца: рука на сенсоре дрожит, и без паузы одно касание успевает переключить LED три раза. Команда «k» в мониторе порта — моя любимая часть: переставил корпус, удлинил провод, сменилась погода — нажал k, фон переснялся, порог поехал сам.
Скорость порта 115200 — не прихоть: тач-скетчи часто живут рядом с WiFi-логами, а на 9600 лог успевает тормозить цикл. Если захочешь перенести кнопку на другой канал — поменяй T0 на T6 или T9 и поправь порог перекалибровкой, код трогать больше не нужно.
| Канал | GPIO | Можно ли вешать электрод |
|---|---|---|
| T0 | GPIO4 | да — лучший выбор, рядом с USB |
| T1 | GPIO0 | нет — пин кнопки BOOT, мешает прошивке |
| T2 | GPIO2 | осторожно — strapping, там же встроенный LED |
| T3 | GPIO15 | осторожно — strapping, шумный пин |
| T4 | GPIO13 | да |
| T5 | GPIO12 | нет — strapping flash-напряжения, плата может не стартовать |
| T6 | GPIO14 | да |
| T7 | GPIO27 | да |
| T8 | GPIO33 | да |
| T9 | GPIO32 | да |
- 1. Что делает touchRead() при касании электрода пальцем?
- 2. Почему порог тача не задают константой вроде «40» в чужих примерах?
- touchRead() ПАДАЕТ при касании: сравнение «v < фон/2», а не наоборот
- порог = доля от калибровочного фона, константа из чужого примера не работает
- T0 (GPIO4), T6-T9 — рабочие каналы; T1 (GPIO0) и T5 (GPIO12) не трогать
- электрод: фольга 2-5 см², провод до 20-30 см, перекалибровка на новом месте
Частые вопросы по теме урока
- Что делает touchRead() при касании электрода пальцем?
- Палец добавляет ёмкость, заряд длится дольше, а счётчик времени заряда у ESP32 при этом уменьшается. Отсюда инвертированное сравнение «v < порог», о которое ломаются все, кто пришёл с TTP223. значение падает — сравниваем «v < порог»
- Почему порог тача не задают константой вроде «40» в чужих примерах?
- «Сухой» фон плавает от длины провода, толщины корпуса и погоды. Рабочая схема: эталон в setup(), порог как доля от него и перекалибровка по команде с Serial. фон зависит от провода, корпуса и влажности — его нужно калибровать