Category Archives: DIY

Будильник своими руками: Част 2 – Пищалка

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

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

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

// PIN Для подключения Quad Display От Amperka
#define DISPLAY_PIN 7
// Кнопка
#define KEY_PIN 8
// Количество вариатнтов отображения информации:
// Часы, Дата, будильник, День недели, Температура, влажность
#define MODE_NUM 6
// задержка для функции loop
#define FREQ 200
// PIN для зуммера
#define BUZ_PIN 11
// PIN для датчика влажности и температуры
#define DHT_PIN 6
// Тип датчика влажности и температуры
#define DHT_TYPE DHT22

// количество циклов для долгого нажатия
#define EDIT_COUNTER 7

#define LONG_PRESS 2
#define SHORT_PRESS 1

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

// Объект для работы с датчиком температцры и влажности
DHT dht(DHT_PIN,DHT_TYPE);


byte longpress = 0;
byte shortpress = 0;

// Тип Текущей отображаемой информации
byte mode = 0;
// Режим редактирования
byte edit = 0;
// Как долго зажата кнопка
byte key_counter = 0;
// Текущее значение для текущего режима отображения
int cur_val;
// Время срабатывания будильника
int alarm_time = -1;
// Время срабатывания Snooze-а
int snooze_time = -1;
// Отображение точки по центру
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()
{
  // Считтываем было ли короткое нажатие или длинное
  byte KeyPressed = GetKeyPressed();

  // Запомнаем текущее значение для режима редактирования
  byte cedit = edit;
  // изменяем значения глобальных переменных в зависимости от нажатия
  AdjustGlobalVars(KeyPressed);
  
  // Если время для срабатывания будильника
    if ((snooze_time != -1) && (IsAlarmTime()))
    {
      // Короткое нажатие - snooze
      if (KeyPressed == 1)
      {
        // Сбрасываем на отображение часов
        mode = 0;
        // Прибавляем к будильнику 1 минуту
        snooze_time = snooze_time + 1;

        // проверяем не вышли ли мы за границы 60 минут
        if (snooze_time % 100 > 59)
          snooze_time += (100 - 60);
        // Проверяем не вышли ли мы за границы 24 часа
        if (snooze_time / 100 > 23)
          snooze_time -= 24;       
      }
      // Если долгое нажатие - отключить будильник
      else if (KeyPressed == 2)
      {
        // Сбрасываем все переменные
        snooze_time = alarm_time;
        mode = -1;
        edit = 0;
      }
      AlarmSound();
    }

  // Если режим редактирования
  if (edit > 0)
    EditCurrentMode(KeyPressed);
  else {
    // Если мы вышли из режима редактирования
    if (cedit != edit)
    {
      // то меняем текущее значение
      SetCurrentMode();
    }
    // Отображаем текущее значение
    DisplayCurrentMode();
  }

  // спим
  delay(FREQ);
}

// Не пора ли будильнику звонить
bool IsAlarmTime()
{
  uint8_t hour = clock.getHour();
  uint8_t minute = clock.getMinute();

  if ((snooze_time / 100 == hour) && (snooze_time %100 == minute))
  {
    return 1;
  }

  return 0;
}

// Было длинное или короткое нажатие?
byte GetKeyPressed()
{
  byte result = 0;
  if (digitalRead(KEY_PIN) == HIGH)
  {
    key_counter++; 
    if (key_counter > EDIT_COUNTER)
    {
      result = 2;
      key_counter = 0;      
    }    
  }
  else
  {

    if(key_counter > 0)
    {
      result = 1;
    }
    key_counter = 0;   
  }

  return result;
}

// Изменяем глобальные переменные в зависимости от нажатия
void AdjustGlobalVars(byte KeyPressed)
{
  if (KeyPressed == LONG_PRESS)
  {
    LongPressSound();
        
    // Редактируем либо первую либо вторую пару чисел
    edit++;
    if (edit > 2)
      edit = 0;
  }      
  else if(KeyPressed == SHORT_PRESS)
  {
    ShortPressSound();
    
    // В режиме отображения короткое нажатие меняет отображаемую информацию
    if (edit == 0)
      mode++;
    if (mode > MODE_NUM)
      mode = 0;
  }

}

void AlarmSound()
{
  tone(BUZ_PIN, 3500,40);
  tone(BUZ_PIN, 4500, 40);
  delay(100);
  tone(BUZ_PIN, 4500, 40);
}

void LongPressSound()
{
    tone(BUZ_PIN, 4500, 40);    
    delay(100);      
    tone(BUZ_PIN, 4500, 40);    
}

void ShortPressSound()
{
  tone(BUZ_PIN, 3500, 40);    
}

// Что именно мы редактируем
void EditCurrentMode(byte pressed)
{
  clock.read();

  // совпадает с тем что отображаем
  if (mode == 0)
    EditHourMinute(pressed);
  else if (mode == 1)
    EditDate(pressed);    
  else if (mode == 2)
    EditAlarm(pressed);
  else if (mode == 3)
    EditDay(pressed);
  
}

// Редактирование законченоо - пора поменять текущие значения
void SetCurrentMode()
{
  // Меняем время
  if (mode == 0)
  {
    uint8_t hour = cur_val / 100;
    uint8_t minute = cur_val % 100;
    clock.set(hour, minute, 0, clock.getDay(), clock.getMonth(),
              clock.getYear(), clock.getDOW());
  }
  // Меняем дату
  else if (mode == 1)
  {
    uint8_t day = cur_val / 100;
    uint8_t month = cur_val % 100;
    clock.set(clock.getHour(), clock.getMinute(),
              clock.getSecond(), day, month, clock.getYear(), 
              clock.getDOW());

    mode = 3;
    edit = 1;
  }
  // Устанавливаем будильник
  else if (mode == 2)
  {
    alarm_time = cur_val;
    snooze_time = cur_val;
    mode = 0;
  }
  // Меняем день недели
  else if (mode == 3)
  {
    clock.set(clock.getHour(), clock.getMinute(),
              clock.getSecond(), clock.getDay(), clock.getMonth(),
              clock.getYear(), cur_val);    
  }
}

// Редактирование времени
void EditHourMinute(byte pressed)
{
  // clock.set(10,25,45,27,07,2005,THURSDAY);
  int hour = int(cur_val / 100);
  int minute = int(cur_val % 100);
   
  // Если редактируем первую пару при коротком нажатии прибавляем час
  if ((pressed==1) && (edit == 1))
    hour = hour +1;
  // Если вторую - прибавляем минуту
  else if ((pressed==1) && (edit == 2))
    minute = minute +1;

  // Проверка выхода за границы
  if (hour > 23)
    hour = 0;
  if (minute > 59)
    minute = 0;
    
  // Обновляем переменую из которой будет считано новое время
  cur_val = hour * 100 + minute;

  // Эфект моргания
  static bool show;
  show = !show;
  if (show)
  {
    // При редактировании первой пары отображаем только минуты
    if (edit == 1)
      displayLastTwo(DISPLAY_PIN, minute);
    // При редактировании второй пары отображаем только часы
    if (edit == 2)
      displayFirstTwo(DISPLAY_PIN, hour);
  }
  else
  {
    // Отображаем целиком
    DisplayHourMinute(cur_val);
  }
}

// Изменение даты аналонично идменению времени
void EditDate(byte pressed)
{
  uint8_t day = int(cur_val / 100);
  uint8_t month = int(cur_val % 100);
  
  static bool blnk;
  blnk = !blnk;

  if ((pressed==1) && (edit == 1))
    day = day +1;
  else if ((pressed==1) && (edit == 2))
    month = month +1;

  if (day > 31)
    day = 1;
  if (month > 12)
    month = 1;

  cur_val = day*100 + month; 

  if (blnk)
  {
    if (edit == 1)
      displayLastTwo(DISPLAY_PIN, month);
    if (edit == 2)
      displayFirstTwo(DISPLAY_PIN, day);
  }
  else
  {
    displayInt(DISPLAY_PIN, cur_val, true, 0x0);
  }
}

// Изменение дня недели
void EditDay(byte pressed)
{
  if (pressed == 1)
    cur_val++;

  if (cur_val > 6)
    cur_val = 0;
    
  static bool show;
  show = !show;
  
  if (show)
  {
    DisplayDay(cur_val, true);
  }
  else
  {
    DisplayDay(cur_val, false);
  }
}

// Изменение будильника
void EditAlarm(byte pressed)
{
  uint8_t minute = cur_val % 100;
  uint8_t hour = cur_val / 100;

  if ((pressed==1) && (edit == 1))
    hour = hour +1;
  else if ((pressed==1) && (edit == 2))
    minute = minute +1;

  if (hour > 23)
    hour = 0;

  if (minute > 59)
    minute = 0;

  cur_val = hour * 100 + minute;
  
  static bool blnk;
  blnk = !blnk;
  
  if (blnk)
  {
    if (edit == 1)
      displayLastTwo(DISPLAY_PIN, minute);
    if (edit == 2)
      displayFirstTwo(DISPLAY_PIN, hour);
  }
  else
  {
    displayInt(DISPLAY_PIN, cur_val, true, 0x0);
  }
}

// Отображаем текущую выбраную информацию
void DisplayCurrentMode()
{
  if (mode == MODE_NUM)
    mode = 0;
    
  //displayClear(DISPLAY_PIN);

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

  if (mode == 0)
    DisplayHourMinute(-1);
  else if (mode == 1)
    DisplayDate();
  else if (mode == 2)
    DisplayAlarm();
  else if (mode == 3)
    DisplayDay(-1, false);
  else if (mode == 4)
    DisplayTemp();
  else if (mode == 5)
    DisplayHum();
  else
    DisplayHourMinute(-1);

}

// Отображаем влажность
void DisplayHum()
{
  float h = dht.readHumidity();
  displayFloat(DISPLAY_PIN, h, 2, true);
}

// Отображаем температуру
void DisplayTemp()
{
  float t = dht.readTemperature();
  displayTemperatureC(DISPLAY_PIN, round(t), false);
}

// Отображаем время
void DisplayHourMinute(int cv)
{
  int hm = cv;
  if (cv == -1)
  {
    hm = clock.getHour() * 100 + clock.getMinute();  
    cur_val = hm;
  }
  
  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();

  cur_val = dm;
  displayInt(DISPLAY_PIN, dm, true, 0x4);
}

// Отображаем будильник
void DisplayAlarm()
{
  cur_val = alarm_time;
  if (alarm_time != -1)
    displayInt(DISPLAY_PIN, alarm_time, true, 0x4);
  else
    displayDigits(DISPLAY_PIN, QD_MINUS, QD_MINUS,
                                QD_MINUS, QD_MINUS);
}

// Отображаем день недели
void DisplayDay(int cv, bool minus)
{
  
  uint8_t dow = cv;
  if (cv == -1)
  {
    dow = clock.getDOW();
    cur_val = dow;
  }
  uint8_t first = QD_NONE;
  if (minus)
    first = QD_MINUS;
  if (dow == 0)
    displayDigits(DISPLAY_PIN, first, QD_S, QD_U, QD_n);
  else if (dow == 1)
    displayDigits(DISPLAY_PIN, first, QD_H, QD_O, QD_n);
  else if (dow == 2)
    displayDigits(DISPLAY_PIN, first, QD_t, QD_U, QD_E);
  else if (dow == 3)
    displayDigits(DISPLAY_PIN, first, QD_H, QD_E, QD_d);
  else if (dow == 4)
    displayDigits(DISPLAY_PIN, first, QD_t, QD_h, QD_u);
  else if (dow == 5)
    displayDigits(DISPLAY_PIN, first, QD_F, QD_r, QD_I);
  else if (dow == 6)
    displayDigits(DISPLAY_PIN, first, QD_S, QD_a, QD_t);
  else 
  {    
    displayDigits(DISPLAY_PIN, QD_o, QD_o, QD_o, QD_o);
  }
    
}

И пример работы

Теперь ситуация исправлена и пришло время двигаться дальше. Единственное пока, я это делал, то наблюдал странные артефакты, когда пытался задать дату, и для отладки приобрёл ещё один 4 значный дисплей, так как простое логирование с использованием Serial мне не помогало.

20160411_23201620160412_204444

Этот дисплей немного меньше и имеет регулируему яркость.

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

Однако вернёмся к проблеме неправильного отображения информации при мигании дисплея – пришёл к выводу, что проблема возникает при очистке дисплея слишком быстро если не все разряды что-нибудь да отображают. Поэтому для решения проблемы, там где ничего не должно отображаться, например если будильник не установлен или при задании дня недели, я  отображаю символ “-“.

20160417_191527 20160417_191535

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

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

Update: Обновил коментарии в скетче.

Update2: Две функции используемы в скетче displayFirstTwo() и displayLastTwo() это функции, которые я добавил в библиотеку для работы с QuadDisplay от амперки.

Сделай сам себе ардуину: Часть 1 – ATMega

Поскольку Arduino – это открытая платформа, то каждый встрченый-поперечный может сделать себе Arduino сам.
На сайте Arduino в общем-то даже есть инструкция.
Вот, например, видео в котором показан примерный процесс

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

Разумеется, в зависимости от реализации, можно отбросить те или иные части Arduino для уменьшения проекта.

Меня это момент заинтересовал и я решил попробовать сделать себе Arduino на макетке. Всё, как и на видео выше, очень просто, с той лишь разницей, что я купил микросхему ATmega328p-pu без bootloader-а.

20160205_205831Одна микросхема ATmega328p-pu, один кристалл на 16 Mhz, два конденсатора на 22 пикофарад и резистор – вот и весь Arduino Uno. Да у него нет USB и ещё много чего вроде самовостанавливающегося предохранителя, но для конечного проекта этого как правило и не важно.

Чтобы собрать такой Arduino нужно вставить 16 МГц кристалл между 9 и 10 ножками. Два конденсатора идут между землёй – ножка 8 и ножками кристалла, например один конденсатор между и 10й, а второй между и .

Сначала нужно залить скетч ArduinoISP в тот Arduino, который будет выступать в качестве программатора.

ArduinoISPЗатем поменять настройки и выбрать плату в которую будут заливаться скетчи, а в качестве программатора выбрать Arduino as ISP. Залить bootloader используя Arduino Uno, соединив контакты следующим образом (для Arduino Uno/Nano)

D10 -> Reset (Первая нога)
D11 (MOSI) -> MOSI (17я нога)
D12 (MISO) -> MISO (18я нога)
D13 (SCK) -> SCK (19я нога)

и добавив конденсатор на Arduino Uno, который используется в качестве программатора, между Reset-ом и землёй.

nanoviauno 20160211_210204

Теперь можно заливать Sketch-и в Arduino у которых поломан или отсутствует USB.

Вот, например, мой проект по мониторингу качества воздуха работает на самодельной Arduino Uno собранной на макетной плате:

IMG_20160206_153044Но что если хочется ещё меньше? Первое, что попалось мне в интернете это ATTiny45/85:

Собственно работает неплохо. Например я собрал простенькую схему для управления светодиодами на основе ws2812b:

20160117_204324Так любимая мной шина i2c тоже работает но с некоторыми переделками.

Схема подключения для программирования с использованием Arduino Uno в качестве программатора аналогична подключению к Arduino Nano, достаточно посмотреть распиновку для ATTiny в Googl-е:

20160407_135833 20160407_140704

Другая альтернатива – это Arduino Nano или ATmega328p-au, которая немного больше ATtiny, но полнофункиональна как ATmega328p-pu на Arduino Uno.

20160206_12302120160227_124652

Мне понравился вариант SEM0010, который идёт сразу с большой макетной областью:

20160206_123029Минусом является то, что отсутствует USB для заливки скетчей, однако тут тоже можно использовать другой Arduino в качестве программатора. К тому же тут есть ICSP разъём, что позволяет подключить её без пайки. Вот например простейший пример Blink, залитый через ICSP:

20160227_165127Собственно ICSP это те же самые MOSI/MISO/SCK, так что по прежнему используются pin-ы с 10 по 13й на Arduino, которая выступает в качестве программатора. Кстати если вы откроете стандартный sketch ArduinoISP вы даже найдёте там описание того, как использовать Arduino в качестве программатора.

Ещё один вариант миниатюризации – это Искра Мини от Амперки.

20160315_150541Это та же ATMega328p-au, так что с ней можно работать так же как и с Arduino Nano.

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

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

Будильник своими руками: Часть 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);
}

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

Цветомузыка в машину: Част 2 – Оцифровка

После того, как заработал прототип, я начал изучать варианты для захвата звука. На Nexus 7 есть выход на наушники, который я подключаю к AUX входу в машине. В целом работает неплохо, но есть посторонние шумы. Кто именно виноват не понятно, а вот что делать – есть варианты.

  • Первый и самый простой – это забить.
  • Второй – это использовать внешнюю звуковух вместе с Nexus 7
  • третий – это отказаться от использования планшета и собрать мультимедиа систему на базе одноплатного компьютера типа Raspberry Pi 2 или ODROID C1+. Вместе с HiFi Shield-ом (Для Raspberry Pi вот и Odroid C1 вот)

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

  • Первый, очевидный способ – это взять уже имеющийся EasyCap, по сколько он не только оцифровывает видео, но и аудио.
  • Второй – взять USB звуковуху специально для этой задачи.

20151103_220604

Решил попробовать оба. EasyCap на основе utv007 у меня уже был, а вот звуковуху пришлось купить, к счастью стоит она ~500р.

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

Для того, чтобы указать название звуковой карты нужно запустить скрипт (audio_in_cards.py), который идёт в комплекте – он то и выдаст список подключенных звуковых устройств. В моем случае правда он ничего не принтит и мне пришлось сделать небольшую модификацию в нём, чтобы он заработал:

cd lightshowpi/tools
vim audio_in_cards.py

надо на последнюю строчку добавить print

import alsaaudio as aa

if __name__ == "__main__":
    print aa.cards()

теперь всё как надо – запускаем:

pi@rpitestmusic ~/lightshowpi/tools $ sudo python audio_in_cards.py
[u'ALSA', u'usbtv']

Тут видно, что для EasyCap – это usbtv. Соответственно просто пропишем это в конфиг.

vim /home/pi/.lights.cfg

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

[hardware]
gpio_pins = 0,1,2

[lightshow]
mode = audio-in
audio_in_card = usbtv

Собственно, надо попробовать – как это работает. Запускаем скрипт в режиме захвата

sudo python py/synchronized_lights.py

Первое, чтоб бросается в глаза – это “шум”. Даже если нет никаких звуков лента всё равно моргает.

Теперь заменим EasyCap на купленную USB звуковуху. Честно взял некую рандомную. Вот dmesg если вдруг интересно:

cmediadmesgСнова запустив скрипт, который показывает название аудио устройств в системе, увидим, что имя, которое нужно поместить в конфиг – это просто Device.

pi@rpitestmusic ~/lightshowpi/tools $ sudo python audio_in_cards.py
[u'ALSA', u'Device']

Заменим usbtv на Device, используя любимый редактор vim /home/pi/.lights.cfg

[hardware]
gpio_pins = 0,1,2

[lightshow]
mode = audio-in
#audio_in_card = usbtv
audio_in_card = Device

И ребутнем Raspberry для профилактики возможных USB глитчей. После чего запустим скрипт syncronized_lights.py теперь уже с USB звуковой картой.

Заодно прикинул как это будет выглядеть если прилепить светодиодную ленту за телевизором:

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

Что скажите?

 

 

Цветомузыка в машину: Част 1 – прототип

Как я уже писал в этом посте при постройке клона Ambilight-а я купил “неправильную” светодиодную ленту, которую решил использовать для цветомузыки. И не просто цветомузыки, а цветомузыки в машине, заодно сделав из неё mood-light.

20151104_112108 20151107_115258 20151107_115336 20151107_115237

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

В качестве железа, опять таки Raspberry Pi 2 и EascyCap на базе utv007.

20151103_220658 20151103_220604

В качестве операционки всё тот-же Raspbian от 2015-05-05, который уже включает в себя драйвер для UTV007 скачал с официального сайта.

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

  • Raspberry pi 2
  • хороший источник питания для Raspberry pi 2 (5 вольт)
  • RGB светодиодная лента на 12 вольт
  • 12 вольт источник питания для светодиодной ленты
  • EasyCap AV video grabber (да – это перебор, но так сложилось)
  • 3 низковольтовых NPN транзистора (например 2sd882p, вот datasheet на него)
  • 3 резистора на 510 Ом
  • 3 Резистора на 10 КОм
  • провода
  • макетная плата (опционально)

В качестве питания для Raspberry Pi 2 я пользуюсь SmartBuy-вским переходником 220в на 5в / 2 ампера

20151103_220456Подключение

Чтобы подключить ленту к Raspberry Pi 2, нам понадобятся 3 NPN транзистора (в моем случае NEC 2sd882p), так как на ленте три цвета: Красный, Зелёный и Синий, которыми мы и будем управлять. Схема подключения выглядит следующим образом:

LCD

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

Красный, зелёный и синий канал с помощью красного, зелёного и синего проводов соответственно подключаем к коллекторам транзисторов (согласно datasheet-у – средняя ножка), используя макетную плату:

20151129_195803

Эмиттеры транзисторов (ножка номер 3 – слева от базы) и землю Raspberry Pi (например pin 6) соединяем с общей землей, используя черные провода, а базы транзисторов через резистора 510 Ом подключаем к Raspberry P (pin 11,12,13):

20151129_201934 moodlightpinout

Также базы транзисторов соединяем с общей землей через резистора 10 КОм.

20151129_202334

И заключительный этап – соединяем общую землю с минусом источника питания, используя чёрный провод, а 12 вольт ленты с плюсом источника питания используя белый провод:

20151129_202605

в результате это должно выглядеть примерно вот так:

20151129_205320

Установка софта

После того как всё подключено, нам надо поставить софт. На сайте lightshowpi.org есть достойная инструкция.

Как обычно первым делом надо обновиться:

sudo apt-get update
sudo apt-get upgrade

Затем скачать и установить lightshowpi

git clone https://togiles@bitbucket.org/togiles/lightshowpi.git
cd lightshowpi
./install.sh

вот и всё перезагрузите ваш Raspberry Pi и запустите тест:

cd lightshowpi
sudo python py/hardware_controller.py --state=flash

Тест начнёт по очереди включать каналы. Если вы всё сделали правильно, то 0 канал должен включить красный, первый – зелёный, второй – синий, а остальные каналы не должны оказывать влияние на ленту:

test

Чтобы это исправить и оставить только 3 канала, а не 8, можно переопределить дефолтные значения. Просто создадим файл, в котором укажем, что мы хотим использовать только 3 pin-а на Raspberry Pi:

vim /home/pi/.lights.cfg

Для понимания того, что и как надо переопределить – откройте дефолтный конфиг. В случае с vim-ом это можно сделать открыв его в другом табе:

:tabnew /home/pi/lightshowpi/config/defaults.cfg

просто найдите строчку gpio_pins

gpio_pins_lightshowpi

Тут есть комментарий, что нужно указать номера pin-ов исходя из спецификации WiringPi, а не физической нумерации. К счастью на картинке, которую я уже показывал (а полную версию можно посмотреть тут), присутствует и нумерация в соответствии со спецификацией WiringPi:

moodlightpinout

Видно, что pin 11, который мы подключили к красному каналу соответсвует WiringPi = 0, pin 12 соответсвует WiringPi = 1 и pin 13 соответсвует WiringPi 2. То есть нам в нашем новом конфиге, который будет лежать в /home/pi/.lights.cfg просто нужно создать секцию hardware и  указать, что мы хотим использовать gpio_pins 0,1 и 2:

repin

Запустив тест ещё раз мы увидим, что используются только каналы с 0го по 2й:

3chanelsonly

Теперь осталось попробовать какой-нить mp3 файл:

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

 

Ambilight своими руками: Часть 5 – Tuning

После того, как всё заработало в предыдущем посте, осталось сделать финальную настройку, чтобы светодиоды более точно передавали изображение на экране. Для начала посмотрим на картинку, на основе которой мы выставляем цвета, с помощью простого скрипта, который делает чтение буфера из EasyCap и сохраняет его в файл. Как обычно мой любимый vim screenshot.py:

import numpy as np
import cv2
import sys

cap = cv2.VideoCapture(0)

if not cap.isOpened():
    print "Failed to initialize"
    exit(0)

ret, frame = cap.read()

if not ret:
    print "Failed to read frame"
    exit(0)

cv2.imwrite(sys.argv[1],frame)

Единственная новшество тут – это запись в файл с помощью функции cv2.imwrite, которая сама определяет формат сохранения в зависимости от расширения файла. То есть, если туда передать .jpg – то будет JPEG, а если .png то PNG.

sys.argv[1] – это чтение параметра из командной строки. Нулевой элемент – это имя скрипта. Таким образом, объединив эти две вещи, скрипт будет сохранять в формат файла в зависимости от параметра который мы передадим. Проверьте переключатель формата на устройстве конвертации из HDMI в аналоговый видеосигнал. У меня он установлен в NTSC поэтому делаю первый снимок с соответствующим именем:

sudo python screenshot.py /run/ntsc.jpg

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

sudo python screenshot.py /run/pal.jpeg

pal_defТут также здоровенная рамка, да ещё и картинка сильнее обрезана справа и снизу. Так что мой выбор очевиден – NTSC. Если у вас такая же ситуация, то переведите переключатель обратно.

Теперь нам нужно модифицировать наш скрипт управления лентой, чтобы он не учитывал чёрные пиксели рамки, а работал только с той частью, которая показывает картинку с устройства. Для этого нужно померить количество пикселей с каждой стороны в любом графическом редакторе, например mspaint-е. После этого заведём четыре переменные, которые и будут определять начало значимых для нас пикселей на картинке. Для этого откроем наш скрипт и добавим новые переменные. vim run.py:

import numpy as np
import time
import cv2
import sys
from neopixel import *

LED_PIN = 18
LED_HEIGHT = 15
LED_WIDTH = 25
LED_FREQ = 800000
LED_DMA = 5
LED_BRIGHTNESS = 255
LED_INVERT = False

xborderl = 23
xborderr = 21
ybordert = 2
yborderb = 9

Последние четыре строчки показывают количество пикселей, которое я намерил для каждой стороны. Теперь, после того, как мы прочитали картинку с девайса – необходимо выкусить только ту часть, которая не содержит в себе чёрную рамку:

strip = Adafruit_NeoPixel(LED_HEIGHT*2+LED_WIDTH*2,LED_PIN,LED_FREQ,\
                                LED_DMA,LED_INVERT,LED_BRIGHTNESS)
strip.begin()

cap = cv2.VideoCapture(0)

if (not cap.isOpened()):
    print "unable to initialize!"
    exit(0)

ret,frame1 = cap.read()
ret,frame2 = cap.read()

while(cap.isOpened()):
    ret,frame1 = cap.read(frame1)
    ret,frame2 = cap.read(frame2)
    img = frame2[ybordert:frame2.shape[0]-yborderb,\
                        xborderl:frame2.shape[1]-xborderr]

Последняя строчка определяет как раз границы, по-которым мы будем обрезать. Теперь нам нужно уже resize-ить не всю картинку, а только значимую область:

    rsz1 = cv2.resize(img, (LED_HEIGHT, LED_WIDTH))

а всё остальное остаётся по старому:

    for i in range(LED_WIDTH):
        BGR = rsz1[LED_WIDTH-i-1,0]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i, color)
    for i in range(LED_HEIGHT):
        BGR = rsz1[0,i]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_WIDTH, color)
    for i in range(LED_WIDTH):
        BGR = rsz1[i,LED_HEIGHT-1]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_HEIGHT+LED_WIDTH, color)
    for i in range(LED_HEIGHT):
        BGR = rsz1[LED_WIDTH-1,LED_HEIGHT-i-1]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_HEIGHT+LED_WIDTH*2, color)
    strip.show()

Пришло время попробовать. В youtube полно видео для тестирования ambilight. Мне понравилось вот это (не забудьте включить полный экран):

Вот как это выглядит у меня:

Так же нашёл прикольное видео с Laser show – решил попробовать свой билд на нём:

В общем видно, что ещё есть над чем работать. Задержка заметна, видно пропуски, да и ленту надо чуть переклеить. Думаю, что на новогодних каникулах разберусь