Часы на Arduino с анимированным кольцом

30.09.2016 19:33

genaonyx



Перевод статьи "Arduino Clock with Neopixel Ring Animation".


Комплектующие

  • Arduino Nano R3 - 1 шт.
  • RTC модуль DS1307 (часы реального времени) – 1 шт.
  • Adafruit NeoPixel Ring: WS2812 5050 RGB LED (Кольцо с 24 RGB светодиодами) – 1 шт.
  • 4-значный 7-сегментный дисплей – 1 шт.
  • Резистор 220 Ом – 8 шт.
  • Резистор 440 Ом – 1 шт.
  • Конденсаторы 1000 µF – 2 шт.
  • Кнопки – 2 шт.
  • Конденсаторы 100 nF – 2 шт.
  • Блок питания постоянного тока 5В 1А – 1 шт.
  • Сдвиговый регистр 74HC595 – 1 шт.


О проекте

Как говорил Ларри Уолл (создатель языка программирования Perl) у программиста есть три отличных достоинства: Лень, Нетерпение и Высокомерие.

К сожалению, у меня есть все из них и мои программы не идеальны. И что же мне делать? Использовать Arduino. Ведь оно настолько мало, чтобы хранить в себе все мои ошибки.

Я считаю, что неопиксельное кольцо было создано великими людьми. Оно настолько красиво и отлично организованно, что я не смог остаться в стороне. Это мой первый проект на Arduino с использованием данного кольца. Да и 4-значный дисплей идеально подходит к данному кольцу, состоящему из 24 светодиодов.

Проект имеет 6 различных алгоритмов анимации часов. Они выбираются в случайном порядке, зависящем от текущего времени. Цвет кольца так же выбирается случайным образом.

Никогда не используйте модуль часов DS1307! Он больше показывает случайные числа, нежели время. Но, что делать, если вы его уже купили? Существует способ, чтобы исправить систематическую погрешность этих часов за счет автоматической подстройки каждую ночь. Этот проект имеет такую особенность. Каждый раз, когда вы вручную настраиваете часы (лучше один раз в месяц) текущая дата и время сохраняется в памяти часов (в секундах с 1970 года) уже с погрешностью (в секундах за 30 дней).


Схема


Видео


Скетч

//The clock with 4x7-segment indicator with a shift register and neopixel rgb 24 led ring @ arduino nano

#include "Wire.h"
#include "DS3232RTC.h"
#include "Time.h" 

// The 4 7-segment indicator with the shift register
const byte digit_pin[4]   = { 14, 15, 16, 17 };
const byte dataPIN_14  = 7;
const byte latchPIN_12 = 8;
const byte clockPIN_11 = 9;

// Neopixel ring
#include "Adafruit_NeoPixel.h"
#ifdef __AVR__
  #include 
#endif
const byte NEOPIXEL = 10;                    // Pin of Neopixel Ring
const byte RingSize = 24;
const byte HZ = 10;                          // Redraw neopixel ring frequency

const byte BTN_MENU_PIN = 2;
const byte BTN_INCR_PIN = 3;

void print_time(byte Hour, byte Minute, byte dot, byte DisplayMask = 0xf);
void flashLabels(uint16_t S, int r, int g, int b, byte pos = 255);
void SwitchOffRing(void);
uint32_t Wheel(byte WheelPos);
void showSeconds(uint16_t S, uint32_t Color, bool erase);
void show1Seconds(uint16_t S, uint32_t Color, bool erase);
void show2Seconds(uint16_t S, uint32_t Color, bool erase);
void show3Seconds(uint16_t S, uint32_t Color, bool keep, bool erase);
void show4Seconds(uint16_t S, uint32_t Color, bool erase);
void show5Seconds(uint16_t S, uint32_t Color, bool erase);
void showRainbow(uint16_t S, bool erase);
void show1Rainbow(uint16_t S, bool erase);

//------------------------------------------ class BUTTON ------------------------------------------------------
class BUTTON {
  public:
    BUTTON(byte ButtonPIN, uint16_t timeout_ms = 3000) {
      buttonPIN = ButtonPIN; 
      pt = tickTime = 0; 
      overPress = timeout_ms;
    }
    void init(void) { pinMode(buttonPIN, INPUT_PULLUP); }
    void setTimeout(uint16_t timeout_ms = 3000) { overPress = timeout_ms; }
    byte buttonCheck(void);
    byte intButtonStatus(void) { byte m = mode; mode = 0; return m; }
    bool buttonTick(void);
    void buttonCnangeINTR(void);
  private:
    const uint16_t shortPress = 900;           // If the button was pressed less that this timeout, we assume the short button press
    const uint16_t tickTimeout = 200;          // Period of button tick, while tha button is pressed
    const byte bounce = 50;                    // Bouncing timeout (ms)  
    uint16_t overPress;                        // Maxumum time in ms the button can be pressed
    volatile byte mode;                        // The button mode: 0 - not presses, 1 - short press, 2 - long press
    volatile uint32_t pt;                      // Time in ms when the button was pressed (press time)
    volatile uint32_t tickTime;                // The time in ms when the button Tick was set
    byte buttonPIN;                            // The pin number connected to the button
};

byte BUTTON::buttonCheck(void) {               // Check the button state, called each time in the main loop

  mode = 0;
  bool keyUp = digitalRead(buttonPIN);         // Read the current state of the button
  uint32_t now_t = millis();
  if (!keyUp) {                                // The button is pressed
    if ((pt == 0) || (now_t - pt > overPress)) pt = now_t;
  } else {
    if (pt == 0) return 0;
    if ((now_t - pt) < bounce) return 0;
    if ((now_t - pt) > shortPress)             // Long press
      mode = 2;
    else
      mode = 1;
    pt = 0;
  } 
  return mode;
}

bool BUTTON::buttonTick(void) {                // When the button pressed for a while, generate periodical ticks

  bool keyUp = digitalRead(buttonPIN);         // Read the current state of the button
  uint32_t now_t = millis();
  if (!keyUp && (now_t - pt > shortPress)) {   // The button have been pressed for a while
    if (now_t - tickTime > tickTimeout) {
       tickTime = now_t;
       return (pt != 0);
    }
  } else {
    if (pt == 0) return false;
    tickTime = 0;
  } 
  return false;
}

void BUTTON::buttonCnangeINTR(void) {          // Interrupt function, called when the button status changed
  
  bool keyUp = digitalRead(buttonPIN);
  unsigned long now_t = millis();
  if (!keyUp) {                                // The button has been pressed
    if ((pt == 0) || (now_t - pt > overPress)) pt = now_t; 
  } else {
    if ((now_t - pt) < bounce) return;
    if (pt > 0) {
      if ((now_t - pt) < shortPress) mode = 1; // short press
        else mode = 2;                         // long press
      pt = 0;
    }
  }
}

// -------------------------------------- class adjustClock ------------------------------------------- 
class adjustClock {
  public:
    adjustClock() {
      adjusted = 0;
      incr = autoAdjusted = 0;
    }
    void init(void);                                  // Load adjustment parameters from the clock SRAM
    void adjustRTC(void);                             // adjust RTC. This routine should run periodicaly
    void saveManualAdjustment(long delta);            // save parameters after manual clock adjustment
  private:
    time_t adjusted;                                  // the last time the clock was udjusted manually
    time_t nextAutoAdjust;                            // the time when the clock should be automatically adjusted
    long incr;                                        // adjust increment (decrement) in seconds per 30 days
    long autoAdjusted;                                // the number of seconds the time was adjusted automatically since last update
    void saveData(void);
    void loadData(void);
    void long2Byte(byte *p, long data);
    long byte2Long(const byte *p);
    void calculateAutoAdjustTime(void);
    const long thirtyDays = 2592000;                  // Second in 30 days. Clock incremant adjust calculated in seconds per month
};

void adjustClock::init(void) {
  adjusted = 0;
  incr = autoAdjusted = 0;
  loadData();
  calculateAutoAdjustTime();
}

void adjustClock::adjustRTC(void) {
  if (incr == 0) return;
  time_t nowT = now();
  if (nowT < nextAutoAdjust) return;
  long e = nowT - adjusted;                            // The seconds from time when the clock was adjusted
  if (e < 0) {
    calculateAutoAdjustTime();
    return;
  }
  e *= incr;
  long adj = (long)(e/thirtyDays);                     // normalyze to the seconds in 30 days
  if (adj != autoAdjusted) {
    adj -= autoAdjusted;
    nowT = now() + adj;
    RTC.set(nowT);
    autoAdjusted += adj;
    saveData();
  }
  calculateAutoAdjustTime();
}

void adjustClock::saveManualAdjustment(long delta) {
  time_t nowT = now();
  incr = 0;
  if (adjusted) {                                       // If the clock was previously setup
    long delta_time = nowT - adjusted;                  // The period from last adjustment
    if (delta_time < 43200) {                           // The clock was adjusted less than 12 hours ago
      incr = 0;                                         // The clock month adjustment limited to 1 minute
      if (delta > 0) incr = 60;
      if (delta < 0) incr = -60;
    } else {
      delta += autoAdjusted;                            // since last manual adjustment, the clock was automatically updated
      delta *= thirtyDays;                          
      incr   = delta / delta_time;                      // Number of seconds the clock should be adjusted in 30 days
    }
  }
  adjusted     = nowT;
  autoAdjusted = 0;
  saveData();
}

void adjustClock::long2Byte(byte *p, long data) {
  for (byte i = 0; i < 4; ++i) {
    p[i] = data & 0xff;
    data >>= 8;
  }
}

long adjustClock::byte2Long(const byte *p) {
  long data = 0;
  for (char i = 3; i >= 0; --i) {
    data <<= 8;
    data |= p[i];
  }
  return data;
}

void adjustClock::saveData(void) {
 byte buff[16];

  long2Byte(buff, adjusted);
  long2Byte(&buff[4], incr);
  long2Byte(&buff[8], autoAdjusted);

  long crc = 0;
  for (byte i = 0; i < 12; ++i) {
    crc <<= 2;
    crc += buff[i];
  }
  long2Byte(&buff[12], crc);

  RTC.writeRTC(SRAM_START_ADDR, buff, 16);
}

void adjustClock::loadData(void) {
 byte buff[16];

  byte ans = RTC.readRTC(SRAM_START_ADDR, buff, 16);

  long crc = 0;
  for (byte i = 0; i < 12; ++i) {
    crc <<= 2;
    crc += buff[i];
  }
  long crc_written = byte2Long(&buff[12]);
  if (crc_written == crc) {
    adjusted     = byte2Long(buff);
    incr         = byte2Long(&buff[4]);
    autoAdjusted = byte2Long(&buff[8]);
  }
}

void adjustClock::calculateAutoAdjustTime(void) {
  // The clock should be automatically adjusted at 2:00 am
  time_t nowT = RTC.get();
  long sec = nowT % 86400;        // Seconds since midnight
  long delta = 7200L - sec;       // difference between actual time and 2:00 am
  long last_time = 0;
  if (autoAdjusted < 0) 
    last_time = autoAdjusted * (-1);
  if (delta <= last_time) {       // The clock should be adjusted tomorrow
    delta += 86400;
  }
  nextAutoAdjust = nowT + delta;
}

//------------------------------------------ class SCREEN ------------------------------------------------------
class SCREEN {
  public:
    SCREEN* next;
    SCREEN* nextL;

    SCREEN() {
      next = nextL = 0;
    }
    virtual void init(void) { }
    virtual void show(void) { }
    virtual SCREEN* menu(void) {if (this->next != 0) return this->next; else return this; }
    virtual SCREEN* menu_long(void) { if (this->nextL != 0) return this->nextL; else return this; }
    virtual void inc(void) { }
    virtual void inc_long(void) { }
};


class clockSCREEN: public SCREEN {
  public:
    clockSCREEN() : SCREEN() {
      H = M = S = 0;
      mS = HZ;
      disp_mode = 2; ring_mode = 0;
      ShowDot = true;
      ShowRing = true;
      NightMode = true;
      next_sec = disp_mode_change = ring_mode_change = 0;
      ring_mode = 0;
    }
    virtual SCREEN* menu(void) {
      ++disp_mode;
      if (disp_mode > 2) disp_mode = 0;
      return this;
    }
    virtual SCREEN* menu_long(void) {
      if (nextL != 0) {
        nextL->init();
        return nextL;
      } else return this;
    }
    virtual void init(void);
    virtual void show(void);
    virtual void inc(void) { NightMode = !NightMode; if (!NightMode) ShowRing = true; }
  private:
    unsigned long next_sec = 0;
    unsigned long disp_mode_change;
    unsigned long ring_mode_change;
    const byte deltaMS = (1000 / HZ);
    const byte morning = 9;
    const byte night   = 21; 
    byte mS;
    byte disp_mode;               // Display info: 0 - clock, 1 - day&month, 2 - year
    byte ring_mode;               // How to show the ring this time
    bool NightMode;
    bool ShowRing;
    bool ShowDot;
    byte H, M, S;
    time_t nowRTC;
};

void clockSCREEN::init(void) {
  nowRTC = RTC.get();
  setTime(nowRTC);
  S = second();
  M = minute();
  H = hour();
  ring_mode = random(9);
  ring_mode_change = millis() + ((unsigned long)random(120, 600) * 1000);
}

void clockSCREEN::show(void) {

  unsigned long nowMS = millis();
  
  if (nowMS >= next_sec) {            // Calculate seconds
    next_sec = nowMS + deltaMS;       // Several times per second

    if (NightMode) {
      if (ShowRing && ((H >= night) || (H <= morning))) {
        SwitchOffRing();
        ShowRing = false;
      }
      if (!ShowRing && (H > morning) && (H < night)) ShowRing = true;
    } else {
      if (!ShowRing) ShowRing = true;
    }
    
    ++mS;
    if (mS >= HZ) {
      mS = 0;
      ++S;
      ShowDot = !ShowDot;
      if (S >= 60) {
        nowRTC = RTC.get();
        setTime(nowRTC);
        S = second();
        M = minute();
        H = hour();
      }
    }
  }
  
  // Sometimes show date
  if (nowMS >= disp_mode_change) {
    if (disp_mode != 0)
      ++disp_mode;
    else
      if ((S > 5) && (S < 50)) ++disp_mode;
    if (disp_mode > 1) disp_mode = 0;
    if (disp_mode > 0) disp_mode_change = nowMS + 3000;
    else disp_mode_change = nowMS + ((unsigned long)random(20, 30) * 1000);
  }
  
  byte Century = year() / 100;
  byte y = year() % 100;
  switch (disp_mode) {
    case 1:                      // Show the date (day, month)
      print_time(day(), month(), 2);
      break;

    case 2:                      // Year
      print_time(Century, y, 4);
      break;
      
    default:                      // Hour and Minute
      if (ShowDot) 
        print_time(H, M, 2);
      else
        print_time(H, M, 4);
      break;
  }
  
  if (ShowRing) {
    byte loop = H * 60 + M;
    if (M % 2) loop += 86;
    if ((S == 0) && nowMS >= ring_mode_change) {
      byte next_mode = random(8);
      if (next_mode >= ring_mode) {
        next_mode ++;
      }
      ring_mode = next_mode;
      ring_mode_change = nowMS + ((unsigned long)random(120, 600) * 1000);
    }
    bool last_leap = (nowMS + 5000) > ring_mode_change;
    switch (ring_mode) {
      case 1:
        show1Seconds((uint16_t)S*HZ + mS, Wheel(loop), last_leap);
        break;
      case 2:
        show2Seconds((uint16_t)S*HZ + mS, Wheel(loop), last_leap);
        break;
      case 3:
        show3Seconds((uint16_t)S*HZ + mS, Wheel(loop), false, last_leap);
        break;
      case 4:
        show3Seconds((uint16_t)S*HZ + mS, Wheel(loop), true, last_leap);
        break;
      case 5:
        showRainbow((uint16_t)S*HZ + mS, last_leap);
        break;
      case 6:
        show1Rainbow((uint16_t)S*HZ + mS, last_leap);
        break;
      case 7:
        show4Seconds((uint16_t)S*HZ + mS, Wheel(loop), last_leap);
        break;
      case 8:
        show5Seconds((uint16_t)S*HZ + mS, Wheel(loop), last_leap);
        break;
      default:
        showSeconds((uint16_t)S*HZ + mS, Wheel(loop), last_leap);
        break;
    }
  } else {              // If the Ring is not working
    delay(10);
  }
}


class clockSetupSCREEN: public SCREEN {
  public:
    clockSetupSCREEN(adjustClock* AC) : SCREEN() {
      next_sec = 0;
      EditEntity = 0;
      ShowDigit = true;
      pAc = AC;
    }
    virtual SCREEN* menu(void) {
      ++EditEntity;
      if (EditEntity >= 5) EditEntity = 0;
      return this;
    }
    virtual SCREEN* menu_long(void);
    virtual void inc(void);
    virtual void init(void);
    virtual void show(void);
  private:
    const uint16_t timeout = 10000;
    unsigned long next_sec;
    unsigned long edit_timeout;
    bool ShowDigit;
    byte EditEntity;                          // 0 - Hour, 1 - Minute, 2 - Day, 3 - Month, 4 - Year
    tmElements_t tm;                          // time to be set-up
    adjustClock* pAc;                         // The pointer to the adjustClock instance;
};

void clockSetupSCREEN::init(void) {
  RTC.read(tm);
  EditEntity = 0;
  next_sec = millis();
}

SCREEN* clockSetupSCREEN::menu_long(void) {
  if (nextL != 0) {
    tm.Second = 0;
    time_t old_time = RTC.get();
    time_t new_time = makeTime(tm);
    long difference = 0;
    if (new_time >= old_time) {
      difference = new_time - old_time;
    } else {
      difference = old_time - new_time;
      difference *= -1;
    }
    RTC.write(tm);
    pAc->saveManualAdjustment(difference);
    nextL->init();
    return nextL;
  } else return this;
}


void clockSetupSCREEN::show(void) {
  
  unsigned long nowMS = millis();
  
  if (nowMS >= next_sec) {
    next_sec = nowMS + 200;               // Blink with digit
    ShowDigit = !ShowDigit;
  }
  
  byte DisplayMask = 0b1111;
  byte Cent = (tm.Year + 1970) / 100;
  byte Year = (tm.Year + 70) % 100;
  switch (EditEntity) {
   case 0:                                // Hour
     if (!ShowDigit) DisplayMask &= 0b0011;
     print_time(tm.Hour, tm.Minute, 2, DisplayMask);
     break;

   case 1:                                // Minute
     if (!ShowDigit) DisplayMask &= 0b1100;
     print_time(tm.Hour, tm.Minute, 2, DisplayMask);
     break;

   case 2:                                // Day
     if (!ShowDigit) DisplayMask &= 0b0011;
     print_time(tm.Day, tm.Month, 2, DisplayMask);
     break;

   case 3:                                // Month
     if (!ShowDigit) DisplayMask &= 0b1100;
     print_time(tm.Day, tm.Month, 2, DisplayMask);
     break;

   case 4:                                // Year
     if (!ShowDigit) DisplayMask &= 0b1100;
     print_time(Cent, Year, 4, DisplayMask);
     break;
  }
}

void clockSetupSCREEN::inc(void) {
 static byte days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

  char Year = tm.Year - 30;
  if (Year < 0) Year = 0;
  switch(EditEntity) {
    case 0:                                // Hour
      ++tm.Hour; if (tm.Hour > 23) tm.Hour = 0;
      break;

    case 1:                                // Minute
      ++tm.Minute; if (tm.Minute > 59) tm.Minute = 0;
      break;

    case 2:                                // Day
      days[1] = 28;
      if (((tm.Year+1970) / 4) == 0) days[1] = 29;       // Leap Year 
      ++tm.Day;
      if (tm.Day > days[tm.Month-1]) tm.Day = 1;
      break;

    case 3:                                // Month
      ++tm.Month;
      if (tm.Month > 12) tm.Month = 1;
      break;

    case 4:                                // Year
      ++Year;
      if (Year > 99) Year = 0;
      break;
  }
  tm.Year = Year + 30;
}
// ================================ End of all class definitions ====================================== 

// Class instances
Adafruit_NeoPixel strip = Adafruit_NeoPixel(RingSize, NEOPIXEL, NEO_GRB + NEO_KHZ800);
adjustClock adjClock;

clockSCREEN      mainClock;
clockSetupSCREEN setupClock(&adjClock);

BUTTON menuButton(BTN_MENU_PIN);
BUTTON incrButton(BTN_INCR_PIN);

SCREEN* pScreen = &mainClock;


void setup() {                
  // initialize shift register PINS
  pinMode(dataPIN_14, OUTPUT);
  pinMode(latchPIN_12, OUTPUT);
  pinMode(clockPIN_11, OUTPUT);

  // initialize the digit pin as an output.
  for (int i = 0; i < 4; ++i) {
    pinMode(digit_pin[i], OUTPUT);
    digitalWrite(digit_pin[i], HIGH);
  }

  time_t nowRTC = RTC.get();
  randomSeed(nowRTC);
  mainClock.init();
  adjClock.init();
  
  #if defined (__AVR_ATtiny85__)
    if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
  #endif
  // End of trinket special code

  menuButton.init();
  incrButton.init();
  incrButton.setTimeout(10000);
  attachInterrupt(digitalPinToInterrupt(BTN_MENU_PIN), menuButtonChange, CHANGE);
  
  mainClock.nextL  = &setupClock;
  setupClock.nextL = &mainClock;

  strip.begin();
  delay(500);
  strip.setBrightness(255);
  strip.show(); // Initialize all pixels to 'off'
}

// Interrupts functions just call the corresponding method
void menuButtonChange(void) {
  menuButton.buttonCnangeINTR();
}

//================================================================================
// The Main LOOP                                                                  
//================================================================================
void loop() {
 static unsigned long return_to_main = 0;
 static bool RingOff = false;

  bool buttonPressed = false;

   if ((pScreen != &mainClock) && millis() > return_to_main) {
     return_to_main = 0;
     pScreen = &mainClock;
   }

  // Check status of the main button, that supports interrupts
  if (byte Mode = menuButton.intButtonStatus()) {
    buttonPressed = true;
    if (Mode == 1) pScreen = pScreen->menu();
    if (Mode == 2) pScreen = pScreen->menu_long();
  }
  // Check status of the increment button
  if (byte Mode = incrButton.buttonCheck()) {
    buttonPressed = true;
    if (Mode == 1) pScreen->inc();
    if (Mode == 2) pScreen->inc_long();
  }  

  if (pScreen == &setupClock)
    if (incrButton.buttonTick()) pScreen->inc();
    
  if (pScreen != &mainClock) {
      if (!RingOff) {
        SwitchOffRing();
        RingOff = true;
      }
      if (buttonPressed == true) return_to_main = millis() + 30000;
  } else
    RingOff = false;

   pScreen->show();
   adjClock.adjustRTC();
}


//Low-level functions

void SwitchOffRing(void) {
  for (byte i = 0; i < strip.numPixels(); ++i) strip.setPixelColor(i, 0);
  strip.show();
}

// 4 indicators (0h, 3h, 6h, 9h) is flashing every 5 seconds
void flashLabels(uint16_t S, int r, int g, int b, byte pos) {
 static byte br_lev[] = { 127, 64, 50, 40, 30, 25, 30, 40, 50, 64 };
 const uint16_t tick = 5 * HZ;                   // ticks in 5 seconds

  byte secPart  =  S % tick; 
  byte divider = tick / 10;                      // Normalize time interval to the array size
  byte pos1 = secPart / divider;                 // index in the brightness array 
  byte k = secPart % divider;
  byte pos2 = pos1 + 1; if (pos2 >= 10) pos2 = 0;
  //int delta = ((br_lev[pos2] - br_lev[pos1]) * k) / divider;
  //byte br = br_lev[pos1] + delta;
  byte br = map(k, 0, divider, br_lev[pos1], br_lev[pos2]);
  r *= br; r >>= 7;
  g *= br; g >>= 7;
  b *= br; b >>= 7;
  uint32_t white = strip.Color(r, g, b);
  for (byte label = 0; label < 24; label += 6) { 
    if (label == 0 || label != pos) strip.setPixelColor(label, white);
  }
}

void FadeRing(uint32_t Color, byte shift) {
  byte b = Color;
  byte g = Color >> 8;
  byte r = Color >> 16;
  r >>= shift;
  g >>= shift;
  b >>= shift;
  uint32_t Color_faded = strip.Color(r, g, b);
  for (byte i = 0; i < RingSize; ++i) strip.setPixelColor(i, Color_faded);
}

// Fill ring as seconds past, new dot every 2.5 sec
void showSeconds(uint16_t S, uint32_t Color, bool erase) {
 const uint16_t Last8Ticks = 60 * HZ - 8;
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;
 static unsigned int old_S    = 0;
 static byte old_pos = 0;
 static byte r = 128;
 static byte g = 128;
 static byte b = 128;
 
  if (old_S == S) return;
  old_S = S;
  if (S == 0) {
     r = random(32);
     g = random(32);
     b = random(32);
     r <<= 2; g <<= 2; b <<= 2;
  }

  if (S < RingSize) strip.setPixelColor(S, 0);
  if (erase && (S > Last8Ticks)) {
    S -= Last8Ticks;
    FadeRing(Color, S);
    flashLabels(S, r, g, b);
  } else {
    byte pos = (uint32_t)S * RingSize / ((uint32_t)HZ * 60);
    if (pos != old_pos) {
      strip.setPixelColor(pos, Color);
      old_pos = pos;
    }
    flashLabels(S, r, g, b, pos);
  }
  
  strip.show();  
}

// each dot is coming counterclockwize from 12 o'clock
void show1Seconds(uint16_t S, uint32_t Color, bool erase) {
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;
 const uint16_t Last8Ticks = 60 * HZ - 8;
 static unsigned int old_S    = 0;
 static byte runPos = 255;
 static byte r = 128;
 static byte g = 128;
 static byte b = 128;

  if (old_S == S) return;
  old_S = S;

  if (S == 0) {
     r = random(32);
     g = random(32);
     b = random(32);
     r <<= 2; g <<= 2; b <<= 2;
  }

  if (erase && (S > Last8Ticks)) {
    S -= Last8Ticks;
    FadeRing(Color, S);
    flashLabels(S, r, g, b);
  } else {
    byte pos = (uint32_t)S * RingSize / ((uint32_t)HZ * 60);

    byte runSecond = S % onedot;
    byte newRunPos = RingSize - RingSize * runSecond / onedot;
    if ((newRunPos < RingSize) && newRunPos > pos) {
      if (newRunPos != runPos) {
        strip.setPixelColor(newRunPos, Color);
        if (runPos < RingSize) strip.setPixelColor(runPos, 0);
        runPos = newRunPos;
      } 
    }
    strip.setPixelColor(pos, Color);
    flashLabels(S, r, g, b, runPos);
  }
  strip.show();  
}

// each dot is coming counterclockwize from last position
void show2Seconds(uint16_t S, uint32_t Color, bool erase) {
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;
 const uint16_t LastTick = 60 * HZ - 1;
 static unsigned int old_S    = 0;
 static byte runPos = 255;
 static byte r = 128;
 static byte g = 128;
 static byte b = 128;

  if (old_S == S) return;
  old_S = S;

  if (S == 0) {
     r = random(32);
     g = random(32);
     b = random(32);
     r <<= 2; g <<= 2; b <<= 2;
  }

  byte pos = (uint32_t)S * RingSize / ((uint32_t)HZ * 60);
 
  byte runSecond = S % onedot;
  char newRunPos = pos - RingSize * runSecond / onedot;
  if (newRunPos < 0) newRunPos += RingSize; 
  if ((newRunPos < RingSize) && newRunPos != pos) {
    if (newRunPos != runPos) {
      strip.setPixelColor(newRunPos, Color);
      if (runPos < RingSize) strip.setPixelColor(runPos, 0);
      runPos = newRunPos;
    } 
  }
  strip.setPixelColor(pos, Color);
 if (erase && (S >= LastTick)) strip.setPixelColor(RingSize-1, 0);
 

  flashLabels(S, r, g, b, pos);
  strip.show();  
}

// Rise the dot slowly
void show3Seconds(uint16_t S, uint32_t Color, bool keep, bool erase) {
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;
 const uint16_t Last8Ticks = 60 * HZ - 8;
 static unsigned int old_S    = 0;
 static byte r = 128;
 static byte g = 128;
 static byte b = 128;

  if (old_S == S) return;
  old_S = S;

  if (S == 0) {
     r = random(32);
     g = random(32);
     b = random(32);
     r <<= 2; g <<= 2; b <<= 2;
  }

  if (S < RingSize) strip.setPixelColor(S, 0);
    
  byte pos = (uint32_t)S * RingSize / ((uint32_t)HZ * 60);

  byte b1 = Color;
  byte g1 = Color >> 8;
  byte r1 = Color >> 16;
  byte runSecond = S % onedot;
  byte shift = (runSecond << 3) / onedot;
  r1 >>= (7 - shift);
  g1 >>= (7 - shift);
  b1 >>= (7 - shift);
  strip.setPixelColor(pos, strip.Color(r1, g1, b1));

  if (!keep) {
    byte fadePos = RingSize-1;
    if (pos >= 1) fadePos = pos - 1;
    uint32_t fadeColor = strip.getPixelColor(fadePos);
    b1 = fadeColor;
    g1 = fadeColor >> 8;
    r1 = fadeColor >> 16;
    if (shift > 3) {
      r1 >>= 1;
      g1 >>= 1;
      b1 >>= 1;
    } else if (shift == 7) strip.setPixelColor(fadePos, 0);
    strip.setPixelColor(fadePos, strip.Color(r1, g1, b1));
  }

  if (erase && (S >  Last8Ticks)) { 
    if (keep) FadeRing(Color, S);
    if (!keep && (shift == 7)) strip.setPixelColor(RingSize-1, 0);
  }

  flashLabels(S, r, g, b, pos);
  strip.show();  
}

// run the sector clockwise
void show4Seconds(uint16_t S, uint32_t Color, bool erase) {
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;
 const uint16_t Last8Ticks = 60 * HZ - 8;
 static unsigned int old_S    = 0;
 static byte firstPos = 255;
 static byte r = 128;
 static byte g = 128;
 static byte b = 128;

  if (old_S == S) return;
  old_S = S;

  if (S == 0) {
     r = random(32);
     g = random(32);
     b = random(32);
     r <<= 2; g <<= 2; b <<= 2;
  }

  if (erase && (S > Last8Ticks)) {
    S -= Last8Ticks;
    FadeRing(Color, S);
    flashLabels(S, r, g, b);
  } else {
    byte length = (uint32_t)S * RingSize / ((uint32_t)HZ * 60) + 1;
    byte runSecond = S % onedot;
    byte newFirstPos = RingSize * runSecond / onedot;
    char lastPos = newFirstPos - length;
    if (lastPos < 0) lastPos += RingSize;
    if (newFirstPos < RingSize) {
      if (newFirstPos != firstPos) {
        strip.setPixelColor(newFirstPos, Color);
        if (lastPos >= 0) strip.setPixelColor(lastPos, 0);
        firstPos = newFirstPos;
      } 
    }
    flashLabels(S, r, g, b, firstPos);
  }
  strip.show();  
}

// swing the sector
void show5Seconds(uint16_t S, uint32_t Color, bool erase) {
 const byte onedot = ((uint16_t)HZ * 60) / RingSize;
 const uint16_t Last8Ticks = 60 * HZ - 8;
 static unsigned int old_S    = 0;
 static byte firstPos = 255;
 static byte r = 128;
 static byte g = 128;
 static byte b = 128;

  if (old_S == S) return;
  old_S = S;

  if (S == 0) {
     r = random(32);
     g = random(32);
     b = random(32);
     r <<= 2; g <<= 2; b <<= 2;
  }

  if (erase && (S > Last8Ticks)) {
    S -= Last8Ticks;
    FadeRing(Color, S);
    flashLabels(S, r, g, b);
  } else {
    byte length = (uint32_t)S * RingSize / ((uint32_t)HZ * 60) + 1;
    byte runSecond = S % onedot;
    byte newFirstPos;
    char lastPos;
    if ((length % 2) == 0) {                      // even
      newFirstPos = RingSize - RingSize * runSecond / onedot;
      lastPos = newFirstPos + length;
      if (lastPos >= RingSize) lastPos = -1;
    } else {                                      // odd
      newFirstPos = RingSize * runSecond / onedot;
      lastPos = newFirstPos - length;
      if (lastPos < 0) lastPos = -1;
    }
    if ((newFirstPos < RingSize) && (newFirstPos >= 0)) {
      if (newFirstPos != firstPos) {
        strip.setPixelColor(newFirstPos, Color);
        if ((lastPos >= 0) && (lastPos < RingSize))
          strip.setPixelColor(lastPos, 0);
        firstPos = newFirstPos;
      } 
    }
    flashLabels(S, r, g, b, firstPos);
  }
  strip.show();  
}

// Fill ring wth the rainbow as seconds past
void showRainbow(uint16_t S, bool erase) {
 const uint16_t LastRSTicks = 60 * HZ - RingSize;
 static unsigned int old_S    = 0;
 static byte old_pos = 0;
 static byte r = 128;
 static byte g = 128;
 static byte b = 128;
 
  if (old_S == S) return;
  old_S = S;
  if (S == 0) {
     r = random(32);
     g = random(32);
     b = random(32);
     r <<= 2; g <<= 2; b <<= 2;
  }

  if (S < RingSize) strip.setPixelColor(S, 0);
  if (erase && (S > LastRSTicks)) {
    S -= LastRSTicks;
    strip.setPixelColor(S, 0);
  } else {
    for(uint16_t i = 1; i <= RingSize; ++i) {
      strip.setPixelColor(RingSize - i, Wheel((i+S) & 255));
    }
    flashLabels(S, r, g, b);
  }
  
  strip.show();  
}

// Fill ring wth the rainbow as seconds past
void show1Rainbow(uint16_t S, bool erase) {
 const uint16_t LastRSTicks = 60 * HZ - RingSize;
 static unsigned int old_S    = 0;
 static byte old_pos = 0;
 static byte r = 128;
 static byte g = 128;
 static byte b = 128;
 
  if (old_S == S) return;
  old_S = S;
  if (S == 0) {
     r = random(32);
     g = random(32);
     b = random(32);
     r <<= 2; g <<= 2; b <<= 2;
  }

  if (S < RingSize) strip.setPixelColor(S, 0);
  if (erase && (S > LastRSTicks)) {
    S -= LastRSTicks;
    strip.setPixelColor(S, 0);
  } else {
    for(uint16_t i = 1; i <= RingSize; ++i) {
      strip.setPixelColor(RingSize - i, Wheel(((i * 256 / RingSize) + S) & 255));
    }
    flashLabels(S, r, g, b);
  }
  
  strip.show();  
}

void print_time(byte Hour, byte Minute, byte dot, byte DisplayMask) {
 static byte decimal[] = {
  0b11111100, 0b01100000, 0b11011010, 0b11110010, 0b01100110, 
  0b10110110, 0b10111110, 0b11100000, 0b11111110, 0b11110110 };
  byte digit;

  byte mask = 1;
  for (byte d = 0; d < 4; ++d) {        // Print out digit from right to left
    if (d < 2) {                        // Minutes
      digit = Minute % 10;
      Minute /= 10;
    } else {                            // Hours
      digit = Hour % 10;
      Hour /= 10;
    }
    byte symbol = decimal[digit];
    if (dot == d) symbol |= 1;
    if (DisplayMask & mask) {
      digitalWrite(latchPIN_12, LOW);
      shiftOut(dataPIN_14, clockPIN_11, LSBFIRST, symbol);    
      digitalWrite(latchPIN_12, HIGH);
    
      digitalWrite(digit_pin[d], LOW);
      delayMicroseconds(50);
      digitalWrite(digit_pin[d], HIGH);
    }
    mask <<= 1;
  }
}

uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  const byte br = 5;
  byte c1 = 255 - WheelPos * 3; c1 /= br;
  byte c2 = WheelPos * 3;       c2 /= br;
  if(WheelPos < 85) {
    return strip.Color(c1, 0, c2);
  }
  if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, c2, c1);
  }
  WheelPos -= 170;
  return strip.Color(c2, c1, 0);
}


Расскажи о нас

Сообщение

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