środa, 16 marca 2011

DIY: Zegar słowny (ATmega328 + DS1307)


Autor: siwy2411
Redakcja: Dondu

Zobacz inne artykułu z cyklu: Arduiono ... czyli prościej się nie da :-)

Witam Was w moim artykule konkursowym, którego tematem jest zegar.

Jednak nie jest to zegar standardowy, ponieważ wyświetla on aktualną godzinę w bardzo nietypowy sposób. Zegar ten pokazuje zdanie (w języku angielskim), które odczytane da nam informację o aktualnej godzinie.

Na początku zaprezentuję Wam zdjęcie, które dawno temu znalazłem w Internecie, a które zainspirowało mnie do budowy niniejszego zegara.


Zdjęcie będące inspiracją do stworzenia projektu.

Mam nadzieję, że w tym momencie wszystko jest już jasne. W tym miejscu chciałbym zaznaczyć, że mój projekt jest inspirowany projektem Pana Doug’a Jackson’a - dougswordclock.com, jednak został w CAŁOŚCI wykonany przeze mnie:




Na powyższym, filmie widać pracę mojego zegara. Wgrany jest tutaj program, w którym jedna minuta została przyspieszona do 5 sekund, co pozwala zaobserwować dokładnie w jaki sposób wyświetlany jest czas na zegarze. Przepraszam za jakość nagrania, ale ze względu na sposób wyświetlania godziny przez zegar bardzo ciężko jest go filmować i robić zdjęcia.

Zegar oparty będzie oczywiście o mój ulubiony mikrokontroler ATmega328, zaprogramowanym przy pomocy Arduino IDE. Za podświetlenie konkretnych słów na froncie zegara odpowiadać będą odpowiednio umieszczone białe paski LED zasilane napięciem stałym 12V. Będą to standardowe paski z taśmy, z możliwością cięcia co 3 diody (5 cm).

Prąd pobierany przez pojedynczy, 5 centymetrowy segment jest ograniczany przez rezystor SMD o oznaczeniu 151 (czyli o rezystancji znamionowej 150Ω). Z prawa Ohma łatwo policzyć, że dla napięcia 12V, spadku napięcia na trzech białych diodach (typowo ~3V na jednej) i rezystancji 150Ω pobór prądu pojedynczej sekcji wyniesie (12V-3*3V)/150Ω=0,02A=20mA. Niektóre słowa (ze względu na ich długość) podświetlane będą 6 diodami. Segmenty na taśmie połączone są ze sobą równolegle, więc z pierwszego prawa Kirchhoffa wiemy, że dwa segmenty pobiorą 40mA prądu.

Problematyczne, z punktu widzenia sterowania z poziomu mikrokontrolera zasilanego napięciem 5V, jest sterowanie paskami zasilanymi napięciem 12V. Można by oczywiście wykorzystać do tego celu tranzystory, jednak ze względu na sporą liczbę pól, którymi trzeba sterować (jak łatwo policzyć na powyższym zdjęciu na wyświetlaczu znajdują się 22 słowa + ja w swojej realizacji chcę dołożyć dodatkowe 4 pola, o których powiem później) – nasza płytka zawierałaby prawie tylko tranzystory.

Z pomocą przyjdą nam dwa proste układy. Pierwszy z nich to rejestr przesuwny. Większość z Was zapewne wie, „z czym to się je”, ale dla niewtajemniczonych dwa słowa komentarza. Za wikipedią:
Rejestr przesuwający – zwany też (nieprawidłowo) rejestrem przesuwnym, to rejestr zbudowany z przerzutników połączonych ze sobą w taki sposób, iż w takt impulsów zegarowych przechowywana informacja bitowa jest przemieszczana (przesuwana) do kolejnych przerzutników.”

Ups, człowiek uczy się całe życie – dobrze, niech będzie rejestr przesuwający. Jak w praktyce działa taki rejestr? Wykorzystany przez nas rejestr SIPO (Serial Input, Paralell Output – wejście szeregowe, wyjście równoległe)  CD4094BC posiada cztery linie wejściowe i 10 linii wyjściowych.


Pinout układu CD4094BC.

Linie wejściowe to linia danych (DATA), linia zegarowa (CLOCK), zatrzask (STROBE) oraz pin o samo tłumaczącej się nazwie OUTPUT ENABLE, natomiast linie wyjściowe to w rzeczywistości 8 wyjść równoległych (Q1-Q8), oraz dwie linie (Q’s oraz Qs) o której więcej opowiem za chwilę.
W jaki sposób wykorzystywane są dane z tych pinów? Wyciągamy sobie tabelę prawdy z datasheetu i analizujemy.


Tabela prawdy układu CD4094BC,

Po pierwsze widzimy, że stan niski na pinie OUTPUT ENABLE powoduje przejście wszystkich wyjść rejestru w stan wysokiej impedancji.

Po drugie, podawanie informacji na piny wejściowe (DATA,CLOCK) przy niskim stanie wejścia STROBE nie spowoduje żadnej zmiany w układzie. Takie rozwiązanie pozwala wybrać nam, do którego rejestru chcemy „mówić” w danym momencie, dzięki czemu do jednej magistrali możemy podłączyć wiele rejestrów i niezależnie nimi sterować.

Po trzecie, gdy wejścia STROBE oraz OUTPUT ENABLE są w stanie wysokim, oraz na linii zegarowej wykryte zostanie zbocze narastające (przejście ze stanu niskiego do wysokiego) stan linii danych jest próbkowany (zapisywany) i (zgodnie z nazwą urządzenia) następuje przesunięcie. Co przesuwamy?

Przesuwamy stany 8 wyjść urządzenia, tak że stan, który został pobrany z linii DATA zostaje przepisany na wyjście Q1, stan, który dotychczas był na wyjściu Q1 przechodzi na wyjście Q2, ten z Q2 na Q3 i tak dalej. Dzięki temu, po 8 cyklach zegara możemy mieć całkowicie nowe dane na wyjściach rejestru.
A co stało się z danymi, które były tam dotychczas? Wspomniałem o dodatkowych wyjściach Qs oraz Q’s. Te dwa wyjścia sprawiają, że rejestry przesuwające są tak popularne – stan tych wyjścia jest niejako pamięcią „9 bitu” – przy wpisaniu nowych danych, na to wyjście przechodzi stan, który dotychczas był na ostatnim wyjściu.

Co w tym rewolucyjnego? Podłączając to wyjście jako WEJŚCIE linii danych (DATA) kolejnego rejestru dane, które dotychczas były „gubione” – teraz zostaną przepisane do drugiego rejestru! Dzięki temu (w przypadku dwóch rejestrów 8 bitowych) wykorzystując dwie linie mikrokontrolera sterujemy 16 wyjściami!

A nic nas nie powstrzymuje przed dalszym łączeniem ze sobą rejestrów – do drugiego możemy podłączyć trzeci, do trzeciego czwarty itd. Oczywiście należy przy tym pamiętać o czasie propagacji danych. Jeżeli podłączymy pojedynczy rejestr to aktualizacja wszystkich stanów jego wyjść zajmie nam 8 cykli zegara, ale jeżeli podłączymy takich rejestrów 8, to będziemy już potrzebowali tych cykli 64.

Należałoby jeszcze wyjaśnić różnicę pomiędzy pinami Qs a Q’s. Różnica ta jest bardzo subtelna – dotychczasowy stan z pinu Q8 zostaje przepisany do Qs przy narastającym zboczu linii zegara, a do Q’s – przy zboczu opadającym.

Ale dobrze, bo miały być tylko dwa słowa. Drugim z układów pomocniczych będzie scalony zestaw 8 tranzystorów w układzie Darlingtona. Kolejna trudna nazwa, kolejne dwa słowa komentarza.

Tym razem nie będę się aż tak rozwlekał, a więc w żołnierskich słowach – układ Darlingotna składa się z dwóch (lub więcej) tranzystorów połączonych ze sobą w następujący sposób:

Schemat tranzystorów
w układzie Darlingtona

Takie połączenie pozwala na sterowanie sygnałami o dużych wartościach prądu przy podawaniu na bazę pierwszego tranzystora bardzo małego prądu. Prościej się nie da, jeżeli kogoś interesują zjawiska fizyczne i wzory, to odsyłam do Wikipedii: Układ Darlingtona

Wspomniane przeze mnie układy „pomocnicze” będą ze sobą połączone w taki sposób, że wyjścia rejestru przesuwającego będą wchodziły na bazę tranzystorów w układzie Darlingtona. Zastosujemy tranzystory NPN, a więc paski LED będą stale podłączone do napięcia zasilania 12V, natomiast to, które z nich będą zapalone będzie zależało od tego, które zostaną zwarte do masy przez układy Darlingtona.

Po przejrzeniu dostępnych układów Darlingtona wybrałem układy ULN28003APG, głownie ze względu na fakt, iż posiadają one 8 zestawów tranzystorów (dzięki czemu jeden ULN będzie przypadał na jeden rejestr przesuwający), a nie jak większość ULNów – 7.

Wystarczy tych opisów, czas na schemat układu:


Schemat zegara.

Na schemacie, oprócz rzeczy oczywistych (mikrokontroler, stabilizator napięcia, rejestry, ULNy) widać także m.in. układ fotorezystora, oraz wtyczkę Jack. Spieszę z wyjaśnieniami.

Układ fotorezystora służyć będzie oczywiście do detekcji aktualnej jasności otoczenia, co w połączeniu z możliwością sterowania wyjściami rejestrów przesuwnych przy pomocy sygnału PWM podawanego na pin OUTPUT ENABLE pozwoli na dostosowywanie oświetlenia zegara.

Wtyczka Jack natomiast służyć będzie do sterowania zegarem. Przystępując do realizacji tego projektu planowałem umieścić w zegarze moduł Bluetooth w celu umożliwienia sterowania zegarem (przestawianie godziny, dostosowywanie jasności podświetlenia itp.), jednak ze względu na ceny modułów Bluetooth ostatecznie zarzuciłem to rozwiązanie.

Już miałem umieszczać na schemacie przyciski, gdy przypomniałem sobie, że po starej karcie telewizyjnej ostał mi się pilot, wraz z odbiornikiem (oczywiście na podczerwień). Pilot podłączany był do karty telewizyjnej właśnie przy pomocy złącza microJack, stąd jego obecność w projekcie. Jedyną trudnością było dojście do tego, w jaki sposób należy podłączyć poszczególne sygnały (+5V, GND oraz DATA) do wtyczki microJack. Na szczęście z pomocą przyszli nieocenieni użytkownicy elektrody, dzięki którym wiem, że sygnały należy podłączyć tak jak zaprezentowałem to na schemacie.

Wyjaśnienia może wymagać podłączenie sygnału IR_CHECK – gniazdo microJack, które zakupiłem posiada dwa piny podłączone do końcówki wtyczki, co pozwala na wykrycie, czy wtyczka jest umieszczona w gnieździe. Postanowiłem wykorzystać to w swoim projekcie. Jako, że na końcówce wtyczki podawany jest sygnał +5V pin ten jest zwarty przez rezystor o dużej wartości (10kΩ) do masy układu. Odczytanie stanu pinu IR_CHECK w sytuacji, gdy wtyczka będzie niepodłączona da odczyt logicznego „0”, a gdy wtyczka będzie podłączona – logicznej „1”.

3 gniazda opisane jako „wyprowadzenia przyszłościowe” służą, jak sama nazwa wskazuje potencjalnemu przyszłościowemu rozwojowi projektu – rozważam dodanie do obudowy zegara dookoła diody (prawdopodobnie czerwone), które będą służyły jako sekundnik. Prawdopodobnie w tym celu powstanie kiedyś płytka drukowana, która będzie podłączona do tej przy pomocy tych pinów.

Płytka drukowana obwodu została wykonana przy pomocy termo transferu – po raz pierwszy użyłem w tym celu specjalnie zakupionej laminarki (popularna Lervia) zamiast żelazka. BARDZO polecam ten sposób – o ile z żelazkiem zawsze wychodziły mi jakieś niedociągnięcia, to tutaj nie ma o tym mowy – wszystko wychodzi pięknie za pierwszym podejściem.

Schemat płytki:

Jednostronna płytka PCB.

Od razu chciałbym zaznaczyć – płytkę można by oczywiście dość mocno zmniejszyć – nie zrobiłem tego, ponieważ niniejszy projekt jest moją drugą wersją tego zegara, a wersja pierwsza miała płytkę tego właśnie rozmiaru i w obudowie zegara mam już przygotowane otwory montażowe pod płytkę w takim rozmiarze.

Zdjęcia płytki:







W celu dopełnienia części „fizycznej” projektu musiałem jeszcze stworzyć obudowę zegara. Nie będę się tutaj za bardzo rozwodził – myślę, że zdjęcia lepiej wyjaśnią cały proces niż opis słowny.















Obudowa wykonana jest w zdecydowanej większości z pleksiglasu (obudowa zewnętrzna, ścianki oddzielające poszczególne sekcje), jedynym elementem nie pleksiglasowym jest tył (element, na którym zamontowana jest płytka drukowana i do którego z drugiej strony przyklejone są paski LED) – został on wykonany z płyty pilśniowej pozyskanej z odzysku (oryginalnie płyta ta stanowiła tylną ściankę jakiejś starej szafki).

Największym wyzwaniem w budowie obudowy był front zegara. Pierwszy powód to konieczność umieszczenia na nim napisów, które podświetlone utworzą pożądany efekt. Front zegara został przeze mnie zaprojektowany w Corelu, a następnie wycięty w pobliskiej firmie poligraficzno/reklamowej na ploterze w czarnej folii samoprzylepnej. Usługa kosztowała mnie jakieś 20zł (łącznie z kosztem folii), co uważam za niezłą cenę.

Drugi powód to pewna problematyczność z zamontowaniem frontu zegara. Z jednej strony musi to być zamocowane estetycznie, a z drugiej strony musi istnieć możliwość zdjęcia frontu w celu ewentualnej wymiany pasków LED/poprawienia jakiś połączeń. Ostatecznie zdecydowałem się na zrobienie z odpadków pleksi czegoś na kształt kątowników, które z jednej strony przyklejone są od tyłu do frontu zegara, a z drugiej przykręcane są do obudowy przy pomocy śrubek. Dokładniejsze zdjęcie tego rozwiązania:




W tym momencie pozostało „tylko” stworzyć program do obsługi zegara. Program będzie składał się z paru głównych części, które muszą współpracować ze sobą:
  1. obsługa magistrali I2C w celu uzyskiwania informacji od układu RTC.
  2. obsługa magistrali SPI w celu podawania danych na rejestry przesuwające.
  3. obsługa fotorezystora oraz podawanie sygnału PWM sterującego jasnością wyświetlacza.
  4. obsługa pilota podczerwieni.

Pierwsze dwie części będą stosunkowo proste ze względu na fakt, że obsługa magistrali I2C (lub, zamiennie TWI) oraz SPI jest zaimplementowana w mikrokontrolerach ATmega sprzętowo. W Arduino istnieją dwie klasy, które służą do obsługi tych magistral. W przypadku magistrali I2C klasa ta to klasa Wire, a w przypadku SPI – klasa SPI. Ich obsługa jest naprawdę prosta i bardzo dobrze opisana na stronie Arduino przy pomocy prostych przykładów.

Część trzecia ograniczy się do odczytu wartości z przetwornika ADC, a następnie zmapowaniu jej na wartość, która zostanie podana przez PWM na odpowiedni pin (połączony z pinem OUTPUT_ENABLE rejestrów przesuwających).

Część czwarta została zrealizowana przeze mnie od podstaw. Mogłem oczywiście zastosować jedną z wielu gotowych bibliotek do Arduino pozwalających na odczytywanie kodów z pilotów na podczerwień, ale jako, że żadna z bibliotek nie spełniała do końca moich założeń i fakt, że zawsze lubiłem wyzwania – postanowiłem napisać obsługę pilota od podstaw. Kod obsługi jest licznie opatrzony komentarzami, przedstawię wobec tego tylko ogólną zasadę działania.

Zanim opiszę jak dokonałem implementacji, napiszę może jaki protokół implementuję. Pilot, który wygrzebałem z szuflady okazał się nadawać komunikaty w protokole NEC. Protokół ten jest szeroko opisany w Internecie, przytoczę tylko parę charakterystycznych cech:
  • ramka danych (cała) zawsze trwa tyle samo czasu
  • zarówno adres urządzenia nadającego (pilota) jak i przesyłany rozkaz jest nadawany dwukrotnie – raz normalnie, a raz zanegowany
  • bit „0” od bitu „1” rozróżniany jest przez czas trwania stanu niskiego – bit „0” to stan wysoki przez 560us z następującym stanem niskim o czasie 560us, a bit „1” to 560us stanu wysokiego, po którym następuje 1690us stanu niskiego 
oczywiście stan niski i wysoki to określenia umowne – stan niski to brak nadawania, a stan wysoki – ciągłe nadawanie sygnału o częstotliwości 38kHz.

Najważniejsza część kodu wykonywana jest w przerwaniu timera, które wywoływane jest co ok. 25ms. Procedura przerwania sprawdza jaki jest aktualny stan odczytany z odbiornika i (w przypadku gdy aktualny stan jest różny od poprzedniego) zapisuje czas trwania poprzedniego stanu do tablicy. Procedura liczy także odebrane stany i po odebraniu odpowiedniej ilości, wystawia flagę informującą o odebraniu całego komunikatu.

Po „zauważeniu” tej flagi przez program wywoływana jest funkcja, która ma za zadanie na podstawie tablicy zawierającej czasy poszczególnych stanów na linii odczytać zawarty w przekazie komunikat. Funkcja sprawdza najpierw każdy stan, czy odpowiada on standardom protokołu (wszystkie wartości przyjęte są z 20% tolerancją). Jeżeli odebrane czasy są faktycznie ramką protokołu NEC – następuje ich odczytanie, a następnie dwustopniowa weryfikacja.

Pierwszą sprawdzaną rzeczą jest poprawność komunikatu – dzięki faktowi, iż adres i rozkaz jest nadany najpierw normalnie, a potem zanegowany – odpowiednie porównanie tych wartości pozwala sprawdzić, czy dane nie zostały przekłamane przez jakieś zakłócenia. Drugim stopniem weryfikacji jest nadawca komunikatu – program odrzuca wszystkie komunikaty w standardzie NEC, które nie pochodzą z mojego pilota (który przedstawia się adresem 0xC0). Ostatecznie – funkcja zwraca odczytany rozkaz, który jest następnie interpretowany przez program.

Ostateczny kod programu, opatrzony obszernymi komentarzami przedstawia się następująco:

#include <Wire.h>
#include <SPI.h>
//-------------------------------
//pin pod który podłączony jest odbiornik IR
#define IRPin A2
//pin sprawdzający obecność wtyczki
#define IRdetect 2
//odczytane kody z pilota
#define but_power     0x00
#define but_red       0xD2
#define but_green     0x32
#define but_yellow    0xB2
#define but_blue      0x72
#define but_left      0x10
#define but_right     0x20
#define but_up        0x30
#define but_down      0x08
#define but_enter     0xC8
#define but_0         0x48
#define but_1         0xA0
#define but_2         0x60
#define but_3         0xE0
#define but_4         0x90
#define but_5         0x50
#define but_6         0xD0
#define but_7         0xB0
#define but_8         0x70
#define but_9         0xF0
#define but_dot       0x82
#define IR_DEVICE_ADDR 0xC0
//co ile czasu występuje przerwanie, wartość w us
#define US_PER_INTERRUPT    25
//tolerancja czasów, wyrażona w %
#define TIME_TOL        30
//zakresy akceptowalnych wartości poszczególnych czasów, wyrażone w us
#define STARTBITH_TIME  9000
#define STARTH_U        (int)(STARTBITH_TIME*((100+TIME_TOL)/100.))
#define STARTH_L        (int)(STARTBITH_TIME*((100-TIME_TOL)/100.))

#define STARTBITL_TIME  4500
#define STARTL_U        (int)(STARTBITL_TIME*((100+TIME_TOL)/100.))
#define STARTL_L        (int)(STARTBITL_TIME*((100-TIME_TOL)/100.))

#define LH_TIME         560
#define LH_U            (int)(LH_TIME*((100+TIME_TOL)/100.))
#define LH_L            (int)(LH_TIME*((100-TIME_TOL)/100.))

#define LONEL_TIME      1690
#define LONEL_U         (int)(LONEL_TIME*((100+TIME_TOL)/100.))
#define LONEL_L         (int)(LONEL_TIME*((100-TIME_TOL)/100.))

#define LZEROL_TIME     460
#define LZEROL_U        (int)(LZEROL_TIME*((100+TIME_TOL)/100.))
#define LZEROL_L        (int)(LZEROL_TIME*((100-TIME_TOL)/100.))
//-------------------------------
#define bat_ADC       A3
#define bat_update_h  800
//-------------------------------
//RTC
#define DS1307_ADDR   B1101000
#define DS1307_SQW    3
#define DS1307_SQWint 1
//-------------------------------
//pomocnicze do jeżdżenia po tablicy bitów do wysłania
#define wHALF         4
#define wTEN          0
#define wTWENTY       5
#define wMINUTES      1
#define wPAST         6
#define wTO           2
#define wFIVE         7
#define wQUARTER      3
#define wOCLOCK       14
#define minutes1      10
#define minutes2      15
#define minutes3      11
#define minutes4      23
#define whONE         19
#define whTHREE       12
#define whTWO         8
#define whFOUR        9
#define whFIVE        13
#define whSIX         21
#define whSEVEN       16
#define whEIGHT       20
#define whNINE        26
#define whTEN         31
#define whELEVEN      27
#define whTWELVE      18
#define wITIS         22
#define test_delay    250
#define PWMpin        9
#define FOTOpin       A1
//-------------------------------
volatile byte time_counter=
  0;            //zmienna licząca zbocza przychodzące z zegara
volatile boolean update_time_flag=true; //flaga aktualizacji godziny
boolean battery_show=false;              //flaga prezentacji stanu baterii
volatile byte IRbitcount=0;              //zmienna licząca odebrane bity
volatile unsigned int IRtime=
  0;          //zmienna licząca czas odebranego bitu
volatile int
IRTimes[67];              //tablica przechowujaca czasy bitow calego komunikatu
volatile boolean IRRecieved=false;     //flaga odebrania calosci komunikatu
boolean disable=false;
int PWM_mod=0;                          //modyfikowanie jasności

byte decToBcd(byte val)
{
  //funkcja konwersji liczby dziesiętnej na liczbę w formacie BCD
  return ((val/10*16) + (val%10));
}

byte bcdToDec(byte val)
{
  //funkcja konwersji liczby w formacie BCD na liczbę dziesiętną
  return ((val/16*10) + (val%16));
}
//powyższe funkcje do konwersji zostały zaczerpnięte z niniejszej strony: http://tronixstuff.files.wordpress.com/2010/05/example7p41.pdf


byte battery_check()
{
  //zwraca przeskalowaną wartość napięcia na baterii, gdzie 0~2V,255~3,2V
  int temp;
  temp=analogRead(bat_ADC);
  //odczyt 10-bitowy, 0 - 0V, 1023 - 5V
  temp=map(temp,400,670,0,4);
  if(temp<=0) {
    return 0;
  } else if(temp>=4) {
    return 4;
  } else {
    return temp;
  }
}

int gettime()
{
  //zwraca inta postaci HHMM np 1234 -> 12:34
  int temp=0;
  Wire.beginTransmission(
    DS1307_ADDR); //inicjalizacja komunikacji z zegarem pod adresem
  Wire.write(
    0x01);                    //ustawiamy wewnętrzny wskaźnik rejsetru na rejestr minut
  temp=Wire.endTransmission();
  if(temp==0) {
    //jeżeli wskaźnik został prawidłowo nadany (flaga==0) to odczytujemy pożądane dane
    Wire.requestFrom(DS1307_ADDR,2);
    if(Wire.available()==2) {
      //oczekujemy dwóch kolejnych bajtów z urządzenia (kolejno rejestr minut i godzin), jeżeli otrzymaliśmy oba to ok i przechodzimy do ich odbioru
      byte minuty,godziny;
      minuty = Wire.read();
      godziny = Wire.read();
      godziny=bcdToDec(godziny&B00011111);
      if(godziny==0) {
        godziny=12;  //upewniamy się, że czas jest w trybie 12-godzinnym
      }
      temp=(godziny*100)+bcdToDec(minuty);//uzyskujemy inta w pożądanej postaci
      return temp;
    } else {
      return (-1);
    }
  } else {
    Serial.print("BŁĄD KOMUNIKACJI! ");
    switch(temp) {
      //kody błędów wraz z opisami za arduino.cc
    case 1:
      Serial.println("data too long to fit in transmit buffer");
      break;
    case 2:
      Serial.println("received NACK on transmit of address");
      break;
    case 3:
      Serial.println("received NACK on transmit of data");
      break;
    default:
      Serial.println("other error");
    }
    return (-1);
  }
}

boolean RTCconfig()
{
  Wire.begin();                        //inicjalizacja I2C
  Wire.beginTransmission(
    DS1307_ADDR); //inicjalizacja komunikacji z zegarem pod adresem
  Wire.write(
    0x07);                    //ustawiamy wewnętrzny wskaźnik rejsetru na rejestr konfiguracyjny
  Wire.write(B00010000);               //ustawiamy wyjście na przebieg 1HZ
  if(Wire.endTransmission()==0) {
    //prawidłowa komunikacja
    return true;
  } else {
    return false;
  }
}

boolean settime(int nowy_czas)
{
  //nadanie nowego czasu do RTC, postać danej wejściowej analogiczna HHMM
  if((nowy_czas>=0)&&((nowy_czas%100)<60)&&((nowy_czas/100)<=12)) {
    //podany czas jest w prawidłowym formacie
    byte temp;
    Wire.begin();                        //inicjalizacja I2C
    Wire.beginTransmission(
      DS1307_ADDR); //inicjalizacja komunikacji z zegarem pod adresem
    Wire.write(
      0x00);                    //ustawiamy wewnętrzny wskaźnik rejsetru na rejestr sekund
    Wire.write(0x00);                    //sekundy ustawiamy na 0
    temp=decToBcd(nowy_czas%100);
    Wire.write(temp);
    temp=decToBcd(nowy_czas/100);
    temp|=B01000000;                     //ustawiamy bit odpowiedzialny za tryb 12-godzinny
    Wire.write(temp);
    temp=Wire.endTransmission();
    if(temp==0) {
      //prawidłowa komunikacja
      return true;
    } else {
      Serial.print("BŁĄD KOMUNIKACJI! ");
      switch(temp) {
        //kody błędów wraz z opisami za arduino.cc
      case 1:
        Serial.println("data too long to fit in transmit buffer");
        break;
      case 2:
        Serial.println("received NACK on transmit of address");
        break;
      case 3:
        Serial.println("received NACK on transmit of data");
        break;
      default:
        Serial.println("other error");
      }
      return false;
    }
  } else {
    Serial.println("NIEPRAWIDŁOWA GODZINA DO USTAWIENIA!");
    return false;
  }
}

boolean showtime(int czas,boolean itis=true)
{
  //przesyłanie nowej godziny na wyświetlacz
  if((czas>=0)&&((czas%100)<60)&&((czas/100)<=12)) {
    //podany czas jest w prawidłowym formacie
    unsigned long data=
      0; //obsługujemy 4 rejestry po 8 bitów każdy, a więc w sumie 8*4=32 bity, a dokładnie tyle ma zmienna typu unsigned long
    boolean nastepna_godzina=
      false;  //zmienna pomocnicza, wynikająca ze specyfiki języka (14:30 to wpół do TRZECIEJ)
    int godzina,minuty;
    minuty = czas%100;
    godzina = czas/100;
    if(itis) {
      data|=(1UL<<wITIS);
    }
    switch(minuty) {
    case 0:
    case 1:
    case 2:
    case 3:
    case 4: {
      data|=(1UL<<wOCLOCK);
    }
    break;
    case 5:
    case 6:
    case 7:
    case 8:
    case 9: {
      data|=(1UL<<wFIVE)|(1UL<<wMINUTES)|(1UL<<wPAST);
    }
    break;
    case 10:
    case 11:
    case 12:
    case 13:
    case 14: {
      data|=(1UL<<wTEN)|(1UL<<wMINUTES)|(1UL<<wPAST);
    }
    break;
    case 15:
    case 16:
    case 17:
    case 18:
    case 19: {
      data|=(1UL<<wQUARTER)|(1UL<<wPAST);
    }
    break;
    case 20:
    case 21:
    case 22:
    case 23:
    case 24: {
      data|=(1UL<<wTWENTY)|(1UL<<wMINUTES)|(1UL<<wPAST);
    }
    break;
    case 25:
    case 26:
    case 27:
    case 28:
    case 29: {
      data|=(1UL<<wTWENTY)|(1UL<<wFIVE)|(1UL<<wMINUTES)|(1UL<<wPAST);
    }
    break;
    case 30:
    case 31:
    case 32:
    case 33:
    case 34: {
      data|=(1UL<<wHALF)|(1UL<<wPAST);
    }
    break;
    case 35: {
      data|=(1UL<<wTWENTY)|(1UL<<wFIVE)|(1UL<<wMINUTES)|(1UL<<wTO);
      nastepna_godzina=true;
    }
    break;
    case 36:
    case 37:
    case 38:
    case 39:
    case 40: {
      data|=(1UL<<wTWENTY)|(1UL<<wMINUTES)|(1UL<<wTO);
      nastepna_godzina=true;
    }
    break;
    case 41:
    case 42:
    case 43:
    case 44:
    case 45: {
      data|=(1UL<<wQUARTER)|(1UL<<wTO);
      nastepna_godzina=true;
    }
    break;
    case 46:
    case 47:
    case 48:
    case 49:
    case 50: {
      data|=(1UL<<wTEN)|(1UL<<wMINUTES)|(1UL<<wTO);
      nastepna_godzina=true;
    }
    break;
    case 51:
    case 52:
    case 53:
    case 54:
    case 55: {
      data|=(1UL<<wFIVE)|(1UL<<wMINUTES)|(1UL<<wTO);
      nastepna_godzina=true;
    }
    break;
    case 56:
    case 57:
    case 58:
    case 59: {
      data|=(1UL<<wOCLOCK);
      nastepna_godzina=true;
    }
    break;
    }
    minuty=minuty%5;
    switch(minuty) {
    case 0:
      break;
    case 1: {
      if(nastepna_godzina) {
        data|=(1UL<<minutes1)|(1UL<<minutes2)|(1UL<<minutes3)|(1UL<<minutes4);
      } else {
        data|=(1UL<<minutes1);
      }
    }
    break;
    case 2: {
      if(nastepna_godzina) {
        data|=(1UL<<minutes1)|(1UL<<minutes2)|(1UL<<minutes3);
      } else {
        data|=(1UL<<minutes1)|(1UL<<minutes2);
      }
    }
    break;
    case 3: {
      if(nastepna_godzina) {
        data|=(1UL<<minutes1)|(1UL<<minutes2);
      } else {
        data|=(1UL<<minutes1)|(1UL<<minutes2)|(1UL<<minutes3);
      }
    }
    break;
    case 4: {
      if(nastepna_godzina) {
        data|=(1UL<<minutes1);
      } else {
        data|=(1UL<<minutes1)|(1UL<<minutes2)|(1UL<<minutes3)|(1UL<<minutes4);
      }
    }
    break;
    }
    if(nastepna_godzina) {
      godzina++;
    }
    godzina=godzina%12;
    if(godzina==0) {
      godzina=12;  //zmienna godzina ma teraz przedział 1-12
    }
    switch(godzina) {
    case 1:
      data|=(1UL<<whONE);
      break;
    case 2:
      data|=(1UL<<whTWO);
      break;
    case 3:
      data|=(1UL<<whTHREE);
      break;
    case 4:
      data|=(1UL<<whFOUR);
      break;
    case 5:
      data|=(1UL<<whFIVE);
      break;
    case 6:
      data|=(1UL<<whSIX);
      break;
    case 7:
      data|=(1UL<<whSEVEN);
      break;
    case 8:
      data|=(1UL<<whEIGHT);
      break;
    case 9:
      data|=(1UL<<whNINE);
      break;
    case 10:
      data|=(1UL<<whTEN);
      break;
    case 11:
      data|=(1UL<<whELEVEN);
      break;
    case 12:
      data|=(1UL<<whTWELVE);
      break;
    }
    //mamy już przygotowane 32 bity danych do nadania
    SPI.begin();
    SPI.setBitOrder(
      LSBFIRST);  //lub, do wyboru MSBFIRST - kolejność wysyłania bitów w komunikacji SPI
    SPI.setClockDivider(
      SPI_CLOCK_DIV4);  //zgodnie z notą katalogową układu rejestru, dla napięcia zasilania +5V częstotliwość zegara powinna wynosić do 3MHz, dla 8MHz wewnątrz mikrokontrolera, dzielnik ustawiamy na 4
    SPI.setDataMode(SPI_MODE0);
    SPI.transfer((data>>24)&(B11111111));
    SPI.transfer((data>>16)&(B11111111));
    SPI.transfer((data>>8)&(B11111111));
    SPI.transfer((data>>0)&(B11111111));
    SPI.end();
    return true;
  } else {
    Serial.println("NIEPRAWIDŁOWA GODZINA DO USTAWIENIA!");
    return false;
  }
}

void sendSPI(unsigned long data)
{
  SPI.begin();
  SPI.setBitOrder(
    LSBFIRST);  //lub, do wyboru MSBFIRST - kolejność wysyłania bitów w komunikacji SPI
  SPI.setClockDivider(
    SPI_CLOCK_DIV4);  //zgodnie z notą katalogową układu rejestru, dla napięcia zasilania +5V częstotliwość zegara powinna wynosić do 3MHz, dla 8MHz wewnątrz mikrokontrolera, dzielnik ustawiamy na 4
  SPI.setDataMode(SPI_MODE0);
  SPI.transfer((data>>24)&(B11111111));
  SPI.transfer((data>>16)&(B11111111));
  SPI.transfer((data>>8)&(B11111111));
  SPI.transfer((data>>0)&(B11111111));
  SPI.end();
}

void time_count()
{
  time_counter++;
  if(time_counter>=60) {
    update_time_flag=true;
    time_counter=0;
  }
}

void IRsetup()
{
  //dla zegara 8 000 000
  //prescalera 8
  //timera 8-bitowego
  //i wartosci poczatkowej równej 231
  //przerwanie przepełnienia timera występuje co 25us
  cli();
  TCCR2A=0;           //normal mode
  TCCR2B=(1<<CS21);   //prescaler=8
  TIMSK2|=(1<<TOIE2); //przerwanie przepełnienia timera
  TCNT2=231;          //generacja przerwania co 50us
  sei();
  pinMode(IRPin,INPUT);
}

ISR(TIMER2_OVF_vect)
{
  //kod obsługi przerwania timera 2 - obsługa czujnika IR
  TCNT2=231;    //reset timera
  if(IRbitcount>=67) {
    IRRecieved=true;
  } else {
    if((IRtime>=(20000/US_PER_INTERRUPT))&&(IRbitcount!=0)) {
      //wykryj nieaktywnosc w stanie innym niz 0 (oczekiwanie na dane) dluzsza niz 20 000 us, jezeli taka wystapi - zresetuj odbior
      IRtime=0;
      IRbitcount=0;
    }
    byte new_val = digitalRead(IRPin);
    new_val=!new_val;//odwrócona logika
    if((new_val==HIGH)&&(IRbitcount==0)) {
      //jeżeli odebrany stan wysoki, a wcześniej nic nie było (0 bit)
      IRtime=0;
      IRbitcount++;
    }
    if((IRbitcount%2==1)&&
        (new_val==HIGH)) { //bity 1,3,5,7,(...) są stanem wysokim
      //odczytany wysoki, więc liczymy dalej czas
      IRtime++;
    } else if((IRbitcount%2==1)&&(new_val==LOW)) {
      //odczytany niski, więc zapisujemy czas wysokiego i przechodzimy do kolejnego bitu
      IRTimes[IRbitcount-1]=IRtime*US_PER_INTERRUPT;
      IRtime=0;
      IRbitcount++;
    } else if((IRbitcount%2==0)&&
              (new_val==LOW)) { //bity 2,4,6,8,(...) są stanem niskim
      IRtime++;
    } else if((IRbitcount%2==0)&&(new_val==HIGH)) {
      IRTimes[IRbitcount-1]=IRtime*US_PER_INTERRUPT;
      IRtime=0;
      IRbitcount++;
    }
  }
}

int IRDecode()
{
  boolean IRTimes_OK=true;
  //najpierw sprawdzamy, czy te czasy pasują do standardu NEC
  if((IRTimes[0]>=STARTH_L)&&(IRTimes[0]<=STARTH_U)) {} else {
    IRTimes_OK=false;
  }
  if((IRTimes[1]>=STARTL_L)&&(IRTimes[1]<=STARTL_U)) {} else {
    IRTimes_OK=false;
  }
  for(int i=2; i<66; i++) {
    if(i%2==0) { //bit wysoki
      if((IRTimes[i]>=LH_L)&&(IRTimes[i]<=LH_U)) {} else {
        IRTimes_OK=false;
      }
    } else {
      if((IRTimes[i]>=LONEL_L)&&(IRTimes[i]<=LONEL_U)) {}
      else if((IRTimes[i]>=LZEROL_L)&&(IRTimes[i]<=LZEROL_U)) {} else {
        IRTimes_OK=false;
      }
    }
  }
  if(IRTimes_OK) {
    byte addr_normal,addr_inv,data_normal,data_inv;  //zmienne pomocnicze
    addr_normal=0;
    for(int i=0; i<8; i++) {
      //wybierz pierwszy bajt (adres niezanegowany)
      if((IRTimes[2*i+3]>=LONEL_L)&&(IRTimes[2*i+3]<=LONEL_U)) {
        //ten znak to logiczna jedynka
        addr_normal|=(B1<<(7-i));
      }//jeżeli nie, to ponieważ już wcześniej się upewniliśmy, że czasy są poprawne - zero
    }
    addr_inv=0;
    for(int i=0; i<8; i++) {
      //wybierz drugi bajt (adres zanegowany)
      if((IRTimes[2*i+19]>=LONEL_L)&&(IRTimes[2*i+19]<=LONEL_U)) {
        //ten znak to logiczna jedynka
        addr_inv|=(B1<<(7-i));
      }//jeżeli nie, to ponieważ już wcześniej się upewniliśmy, że czasy są poprawne - zero
    }
    addr_inv=~addr_inv;
    if(addr_normal!=addr_inv) {
      IRbitcount=0;
      //jeżeli adres zanegowany i niezanegowany się różnią to odebraliśmy coś nie tak, odrzucamy
      IRRecieved=false;
      return -1;
    }
    if(addr_normal!=IR_DEVICE_ADDR) {
      IRbitcount=0;
      //jeżeli to co odebraliśmy nie pochodzi z naszego pilota, odrzucamy
      IRRecieved=false;
      return -1;
    }
    data_normal=0;
    for(int i=0; i<8; i++) {
      //wybierz trzeci bajt (dane niezanegowane)
      if((IRTimes[2*i+35]>=LONEL_L)&&(IRTimes[2*i+35]<=LONEL_U)) {
        //ten znak to logiczna jedynka
        data_normal|=(B1<<(7-i));
      }//jeżeli nie, to ponieważ już wcześniej się upewniliśmy, że czasy są poprawne - zero
    }
    data_inv=0;
    for(int i=0; i<8; i++) {
      //wybierz czwarty bajt (dane zanegowane)
      if((IRTimes[2*i+51]>=LONEL_L)&&(IRTimes[2*i+51]<=LONEL_U)) {
        //ten znak to logiczna jedynka
        data_inv|=(B1<<(7-i));
      }//jeżeli nie, to ponieważ już wcześniej się upewniliśmy, że czasy są poprawne - zero
    }
    data_inv=~data_inv;
    if(data_normal!=data_inv) {
      IRbitcount=0;
      //jeżeli dane zanegowane i niezanegowane się różnią, to coś nie tak, odrzucamy
      IRRecieved=false;
      return -1;
    }
    IRbitcount=0;
    IRRecieved=false;
    return data_normal;  //zwracamy odebrany kod polecenia
  } else {
    IRbitcount=0;
    IRRecieved=false;
    return -1;
  }
}

void display_test()
{
  sendSPI(0);
  delay(test_delay);
  sendSPI(1UL<<wITIS);
  delay(test_delay);
  sendSPI((1UL<<wITIS)|(1UL<<wHALF));
  delay(test_delay);
  sendSPI((1UL<<wITIS)|(1UL<<wHALF)|(1UL<<wTEN));
  delay(test_delay);
  sendSPI((1UL<<wHALF)|(1UL<<wTEN)|(1UL<<wQUARTER));
  delay(test_delay);
  sendSPI((1UL<<wTEN)|(1UL<<wQUARTER)|(1UL<<wTWENTY));
  delay(test_delay);
  sendSPI((1UL<<wQUARTER)|(1UL<<wTWENTY)|(1UL<<wFIVE));
  delay(test_delay);
  sendSPI((1UL<<wTWENTY)|(1UL<<wFIVE)|(1UL<<wMINUTES));
  delay(test_delay);
  sendSPI((1UL<<wFIVE)|(1UL<<wMINUTES)|(1UL<<wTO));
  delay(test_delay);
  sendSPI((1UL<<wMINUTES)|(1UL<<wTO)|(1UL<<wPAST));
  delay(test_delay);
  sendSPI((1UL<<wTO)|(1UL<<wPAST)|(1UL<<whONE));
  delay(test_delay);
  sendSPI((1UL<<wPAST)|(1UL<<whONE)|(1UL<<whTHREE));
  delay(test_delay);
  sendSPI((1UL<<whONE)|(1UL<<whTHREE)|(1UL<<whTWO));
  delay(test_delay);
  sendSPI((1UL<<whTHREE)|(1UL<<whTWO)|(1UL<<whFOUR));
  delay(test_delay);
  sendSPI((1UL<<whTWO)|(1UL<<whFOUR)|(1UL<<whFIVE));
  delay(test_delay);
  sendSPI((1UL<<whFOUR)|(1UL<<whFIVE)|(1UL<<whSIX));
  delay(test_delay);
  sendSPI((1UL<<whFIVE)|(1UL<<whSIX)|(1UL<<whSEVEN));
  delay(test_delay);
  sendSPI((1UL<<whSIX)|(1UL<<whSEVEN)|(1UL<<whEIGHT));
  delay(test_delay);
  sendSPI((1UL<<whSEVEN)|(1UL<<whEIGHT)|(1UL<<whNINE));
  delay(test_delay);
  sendSPI((1UL<<whEIGHT)|(1UL<<whNINE)|(1UL<<whTEN));
  delay(test_delay);
  sendSPI((1UL<<whNINE)|(1UL<<whTEN)|(1UL<<whELEVEN));
  delay(test_delay);
  sendSPI((1UL<<whTEN)|(1UL<<whELEVEN)|(1UL<<whTWELVE));
  delay(test_delay);
  sendSPI((1UL<<whELEVEN)|(1UL<<whTWELVE)|(1UL<<wOCLOCK));
  delay(test_delay);
  sendSPI((1UL<<whTWELVE)|(1UL<<wOCLOCK)|(1UL<<minutes1));
  delay(test_delay);
  sendSPI((1UL<<wOCLOCK)|(1UL<<minutes1)|(1UL<<minutes2));
  delay(test_delay);
  sendSPI((1UL<<minutes1)|(1UL<<minutes2)|(1UL<<minutes3));
  delay(test_delay);
  sendSPI((1UL<<minutes2)|(1UL<<minutes3)|(1UL<<minutes4));
  delay(test_delay);
  sendSPI((1UL<<minutes3)|(1UL<<minutes4));
  delay(test_delay);
  sendSPI(1UL<<minutes4);
  delay(test_delay);
  sendSPI(0);
  delay(test_delay);
  delay(test_delay);
}

void RTCsetup()
{
  //Wire.begin();              //inicjalizacja I2C
  if(RTCconfig()) {
    Serial.println("RTC on!");
    pinMode(DS1307_SQW,INPUT); //pin SQW jako wejście
    attachInterrupt(DS1307_SQWint,time_count,
                    RISING); //z podpiętym przerwaniem na zbocze narastające
  }
}

int maketime(byte digits[])
{
  int temp_time=0;
  if((digits[0]>=0)&&(digits[0]<=1)) {
    temp_time=digits[0]*1000;
  } else {
    return -1;
  }
  if(((digits[0]==0)&&(digits[1]>=0)&&(digits[1]<=9))||((digits[0]==1)&&
      (digits[1]>=0)&&(digits[1]<=2))) {
    temp_time+=digits[1]*100;
  } else {
    return -1;
  }
  if((digits[2]>=0)&&(digits[2]<=5)) {
    temp_time+=digits[2]*10;
  } else {
    return -1;
  }
  if((digits[3]>=0)&&(digits[3]<=9)) {
    temp_time+=digits[3];
  } else {
    return -1;
  }
  return temp_time;
}

void PWMsetup()
{
  pinMode(PWMpin,OUTPUT);
  pinMode(FOTOpin,INPUT);
}

void PWMset()
{
  int light = analogRead(FOTOpin);
  light=map(light,800,200,255,70);
  light=light*((100+PWM_mod)/100.);
  if(light>255) {
    light=255;
  } else if(light<20) {
    light=20;
  }
  analogWrite(PWMpin,light);
}

void setup()
{
  Serial.begin(9600);
  delay(10000);
  Serial.println("jedziemy");
  pinMode(IRdetect,INPUT);
  if(digitalRead(IRdetect)==HIGH) {
    //jeżeli odbiornik jest podłączony
    Serial.println("IR on!");
    IRsetup();             //inicjalizacja pilota
  }
  RTCsetup();              //inicjalizacja RTC
  PWMsetup();
  PWMset();
  Serial.println("go!");
}

void loop()
{
  /*if((update_time_flag)&&(!battery_show)&&(!disable))
  {//minęła minuta od ostatniej aktualizacji czasu, nie wyświetlamy aktualnie stanu baterii i nie mamy tymczasowo wyłączonego wyśw.
    int time;
    time = gettime();
    if(time!=(-1))
    {//jeżeli czas odebrany prawidłowo
      if(showtime(time))
      {//jeżeli czas został prawidłowo wyświetlony
        update_time_flag=false; //wyzeruj flagę odświeżenia czasu
      }//jeżeli nie - flaga nie zostaje wyzerowana, przy następnej pętli spróbuj ponownie
    }
  }*/
  if(IRRecieved) {
    //flaga odebrania kodu przycisku
    Serial.println("cos!");
    int data=IRDecode();
    switch(data) {
    case(-1):
      break;
    case but_green: {
      //zielony przycisk to test baterii
      if(battery_show) {
        //jeżeli bateria była już wyświetlana, przestań ją wyświetlać (drugie kliknięcie anuluje)
        battery_show=false;
        update_time_flag=true;
      } else {
        battery_show=true;
        byte battery;
        battery = battery_check();
        switch(battery) {
          //w zależności od odczytu stanu baterii zapalamy od 0 (bateria rozładowana) do 4 (bateria w pełni naładowana) diod "minutowych"
        case 0: {
          sendSPI(0);
        }
        break;
        case 1: {
          sendSPI(1UL<<minutes1);
        }
        break;
        case 2: {
          sendSPI((1UL<<minutes1)|(1UL<<minutes2));
        }
        break;
        case 3: {
          sendSPI((1UL<<minutes1)|(1UL<<minutes2)|(1UL<<minutes3));
        }
        break;
        case 4: {
          sendSPI((1UL<<minutes1)|(1UL<<minutes2)|(1UL<<minutes3)|(1UL<<minutes4));
        }
        break;
        default:
          break;
        }
      }
    }
    break;
    case but_power: {
      //tymczasowe wyłączenie wyświetlania godziny
      disable=!disable;
      if(disable) {
        digitalWrite(PWMpin,LOW);
      } else {
        PWMset();
      }
    }
    break;
    case but_red: {
      //test wyświetlacza
      display_test();
      update_time_flag=
        true;  //po teście na wyświetlaczu nic nie będzie, zaktualizuj wyświetlaną godzinę
    }
    break;
    case but_yellow: {
      //na razie brak pomysłu na akcję tego przycisku
    }  break;
    case but_blue: {
      //przestawianie zegara
      boolean complete=false;
      boolean escape=false;
      int mod_time=gettime();
      byte cur_digit=0;            //którą cyfrę zmieniamy 0123 -> 12:34
      byte digit[4];
      digit[3]=mod_time%10;
      digit[2]=((mod_time%100)-digit[3])/10;
      digit[1]=((mod_time%1000)-digit[2]*10-digit[3])/100;
      digit[0]=(mod_time-digit[1]*100-digit[2]*10-digit[3])/1000;
      while(!complete) {
        showtime(mod_time,
                 false);//wyświetl czas, bez zapalania "it is" - sygnalizacja, że przestawiamy godzinę!
        if(IRRecieved) {
          int new_data=IRDecode();
          switch(new_data) {
          case(-1):
            break;
          case but_power: {
            //zakończ ustawianie bez zapisu czasu (escape)
            complete=true;
            escape=true;
          }
          break;
          case but_blue: {
            //zakończ ustawianie godziny
            complete=true;
          }
          break;
          case but_left: {
            //przesuń na cyfrę w lewo
            cur_digit--;
            if(cur_digit>3) {
              cur_digit=3;
            }
          }
          break;
          case but_right: {
            //przesuń na cyfrę w prawo
            cur_digit++;
            if(cur_digit>3) {
              cur_digit=0;
            }
          }
          break;
          case but_up: {
            //zwiększ tą cyfrę
            digit[cur_digit]++;
            switch(cur_digit) {
            case 0: { //pierwsza cyfra - 0,1
              if(digit[cur_digit]>1) {
                digit[cur_digit]=0;
              }
            }
            break;
            case 1: { //druga cyfra - 0,1,2 lub 0-9
              if(digit[0]==1) {
                //dozwolone 0,1,2
                if(digit[cur_digit]>2) {
                  digit[cur_digit]=0;
                }
              } else {
                if(digit[cur_digit]>9) {
                  digit[0]=1;
                  digit[cur_digit]=0;
                }
              }
            }
            break;
            case 2: { //trzecia 0-5
              if(digit[cur_digit]>5) {
                digit[cur_digit]=0;
              }
            }
            break;
            case 3: { //czwarta 0-9
              if(digit[cur_digit]>9) {
                digit[2]++;
                if(digit[2]>5) {
                  digit[2]=0;
                }
                digit[cur_digit]=0;
              }
            }
            }
          }
          break;
          case but_down: {
            //zmniejsz tą cyfrę
            digit[cur_digit]--;
            switch(cur_digit) {
            case 0: { //pierwsza cyfra - 0,1
              if(digit[cur_digit]>1) {
                digit[cur_digit]=1;
              }
            }
            break;
            case 1: { //druga cyfra - 0,1,2 lub 0-9
              if(digit[0]==1) {
                //dozwolone 0,1,2
                if(digit[cur_digit]>2) {
                  digit[0]=0;
                  digit[cur_digit]=9;
                }
              } else {
                if(digit[cur_digit]>9) {
                  digit[cur_digit]=9;
                }
              }
            }
            break;
            case 2: { //trzecia 0-5
              if(digit[cur_digit]>5) {
                digit[cur_digit]=5;
              }
            }
            break;
            case 3: { //czawrta 0-9
              if(digit[cur_digit]>9) {
                digit[2]--;
                if(digit[2]>5) {
                  digit[2]=5;
                }
                digit[cur_digit]=9;
              }
            }
            }
          }
          break;
          case but_0: {
            //cyfra 0
            digit[cur_digit]=0;        //wpisz 0
            cur_digit=(cur_digit+1)%4; //przesuń do kolejnej cyfry
          }
          break;
          case but_1: {
            //cyfra 1
            digit[cur_digit]=1;        //wpisz 1
            cur_digit=(cur_digit+1)%4; //przesuń do kolejnej cyfry
          }
          break;
          case but_2: {
            //cyfra 2
            digit[cur_digit]=2;        //wpisz 2
            cur_digit=(cur_digit+1)%4; //przesuń do kolejnej cyfry
          }
          break;
          case but_3: {
            //cyfra 3
            digit[cur_digit]=3;        //wpisz 3
            cur_digit=(cur_digit+1)%4; //przesuń do kolejnej cyfry
          }
          break;
          case but_4: {
            //cyfra 4
            digit[cur_digit]=4;        //wpisz 4
            cur_digit=(cur_digit+1)%4; //przesuń do kolejnej cyfry
          }
          break;
          case but_5: {
            //cyfra 5
            digit[cur_digit]=5;        //wpisz 5
            cur_digit=(cur_digit+1)%4; //przesuń do kolejnej cyfry
          }
          break;
          case but_6: {
            //cyfra 6
            digit[cur_digit]=6;        //wpisz 6
            cur_digit=(cur_digit+1)%4; //przesuń do kolejnej cyfry
          }
          break;
          case but_7: {
            //cyfra 7
            digit[cur_digit]=7;        //wpisz 7
            cur_digit=(cur_digit+1)%4; //przesuń do kolejnej cyfry
          }
          break;
          case but_8: {
            //cyfra 8
            digit[cur_digit]=8;        //wpisz 8
            cur_digit=(cur_digit+1)%4; //przesuń do kolejnej cyfry
          }
          break;
          case but_9: {
            //cyfra 9
            digit[cur_digit]=9;        //wpisz 9
            cur_digit=(cur_digit+1)%4; //przesuń do kolejnej cyfry
          }
          break;
          default:
            break;
          }
        }
        int tmp = maketime(
                    digit);  //złóż cyfry do kupy, aby utworzyć pojedynczą wartość godziny
        if(tmp!=(-1)) {
          //jeżeli złożone prawidłowo
          mod_time=tmp;  //zaktualizuj wyświetlaną godzinę
        }
        delay(50);
      }//jeżeli zakończone przestawianie godziny
      if(!escape) {
        settime(mod_time);  //wyślij nową godzinę do zegara
      }
      update_time_flag=true;  //wystaw flagę aktualizacji wyświetlonej godziny
    }
    break;
    case but_up: {
      //zwiększ jasność
      PWM_mod+=10;
      if(PWM_mod>100) {
        PWM_mod=100;
      }
    }
    break;
    case but_down: {
      //zmniejsz jasność
      PWM_mod-=10;
      if(PWM_mod<-100) {
        PWM_mod=-100;
      }
    }
    break;
    default:
      break;
    }
  }
  //if(!disable) PWMset();
}


Do pobrania: wordclockv2.ino (kopia)


Zobacz inne artykułu z cyklu: Arduiono ... czyli prościej się nie da :-)

Na koniec przedstawiam zdjęcie skończonego, działającego zegara.




1 komentarz:

  1. Bardzo ładny projekt. Sam chcę sobie taki zegar wykonać na podstawie tego projektu. Przed zaczęciem kompletacji części oraz wykonania wstrzymuje mnie jedno pytanie. Jak ustawić czas jeżeli nie posiadam takiego pilota jak autor. Czy jest możliwość podłączenia przycisków, które to umożliwią? Niestety ale na programowaniu się nie znam i nie potrafię zmodyfikować tak kodu. Mogę prosić o jakąś pomoc jak najłatwiej ustawić czas?

    OdpowiedzUsuń