ESP32:Примеры/Использование ESP32 вместе с PIR-датчиком движения

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Использование ESP32 вместе с PIR-датчиком движения

В этом примере мы научимся обнаруживать движения при помощи PIR-датчика движения. В нашем проекте-примере плата ESP32, обнаружив движение, запустит таймер, а затем включит светодиод на предварительно заданное количество секунд. Когда отсчет таймера завершится, светодиод автоматически выключится.

В этом примере мы представим два новых концепта: прерывания и таймеры.

Чтобы запустить событие при помощи PIR-датчика движения, вам понадобятся прерывания. Они используются для автоматического выполнения задач в микроконтроллерных программах и хороши при решении проблем, завязанных на время.

Благодаря прерываниям вам не нужно постоянно проверять текущее значение контакта. Потому что, когда значение изменится, это событие запустится само (с помощью вызова функции). В IDE Arduino для того, чтобы задать прерывание, используется функция attachInterrupt(). Она принимает три параметра: GPIO-контакт, название выполняемой функции и режим работы:

attachInterrupt(digitalPinToInterrupt(GPIO), funcion, mode);

Теперь о каждом из этих параметров более подробно.

Первый параметр – это номер GPIO-контакта, на котором выполняется прерывание. Как правило, чтобы на каком-либо GPIO-контакте можно было использовать прерывания, для него необходимо вызвать функцию digitalPinToInterrupt(GPIO). К примеру, чтобы использовать для прерываний контакт GPIO27, в коде нужно написать следующее:

digitalPinToInterrupt(27)

На схеме ниже GPIO-контакты, которые можно использовать для прерываний, помечены красной рамкой. В нашем проекте для прерываний, вызванных данными от PIR-датчика движения, будет использоваться контакт GPIO27.

Второй параметр функции attachInterrupt() – это «function», т.е. функция, запускаемая при каждом прерывании.

Третий параметр – это «mode», т.е. режим активации прерывания. Всего их 5:

  • LOW – прерывание будет запущено, если на контакте будет значение «LOW»
  • HIGH – прерывание будет запущено, если на контакте будет значение «HIGH»
  • CHANGE – прерывание будет запущено, если значение на контакте изменится (например, с «LOW» на «HIGH» или с «HIGH» на «LOW»)
  • FALLING – прерывание будет запущено, если значение на контакте изменится с «HIGH» на «LOW»
  • RISING – прерывание будет запущено, если значение на контакте изменится с «LOW» на «HIGH»

В этом примере мы воспользуемся режимом «RISING», т.к. когда PIR-датчик движения определяет движение, GPIO-контакт, к которому он подключен, переключается с «LOW» на «HIGH».

В этом проекте-примере мы также впервые воспользуемся таймером. Нам нужно, чтобы светодиод после обнаружения движения оставался включенным определенное количество секунд. Вместо функции delay(), которая просто блокирует код и не дает ему ничего делать в течение заданного количества секунд, мы воспользуемся таймером.

Ранее мы использовали функцию delay(), которая работала очень просто. Ее единственным параметром служит целочисленное значение (int) – в нем задается количество миллисекунд, которые программа будет ждать до перехода к следующей строчке кода.

delay(время_в_миллисекундах)

Например, если вписать в код delay(1000), программа затормозит на этой строчке на 1 секунду.

То есть функция delay() – это блокирующая функция. Блокирующие функции не дают программе делать что-либо до того, пока не будет выполнена некоторая задача. Если вы хотите, чтобы ваш код одновременно выполнял несколько задач, функцию delay() использовать нельзя.

В большинстве проектов нужно избегать функции delay(), используя вместо нее таймер.

Функция millis() возвращает количество миллисекунд, прошедших с момента запуска программы.

millis()

Что делает эту функцию полезной? Проделав некоторые несложные расчеты, вы можете легко проверить, сколько времени прошло, не блокируя при этом весь код.

Загрузите этот код на ESP32. Перед этим убедитесь, что в IDE Arduino выбраны правильная плата и COM-порт. Откройте монитор порта на скорости 115200 бод.

Поместите руку перед PIR-датчиком движения. Светодиод должен включиться, а в мониторе порта должно появиться сообщение «MOTION DETECTED!!!» Спустя 10 секунд светодиод должен выключиться.

Давайте рассмотрим этот код более подробно.

В начале задаем номера GPIO-контактов для светодиода и PIR-датчика движения в переменных «led» и «motionSensor».

const int led = 26;
const int motionSensor = 27;

Затем создаем переменные, позволяющие задать таймер для выключения светодиода после обнаружения движения.

unsigned long now = millis();
unsigned long lastTrigger = 0;
boolean startTimer = false;

Переменная «now» служит для хранения текущего времени. В переменной «lastTrigger» хранится время, когда PIR-датчик определил движение. Переменная «startTimer» – это булева переменная, которая запускает таймер после обнаружения движения.

В блоке setup() сначала инициализируем последовательную коммуникацию на 115200 бод.

Serial.begin(115200);

Выставляем контакт PIR-датчика движения в режим «INPUT_PULLUP».

pinMode(motionSensor, INPUT_PULLUP);

Далее используем описанную выше функцию attachInterrupt(), чтобы сделать контакт PIR-датчика контактом для прерываний.

attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);

Таким образом, благодаря этой функции мы задаем, что если на контакте GPIO27 будет вызвано прерывание типа «RISING», это запустит функцию detectsMovement().

Делаем контакт «led» выходным (OUTPUT) контактом, а его начальным значением делаем «LOW».

pinMode(led, OUTPUT);
digitalWrite(led, LOW);

Код в блоке loop() будет повторяться снова и снова. Таким образом, при каждом проходе через loop() в переменную «now» будет записываться текущее время.

now = millis();

Больше никаких задач в блоке loop() не выполняется. Но при обнаружении движения вызывается функция detectsMovement(), т.к. ранее мы задали ее в блоке setup() как функцию, запускаемую при прерывании.

Эта функция печатает в мониторе порта сообщение «MOTION DETECTED!!!» («ОБНАРУЖЕНО ДВИЖЕНИЕ!!!»), включает светодиод, записывает в булеву переменную «startTimer» значение «true» и записывает в переменную «lastTrigger» текущее время.

void IRAM_ATTR detectsMovement() {
 Serial.println("MOTION DETECTED!!!");
            //  "ОБНАРУЖЕНО ДВИЖЕНИЕ!!!"
 digitalWrite(led, HIGH);
 startTimer = true;
 lastTrigger = millis();
}
Примечание

Конструкция «IRAM_ATTR» используется для того, чтобы запустить код прерывания в памяти RAM. В противном случае код будет сохранен на flash-память, из-за чего программа будет работать медленнее.

После этого код снова возвращается в блок loop().

Сейчас в переменной «startTimer» хранится значение «true». Таким образом, при прошествии заданного количества секунд после обнаружения движения результатом if() будет «true».

if(startTimer && (now - lastTrigger > (timeSeconds*1000))) {
 Serial.println("Motion stopped...");
            //  "Движение прекратилось..."
 digitalWrite(led, LOW);
 startTimer = false;
}

В результате в мониторе порта будет напечатано сообщение «Motion stopped...» («Движение прекратилось...»), светодиод будет выключен, а в переменную «startTimer» будет записано значение «false».

Итак, если вкратце, прерывания используются для обнаружения изменений состояния GPIO-контакта, чтобы вам не приходилось постоянно считывать текущее состояние GPIO-контакта. Благодаря прерываниям при обнаружении такого изменения запускается заданная заранее функция.

В этом примере мы также научились настраивать простой таймер, позволяющий проверить, прошло ли заданное количество секунд, не прибегая при этом к блокировке остального кода.

Прерывания и таймеры – это два очень важных концепта, которые пригодятся нам для работы над дальнейшими проектами.

Необходимое оборудование

Схема

Примечание

На этой схеме показана 30-контактная версия ESP32 DEVKIT V1 DOIT. Если вы используете другую модель, обязательно сверьтесь с ее распиновкой

Эта цепь собирается довольно просто. Нам понадобятся, во-первых, светодиод и резистор. Светодиод нужно подключить к контакту GPIO26. Во-вторых, нам будет нужен PIR-датчик движения, работающий на 3.3 вольтах, который нужно будет подключить к контакту GPIO27. При сборке следуйте схеме ниже.

Код

const int timeSeconds = 10; // количество секунд, в течение которых светодиод будет оставаться включенным после обнаружения движения. 

// задаем GPIO-контакты для светодиода и PIR-датчика движения:
const int led = 26;
const int motionSensor = 27;

// вспомогательные переменные для таймера:
unsigned long now = millis();
unsigned long lastTrigger = 0;
boolean startTimer = false;

// проверяем, обнаружено ли движение,
// и если обнаружено, переключаем контакт «led» на «HIGH»
// и запускаем таймер:
void IRAM_ATTR detectsMovement() {
  Serial.println("MOTION DETECTED!!!");
             //  "ОБНАРУЖЕНО ДВИЖЕНИЕ!!!"
  digitalWrite(led, HIGH);
  startTimer = true;
  lastTrigger = millis();
}

void setup() {
  // запускаем последовательную коммуникацию (в целях отладки):
  Serial.begin(115200);
  
  // выставляем контакт датчика движения в режим «INPUT_PULLUP»:
  pinMode(motionSensor, INPUT_PULLUP);
  // делаем контакт «motionSensor» контактом для прерываний,
  // задаем функцию, которая будет запущена при прерывании,
  // а также выставляем режим активации прерывания на «RISING»:
  attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);

  // переключаем контакт светодиода («led») на «LOW»:
  pinMode(led, OUTPUT);
  digitalWrite(led, LOW);
}

void loop() {
  // текущее время:
  now = millis();
  // выключаем светодиод спустя определенное количество секунд,
  // заданное в параметре «timeSeconds»:
  if(startTimer && (now - lastTrigger > (timeSeconds*1000))) {
    Serial.println("Motion stopped...");
               //  "Движение прекратилось..."
    digitalWrite(led, LOW);
    startTimer = false;
  }
}

См.также

Внешние ссылки