В этом материале мы расскажем о том, как сконструировать сенсорный узел на базе Intel Edison. Займёмся сборкой компонентов, программированием и тестированием. Правда, автономное устройство в век интернета вещей – это не так уж и интересно. Поэтому после того, как все датчики и алгоритмы заработают, мы подключим то, что у нас получится, к Сети. Этому, кстати, будет посвящён наш следующий материал. А сейчас предлагаем заняться железом и кодом.
Сенсорный узел
Сенсорный узел состоит из нескольких датчиков.
- Пассивный инфракрасный датчик движения
- Аналоговый датчик температуры. Это может быть либо TMP036, либо LM35. За исключением некоторых смещений переменных, и тот и другой обладают одинаковыми температурными характеристиками в 10 мВ напряжения на каждый градус Цельсия.
- Фоторезистор (датчик освещённости).
- Командная кнопка.
Сенсорный узел так же включает в себя пару светодиодов, они нужны для отладочных целей.
- Красный светодиод, который включается и выключается при нажатии на кнопку.
- Зелёный светодиод, который включается, когда срабатывает датчик движения.
Вот принципиальная схема сенсорного узла, созданная средствами приложения Fritzing.
Принципиальная схема устройства
Для сборки сенсорного узла нужны следующие компоненты:
- 1 x модуль Edison.
- 1 x плата расширения Arduino для Edison.
- 2 x резистор на 200 Ом.
- 2 x резистор на 1 кОм.
- 1 x резистор на 10 кОм.
- 1 x командная кнопка.
- 2 x светодиод. Мы использовали красный и зелёный, но подойдут и любые другие.
- 1 x датчик температуры, TMP036 или LM35.
- 1 x инфракрасный датчик движения.
- 1 x плата для беспаечного макетирования.
- Провода или перемычки для прототипирования.
Подобные компоненты можно найти у разных поставщиков, которые обслуживают розничных покупателей. Например, в Adafruit или Sparkfun. Если нужны большие объёмы компонентов, в Mouser Electronics или DigiKey можно попытаться найти то же самое, но немного дешевле.
Программа для Arduino
Программа для Arduino, реализующая функционал сенсорного узла, показана ниже. Читатели, знакомые с разработкой для Arduino, смогут без проблем с этим кодом разобраться. Однако, мы хотим пояснить здесь некоторые особенности реализации и указать на несколько отличий между Edison и обычным Arduino (например, Arduino Uno).
Для обработки сигналов от кнопки и датчика движения используются прерывания. Так как события ввода для этих устройств происходят асинхронно, их лучше всего обрабатывать с помощью механизма прерываний. Прерывания в Edison ведут себя не так, как в Arduino Uno. Ключевые различия заключаются в следующем:
- Прерывания в Edison могут генерировать все выводы GPIO. В Arduino Uno генерировать прерывания могут только выводы 2 и 3. В результате, на Edison можно реализовать более гибкие конструкции.
- Так как в Edison все выводы могут генерировать прерывания, в вызове «attach_interrupt» используется номер вывода в качестве канала прерывания, а не предопределённые номера 0 или 1, как в Arduino Uno.
- В Edison таймер millis() продолжает работать в обработчике прерывания. Это даёт возможность выполнять вызовы для измерения временных интервалов.
Датчик движения устанавливает высокий (HIGH) логический уровень напряжения на сигнальном контакте при обнаружении движения. Сигнал остаётся на этом уровне несколько секунд, а затем переключается на низкий (LOW) логический уровень. Для того, чтобы получить историю зафиксированных датчиком событий, мы измеряем длительность импульсов, соответствующих обнаружению движений и накапливаем их в глобальном счётчике. Каждую минуту измеряем уровень активности, вычисляя отношение количества времени, в котором датчик движения выдавал значение HIGH, к общей длительности интервала наблюдения (1 минута). Сведения об уровне активности выводятся в виде процентной величины. Таким образом, если движений зафиксировано не было, уровень активности окажется нулевым. Если всю минуту датчик регистрировал перемещения, уровень активности будет близок к 100%. Уровень активности сбрасывается в ноль в начале следующего интервала наблюдения.
Для того, чтобы предотвратить возникновение ложных прерываний от кнопки, возникающих от дребезга контактов переключателя, будем отслеживать время, когда возникло последнее прерывание. Если новое поступает быстрее, чем за 200 мс после предыдущего, считаем его ложным и игнорируем.
В качестве опорного напряжения для аналого-цифрового преобразователя (АЦП) Edison использует VCC, положительный полюс источника постоянного тока. При питании платы от USB-порта, величина напряжения VCC может быть не точно равна 5 В. Наиболее вероятно то, что оно будет находиться в диапазоне от 4,8 В до 5,1 В. Необходимо использовать реальное опорное напряжение для вычисления разрешения АЦП. Для того, чтобы измерить напряжение питания и вычислить разрешение (VCC/1024), можно использовать вольтметр. Полученное значение будет являться разрешением 10-битного аналого-цифрового преобразователя Edison.
/* Сенсорный узел для интернета вещей с использованием платы Intel Edison
Технические возможности:
1) Детектор движения: обнаруживает движения, включает зелёный светодиод при срабатывании
2) Датчик освещённости: измеряет окружающее освещение
3) Датчик температуры: измеряет окружающую температуру
4) Командная кнопка: включает и выключает красный светодиод
Copyright (c) 2015 Intel Corporation
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <Arduino.h>
// цифровые контакты
#define RED_LED 10 // Красный светодиод
#define GREEN_LED 11 // Зелёный светодиод
#define PIR_SENSOR 12 // Инфракрасный датчик движения
#define BUTTON 13 // Командная кнопка с нагрузочным резистором на 10K
// аналоговые контакты
#define LIGHT_SENSOR A0 // датчик освещённости
#define TEMP_SENSOR A1 // датчик температуры TMP36 или LM35
#define MIN_PULSE_SEPARATION 200 // для исключения дребезга кнопки
#define ADC_STEPSIZE 4.61 // в мВ, для конверсии данных о температуре. 4.72 это напряжение VCC, измеренное вольтметром
#define USE_TMP036 1
#if (USE_TMP036 == 1)
#define TEMP_SENSOR_OFFSET_VOLTAGE 750
#define TEMP_SENSOR_OFFSET_TEMPERATURE 25
#else // датчик температуры LM35
#define TEMP_SENSOR_OFFSET_VOLTAGE 0
#define TEMP_SENSOR_OFFSET_TEMPERATURE 0
#endif
// Глобальные переменные
unsigned long updateTime = 0;
// Переменные датчика движения
volatile unsigned long activityMeasure;
volatile unsigned long activityStart;
unsigned long resetActivityCounterTime;
// Переменные кнопки
boolean toggleLED = 1;
volatile unsigned long previousEdgeTime = 0;
volatile unsigned long count = 0;
/**********************************************************************************************
Настройка
***********************************************************************************************/
void setup() {
Serial.begin(115200);
delay(3000);
Serial.println("Ready");
pinMode(RED_LED, OUTPUT);
pinMode(GREEN_LED, OUTPUT);
pinMode(BUTTON, INPUT_PULLUP);
pinMode(PIR_SENSOR, INPUT);
attachInterrupt(BUTTON, buttonISR, RISING); // вызов прерывания при отпускании кнопки
attachInterrupt(PIR_SENSOR, pirISR, CHANGE); // нужны оба значения для нахождения ширины импульса
resetActivityCounterTime = millis(); // сбрасываем счётчик активности датчика движения каждую минуту
digitalWrite(RED_LED, LOW);
digitalWrite(GREEN_LED,LOW);
}
/**********************************************************************************************
Главный цикл
***********************************************************************************************/
void loop() {
if (millis() > resetActivityCounterTime) {
// сбрасываем счётчик датчика движения каждые 60 секунд
resetActivityCounterTime = millis() + 60000;
Serial.print("ActivityLevel: ");Serial.println(100.0*activityMeasure/60000.0);
activityMeasure = 0;
}
if (millis() > updateTime) {
// обновляем показатели температуры и освещённости каждые 10 секунд
updateTime = millis() + 10000;
Serial.print("Temperature sensor: ");
Serial.println(readTemperature());
Serial.print("Light sensor: ");
Serial.println(readLightSensor());
}
delay(100);
}
/**********************************************************************************************
Датчик движения
Каждый раз, когда датчик фиксирует перемещение, выход переводится в состояние HIGH и остаётся в нём всё время, пока длится движение.
Когда движение прекращается, контакт остаётся в состоянии HIGH ещё 3-5 секунд, затем переводится в состояние LOW.
Подсчитываем время нахождения выхода датчика в состоянии HIGH, это служит мерой оценки длительности зафиксированных движений.
Главный цикл сообщает об уровне активности в предыдущем временном интервале в виде процентной величины
***********************************************************************************************/
void pirISR()
{
int pirReading;
unsigned long timestamp;
timestamp = millis();
pirReading = digitalRead(PIR_SENSOR);
if (pirReading == 1) {
Serial.print(millis()); Serial.println(": PIR motion started");
// отмечаем начало импульса
activityStart = timestamp;
}
else {
int pulseWidth = timestamp-activityStart;
activityMeasure += pulseWidth;
Serial.print(millis()); Serial.println(": PIR motion ended");
Serial.print("Duration: "); Serial.print(pulseWidth); Serial.println(" ms");
Serial.print("ActivityMeasure: "); Serial.print(activityMeasure); Serial.println(" ms");
}
// когда движение зафиксировано, включаем зелёный светодиод digitalWrite(GREEN_LED, pirReading);
}
/**********************************************************************************************
возврат данных датчика освещённости
***********************************************************************************************/
int readLightSensor()
{
return analogRead(LIGHT_SENSOR);
}
/**********************************************************************************************
Возврат данных о температуре в градусах Фаренгейта
***********************************************************************************************/
float readTemperature()
{
int sensorReading;
float temperature;
sensorReading = analogRead(TEMP_SENSOR);
temperature = sensorReading * ADC_STEPSIZE; // конвертируем в милливольты
temperature = (temperature - TEMP_SENSOR_OFFSET_VOLTAGE)/10.0 + TEMP_SENSOR_OFFSET_TEMPERATURE;
temperature = temperature * 1.8 + 32.0; // конвертируем в градусы Фаренгейта
return temperature;
}
/**********************************************************************************************
Обработчик прерываний для кнопки
***********************************************************************************************/
void buttonISR()
{
// если текущее нажатие оказалось слишком близко к предыдущему, считаем это дребезгом
Serial.print(millis()); Serial.println(": Button ISR");
if ((millis()-previousEdgeTime) >= MIN_PULSE_SEPARATION) {
digitalWrite(RED_LED, digitalRead(RED_LED) ^ 1);
count++;
Serial.print("Count: "); Serial.println(count);
}
previousEdgeTime=millis();
}
Тестирование
Для того, чтобы проверить, что узел сенсоров работает правильно, нужно провести с ним, как минимум, следующие эксперименты.
- Если помахать рукой перед датчиком движений, должен включиться зелёный светодиод. После того, как руку убрали, он должен гореть ещё 2-3 секунды и выключиться.
- Если поднести руку к датчику движения, включится зелёный светодиод. Если, не убирая руку, держать её перед датчиком неподвижно, то есть, прекратить движение, зелёный светодиод должен выключиться через вышеупомянутый интервал.
- Нажатие на кнопку должно включать и выключать красный светодиод.
- В окне отладки, выводящем данные, поступающие с последовательного порта, должны выводиться сведения с датчиков температуры и освещённости каждые 10 секунд. Сообщения об уровне активности, зафиксированной датчиком движения, должны выводиться каждую минуту.
- Если прикрыть рукой датчик освещённости, это должно отразиться в уменьшении значений показателей, поступающих с него. Если посветить в этот датчик, например, LED-вспышкой, значение должно оказаться близким к максимуму, то есть – к 1023.
- Если зажать в пальцах верхушку датчика температуры, система должна зафиксировать повышение температуры. При этом температура должна постепенно достигнуть нормальной температуры тела – около 95-98 градусов Фаренгейта.
Вот видео, в котором показаны наши эксперименты:
Выводы
Мы собрали сенсорный узел с использованием Intel Edison и написали программу для него в Arduino IDE. Датчики такого рода можно обнаружить в домашних системах безопасности. Так же были рассмотрены некоторые различия в программировании для Edison и Arduino. В следующем материале этой серии подключим сенсорный узел к интернету для того, чтобы сделать его полноправным участником интернета вещей, настоящим IoT-устройством. В итоговом проекте Mosquitto MQTT-брокер, о котором мы уже писали, будет использован как интерфейс передачи данных для сенсорного узла.
Автор: Intel