Tag Archives: Arduino

Будильник своими руками: Часть 1 – Время, дата и день недели

Сейчас у всех есть телефоны и традиционные часы-будильники уже не так популярны, однако производители таких девайсов не сдаются и продолжают придумывать фишки, которые бы позволили им оставаться на плаву. Например, светобудильник, который синхронизируется с IPhone-ом:

20160305_220218Фактически это будильник + док станция. Я решил что мне нужен наподобие, только для Android-а… с беспроводной зарядкой… какой-нить весёленькой индикацией событий в телефоне… Ну и добавлю ещё каких-нить свистелок-перделок.

Первое, что нужно сделать – это конечно сами часы. Купил индикатор и часы реального времени (RTC) с батарейкой cr1220.

20160305_160942 20160305_160952

разместил это дело на макетке и подключил к Arduino

20160305_221732нашёл на сайте Амперки примеры для работы с часами реального времени и индикатором. Скачал библиотеки, необходимые для работы с ними и сделал простой скетч, который просто установил бы текущее время. Получилось примерно вот так:

20160305_222041Следующим шагом добавил кнопку, для изменения отображаемой информации и зуммер для индикации успешного нажатия. Для добавления Зуммера в свой проект не требуются дополнительной библиотеки – можно воспользоваться встроенной функцией tone.

Кнопку пока сделал как переключатель между временем, датой и днём недели. То есть по умолчанию “будильник” показывает часы и минуты, нажимаешь на кнопку – показывает дату, нажимаешь ещё раз – показывает день недели и так по кругу.

Вот как это выглядит и работает:

Ниже приведён полный исходный текст программы.

// библиотека для работы I²C
#include <Wire.h>
#include <QuadDisplay.h>
// библиотека для работы с часами реального времени
#include "TroykaRTC.h"

// EEPROM — энергонезависимая память
// библиотека для записи и считывания информации с EEPROM
#include <EEPROM.h>

// размер массива для времени с учётом завершающего нуля
#define DISPLAY_PIN 7 // Ножка QuadDisplay-я
#define KEY_PIN 8  // Ножка для кнопки
#define MODE_NUM 3  // Количество вариантов отображения инфы
#define FREQ 250  // Задержка в основном цикле
#define BUZ_PIN 11  // Ножка для зуммера

// создаём объект для работы с часами реального времени
RTC clock;

unsigned short mode = 0;

uint8_t dot = 0x4;

void setup()
{
  pinMode(KEY_PIN,INPUT);
  pinMode(BUZ_PIN,OUTPUT);

  Serial.begin(9600);
 
  // инициализация часов
  clock.begin();
  // метод установки времени и даты в модуль вручную
  // clock.set(10,25,45,27,07,2005,THURSDAY);
  // метод установки времени и даты автоматически при компиляции
  //clock.set(__TIMESTAMP__);
}

void loop()
{
  // Проверяем нажата ли кнопка
  if (digitalRead(KEY_PIN) == HIGH)
  {
    // Меняем индекс отображаемой инфорамции
    mode++;
    // Издаем короткий звук для подтверждения что нажатие кнопки считано
    tone(BUZ_PIN, 3500, 40);
  }

  // Проверяем не вышел ли наш индекс за границы
  if (mode == MODE_NUM)
    mode = 0;  // Если да то меняем на начальный

  // запрашиваем данные с часов
  clock.read();

  if (mode == 0)
    DisplayHourMinute(); // Отображаем Часы и Минуты
  else if (mode == 2)
    DisplayDate();  // Отображаем день и месяц
  else
    DisplayDay();   // Отображаем день недели

  delay(FREQ);
}

void DisplayHourMinute()
{
  int hm = clock.getHour() * 100 + clock.getMinute();  
  uint8_t dot = 0x4;  // Нам нужна точка между второй и третьей цифрами
  uint8_t sec = clock.getSecond();
  if (sec % 2) // Для эффекта мигания отображаем точку только
    dot = 0x0;  // на нечётной секунде
 
  displayInt(DISPLAY_PIN, hm,true,dot);
}

void DisplayDate()
{
  int dm = clock.getDay()*100 + clock.getMonth();
  displayInt(DISPLAY_PIN, dm, true, 0x4);
}

// Ниже я просто отображаю константы заданные в .h файле
// для часов, чтобы получились три буквы дня недели...
// ну или что-то похожее
void DisplayDay()
{
  uint8_t dow = clock.getDOW();
  if (dow == 7)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_S, QD_U, QD_n);
  else if (dow == 1)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_H, QD_O, QD_n);
  else if (dow == 2)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_t, QD_U, QD_E);
  else if (dow == 3)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_H, QD_E, QD_d);
  else if (dow == 4)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_t, QD_h, QD_u);
  else if (dow == 5)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_F, QD_r, QD_I);
  else
    displayDigits(DISPLAY_PIN, QD_NONE, QD_S, QD_a, QD_t);
    
}

Ещё одна полезная функция – это датчик влажности и температуры на основе уже знакомого мне DHT22.

20160130_130724Добавить его в проект не составляет труда: требуется один дополнительный пин,

#define DHT_PIN 6

новый объект для работы с датчиком,

#define DHT_TYPE DHT22

DHT dht(DHT_PIN,DHT_TYPE);

две процедуры DisplayTemperature() и DisplayHumidity(), которые будут отображать температуру и влажность соответственно и будут работать аналогично DisplyaHourMinute()

void DisplayHumidity()
{
  float h = dht.readHumidity();
  displayFloat(DISPLAY_PIN, h, 2, true);
}

void DisplayTemperature()
{
  float t = dht.readTemperature();
  displayFloat(DISPLAY_PIN, t, 2, true);
}

и изменение количество вариантов отображаемой информации с 3х на 5, так как устройство теперь может показывать температуру и влажность.

#define MODE_NUM 5

Правда на половинной макетке, которую я использовал для прототипирования закончилось место, и я решил воспользоваться Troyka Shield-ом для прототипирования, для того, чтобы вынести часы реального времени и зуммер и освободить место для датчика влажности и температуры:

20160329_224355И хотя конечная цель проекта – это спарить Arduino с Android-ом, думаю что возможность устанавливать дату, время и будильник независимо от наличия телефона тоже пригодиться.

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

Монитор качества воздуха своими руками: Часть 2 – Сделай мне красиво

В предыдущем посте получилось пркатически готовое устройство, хоть и располагается на макетной плате.

IMG_20160206_153044Тем не менее, поскольку идея была именно переносного устройства, было решено организовать для всего этого дела корпус. Честно никогда не имел опыта создания чего либо подобного, поэтому первый блин вышел комом.

Для начала купил пластиковый корпус 100 на 100 на 40 с крышкой (взял на глаз). Оказалось, что Arduino Uno малость великоват,  для того, чтобы засунуть его в этот корпус вместе с батарейками (Кто бы мог подумать?), да и в целом ATmega328p-pu какая-то здоровая, поэтому решил искать варианты.

Первое что попалось на глаза – это ATTiny45, однако сходу перенести код не удалось, так как i2c тут реализован по-другому.

Следующим стал Arduino Nano. По сути тот же самый чип просто в другом форм факторе. Вот только Arduino IDE категорически отказывался заливать туда мои скетчи. Видимо через чур китайский

20160206_123021На просторах интернета был найден лёгкий способ бросить треску тебе в ебало мразь пример решения похожей проблемы. Собственно так я и сделал – поменял секцию Nano в файле boards.txt на нечто вроде этого:

nano.name=Arduino Nano

nano.upload.tool=avrdude
#nano.upload.protocol=arduino
nano.upload.using=arduino:arduinoisp

nano.bootloader.tool=avrdude
nano.bootloader.unlock_bits=0x3F
nano.bootloader.lock_bits=0x0F

nano.build.f_cpu=16000000L
nano.build.board=AVR_NANO
nano.build.core=arduino
nano.build.variant=eightanaloginputs

вариант подключения напрямую через USB так и не заработал, выдавая невнятное “did not find any USB device ‘usb'”, так что пришлось программировать Nano используя Arduino Uno в качестве программатора.

20160211_210204Отлично теперь всё работает, но тут я вспомнил, что у меня есть другой “девайс” со странным названием SEM0010 на ATmega328, который к тому же имеет макетную область. И о чудо – это практически Arduino Nano c ATmega328p-au и теми же 32мя контактами.

20160205_202226Единственное неудобство – это то что у него нет USB, поэтому его также пришлось перепрошивать используя Arduino Uno  в качестве программатора используя те же самые ножки и ICSP header на самом устройстве. Для проверки как обычно лучше всего походит пример blink:

20160227_165127Всё работает, так что залил свой скетч и стал припаивать датчики:

20160206_210002Заодно приобрёл и приделал к датчику пыли небольшой вентилятор, дабы тот протаскивал воздух через сенсор.

Вырезав в корпусе отверстия под датчик влажности и температуры на основе DHT22 и заодно окно под дисплей, стал впихивать всё это хозяйство в корпус.

20160206_20075020160206_222804

И вуаля – оно работает. Оказалось, правда, что я забыл сделать выключатель, поэтому пришлось воспользоваться зажимом типа “крокодил”. Позднее вставил маленький выключатель

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

20160211_221021Однако в коде обнаружилась бага – как не пытался я дымить на устройство и совать его в пыльные места, так и не получилось, чтобы он показывал что-то отличное от нуля намекая, что воздух в Москве кристально чистый.

Заново подключил к Arduino Uno, сняв заднюю крышку:

20160227_171928Попробовал пример от производителя Waveshare, но он показывает какие-то фиксированные значения, и прыгает от “идеальное качества воздуха” до “сильно загрязнённое” – ему веры нет. К тому же, посмотрев в код, я увидел, что там значения домнажаются на какие-то ничем не обоснованные константы, так что ну его нафиг.

Наткнулся на исследования, где данный сенсор сравнивается с дорогим мониторами качества воздуха. Надежды появилась, но ясности нет.

Последняя попытка привела меня на ещё один сайт с подобным проектом, где тоже есть непонятные мне домножения. Посмотрев ещё раз в Datasheet, я решил сделать очень просто. Выводить значение считанное с датчика не в вольтах, а в миливольтах, и больше не вычитать минимальное значения напряжения при отсутствии пыли, дабы увидеть если вообще какое-то изменение при “накуривании” сенсора. Заодно, руководствуясь графиком из Datasheet-а:

graphдобавил просто сообщение о качестве воздуха при разных значениях выдаваемого напряжения, вроде:

if (volt < 2500)
    return 0;
  else if (volt < 3600)
    return 1;
  else
    return 2;

и буду соответственно выводить на дисплей примерно так:

  if (bucket == 0)
    display.println("Perfect air");
  else if (bucket == 1)
    display.println("Avergae air");
  else if (bucket == 2)
    display.println("Light polution");
  else if (bucket == 3)
    display.println("Moderate pollution");
  else if (bucket == 4)
    display.println("Heavy pollution");
  else if (bucket == 5)
    display.println("Serious pollution");

20160227_213401Как будут интересные результаты – напишу финальный пост.

Управление жестами: Часть 1 – простые движения

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

Если вы едете в машине, то нажатие на кнопку вовремя движения (например, чтобы включить обдув лобового стекла) требует от вас оторвать глаза от дороги и найти эту кнопку… ну или нащупать по памяти. Гораздо проще произнести команду или махнуть рукой. И хотя распознавание речи тоже интересная тема, но тут я решил сконцентрироваться исключительно на устройствах распознавания жестов, с которыми мне удалось поработать – это Myo armband, Leap Motion и SparkFun RGB and Gesture Sensor.

20151215_123444 20151215_123832

Три разные “игрушки” , которые обладают разным функционалом, ценой, размерами и, видимо, областью применения.

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

одним из типичных представителей является ультразвуковой датчик определения расстояния, например hc-sr04.

20160227_221429Использовать его просто: согласно datasheet-у подается напряжение на триггер в течении 10 микросекунд, а затем считывается сигнал с ножки echo. Конечно уже есть готовые библиотеки для работы с этим датчиком, например NewPing, правда качество измерения крайне низкое если расстояние больше 10см.

20160301_134902Соответственно можно сделать простой скетч, который бы в случае обнаружения препятствия на расстоянии меньше, скажем, 6 сантиметров срабатывал как нажатие на кнопку. Вот пример реализации с ипользованием простенького i2c дисплея для отображения информации:

#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define Trig_pin 8
#define Echo_pin 7

#define press_range 6
#define measures 20

bool pressed = false;

void setup() {
  // put your setup code here, to run once:
  pinMode(Trig_pin, OUTPUT);
  digitalWrite(Trig_pin, LOW);
  pinMode(Echo_pin, INPUT);
 
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  pressed = false;
  display.display();
}

void loop() {
  unsigned int range = Ranging();


  if (range < press_range)
    pressed = !pressed;

  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
 
  display.print("cm: ");
  display.println(range);
  if (pressed)
    display.println("pressed");
  else
    display.println("released");
 
  display.display();
  delay(300);
}

unsigned int Timing()
{
  digitalWrite(Trig_pin, HIGH);
  delayMicroseconds(10);
  digitalWrite(Trig_pin, LOW);
 
  return pulseIn(Echo_pin, HIGH);
}

unsigned int Ranging()
{
  unsigned int avgduration = 0;
  unsigned int maxdur = 0;
  unsigned int mindur = 0;
 
  for (int i = 0; i < measures; i++)
  {
    unsigned int etime = Timing();
    if (etime > maxdur)
      maxdur = etime;
    if (etime < mindur)
      mindur = etime;

    avgduration += etime;
    delay(1);
  }
 
  avgduration = (avgduration - (maxdur + mindur)) / (measures-2);
  return (avgduration /29 / 2);
}

Функция Timing() замеряет время которое потребовалось отражённому сигналу, чтобы вернуться. Функция Ranging() вызывает её 20 раз с задержкой 1 милисекунда, максимальное и минимальное значение отбрасываются, а оставшиеся усредняются. Затем просто проверяется, что если расстояние меньше заданного значения – 6 см, то произошло нажатие на кнопку. Работает это таким вот образом:

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

20160302_183300У них меньше дальность обнаружения, но есть цифровой выход, который говорит – близко объект или нет. Вот мой пример реализации:

#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define din1 4
#define ain1 A0

#define din2 2
#define ain2 A1

bool t1 = false;
bool t2 = false;
short drctn = 0;

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.display();
 
  pinMode(din1,INPUT);
  pinMode(ain1,INPUT);

  pinMode(din2,INPUT);
  pinMode(ain2,INPUT);  
 
}

void loop() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);  

  unsigned int ad1=analogRead(ain1);
  unsigned int ad2=analogRead(ain2);

  int din1_val = digitalRead(din1);
  int din2_val = digitalRead(din2);

  if (t1 && !t2 && (din2_val == LOW))
    drctn = 1;
  else if (t2 && !t1 && (din1_val == LOW))
    drctn = -1;
  else if (!t1 && !t2)
    drctn = 0;
    
  if(din1_val==LOW)
  {
    display.print("1: Near | ");   
    t1 = true;
  }
  else
  {
    t1 = false;
    display.print("1: Far  | ");       
  }
 
  if(din2_val==LOW)
  {
    display.println("2: Near ");   
    t2 = true;
  }
  else
  {
    t2 = false;
    display.println("2: Far  ");       
  }
 
  display.print(ad1);
  display.print(" | ");
  display.println(ad2);

  if (drctn == -1)
    display.println("left");
  else if (drctn == 1)
    display.println("right");

 
  display.display();
  delay(100);
}

И вот как это работает:

Соответственно если добавить ещё один датчик расстояния, то можно расширить количество распознаваемых движений.

20160303_143009Например теперь можно распознать движения вверх и вниз. Вот как это работает:

Скетч отличается не сильно – идея та же самая. Если сначала сработал верхний дальномер а потом оба нижних, то движение вниз, если сначала оба нижних, а потом верхний, то движение вверх.

Ещё один момент, которй можно сюда добавить – это “нажатие” как в примере с ультразвуковым датчиком. Вот полный скетч, который включает движения влево, вправо, вверх, вниз и нажатие.

#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define din1 4
#define ain1 A0

#define din2 2
#define ain2 A1

#define din3 7
#define ain3 A2

bool t1 = false;
bool t2 = false;
bool t3 = false;
short drctn = 0;

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.display();
 
  pinMode(din1,INPUT);
  pinMode(ain1,INPUT);

  pinMode(din2,INPUT);
  pinMode(ain2,INPUT);  

  pinMode(din3,INPUT);
  pinMode(ain3,INPUT);    
 
}

void loop() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);  

  unsigned int ad1=analogRead(ain1);
  unsigned int ad2=analogRead(ain2);
  unsigned int ad3=analogRead(ain3);

  int din1_val = digitalRead(din1);
  int din2_val = digitalRead(din2);
  int din3_val = digitalRead(din3);

  if (drctn == 0)
  {
    if (t1 && !t2 && (din2_val==LOW))
      drctn = 1;
    else if (t2 && !t1 && (din1_val==LOW))
      drctn = -1;
    else if (t1 && t2 && (din3_val==LOW))
      drctn = 2;
    else if (t3 && !t2 && !t2 && (din1_val==LOW) && (din2_val==LOW))
      drctn = -2;    
    else if (!t1 && !t2 && !t3 &&(din1_val==LOW) && (din2_val==LOW)&& (din3_val==LOW))
      drctn = -3;
  }
  if(din1_val==LOW)
  {
    display.print("1 Near ");   
    t1 = true;
  }
  else
  {
    t1 = false;
    display.print("1 Far ");       
  }
 
  if(din2_val==LOW)
  {
    display.print("2 Near ");   
    t2 = true;
  }
  else
  {
    t2 = false;
    display.print("2 Far ");       
  }

  if (din3_val==LOW)
  {
    display.println("3 Near");
    t3=true;
  }
  else
  {
    t3 = false;
    display.println("3 Far");
  }
 
  display.print(ad1);
  display.print(" ");
  display.print(ad2);
  display.print(" ");
  display.println(ad3);

  if (drctn == -1)
    display.println("left");
  else if (drctn == 1)
    display.println("right");
  else if (drctn == 2)
    display.println("up");
  else if (drctn == -2)
    display.println("down");
  else if (drctn == -3)
    display.println("push");
 
  display.display();

  if (!t1 && !t2 && !t3)
    drctn = 0;  
  delay(100);
}

и  пример того, как это работает

в следующем посте уже будут готовые решения вместо кустарных. Как вам идея управления жестами в целом?

Монитор качества воздуха своими руками: Часть 1 – Начало

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

Для начала я купил датчик пыли на основе sharp GP2Y1010AU0F прменяется для обнаружения PM2.5 частиц (менее 2.5 микрон) и сигаретного дыма. Принцип действия довольно простой: фототранзистор распознает свет, отражённый от частиц, который посылает инфракрасный светодиод.

20160130_130529Для подключения к Arduino не требуется ничего особенного: чёрный провод земля, красный 5 вольт, жёлтый на цифровой Pin 7, для управления (вкл/выкл) тем самым инфракрасным светодиодом, и синий на один из аналоговых пинов, например A0, для чтения показаний датчика.

Для того, чтобы считать данные с датчика, согласно DataSheet-у нужно включить инфракрасный светодиод, подождать 280 микросекунд, и затем померить выходное напряжение датчика.

#define DUST_ILED 7
#define DUST_PIN 0
#define NO_DUST_VOLT 0.9

void setup() {
  pinMode(DUST_ILED, OUTPUT);
  digitalWrite(DUST_ILED, LOW);
 
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(DUST_ILED, HIGH);
  delayMicroseconds(280);

  int outVolt = analogRead(DUST_PIN);

  digitalWrite(DUST_ILED, LOW);

  float calcVoltage = outVolt * 5.0 / 1024.0 - NO_DUST_VOLT;
  if (calcVoltage < 0) {
    calcVoltage = 0;
  }

  delay(1000);
}

Здесь мы делим значение, которое считали с датчика на 1024 – это максимальное значение, которое может считать Arduino Uno на аналоговых пинах и умножаем на 5 – напряжение, которое мы подали на датчик.

Согласно DataSheet-у типичное напряжение, которое показывает датчик при отсутствии пыли – это 0.9 вольт, так что просто вычтем это значение и будем считать, что наличие пыли начинается при напряжении > 0.9 вольт.

Второе, что нужно сделать – это отображать считанные значения. Простейший вариант – это напечатать в консоль, но когда ходишь по городу, то лучше иметь какой-нить маленький дисплей и я купил  0.96″ OLED дисплей с интерфейсом i2c, которому требуется 2 аналоговых пина (SDA на A4, а SCL на A5) плюс напряжение и земля.

20160130_174819У AdaFruit есть отличная, но “тяжёлая” библиотека для работы с подобными дисплеями. Прежде чем ею можно воспользоваться нужно поменять пару вещей в зависимости от модели дисплея. В моём случае это i2c адрес, который нужно выставить в 0x3C . Так же может понадобиться другая AdaFruit библиотека – AdaFruit GFX.

#include <Adafruit_SSD1306.h>

#define DUST_ILED 7
#define DUST_PIN 0
#define NO_DUST_VOLT 0.9

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

void setup() {
  pinMode(DUST_ILED, OUTPUT);
  digitalWrite(DUST_ILED, LOW);
  // put your setup code here, to run once:
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  display.display();
  delay(2000);

  // Clear the buffer.
  display.clearDisplay();
  display.display();
  

  
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(DUST_ILED, HIGH);
  delayMicroseconds(280);

  int outVolt = analogRead(DUST_PIN);

  digitalWrite(DUST_ILED, LOW);

  float calcVoltage = outVolt * 5.0 / 1024.0 - NO_DUST_VOLT;
  if (calcVoltage < 0) {
    calcVoltage = 0;
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("Dust: ");
  display.println(calcVoltage);  
  display.display();
  delay(1000);
}

Теперь необходимо добавить датчики температуры и влажности для полноты информации. Например подойдёт DHT22 – это два датчика в одном.

20160130_130724У него 4 ножки, но одна (третья, согласно DataSheet-у) не используется, так что просто нужно подключить 5 вольт, землю и один из цифровых пинов, например 2й. Так же есть библиотека для работы с этим датчиком, так что считывание показаний можно легко добавить в предыдущий скетч:

#include <Adafruit_SSD1306.h>
#include <DHT.h>

#define DHT_TYPE DHT22
#define DHT_PIN 2

#define DUST_ILED 7
#define DUST_PIN 0
#define NO_DUST_VOLT 0.9

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

DHT dht(DHT_PIN,DHT_TYPE);

void setup() {
  pinMode(DUST_ILED, OUTPUT);
  digitalWrite(DUST_ILED, LOW);
  // put your setup code here, to run once:
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  display.display();
  delay(2000);

  // Clear the buffer.
  display.clearDisplay();
  display.display();
  

  
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(DUST_ILED, HIGH);
  delayMicroseconds(280);

  int outVolt = analogRead(DUST_PIN);

  digitalWrite(DUST_ILED, LOW);

  float calcVoltage = outVolt * 5.0 / 1024.0 - NO_DUST_VOLT;
  if (calcVoltage < 0) {
    calcVoltage = 0;
  }

  float h = dht.readHumidity();
  float t = dht.readTemperature();
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("H: ");
  display.print(h);
  display.print(" T: ");
  display.println(t);  
  display.println("");
  display.setTextSize(2);
  display.print("Dust: ");
  display.println(calcVoltage);  
  display.display();
  delay(1000);
}

просто читаем влажность и температуру с помощью библиотечных функций dht.readHumidity() и dht.readTemperature() и выводим их на экран:

20160130_21055820160130_221420

Ещё нужен источник питания, такой, чтобы можно было легко носить “прибор” с собой. Например подойдут пальчиковые (АА) батарейки, которые можно вставить в батарейный отсек и приделать к ним разъём с клеммой для лёгкого способа подключения к Arduino

20160130_130601Сам Arduino засунуть в корпус и приклеить батарейный отсек с помощью 2х стороннего скотча:

20160130_130621 20160130_130631

Что скажите? Полезный девайс?

Управляемая RGB лампочка: Часть 1 – NeoPixel-и

Сейчас куда не глянь везде всё умное. Умные розетки, кофеварки, телевизоры и конечно же лампочки. Одной из первых таких ламп я увидел Philips HUE, и загорелся идеей сделать свою управляемую RGB лампочку. У такой лампочки масса очевидных преимуществ: задание цвета под настроение, автоматическое включение/выключение, светобудильник, плюс неочевидные, как например светомузыка. Сказано сделано – начинаем копать.

Первое, что нужно решить – это каким будет источник света. Сейчас в моде светодиоды – они компактны и потребляют мало электричества, к тому же у меня завалялось 15 NeoPixel-ей от Adafruit на основе ws2812b.

20160117_185334Это 3 светодиода в одном плюс управляющая электроника. Особенность в том, что управляются они по одному проводу (вместе с напругой и землёй – три). Грубо говоря по проводу с данными вы передаёте сигнал, представляющий массив значений цветов. А дальше первый NeoPixel “откусывает” от этого массива первый элемент, выставляет его себе и передаёт остаток дальше и так далее по цепочке.

20160117_193644Второе – это какую беспроводную технологию использовать. WiFi мне кажется более универсальной и лучше масштабируемой в отличии от Bluetooth – это позволяет добавлять огромное количество таких ламп и заодно даёт возможность управления ими с помощью любого устройства, находящегося в этой же сети (да и за её пределами). Да и радиус действия у них гораздо больше. К тому же у меня валялось 3 модуля на основе esp8266.

20160117_190110Третье – это на чем реализовать управляющую логику. В наличии был Arduino Uno к нему и решил подключить NeoPixel-и, следуя инструкции на сайте Adafruit.

20160117_195326Однако Arduino Uno слишком большой, чтобы он мог поместиться в корпус стандартной лампочки. В интернете мне попалось видео, как уменьшить свои Arduino Uno проекты используя ATTiny45/85. К тому же на instructables есть guide, как заставить ATTiny работать с NeoPixel-ями. Не долго думая я купил ATTiny45 и перенёс пример с полноценной Arduino Uno на эту мелочь.

20160117_201131 1 20160117_204324

С другой стороны esp8266 – это очень мощная штука при малых размерах. Помимо WiFi этот SoC имеет ещё и GPIO. Я взял ESP-01 у которого есть два GPIO. К тому же Arduino IDE поддерживает это семейство начиная с версии 1.6.4 и у Adafruit даже есть гайд по настройке. Единственная проблема – это то, как заливать туда свой софт. Я купил USB-Serail converter который имеет выходной сигнал на 3.3 вольта, так как 5 вольт убьет esp8266 и сделал две кнопки на макетной плате, так как для заливки нового софта требуется перевести один из пинов в Low и затем RESET-нуть esp8266, более подробно о том как заливать свои программы можно прочитать тут:

20160117_193458 20160117_193920 1

Пусть вас не смущает наличие Arduino Uno на картинке справа – его я использовал исключительно, чтобы получить 3.3 вольта и запитать esp8266, а также чтобы получить 5 вольт для NeoPixel-ей.

20160118_205216Стандартный пример от Adafruit заработал и я бросился писать простенькую программку, которая бы коннектилась к моей домашней WiFi сети, поднимала сокет и ждала число, которое бы определяло цвет светодиодов. В интернете я наткнулся на хороший пример того, как общаться с esp8266 и на его основе с небольшими изменениями, используя Adafruit API для управление светодиодами, я добился чего хотел.

Для проверки накропал простенький скрипт на Perl-е, который бы и отправлял цвет для лампы:

use strict;

use IO::Socket::INET;

my $sock = new IO::Socket::INET(PeerAddr => '192.168.1.35',
                                PeerPort => '6969',
                    Proto => 'tcp') || die "Can't bind $@\n";

$sock->send(shift."\n");

И сама прошивка

#include <Adafruit_NeoPixel.h>
#include <ESP8266WiFi.h>

#define NEOPIXEL_DATA_PIN 0
#define PIXELS_NUM 7

Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXELS_NUM,
                                            NEOPIXEL_DATA_PIN,
                                           NEO_RGB + NEO_KHZ800);

const char* ssid = "mynetworkssid";
const char* password = "mynetpass";
WiFiServer server(6969);                                           

void setup() {
  Serial.begin(115200);
  strip.begin();
  strip.show();
 
  WiFi.begin(ssid, password);

  server.begin();
  while(WiFi.status() != WL_CONNECTED)
  {
    delay(500);
  }
}

void loop() {

  WiFiClient client = server.available();  
  if (client)
  {
    Serial.println("client connected.");
    while(client.connected())
    {
      if (client.available())
      {      
        String command;
        char c = client.read();
        while(c != '\n')
        {
          command += c;
          c = client.read();
        }

        if (command != 0)
        {
          char** tmp;
          char colorcommand[10];
          command.toCharArray(colorcommand, sizeof(colorcommand));
          
          uint32_t iColor = strtoul(colorcommand, tmp, 10);
           SetStripColors(iColor);
        }
      }
    }
    Serial.println("Client disconnected.");
    client.stop();
  }
}

void SetStripColors(uint32_t color)
{
  for (uint16_t i=0; i< PIXELS_NUM; i++)
  {
    strip.setPixelColor(i, color);
  }
  strip.show();
  delay(100);
}

Теперь дело за спецэфектами!