Tag Archives: atmega328

Будильник своими руками: Част 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, но это уже в следующий раз.

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