Monthly Archives: February 2016

Монитор качества воздуха своими руками: Часть 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);
}

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

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