Курс Arduino с нуля · Датчики
Урок 15. Датчик света: ночник включается сам
Фоторезистор превращает освещённость в число. Собираем автоматический ночник, измеряем свои пороги и знакомимся с гистерезисом.
Что понадобится
- Плата Uno из набора
- Кабель USB
- Макетная плата
- Фоторезистор
- Резистор 10 кОм
- Светодиод (любого цвета)
- Резистор 470 Ом
- 5 проводов «папа-папа»
Все детали есть в обучающем наборе, по которому идёт курс.
В прошлом уроке ползунком двигали вы. А если поставить деталь, у которой сопротивление меняется само — от света, тепла или давления? Тогда получится настоящий датчик, и устройство начнёт реагировать на мир без единого прикосновения.
Сегодня соберём автоматический ночник: стало темно — светильник включился, рассвело — погас. И по дороге разберёмся с приёмом, без которого ни один настоящий датчик не работает нормально.
Знакомимся с фоторезистором
В наборе их целых пять — маленькие оранжевые диски с тёмной змейкой на лицевой стороне и двумя ножками. Змейка — это две гребёнки электродов, между которыми лежит светочувствительный слой.
Правило простое: чем больше света, тем меньше сопротивление. В темноте фоторезистор может иметь сотни килоом, на ярком свету — меньше килоома. Полярности у него нет: втыкайте любой ножкой куда угодно.
Почему сопротивление не измерить напрямую? Плата умеет измерять только напряжение. Поэтому сопротивление всегда превращают в напряжение — тем самым делителем, который мы разобрали в прошлом уроке. Только теперь вместо ползунка работает свет.
Делитель со светом вместо руки
Ставим фоторезистор сверху (к плюсу), а обычный резистор 10 кОм снизу (к земле). Точку между ними отдаём аналоговому входу:
Получается удобная логика: светло — большое число, темно — маленькое. Если поменять детали местами, всё перевернётся, и это тоже нормально — просто условие в программе будет с другим знаком.
Почему именно 10 кОм? Делитель работает точнее всего, когда постоянный резистор близок к сопротивлению датчика в середине рабочего диапазона. Для комнатного освещения это как раз около 10 кОм. Поставите 1 кОм — прибор станет различать только яркий свет; поставите 100 кОм — только глубокую темноту.
Собираем схему
Отключите USB. Резистор нужен на 10 кОм (кольца коричневое, чёрное, оранжевое) — не перепутайте с 470 Ом для светодиода. Светодиод остаётся там же, где был.
Проверьте перед включением: обе детали приходят в один и тот же столбик — именно там середина делителя; жёлтый провод идёт из этого же столбика на A0; резисторы не перепутаны местами.
Шаг 1. Узнаём свои числа
Самый важный шаг урока — и его почти всегда пропускают. Числа, которые выдаёт ваш датчик в вашей комнате, не совпадут ни с чьими другими. Они зависят от лампы, от окна, от времени суток. Поэтому сначала — измерить.
const int LIGHT = A0;
void setup() {
Serial.begin(9600);
Serial.println("Освещённость:");
}
void loop() {
int light = analogRead(LIGHT);
Serial.println(light);
delay(200);
}
Прошейте, откройте монитор порта и запишите три числа:
- обычный свет в комнате — сколько показывает;
- накрыли датчик ладонью — сколько стало;
- посветили фонариком телефона — до скольких подскочило.
Например, может получиться так: свет — 620, ладонь — 180, фонарик — 950. Ваши числа будут другими, и это нормально. Именно они дальше станут порогами в программе.
Загляните на вкладку «График». Медленно проводите ладонью над датчиком — и увидите живую кривую освещённости. Это лучший способ понять, как датчик реагирует: быстро ли, плавно ли, есть ли шум.
Шаг 2. Первый ночник
Теперь у нас есть числа, и можно назначить порог: темнее его — включаем свет.
const int LIGHT = A0;
const int LED = 10;
// ПОДБЕРИТЕ СВОЁ ЧИСЛО по монитору порта: середина между
// «светло в комнате» и «накрыл датчик ладонью».
const int DARK = 400;
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
}
void loop() {
int light = analogRead(LIGHT);
if (light < DARK) {
digitalWrite(LED, HIGH); // стало темно — включаем свет
} else {
digitalWrite(LED, LOW); // светло — свет не нужен
}
Serial.println(light);
delay(100);
}
Число DARK возьмите своё: примерно посередине между «нормальный свет» и «накрыл ладонью». В нашем примере это было бы около 400.
Прошейте и накройте датчик ладонью — светодиод загорится. Уберите руку — погаснет. Устройство работает само, без кнопок. Поздравляем: вы собрали свой первый прибор с датчиком.
Шаг 3. Лампа дёргается — и это не поломка
А теперь поднесите ладонь медленно, остановившись ровно на границе срабатывания. Светодиод начнёт нервно моргать: то включится, то выключится, несколько раз в секунду.
Причина знакомая: любой датчик слегка шумит, а мы принимаем решение по одному-единственному числу. Когда освещённость топчется вокруг порога, малейшее дрожание перебрасывает её то выше, то ниже.
Лечится это красивым приёмом: сделать два порога вместо одного. Включать, когда стало заметно темнее, а выключать — только когда стало заметно светлее. Между ними — «зона покоя», в которой ничего не меняется.
const int LIGHT = A0;
const int LED = 10;
// Включаем, когда стало темнее 380, а выключаем только когда
// стало светлее 460. Между ними — «зона покоя».
const int TURN_ON = 380;
const int TURN_OFF = 460;
bool lampOn = false;
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
}
void loop() {
int light = analogRead(LIGHT);
if (!lampOn && light < TURN_ON) {
lampOn = true;
Serial.println("темнеет — включаю");
}
if (lampOn && light > TURN_OFF) {
lampOn = false;
Serial.println("светло — выключаю");
}
digitalWrite(LED, lampOn);
delay(50);
}
Приём называется гистерезисом. Слово громкое, а идея простая: у прибора появляется характер — он не меняет решение из-за мелочей. Ровно так работает термостат в утюге, холодильник и обогреватель: включаются при 18 градусах, выключаются при 22, а не дёргаются вокруг двадцати.
Почему условия написаны с lampOn? Потому что решение зависит не только от освещённости, но и от того, что происходит сейчас. Пока лампа выключена, нас интересует только нижний порог; пока горит — только верхний. Программа помнит своё состояние — тот же приём, что и с режимами в тринадцатом уроке.
Шаг 4. Плавный ночник
«Включено или выключено» — грубовато для света. Сделаем как в хороших светильниках: чем темнее в комнате, тем ярче горит лампа.
const int LIGHT = A0;
const int LED = 10;
// Границы, снятые по монитору порта: как бывает у вас темно и светло.
const int DARK = 200;
const int BRIGHT = 700;
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
int light = analogRead(LIGHT);
// Диапазон перевёрнут: маленькая освещённость — большая яркость.
int value = map(light, DARK, BRIGHT, 255, 0);
// constrain не даёт значению выйти за пределы 0…255.
value = constrain(value, 0, 255);
analogWrite(LED, value);
delay(20);
}
Здесь map работает наоборот: диапазон яркости записан от 255 к 0. Значит, тёмному значению датчика соответствует полная яркость, а светлому — ноль. Ровно об этом мы говорили в прошлом уроке.
И новая команда — constrain. Она удерживает значение в заданных границах: что меньше нуля, станет нулём, что больше 255 — станет 255. Без неё в очень яркий полдень map выдал бы отрицательное число, и analogWrite повёл бы себя странно.
Правило на будущее. Всё, что пришло из внешнего мира, ограничивайте. Датчик может выдать больше или меньше, чем вы ожидали: солнце светит ярче лампы, провод дрогнул, деталь нагрелась. Одна строчка constrain спасает от целого класса странных багов.
Не работает? Разбираемся
- Число не меняется, что ни делай. Проверьте, что обе детали приходят в один столбик и что провод на A0 идёт именно из этого столбика. Если делитель разорван, вход снова «висит в воздухе».
- Значение всегда около 1023 или около 0. Скорее всего, вместо 10 кОм стоит 470 Ом или наоборот перепутаны местами фоторезистор и резистор. Сравните кольца: 10 кОм — коричневое, чёрное, оранжевое.
- Светодиод горит всегда. Порог
DARKвзят «из интернета», а не из вашего монитора. Померьте свои числа и поставьте своё. - Свет включается, когда светло, и наоборот. Либо детали в делителе поменяны местами, либо в условии нужен знак «больше» вместо «меньше». И то и другое — нормально, выберите, что понятнее.
- Лампа моргает на границе. Это тот самый случай, ради которого мы делали гистерезис. Разведите пороги подальше друг от друга.
- Ночник реагирует на самого себя. Классика жанра: светодиод светит на фоторезистор, тот видит свет и выключает лампу. Разверните детали в разные стороны или отодвиньте подальше.
Задания для тренировки
- РазминкаПусть плата печатает в монитор не число, а слово: «день», «сумерки» или «ночь». Печатать только при смене состояния.
- ПосложнееСделайте, чтобы ночник включался не мгновенно, а плавно разгорался за пару секунд — и так же плавно гас. Подсказка: храните текущую яркость в переменной и двигайте её к цели по одному шагу за проход.
- Со звёздочкой ⭐Избавьтесь от «магических чисел»: пусть устройство само измеряет самое тёмное и самое светлое при включении, а потом работает по этим границам. Разбор ниже.
Показать разбор «самокалибровка»
Идея взрослая и очень полезная: прибор не должен требовать, чтобы его настраивали вручную под каждую комнату. Пусть при включении он несколько секунд смотрит по сторонам и сам решает, что считать темнотой, а что светом.
const int LIGHT = A0;
const int LED = 10;
int minLight = 1023;
int maxLight = 0;
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
Serial.println("Калибровка: 5 секунд закрывайте и открывайте датчик...");
// Пять секунд запоминаем, каким бывает самое тёмное и самое светлое.
unsigned long start = millis();
while (millis() - start < 5000) {
int light = analogRead(LIGHT);
if (light < minLight) {
minLight = light;
}
if (light > maxLight) {
maxLight = light;
}
}
Serial.print("темнее всего: ");
Serial.println(minLight);
Serial.print("светлее всего: ");
Serial.println(maxLight);
Serial.println("Готово, работаю.");
}
void loop() {
int light = analogRead(LIGHT);
int value = map(light, minLight, maxLight, 255, 0);
analogWrite(LED, constrain(value, 0, 255));
delay(20);
}
Вся калибровка живёт в setup() — она выполняется один раз. Внутри работает while: цикл крутится, пока с начала не прошло 5000 миллисекунд, и всё это время запоминает рекордно тёмное и рекордно светлое значения.
Обратите внимание на начальные значения: minLight = 1023 и maxLight = 0. Они специально выставлены «наоборот», чтобы первое же измерение их сбило. Если начать с нулей, минимум так и останется нулём.
Так устроены настоящие приборы: датчик линии у робота калибруют, проведя его над чёрным и белым; микрофон подстраивается под тишину в комнате. Один раз научили — дальше работает где угодно.
Что мы узнали
- Фоторезистор — резистор, которым управляет свет: больше света — меньше сопротивление.
- Сопротивление превращают в напряжение делителем; постоянный резистор берут близким к рабочему сопротивлению датчика — здесь 10 кОм.
- С новым датчиком всегда начинайте с измерения своих чисел в мониторе порта. Чужие пороги не работают.
- Гистерезис — два порога вместо одного — избавляет прибор от дрожания на границе.
constrainудерживает значение в границах и спасает от неожиданных данных с датчика.
Наш ночник реагирует на свет — то есть на величину, которая меняется плавно. Но бывают датчики-«кнопки»: они не измеряют, а просто замыкаются, когда что-то произошло. В следующем уроке возьмём сразу два таких — датчики наклона и вибрации — и соберём из них сигнализацию.
Частые вопросы
Как работает фоторезистор?
Это резистор, сопротивление которого зависит от света: чем ярче, тем меньше сопротивление. В темноте оно может быть в сотню раз больше, чем на солнце. Полярности у фоторезистора нет, подключать его можно любой стороной.
Какой резистор ставить в пару к фоторезистору?
Обычно 10 кОм — он близок к сопротивлению фоторезистора при комнатном освещении, поэтому делитель даёт самый широкий размах показаний. С резистором 1 кОм прибор станет различать только яркий свет, со 100 кОм — только темноту.
Почему у меня другие числа, чем в уроке?
Так и должно быть: показания зависят от конкретного датчика, лампы, окна и времени суток. Поэтому в уроке пороги не даются готовыми — их измеряют в мониторе порта для своей комнаты.
Что такое гистерезис и зачем он нужен?
Это два порога вместо одного: включаем при одном значении, выключаем при заметно другом. Между ними прибор ничего не меняет. Без гистерезиса лампа начинает нервно моргать, когда освещённость топчется около порога.
