Над лестницей на даче я повесил ночник и приучил семью нажимать кнопку. Кнопка не выжила: свет горел до обеда, потому что «шли мимо и забыли», потом я поставил таймер — и таймер начал врать: летом темнеет в десять, зимой в пять, а он стрелял по одному времени. Два вечера я настраивал сезонные расписания, пока не понял, что прибор, который сам видит темноту, стоит сорок рублей и называется фоторезистором.
В этом уроке соберём автоматический ночник: фоторезистор и резистор 10 кОм образуют делитель напряжения, analogRead читает точку между ними, а два порога с переменной состояния решают, светить или гаснуть. Отдельно разберём гистерезис — приём, который спасает любые пороговые устройства от мигания на границе. Если условия для вас ещё в новинку, откатитесь на урок про if/else и возвращайтесь с ним же.
Фоторезистор: сопротивление, которое зависит от света
Фоторезистор (он же LDR, light dependent resistor) — керамическая табличка с змейкой полупроводника, у которой сопротивление зависит от освещённости. В темноте шкафа мой экземпляр GL5528 показал около 240 кОм, под настольной лампой — примерно 8 кОм; у вашего цифры будут другими, возможно 10–15 кОм на свету, — это нормально, разброс у детали большой. Полярности нет: какой ногой к 5V, какой к A0 — без разницы. Ножки длинные и без цоколя — в макетку вставляются напрямую; для стационарного ночника я капнул каплю термоклея, чтобы датчик смотрел в нужную сторону.
Важно понимать природу детали: LDR не измеряет люксы. Его характеристика нелинейная, между «сумерки» и «полная тьма» сопротивление меняется в разы, а между «лампа» и «пасмурный день» — почти нет. Для ночника это подарок, а не недостаток: нам не нужно число люксов, нужна уверенная разница между «в комнате светло» и «в комнате темно», и она у LDR огромная. Мой порог «свет горит в комнате» держался стабильно весь сезон: фоторезистор прощает и неточность, и пыль, и старый резистор — лишь бы разница свет/тьма оставалась большой.
Есть у фоторезистора и минус — реакция с запаздыванием: резкое включение лампы он видит через десятки миллисекунд; для ночника неважно, для фотоэкспонометра — критично. И запомните главное отличие от потенциометра из прошлого урока: у ручки делитель уже внутри корпуса, а у LDR он голый — сопротивление одно, второе плечо придётся собрать самому. Ровно этим и займёмся.
Делитель напряжения: превращаем сопротивление в число
analogRead меряет напряжение, а не сопротивление — голый LDR к входу не подключишь: второй вывод повиснет в воздухе, и показания поплывут. У моего соседа по лестничной площадке датчик стоял напротив окна — на рассвете порог срабатывал от бликов проезжающих машин; сдвинули его в тёмный угол — и вопрос закрылся. Решение — делитель: фоторезистор сверху к 5V, резистор 10 кОм снизу к GND, среднюю точку сажаем на A0. Чем больше света, тем меньше сопротивление LDR и тем больше напряжения остаётся на нижнем плече — число на A0 растёт со светом. Проверка собранного делителя занимает минуту: при свете на A0 ожидаем половину питания или больше, в темноте — десятые доли вольта; если наоборот — вы собрали зеркальный вариант.
Прикинем цифры для моего экземпляра. Ярко (LDR около 8 кОм): 5 В × 10 / (10 + 8) — примерно 2,8 В, на АЦП это около 570. Темно (240 кОм): 5 × 10 / 250 = 0,2 В — около 40. Между ними — весь диапазон сумерек, и порог где-то посередке. Точный номинал нижнего резистора не критичен: 10 кОм из набора — золотая середина; другой номинал просто сдвинет пороги, а вы их всё равно замерите по Serial.
Если собрать наоборот — 10 кОм сверху к 5V, LDR снизу к GND — логика инвертируется: темно, число на A0 растёт. Оба варианта рабочие, «правильного» нет; неправильный только один — взять чужой скетч со своими порогами, не сверив, как ведёт себя именно ваша схема. Мой рецепт: перед любыми порогами десять секунд смотреть в Serial при свете и в темноте, записать два числа — и уже от них плясать.
const int LDR_PIN = A0;const int LED_PIN = 9; void setup() { pinMode(LED_PIN, OUTPUT); Serial.begin(9600);} void loop() { int raw = analogRead(LDR_PIN); if (raw < 250) { analogWrite(LED_PIN, 160); // темно — светим } else { analogWrite(LED_PIN, 0); // светло — гасим } delay(300);}Порог, гистерезис и память состояния
Версия с одним порогом моргает, и вот почему. В сумерках показания стоят у самой границы — 248, 251, 249 — шум АЦП гоняет их туда-сюда, и ночник строчит включениями. Я сначала грешил на провод: протянул заново, переткнул макетку — не помогло. Потом допёр, что проблема не в железе, а в логике. Одного порога мало, нужны два: включаемся при raw меньше 200, выключаемся при raw больше 320. Между ними — мёртвая полоса, где ничего не меняется. Я просидел с ладонью над датчиком минут десять, пока записывал граничные значения, — лучшая инвестиция вечера: экономит часы подогнанных наугад порогов.
Чтобы два порога работали, программа должна помнить, что было секунду назад. Заводим переменную ledOn: если ночник выключен и стало темнее 200 — включаем; если горит и стало светлее 320 — гасим. Это называется гистерезисом, и вы встречали его в быту сто раз: утюг выключается на сотне градусов и включается снова на шестидесяти — если бы он щёлкал на каждом градусе, вы бы сходили с ума.
Разнос порогов подбирается по месту: моему ночнику хватило 120 единиц АЦП, а светильнику у окна я поставил 200 — там штора дышит от ветра. Мелкий зазор — снова мигание, огромный — ночник включается «позже», чем хочется. И обратите внимание, как это роднит аналоговый мир с цифровым: борьба с дребезгом кнопки — та же история про то, что решение принимается не по мгновенному значению, а с памятью и зазором; мы проходили это в уроке про debounce. Практический ориентир: зазор в 10–15% всего рабочего диапазона датчика — разумный старт, с него и начинал.
const int LDR_PIN = A0;const int LED_PIN = 9;const int DARK = 200; // темнее — включаемconst int LIGHT = 320; // светлее — выключаемint ledOn = 0; // память состояния void setup() { pinMode(LED_PIN, OUTPUT); Serial.begin(9600);} void loop() { int raw = analogRead(LDR_PIN); if (ledOn == 0 && raw < DARK) { ledOn = 1; Serial.println("Temno: vklyuchaem"); } if (ledOn == 1 && raw > LIGHT) { ledOn = 0; Serial.println("Svetlo: vyklyuchaem"); } analogWrite(LED_PIN, ledOn * 160); delay(300);}int raw = analogRead(LDR_PIN);float u = raw * 5.0 / 1023.0; // напряжение в вольтахSerial.print("U na A0: ");Serial.println(u, 2); // два знака после запятой // У меня: светло ~2.8 В (raw ~570), темно ~0.2 В (raw ~40)| Сборка | Светло, LDR ≈ 8 кОм | Сумерки, LDR ≈ 50 кОм | Темно, LDR ≈ 240 кОм |
|---|---|---|---|
| LDR к 5V, 10 кОм к GND | raw ≈ 570 (2.8 В) | raw ≈ 170 (0.8 В) | raw ≈ 40 (0.2 В) |
| 10 кОм к 5V, LDR к GND | raw ≈ 455 (2.2 В) | raw ≈ 850 (4.2 В) | raw ≈ 980 (4.8 В) |
- Замерьте свои пороги: подносите и убирайте ладонь над LDR, глядя в Serial; запишите число «темно» и «светло» и разнесите пороги на треть этого расстояния.
- Подержите датчик ровно на границе: с зазором 120 ночник спокоен, сузьте пороги до 10 единиц — и увидите в Serial, как решения идут подряд. Верните зазор обратно.
- Сделайте включение плавным: в ветке включения поставьте analogWrite(LED_PIN, map(raw, DARK, 0, 60, 255)) — чем темнее в комнате, тем ярче горит ночник.
- Переверните делитель и перепишите пороги: убедитесь, что строка «Svetlo: vyklyuchaem» появляется при тех же условиях жизни датчика, а не при тех же числах.
- Добавьте контрольное напряжение: втащите в основной скетч строку с float u из кода выше — пусть рядом с raw печатается вольтаж, так делитель перестаёт быть чёрным ящиком.
- Фоторезистор — резистор, меняющийся от света: у типового GL5528 в темноте сотни килоом, при лампе единицы килоом; разброс большой, свои числа меряются Serial'ом.
- analogRead не видит сопротивление — нужен делитель: LDR плюс резистор 10 кОм; от порядка плеч зависит, растёт число со светом или падает.
- Один порог — гарантированное мигание на границе: шум АЦП гоняет показания туда-сюда, и ночник строчит.
- Гистерезис — два порога с зазором (вкл 200, выкл 320) плюс переменная состояния: включение и выключение живут в разных точках шкалы.
- Тот же приём управляет термостатами и любыми пороговыми схемами: решение принимается с памятью, а не по мгновенному значению. Термостат в теплице и ночник живут по одному закону: между «включить» и «выключить» обязана быть зона молчания.
- 1. LDR к 5V, резистор 10 кОм к GND, A0 между ними. Зажгли в комнате лампу. Что станет с raw?
- 2. Ночник моргает на рассвете: включается и тут же гаснет. Основная причина?
- 3. Зачем в скетче переменная ledOn?
- 4. Утюг гаснет при 100 °C и включается снова при 60 °C. Как называется такой зазор?
Ночник стал автоматом: сам видит темноту, сам решает, а гистерезис не даёт ему нервничать на границе. Единственное «но»: число 250 на экране — всё ещё абстракция, между ним и физической величиной пропасть. Следующий урок закроет и её: термистор NTC — родственник нашего фоторезистора, только чувствительный к теплу, — и формула Стейнхарта превратит сырые единицы АЦП в настоящие градусы Цельсия с десятыми долями. Возьмите с собой привычку сверять схему с Serial и дружбу с float — пригодятся обе. А если лень ждать темноты, направьте на датчик фонарик: лог в Serial покажет оба порога за минуту.
Частые вопросы по теме урока
- LDR к 5V, резистор 10 кОм к GND, A0 между ними. Зажгли в комнате лампу. Что станет с raw?
- Светло → LDR около 8 кОм → нижнее плечо 10 кОм получает примерно 2.8 В → raw около 570 против 40 в темноте. Соберёте делитель зеркально — логика развернётся. Вырастет — сопротивление LDR упало, на A0 стало больше напряжения
- Ночник моргает на рассвете: включается и тут же гаснет. Основная причина?
- У границы показания прыгают на единицы, а один порог принимает решение по каждому отсчёту. Два порога с зазором и память состояния убирают строчки подряд. Один порог: шум АЦП около границы дёргает решение
- Зачем в скетче переменная ledOn?
- Гистерезис — это память: «включаем, если темно И сейчас выключено». Без ledOn оба порога сводятся к одному, и мигание возвращается. Хранит состояние между проходами loop() — без неё два порога не работают
- Утюг гаснет при 100 °C и включается снова при 60 °C. Как называется такой зазор?
- Два порога вместо одного с зазором между ними — классический гистерезис; та же идея, что лечит мигание ночника и дребезг кнопки. Гистерезис — два порога с зазором вместо одного