"Умные часы"

от
Прочее    arduino, датчики

Сегодня сказ пойдет о бесполезнейшей поделке - о часах и работе с датчиками, в общем о том, как я лепил велосипед и сделал часы. Данные часы работают на базе ардуино. Они измеряют температуру(погрешность 1-2 градуса), влажность и проверяют воздух на наличие углекислого газа(что лично для меня немаловажно). Да, с одной стороны вы можете смело кидаться помидорками и кричать с пеной у рта, что глупо и бесполезно, но не торопитесь. Ведь по факту мы рассмотрим относительно примитивную систему умного дома(если чуть-чуть допилить).


Компоненты
Ну, давайте по порядку, из чего же точно состоят наши часы? Вот примерный список стоимости необходимых компонентов:
arduino(подойдет абсолютно любая) - 200 рублей
12864 v2(дисплей) - 500 рублей
aht10 - 100 рублей
mq135 - 100 рублей
динамик - 5 рублей
Датчик напряжения 0-25В - 20 рублей
повышающий преобразователь напряжения - 40р
модуль часов реального времени - 130 рублей
2 аккумулятора 18560(1200мА/ч каждая батарейка) с платой зарядки tp4056 - 200 рублей
тактовая кнопка - 15 рублей


Подготовка к сборке
aht10, модуль часов - подключаются через I2C. Для того, чтобы подключить 2 или более датчика на один разъем нам нужен хаб/макетка, ну и конечно знать адреса наших модулей(в случае использования других датчиков не из моей статьи). Для этого подключаем наши модули и запускаем скетч:
  1. #include <Wire.h>
  2.  
  3. void setup() {
  4.     Wire.begin();
  5.  
  6.     Serial.begin(9600);
  7.     while (!Serial);
  8.     Serial.println("\nI2C Scanner");
  9. }
  10.  
  11. void loop(){
  12.     byte error, address;
  13.     int nDevices;
  14.  
  15.     Serial.println("Scanning...");
  16.  
  17.     nDevices = 0;
  18.     for(address = 8; address < 127; address++ ){
  19.         Wire.beginTransmission(address);
  20.         error = Wire.endTransmission();
  21.  
  22.         if (error == 0) {
  23.             Serial.print("I2C device found at address 0x");
  24.             if (address<16)
  25.                 Serial.print("0");
  26.             Serial.print(address,HEX);
  27.             Serial.println(" !");
  28.  
  29.             nDevices++;
  30.         }
  31.         else if (error==4) {
  32.             Serial.print("Unknow error at address 0x");
  33.             if (address<16)
  34.                 Serial.print("0");
  35.             Serial.println(address,HEX);
  36.         }
  37.     }
  38.     if (nDevices == 0)
  39.         Serial.println("No I2C devices found\n");
  40.     else
  41.         Serial.println("done\n");
  42.  
  43.     delay(5000);
  44. }

Далее переходим к нашему замечательному дисплею, единственному в своем роде для ардуинки(больше и удобнее дисплея вам нигде не сыскать). Припаеваем проводки по схеме:

f7f6ec5b.jpg

Далее берем датчик газа MQ135 и оставляем его на сутки:
  1. // библиотека для работы с датчиками MQ (Troyka-модуль)
  2. #include <TroykaMQ.h>
  3.  
  4. // имя для пина, к которому подключен датчик
  5. #define PIN_MQ135         A0
  6. // имя для пина, к которому подключен нагреватель датчика
  7. #define PIN_MQ135_HEATER  11
  8.  
  9. // создаём объект для работы с датчиком
  10. // и передаём ему номер пина выходного сигнала и нагревателя
  11. MQ135 mq135(PIN_MQ135);
  12.  
  13. void setup() {
  14.   // открываем последовательный порт
  15.   Serial.begin(9600);
  16.   // включаем нагреватель
  17.   mq135.heaterPwrHigh();
  18.   Serial.println("Heated sensor");
  19. }
  20.  
  21. void loop()
  22. {
  23.   // если прошёл интервал нагрева датчика
  24.   // и калибровка не была совершена
  25.   if (!mq135.isCalibrated() && mq135.heatingCompleted()) {
  26.     // выполняем калибровку датчика на чистом воздухе
  27.     mq135.calibrate();
  28.     // если известно сопротивление датчика на чистом воздухе
  29.     // можно его указать вручную, допустим 160
  30.     // mq135.calibrate(160);
  31.     // выводим сопротивление датчика в чистом воздухе (Ro) в serial-порт
  32.     Serial.print("Ro = ");
  33.     Serial.println(mq135.getRo());
  34.   }
  35.   // если прошёл интевал нагрева датчика
  36.   // и калибровка была совершена
  37.   if (mq135.isCalibrated() && mq135.heatingCompleted()) {
  38.     // выводим отношения текущего сопротивление датчика
  39.     // к сопротивлению датчика в чистом воздухе (Rs/Ro)
  40.     Serial.print("Ratio: ");
  41.     Serial.print(mq135.readRatio());
  42.     // выводим значения газов в ppm
  43.     Serial.print("\tCO2: ");
  44.     Serial.print(mq135.readCO2());
  45.     Serial.println(" ppm");
  46.     delay(100);
  47.   }
  48. }


Советую собрать вот такой тестовый агрегат для калибровки:
IMG_20200125_193221.jpg
После чего нам необходимо будет замерить воздух на улице и вписать полученное значение в скетч. Предельно допустимые значения воздуха в помещении до 1100 ppm, с этого значения и выше - начинает болеть голова.

Ну и как же без красочной 8 битной музыки на оповещении о превышении углекислого газа? Для этого советую воспользоваться сервисом по конвертированию midi файлов в код:
https://extramaster.net/tools/midiToArduino/

В моем примере ее не будет, но вы запросто можете добавить ее в свой проект

Часы настраиваем с помощью скетча:
  1. #include <Wire.h>
  2. #include <TimeLib.h>
  3. #include <DS1307RTC.h>
  4.  
  5. const char *monthName[12] = {
  6.   "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  7.   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  8. };
  9.  
  10. tmElements_t tm;
  11.  
  12. void setup() {
  13.   bool parse=false;
  14.   bool config=false;
  15.  
  16.   // get the date and time the compiler was run
  17.   if (getDate(__DATE__) && getTime(__TIME__)) {
  18.     parse = true;
  19.     // and configure the RTC with this info
  20.     if (RTC.write(tm)) {
  21.       config = true;
  22.     }
  23.   }
  24.  
  25.   Serial.begin(9600);
  26.   while (!Serial) ; // wait for Arduino Serial Monitor
  27.   delay(200);
  28.   if (parse && config) {
  29.     Serial.print("DS1307 configured Time=");
  30.     Serial.print(__TIME__);
  31.     Serial.print(", Date=");
  32.     Serial.println(__DATE__);
  33.   } else if (parse) {
  34.     Serial.println("DS1307 Communication Error :-{");
  35.     Serial.println("Please check your circuitry");
  36.   } else {
  37.     Serial.print("Could not parse info from the compiler, Time=\"");
  38.     Serial.print(__TIME__);
  39.     Serial.print("\", Date=\"");
  40.     Serial.print(__DATE__);
  41.     Serial.println("\"");
  42.   }
  43. }
  44.  
  45. void loop() {
  46. }
  47.  
  48. bool getTime(const char *str)
  49. {
  50.   int Hour, Min, Sec;
  51.  
  52.   if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false;
  53.   tm.Hour = Hour;
  54.   tm.Minute = Min;
  55.   tm.Second = Sec;
  56.   return true;
  57. }
  58.  
  59. bool getDate(const char *str)
  60. {
  61.   char Month[12];
  62.   int Day, Year;
  63.   uint8_t monthIndex;
  64.  
  65.   if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false;
  66.   for (monthIndex = 0; monthIndex < 12; monthIndex++) {
  67.     if (strcmp(Month, monthName[monthIndex]) == 0) break;
  68.   }
  69.   if (monthIndex >= 12) return false;
  70.   tm.Day = Day;
  71.   tm.Month = monthIndex + 1;
  72.   tm.Year = CalendarYrToTm(Year);
  73.   return true;
  74. }

Сборка
Собираем вот такой модуль питания состоящий из 18650, платы tp4056 и преобразователя напряжения(при сборке по моей схеме, выходное напряжение получается в районе 3,6В, что мало для нашей системы, поэтому мы повысим его до 12В и подадим напрямую в ардуино, на вход VIN
IMG_20200125_193034.jpg)

В целом в сборке нет ничего сложного, как и ранее писал, датчик температуры и модуль часов подключаем через I2C, выводим 5В и землю на макетку, дисплей подключаем по схеме, кроме контакта на яркость BLA, его я запитал от 9 пина, для реализации включения подсветки дисплея при нажатии на кнопку(загорается у меня на 30 секунд):
  1. if (val=val>=600){
  2.    m=100;
  3.    n=1;
  4.    analogWrite(6, 110);
  5.  }
  6.  m=m-n;
  7.  if(m<=0){
  8.    digitalWrite(6, LOW);
  9.   n=0;
  10.  }

В чем универсальность данного проекта? По факту через разъем i2c можно прикрутить бесконечное количество датчиков и всю эту лабуду выводить на сервер через NodeMCU и на основе полученных данных, что-то делать, к примеру, можно сделать пожарную сигнализацию, а если углубиться и запариться создать сеть из датчиков с общим выводом данных на единый сервер на NodeMCU.
Данный проект был задуман изначально как проверка себя, на сколько я смогу далеко зайти и не более того. Да это велосипед, но он мой.

и на последок вот весь исходник:
  1. #include <U8glib.h>
  2. #include <Wire.h>
  3. #include <Thinary_AHT10.h>
  4. #include <TimeLib.h>
  5. #include <DS1307RTC.h>
  6. #include <TroykaMQ.h>
  7. #define PIN_MQ135  A0
  8. int analogInput = A1;
  9. float vout = 0.0;
  10. float vin = 0.0;
  11. float R1 = 30000.0; //  
  12. float R2 = 7500.0; //
  13. float zarad=0;
  14. int value = 0;
  15. AHT10Class AHT10;
  16. MQ135 mq135(PIN_MQ135);
  17. #define PIN_MQ135_HEATER  3
  18. U8GLIB_ST7920_128X64_1X u8g(10);                               // Создаём объект u8g для работы с дисплеем, указывая номер вывода CS для аппаратной шины SPI
  19. uint32_t                timeStart;                             // Объявляем переменную для подсчёта количества секунд прошедших с момента старта скетча
  20. uint8_t                 timeHours;                             // Объявляем переменную для вывода часов  прошедших с момента старта скетча
  21. uint8_t                 timeMinutes;                           // Объявляем переменную для вывода минут  прошедших с момента старта скетча
  22. uint8_t                 timeSeconds;
  23. int analogPin = 3;     // номер порта к которому подключен потенциометр
  24. int val = 0;           // переменная для хранения считываемого значения
  25. int m=0;
  26. int n=0;
  27.  
  28.  
  29. void setup()
  30. {
  31.   Serial.begin(9600);              //  установка связи по serial
  32.   mq135.heaterPwrHigh();
  33.    pinMode(analogInput, INPUT);
  34.     mq135.calibrate();
  35.   Wire.begin();
  36.   if(AHT10.begin(eAHT10Address_Low))
  37.     Serial.println("Init AHT10 Sucess.");
  38.   else
  39.     Serial.println("Init AHT10 Failure.");
  40.  
  41. }
  42.  
  43. void loop (){
  44.    value = analogRead(analogInput);
  45.    vout = (value * 5.0) / 1024.0; // see text
  46.    vin = vout / (R2/(R1+R2));
  47.   zarad=10+(((vin-2.5)*100)/4.2);
  48.     val = analogRead(analogPin);     // считываем значение
  49.     Serial.println(val);
  50.   if (val=val>=600){
  51.     m=100;
  52.     n=1;
  53.     analogWrite(6, 110);
  54.   }
  55.   m=m-n;
  56.   if(m<=0){
  57.     digitalWrite(6, LOW);
  58.    n=0;
  59.   }
  60.   tmElements_t tm;
  61.  
  62.     u8g.firstPage();                                           // Всё что выводится на дисплей указывается в цикле: u8g.firstPage(); do{ ... команды ... }while(u8g.nextPage());
  63.     do{  u8g.setColorIndex(1);                                 // Выбираем белый цвет
  64.          u8g.drawBox(0, 0, 128, 11);                           // Выводим прямоугольник с координатами левого верхнего угла 0,0 и размерами 128x11 пикселей
  65.          u8g.drawBox(0, 53, 128, 11);
  66.          u8g.setFont(u8g_font_6x10);                           // Выбираем шрифт u8g_font_6x10
  67.          u8g.setColorIndex(0);                                 // Выбираем цвет фона
  68.          u8g.setPrintPos(82, 9); u8g.print(zarad);
  69.          u8g.drawStr(115 , 9, "%");
  70.         u8g.setPrintPos(  4, 62); u8g.print(AHT10.GetTemperature());
  71.         u8g.drawStr(36, 62, "C");
  72.         u8g.setPrintPos(46, 62); u8g.print(AHT10.GetHumidity());
  73.          u8g.drawStr(77, 62, "%");
  74.          u8g.drawStr(84, 62, "AIR");
  75.          u8g.setPrintPos(102, 62); u8g.print(mq135.readCO2());
  76.          u8g.setColorIndex(1);                                 // Выбираем белый цвет
  77.          u8g.setFont(u8g_font_courB24n);                       // Выбираем шрифт u8g_font_courB24n
  78.           u8g.setPrintPos(35, 43); u8g.print(tm.Hour);    // Выводим старший разряд часов    в позиции   3х43
  79.          u8g.drawStr    ( 55, 43, ":");                        // Выводим двоеточие               в позиции  33х43
  80.          u8g.setPrintPos( 65, 43); u8g.print(tm.Minute);  // Выводим старший разряд минут    в позиции  46х43
  81.                       // Выводим двоеточие               в позиции  76х43
  82.  
  83.     }    while(u8g.nextPage());
  84. }
IMG_20200126_141607.jpg
IMG_20200126_141603.jpg
Оригинального в коде нет ничего, я не знаю язык C, но черт возьми, даже у меня получилось сделать это. Проект ни у кого не тырил, полностью оригинальный продукт. Если зайдет вам такой стиль изложения и будет интересно, то могу ответить на вопросы о сборке и напишу новую статью о сборке умного дома на raspberry и ардуино(сейчас в процессе)