Курс Arduino с нуля · Датчики
Урок 16. Датчики наклона и вибрации: сигнализация
Есть датчики, которые не измеряют, а просто замыкаются. Учимся ловить событие длиной в миллисекунду, считать стуки и собираем сигнализацию.
Что понадобится
- Плата Uno из набора
- Кабель USB
- Макетная плата
- Датчик наклона
- Датчик вибрации
- 2 светодиода
- 2 резистора 470 Ом
- 5 проводов «папа-папа»
Все детали есть в обучающем наборе, по которому идёт курс.
Крутилка и фоторезистор выдавали число — плавную величину, которую нужно измерять. Но огромная часть датчиков устроена иначе: они просто замыкаются, когда что-то произошло. По сути это кнопки — только нажимает их не палец, а мир вокруг.
Сегодня возьмём сразу два таких датчика из набора: наклона и вибрации. Оба подключаются как обычная кнопка, а вот программы для них нужны совсем разные — и это самое интересное.
Знакомимся с деталями
Датчик вибрации — синий цилиндрик с надписью на боку. Внутри полая трубка и несколько металлических шариков. В покое шарики лежат и замыкают выводы, а от толчка подпрыгивают, и контакт на мгновение рвётся. Обозначают его буквами SV.
Датчик наклона — зелёный цилиндрик. Внутри два шарика разного размера, которые перекатываются при повороте детали. Обозначение — ST.
Важная мелочь для датчика вибрации. Его нужно ставить с небольшим наклоном, градусов на 10–30. Строго вертикально или строго горизонтально шарики лежат неустойчиво, и датчик начинает срабатывать сам по себе.
Собираем схему
Раз оба датчика — выключатели, подключаем их точно так же, как кнопку в одиннадцатом уроке: один вывод на цифровой вход с INPUT_PULLUP, другой на землю. Никаких внешних резисторов.
Проверьте перед включением: каждый датчик одним концом сидит в шине «−», другим — в своём столбике; из этого же столбика идёт провод на плату; светодиоды стоят длинной ножкой к своим резисторам.
Шаг 1. Слушаем оба датчика
Как всегда с новой деталью — сначала просто смотрим, что она говорит:
const int TILT = 2; // датчик наклона
const int VIBRO = 3; // датчик вибрации
void setup() {
Serial.begin(9600);
// Оба датчика — обычные замыкатели, подключаем как кнопки.
pinMode(TILT, INPUT_PULLUP);
pinMode(VIBRO, INPUT_PULLUP);
Serial.println("наклон вибрация");
}
void loop() {
Serial.print(digitalRead(TILT));
Serial.print(" ");
Serial.println(digitalRead(VIBRO));
delay(100);
}
Откройте монитор порта. Наклоняйте макетную плату из стороны в сторону — первое число будет переключаться между 1 и 0. Стукните пальцем по столу — второе число на мгновение мигнёт единицей.
Обратите внимание на важную деталь: в покое датчик вибрации показывает 0 — он замкнут. Единица появляется, только когда его тряхнули. Логика обратная привычной, и это нормально.
Шаг 2. Наклон: определяем сторону
Наклон — «медленный» датчик: его состояние держится сколько угодно, пока деталь лежит в одном положении. С таким работать просто:
const int TILT = 2;
const int LED_A = 10;
const int LED_B = 9;
void setup() {
pinMode(TILT, INPUT_PULLUP);
pinMode(LED_A, OUTPUT);
pinMode(LED_B, OUTPUT);
}
void loop() {
// Шарики внутри датчика замкнули контакт — получаем LOW.
bool tilted = (digitalRead(TILT) == LOW);
digitalWrite(LED_A, tilted);
digitalWrite(LED_B, !tilted);
}
Наклоните плату влево — горит один светодиод, вправо — другой. Одна строка digitalWrite(LED_B, !tilted) делает второй светодиод противоположным первому: восклицательный знак означает «наоборот».
Такие датчики стоят в «умных» утюгах: если утюг долго не меняет положение, значит, им никто не пользуется, — и он выключается сам.
Шаг 3. Вибрация: событие длиной в миллисекунду
А вот с вибрацией всё сложнее. Попробуйте написать «если тряхнули — зажги светодиод» — и вы почти ничего не увидите. Толчок длится единицы миллисекунд, светодиод вспыхивает и гаснет быстрее, чем глаз успевает заметить.
Приём тот же, что и в антидребезге, только наоборот: там мы сокращали лишние события, а здесь растягиваем одно короткое. Запоминаем момент последнего толчка и светим, пока с него прошло мало времени:
const int VIBRO = 3;
const int LED = 10;
// Сколько держать свет после последнего толчка.
const unsigned long FLASH_MS = 300;
unsigned long lastShake = 0;
void setup() {
pinMode(VIBRO, INPUT_PULLUP);
pinMode(LED, OUTPUT);
}
void loop() {
// В покое датчик замкнут и даёт LOW. Тряхнули — контакт разорвался,
// и на мгновение появился HIGH.
if (digitalRead(VIBRO) == HIGH) {
lastShake = millis();
}
// Свет горит, пока с последнего толчка прошло меньше FLASH_MS.
if (millis() - lastShake < FLASH_MS) {
digitalWrite(LED, HIGH);
} else {
digitalWrite(LED, LOW);
}
}
Постучите пальцем по столу рядом с макеткой — светодиод отчётливо вспыхивает на треть секунды. Поменяйте FLASH_MS на 2000 — получится «свет по хлопку», который горит две секунды.
Почему lastShake обновляется, а не сравнивается? Потому что толчков за один удар много. Каждый из них просто сдвигает момент «последнего события» вперёд, и вспышка продлевается. Получается очень надёжно: сколько бы импульсов ни пришло, свет горит ровно заданное время после последнего.
Шаг 4. Два стука — включить свет
Раз мы умеем ловить удары, можно считать их и реагировать на комбинации. Сделаем «умный выключатель»: два стука по столу переключают свет.
const int VIBRO = 3;
const int LED = 10;
const unsigned long IGNORE_MS = 120; // дрожание одного удара не считаем
const unsigned long WINDOW_MS = 700; // за столько нужно успеть стукнуть дважды
unsigned long lastKnock = 0;
int knocks = 0;
bool lampOn = false;
void setup() {
pinMode(VIBRO, INPUT_PULLUP);
pinMode(LED, OUTPUT);
Serial.begin(9600);
}
void loop() {
// Новый стук: датчик разомкнулся, и с прошлого стука прошло достаточно.
if (digitalRead(VIBRO) == HIGH && millis() - lastKnock > IGNORE_MS) {
knocks = knocks + 1;
lastKnock = millis();
Serial.print("стук № ");
Serial.println(knocks);
}
// Пауза затянулась — подводим итог серии.
if (knocks > 0 && millis() - lastKnock > WINDOW_MS) {
if (knocks >= 2) {
lampOn = !lampOn;
digitalWrite(LED, lampOn);
Serial.println("два стука — переключаю свет");
}
knocks = 0;
}
}
Здесь работают сразу два интервала времени, и у каждого своя задача:
IGNORE_MS— «слепое» время после удара. Один настоящий удар даёт россыпь импульсов; за 120 мс они успевают закончиться, и мы не считаем один стук за пять.WINDOW_MS— сколько ждём продолжения. Если после последнего стука прошло 700 мс и новых нет, серия считается законченной: два стука были — переключаем, один — забываем.
Стучите по столу, а не по самой плате: датчик отлично ловит вибрацию через столешницу, а от прямых ударов по макетке провода вылетают из отверстий.
Шаг 5. Собираем сигнализацию
Теперь соединим оба датчика в одно устройство. Логика настоящей сигнализации: при включении она запоминает, как вещь лежала, и поднимает тревогу, если вещь сдвинули или потрясли.
const int TILT = 2;
const int VIBRO = 3;
const int LED_ARMED = 9; // зелёный: «на охране»
const int LED_ALARM = 10; // красный: тревога
const unsigned long ALARM_MS = 5000; // сколько тревожиться после последнего толчка
int calmTilt = HIGH; // положение датчика наклона в спокойном состоянии
bool alarmOn = false;
unsigned long alarmStart = 0;
unsigned long lastBlink = 0;
bool blinkOn = false;
void setup() {
pinMode(TILT, INPUT_PULLUP);
pinMode(VIBRO, INPUT_PULLUP);
pinMode(LED_ARMED, OUTPUT);
pinMode(LED_ALARM, OUTPUT);
Serial.begin(9600);
delay(1000); // даём вещи улечься
calmTilt = digitalRead(TILT); // запоминаем, как она лежала
Serial.println("Охрана включена.");
}
void loop() {
bool moved = (digitalRead(TILT) != calmTilt);
bool shaken = (digitalRead(VIBRO) == HIGH);
if (moved || shaken) {
if (!alarmOn) {
Serial.println("ТРЕВОГА!");
}
alarmOn = true;
alarmStart = millis();
}
if (alarmOn && millis() - alarmStart > ALARM_MS) {
alarmOn = false;
Serial.println("тихо, снова на охране");
}
if (alarmOn) {
digitalWrite(LED_ARMED, LOW);
if (millis() - lastBlink >= 120) {
blinkOn = !blinkOn;
digitalWrite(LED_ALARM, blinkOn);
lastBlink = millis();
}
} else {
digitalWrite(LED_ALARM, LOW);
digitalWrite(LED_ARMED, HIGH);
}
}
Обратите внимание на строку calmTilt = digitalRead(TILT) в setup(). Мы не задаём положение заранее — устройство запоминает его само при включении. Тот же приём, что и с калибровкой датчика света: прибор подстраивается под обстановку, а не требует настройки.
Положите макетку на книгу, дождитесь, пока загорится зелёный «на охране», и аккуратно сдвиньте книгу — красный замигает, а в мониторе появится «ТРЕВОГА!». Через пять секунд покоя устройство само вернётся на охрану.
Где такое встречается. Ровно так работает сигнализация в автомобиле: датчик наклона ловит попытку поднять машину домкратом, датчик удара — разбитое стекло. Разница только в чувствительности и в том, что вместо светодиода там сирена.
Не работает? Разбираемся
- Датчик вибрации срабатывает без конца. Скорее всего, он стоит слишком вертикально или слишком горизонтально. Наклоните его на 10–30 градусов — шарики займут устойчивое положение.
- Датчик вибрации не срабатывает совсем. Проверьте, что в
setup()стоитINPUT_PULLUP, и что вы ищетеHIGH, а неLOW: в покое этот датчик замкнут. - Наклон не переключается. Поворачивайте деталь вокруг её оси — датчик чувствует наклон только в одной плоскости. Если не помогает, проверьте провод из столбика датчика на плату.
- Светодиод от стука еле заметен. Значит, вспышка не растянута:
FLASH_MSслишком маленький или программа зажигает светодиод прямо вif, без запоминания времени. - Один стук считается за несколько. Увеличьте
IGNORE_MSдо 200 мс: у разных датчиков «звон» длится по-разному. - Сигнализация срабатывает сразу после включения. Плата запомнила положение, пока вы ещё держали макетку в руках. Дайте ей полежать: секунда паузы в
setup()как раз для этого, при необходимости увеличьте её.
Задания для тренировки
- РазминкаСделайте «датчик уровня»: пусть светодиод горит, только когда макетка лежит ровно, и гаснет при любом наклоне.
- ПосложнееНаучите устройство различать три стука и включать от них другой режим — например, мигание. Подсказка: у вас уже есть счётчик, добавьте ещё одну проверку в конце серии.
- Со звёздочкой ⭐Сделайте сигнализацию честной: пусть тревога не гаснет сама, а снимается кнопкой — как настоящая. Разбор ниже.
Показать разбор «сигнализация с кнопкой»
Добавьте на макетку кнопку из прошлых уроков: один вывод на цифровой вход 4, другой — в ту же шину «−». Программа меняется в двух местах: тревога больше не выключается по времени, зато появляется кнопка, которая её снимает.
const int TILT = 2;
const int VIBRO = 3;
const int BUTTON = 4; // кнопка снятия с охраны
const int LED_ARMED = 9;
const int LED_ALARM = 10;
const unsigned long BOUNCE_MS = 30;
int calmTilt = HIGH;
bool alarmOn = false;
unsigned long lastBlink = 0;
bool blinkOn = false;
int wasState = HIGH;
unsigned long lastChange = 0;
bool buttonPressed() {
int nowState = digitalRead(BUTTON);
bool pressed = false;
if (nowState != wasState) {
if (millis() - lastChange > BOUNCE_MS && nowState == LOW) {
pressed = true;
}
lastChange = millis();
wasState = nowState;
}
return pressed;
}
void setup() {
pinMode(TILT, INPUT_PULLUP);
pinMode(VIBRO, INPUT_PULLUP);
pinMode(BUTTON, INPUT_PULLUP);
pinMode(LED_ARMED, OUTPUT);
pinMode(LED_ALARM, OUTPUT);
Serial.begin(9600);
delay(1000);
calmTilt = digitalRead(TILT);
Serial.println("Охрана включена.");
}
void loop() {
bool moved = (digitalRead(TILT) != calmTilt);
bool shaken = (digitalRead(VIBRO) == HIGH);
if (moved || shaken) {
if (!alarmOn) {
Serial.println("ТРЕВОГА!");
}
alarmOn = true;
}
// Тревога снимается только руками — и заодно заново запоминаем положение.
if (buttonPressed() && alarmOn) {
alarmOn = false;
calmTilt = digitalRead(TILT);
Serial.println("снято с охраны, ставлю заново");
}
if (alarmOn) {
digitalWrite(LED_ARMED, LOW);
if (millis() - lastBlink >= 120) {
blinkOn = !blinkOn;
digitalWrite(LED_ALARM, blinkOn);
lastBlink = millis();
}
} else {
digitalWrite(LED_ALARM, LOW);
digitalWrite(LED_ARMED, HIGH);
}
}
Функция buttonPressed — та самая, из одиннадцатого урока, скопирована без единой правки. Так и работают взрослые программы: приём, который вы однажды разобрали, дальше просто переносится из проекта в проект.
И одна тонкость: при снятии с охраны мы заново запоминаем положение датчика наклона. Иначе, если вещь оставили в новом положении, тревога включится сразу же снова.
Что мы узнали
- Датчики наклона и вибрации — это выключатели. Подключаются как кнопки, через
INPUT_PULLUP, и не требуют внешних резисторов. - Датчик вибрации в покое замкнут, а размыкается от толчка — логика обратная привычной.
- Короткое событие нужно растягивать: запомнить момент и держать реакцию, пока с него прошло мало времени.
- Серию быстрых импульсов от одного удара гасят «слепым» интервалом, а комбинации (два стука, три стука) считают в окне времени.
- Хороший прибор сам запоминает исходное состояние при включении, а не требует настройки вручную.
Остался последний датчик набора — и самый необычный. Он видит то, чего не видит никто: магнитное поле. В следующем уроке соберём детектор магнитов, научимся отличать северный полюс от южного и сделаем датчик открытой двери.
Частые вопросы
Как работает датчик вибрации?
Внутри полой трубки лежат металлические шарики, которые в покое замыкают выводы. От толчка они подпрыгивают, и контакт на несколько миллисекунд рвётся. То есть в покое датчик замкнут, а при тряске размыкается — логика обратная привычной.
Почему датчик вибрации срабатывает сам по себе?
Скорее всего, он стоит строго вертикально или строго горизонтально — шарики лежат неустойчиво. Датчик нужно расположить под небольшим углом, примерно 10–30 градусов.
Как поймать событие, которое длится миллисекунду?
Запомнить момент события в переменную и держать реакцию, пока с него прошло мало времени. Тогда короткий импульс превращается в заметную вспышку, а серия импульсов от одного удара просто продлевает её.
Нужны ли этим датчикам резисторы?
Нет. Оба датчика — обычные замыкатели, поэтому их подключают как кнопку: один вывод на цифровой вход в режиме INPUT_PULLUP, другой на землю. Подтягивающий резистор уже встроен в плату.
