Сенсорная панель на дверцу корпуса: под фольгой — площадка, касание — пальцем. Готовые библиотеки тянут свой тайминг и свои допущения, а хочется понимать, что там вообще происходит, и ужать код до пары десятков строк. Классика без библиотеки — измерение времени заряда: обкладку разряжают пином, отпускают, и она заряжается через большой резистор, пока Arduino циклом считает, сколько шагов digitalRead ушло до переключения входа. Палец рядом — ёмкость обкладки растёт, время заряда длиннее — разница в счёте и есть кнопка.
Физика тут школьная: RC-цепь, где C — обкладка-площадка в пару сантиметров (порядка 10-50 пФ), R — резистор 1-10 МОм. Заряд до порога логической единицы занимает время порядка RC: 4,7 МОм на 20 пФ — около 60-70 мкс, цикл с digitalRead на 16 МГц успевает насчитать сотню-другую шагов. Касание добавляет к обкладке ещё 10-30 пФ — счёт подрастает в полтора-два раза. А вот порог между «фон» и «касание» константой быть не может: он зависит от длины провода, влажности воздуха и того, что под площадкой.
План урока: код замера заряда и почему цикл с digitalRead честнее delay, автокалибровка по скользящему минимуму, чтобы дрейф влажности не выдавал фантомные касания, и честные границы техники — против экранированных библиотек она проигрывает по дальности, но для дверцы корпуса хватает с запасом. Микросекундная база для тех, кто захочет мерить точнее, — в уроке про точные замеры micros().
Замер времени заряда без библиотеки
Последовательность из трёх шагов. Первый: пин OUTPUT LOW — разрядить обкладку в землю, дать 20-50 мкс на полный разряд. Второй: пин INPUT — обкладка отпущена; резистор 4,7 МОм от площадки к 5 В тянет её вверх, и напряжение на площадке ползёт к единице с постоянной времени RC. Третий: цикл while (digitalRead(PIN) == LOW) shag++; — счёт остановится, когда вход пересечёт порог единицы. Больше ёмкость — дольше заряд — больше счёт: вся арифметика кнопки живёт в одном целочисленном аккумуляторе, без float и без таймеров.
Цены и грабли цикла. Итерация с digitalRead на 16 МГц занимает порядка 0,4-0,5 мкс — счёт сотни-другие на сухой площадке, у меня на столе 120-180 при 4,7 МОм и площадке 20×20 мм. Вариант с delayMicroseconds(1) внутри цикла режет скорость вдвое, зато счёт переносимее между экземплярами чипа. Три места, где ломается замер: неполный разряд перед отпуском — остаточный заряд съедает полдиапазона; длинный провод к площадке добавляет паразитные пФ и поднимает фон; прерывания внутри цикла живут своей жизнью — счёт дрожит на единицы, лечится средним из трёх замеров, приём из урока про фильтр ADC.
Порог как константа — ловушка среды: сегодня сухо, счёт 120, порог 200 — работает; завтра дождь и фон уехал на 180 — кнопка «нажата» без всякого касания. Автокалибровка по скользящему минимуму решает это без библиотек: минимум бежит за фоном вниз мгновенно, вверх — медленно, по единице за цикл; порог = минимум + треть минимума. Касание поднимает счёт выше порога — вердикт; без касания минимум подтягивается к фону, и порог дышит вместе с влажностью. А вердикт держи устойчивым, как в уроке про антидребезг четырьмя способами: касание засчитывается после нескольких замеров подряд, не после первого прыжка.
// Ёмкостная кнопка без библиотеки: время заряда + скользящий минимум.// Движок симулятора не заряжает обкладки - скетч для Arduino IDE.const byte PIN_SENSOR = 4; // площадка; резистор 4,7 МОм от площадки к 5 Вconst byte PIN_LED = 8; long skolzMin = 0; // скользящий минимум счётаlong ustojchivyh = 0; // сколько замеров подряд выше порога long meritZaryad() { pinMode(PIN_SENSOR, OUTPUT); digitalWrite(PIN_SENSOR, LOW); // полный разряд обкладки delayMicroseconds(50); pinMode(PIN_SENSOR, INPUT); // отпустили: заряд через 4,7 МОм long shag = 0; while (digitalRead(PIN_SENSOR) == LOW && shag < 30000) shag++; return shag; // больше счёт - больше ёмкость} void setup() { Serial.begin(9600); pinMode(PIN_LED, OUTPUT); for (byte i = 0; i < 16; i++) { // стартовая калибровка по воздуху long t = meritZaryad(); if (skolzMin == 0 || t < skolzMin) skolzMin = t; } Serial.print("Стартовый минимум: "); Serial.println(skolzMin);} void loop() { long t = meritZaryad(); long porog = skolzMin + skolzMin / 3; // порог: минимум + треть (+30%) bool kasanie = t > porog; if (t < skolzMin) skolzMin = t; // минимум вниз - мгновенно else skolzMin += kasanie ? 1 : 3; // вверх всегда, в касание - медленней ustojchivyh = kasanie ? ustojchivyh + 1 : 0; bool klik = ustojchivyh >= 3; // вердикт после трёх замеров подряд digitalWrite(PIN_LED, klik ? HIGH : LOW); Serial.print("t="); Serial.print(t); Serial.print(" min="); Serial.print(skolzMin); Serial.print(" porog="); Serial.println(porog); if (klik) Serial.println("KASANIE"); delay(30);}Разбор по строкам. meritZaryad() начинается с разряда: OUTPUT LOW на 50 мкс — меньше не давай, неполный разряд режет диапазон. Потолок 30000 в цикле — страховка: при отломанном резисторе заряд мгновенный и счёт 0-1 (площадка мертва — проверь монтаж), а без потолка оборванная площадка держала бы цикл вечно. Резистор именно внешний, к 5 В: внутренняя подтяжка INPUT_PULLUP — это 20-50 кОм, она заряжает обкладку за пару микросекунд, счёт выходит в единицы, и порог между «фон» и «касание» не различить. Вердикт — после трёх замеров подряд выше порога: одиночный прыжок от наводки кнопку не включает.
Почему минимум, а не среднее: среднее ползёт за пальцем при долгом касании и проглатывает его; минимум при касании поднимается едва-едва, а дрейф догоняет асимметрией скорости — вниз мгновенно (фон провалился — порог провалился следом), вверх по единице за цикл, в касание ещё медленнее. Положи руку рядом с площадкой: фон уедет на десятки счётов, минимум догонит за сотни циклов, порог переведётся сам. Внутри скобок скользящего минимума и живёт вся разница между сенсором, который держит строй неделю, и сенсором, который к утру врёт.
Честные границы. Против библиотечных схем с экранированием (пара send/receive, как в CapacitiveSensor) самодельный замер проигрывает по дальности: та видит касание через 3-5 мм дерева и накладку, эта — прямой контакт или миллиметр пластика. Несколько площадок — та же логика на каждом пине, а опрашивать их удобнее пачкой регистров из урока про PINB и пачку входов. Неблокирующий цикл замеров веди по millis() из урока про millis() вместо delay() — замер раз в 30-50 мс и вердикт с гистерезисом. Для дверцы корпуса это пять минут пайки, ноль зависимостей и полностью прозрачная арифметика, которую ты сам и чинишь, когда что-то плывёт.
| Настройка | Счёт (порядок) | Следствие |
|---|---|---|
| внутренняя подтяжка 20-50 кОм | 3-10 | счёт слишком мелкий, порог не различить |
| резистор 1 МОм | 20-60 | работает, но запас против дрейфа тонкий |
| резистор 4,7 МОм | 100-250 | рабочий коридор: касание даёт ×1,5-2 |
| резистор 10 МОм | 300-700 | чувствительно к влажности, обязателен фильтр счёта |
| провод к площадке +20 см | +10-20% к фону | автокалибровка обязательна |
- 1. Счёт заряда сухой площадки — 120, с пальцем — 190. Какой порог даст автокалибровка по скользящему минимуму?
- 2. Почему минимум бежит за фоном вниз мгновенно, а вверх — медленно?
- 3. Чем самодельный замер заряда уступает библиотеке с экранированием, вроде CapacitiveSensor?
- разряд OUTPUT LOW → pinMode INPUT → while (digitalRead == LOW) shag++: счёт пропорционален ёмкости обкладки
- резистор к 5 В бери 1-10 МОм: внутренняя подтяжка 20-50 кОм заряжает за микросекунды, счёт не различить
- порог константой не живёт: скользящий минимум (вниз мгновенно, вверх медленно) + треть минимума
- касание засчитывай после 3 устойчивых замеров; против экранированных библиотек — проигрыш в дальности, выигрыш в нуле зависимостей
Частые вопросы по теме урока
- Счёт заряда сухой площадки — 120, с пальцем — 190. Какой порог даст автокалибровка по скользящему минимуму?
- Порог = минимум + минимум/3. При фоне 120 это 160: касание 190 выше порога, фон 120 — ниже. Константный порог не дышит вместе со средой — его место и занимает скользящий минимум. минимум + треть: 120 + 40 = 160, запас над фоном есть
- Почему минимум бежит за фоном вниз мгновенно, а вверх — медленно?
- Асимметрия — весь смысл автокалибровки: минимум обязан догонять дрейф вниз и ползти вверх неторопливо, иначе долгое касание поднимет порог и вердикт исчезнет. быстрый подъём минимума догонит палец и проглотит касание; вниз мгновенно — чтобы фон провалился вместе с порогом
- Чем самодельный замер заряда уступает библиотеке с экранированием, вроде CapacitiveSensor?
- Честная граница техники: без экранирования и пары send/receive чувствительность ниже. Для площадки на корпусе с прямым касанием этого хватает; для клавиатуры под накладкой — уже библиотека. дальность и стабильность: экранированные схемы видят касание через 3-5 мм дерева, самодельный — контакт или миллиметр пластика