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

Leave a Reply