LED -es óra



Ennek a "projektnek" egy kedves barátommal együtt vágtunk neki 2017 magasságában ( most amikor ezt írom 2021 van, amiből látszik, milyen rendkívül bonyolult probléma megoldásáról volt szó ... mennyire elszerencsétlenkedtük az időt és húztuk mint a rétestésztát :-). Az alapgondolat egy újszerű, mégis modern kialakítású óra elkészítése volt. Egyértelműen adódott a gondolat, hogy a modern technikát ( Arduino, mi más ? :-) felhasználva hozzuk létre. Hosszas agyalgás után olyan dizájnban állapodtunk meg, amiben kettős körív mentén vannak elhelyezve a LED -ek. Természetesen egyenként programozható RGB LED -ekről van szó. Felmerült ugyan a készen kapható LED -szalagok alkalmazása, de nagyon sok kötöttséget jelentettt volna, elsősorban a behatárolt fizikai méretekből adódóan. A diszkrét LED -ekkel viszont miénk volt a pálya, úgy alakítottuk ki, ahogy szerettük volna.

Úgy állapodtunk meg, hogy az óratest kialakítását a barátom végzi, én pedig az áramkört valósítom meg, ill. megcsinálom az Android -os alkalmazást is hozzá. Nos igen, ez egy kicsit magyarázatra szorul, ezért kifejtem kicsit részletesebben:

Nyilvánvaló, hogy egy óra működése során a legfontosabb szempont a pontosság. Ez két szempontból is csorbát szenvedhet. Az egyik az áramköri elemek "tökéletlensége". Hasonlóképpen pl. egy hagyományos kakórához, az idő időnként elmászik. Ennek a mértéke lehet kisebb, vagy annyira nagy, hogy már zavaróan befolyásolja a használatot. Másrészt figyelembe kell venni a téli-nyári időszámítást is, ill. az azokra való áttérés lehetőségét.

Mindezen okokból elengedhetetlen a pontosságról, ill. a pontos idő beállításának a lehetőségéről gondoskodni. Kivétel, ha eleve folyamatosan kapja az óra a pontos időt. Erre napjainkban már több lehetőség is van. Az egyik a DCF jel vétele, aminek lényege, hogy egy Frankfurtból sugárzott 77,5 kHz -es jellel adják a pontos időt, amit megfelelő eszköz segítségével venni lehet. Nagy hátránya, hogy ha nem tudjuk biztosan hol lesz felhasználva az óra ( márpedig nálunk ez volt a helyzet ), nem garantálható a jel biztonságos vétele, így az óra pontossága sem. Másik mód az internet felhasználása NTP ( Network Time Protokol ) segítségével. De sajnos nincs mindenhol internet. A GPS műholdak is szorgalmasan szórják a pontos időt, amit viszonylag egyszerűen lehet venni ... ha megfelelő helyen vagyunk. Azt hiszem kitalálta a kedves olvasó: ismeretlen helyen ez sem garantálható :-(.

Maradt tehát, hogy olyan óraáramkört ( RTC ) használjunk, ami megfelelő pontossággal tartja az időt, másrészt pedig biztosítani kell az óra pontos idejének beállítását is, vagy máshogy fogalmazva az idő beállíthatóságát. Erre szolgál a fent említett Android -os alkalmazás.

Mindezek figyelembe vételével lassan össze is állt a kép arról, hogy mire lesz szükség az órához:


Néhány fontos dolog az RTC áramkörrel kapcsolatban. Az egyik, hogy többféle is létezik. Utána olvasgatva egyértelműen a fenti típust ajánlják, mert eléggé megbízható, és elfogadható pontosságú ( nem mellesleg van benne egy 4 k -s EEPROM is, ami jelen helyzetben tök fölösleges, de adott esetben jól jöhet ). A másik fontos dolog, hogy a DS3231 panel alapvetően nem sima CR2032 gombelemre van kitalálva hanem 3,6 V -os LIR2032 lítium gomb akkura, mert van benne egy töltő áramkör, ami folyamatosan tölti. Így az akkumulátor kapocsfeszültsége 4,2 V körül jár, ami egy CR2032 számára nem sok jóval kecsegtet. Ezért el kell távolítani a panelről ezt a töltő áramköri részt. A következő videóban szépen el van magyarázva hogyan. Mondjuk én nem az ellenállást távolítottam el, hanem elvágtam egy vezetéket, de tökmindegy. A modifikáció természetesen azt is jelenti, hogy időnként cserélni kell a gombelemet, de egy elem - állítólag - több év működést is kibír. A Youtube videó: DS 3231 RTC | Disable Battery Charging circuit.

Az áramkör kapcsolási rajza itt látható:

Ora_projekt

Sok kommentet talán nem igényel. Minden +5V -ról működik szerencsére, ami USB csatlakozóból szuperul kinyerhető ( 2 x 60 db LED megtáplálása kicsit zűrös lehet, de egyszerre sosem világít annyi, tehát a gyakorlatban nem lehet probléma ). A BT modul bemenete ( Rx ) többek egybehangzó véleménye szerint nem tolerálja az 5 V -ot, ezért az Arduino -tól érkező jel egy elenállás osztón van megjáratva. A három nyomógomb az óra idejének beállításához kell. Mint látható a 2 LED kör vezérléséhez csupán 2 kimenetre van szükség ( tulajdonképpen 1 -el is megvalísítható lenne kis szoftveres trükközéssel ... de inkább nem trükköztem :-). Az RTC modul I2C buszt használ, és csak két analóg portot foglal az Arduino -n.

Az elvi rajz elkészítése után a következő nagy feladat a LED -ek elhelyezése, azaz a NYÁK panelek megtervezése és elkészítése volt. Ehhez egy Sprint Layout nevezetű egyszerű NYÁK rajzoló programot használtam. A jobb anyagfelhasználás miatt a kört négy cikkre osztottam. A négy cikk teljesen megegyező részekből épül fel:

Ora_projekt

Készen így néznek ki ( elkészítésükben egy másik kedves barátom segített, ezúton is köszönet érte ! ):

Ora_projekt

Deszkapanelen pedig így:

Ora_projekt

Működés közben nehéz volt róla normális fényképet készíteni, az alábbi a jobbak közül való. A belső íven levő piros LED jelképezi a kismutatót, a külső ív kék LED -je a nagymutatót, a zöld LED -ek pedig a másodperc mutatót. Így állóképen kicsit fikciós a mutatók beazonosítása, de élőben nem olyan vészes a helyzet :-)

Ora_projekt

Az óra mellett látható az Androidos alkalmazás első verziója is, mint később láthatod ez azóta sokat változott.

Ezután az óra végleges összeállítása következett. Mint említettem a mechanikai munkák nagy részét a barátom végezte, van erről is kép:

Ora_projekt

Az óra doboz hátsó részét már jómagam farigcsáltam:

Ora_projekt

Ezután behelyeztem az áramköri elemeket:

Ora_projekt

Majd a LED NYÁK -okat is, és összehuzaloztam. Az óraállításhoz szükséges nyomógombokat mikrokapcsolók beépítésével oldottam meg, amiket a jobb felső szélen látni:

Ora_projekt

Közelebbről a mikrokapcsolók:

Ora_projekt

Tisztázás után valamellyest javult a látvány:

Ora_projekt

Az áramkör egy picit közelebbről ( mint - remélhetőleg - látható háromféle USB aljzatot is beépítettem. Inkább több legyen mint kevesebb, nem igaz ? :-)

Ora_projekt

Természetesen nem maradt pucéran, le lett fedve egy kör alakú lappal:

Ora_projekt

Elképzelésünk szerint az előlapon tejfehér plexi lapot használunk, mert az jobban kihozza a LED -ek "mutató jellegét". Ennek a kivágásáról is készült kép ( itt még nem tejfehér, mert rajta van a kék védőfólia ... ami a fényviszonyok miatt a képen inkább szürkének látszik ):

Ora_projekt

És most térjünk át a szoftver(ek)re.

Két részből áll. Az egyik magát az órát működtető Arduino -s rész, a másik pedig az Androidos alkalmazás. Egyikről sem írnék túl sokat, mert mindkét program igen nagy hasonlóságot mutat a Nyomógomb naplózó kütyüével, melyet elég részletesen bemutattam itt az oldalamon. Az óra programja attól elsősorban abban különbözik, hogy nem kell adatokat naplózni ill. küldözgetni hanem csak a LED -eket vezérelni. Tehát mondhatni egyszerűbb. Íme:


/* 
 *  Ez a program a 2018-2021 között készült LED -es óra programja.
 *  2 x 60 db RGB LED -del mutatja a pontos időt. A LED -ek egy kisebb és egy nagyobb koncentrikus kör mentén helyezkednek el.
 *  A belső kör jelképezi az óra mutatót, a külső pedig a percmutatót.
 *  A pontos időt egy DS3231 RTC adja. Lehetőség van külső eszközről bluetooth -on rátölteni és lekérni az időt a lenti
 *  protokol szerint.
 *  
 */

/*  Az rtc kezelő alapprogram innen származik:
http://tronixstuff.com/2014/12/01/tutorial-using-ds1307-and-ds3231-real-time-clock-modules-with-arduino/
*/

/* A 3 db gomb prellmentesített kezelésével ez alapján van megoldva:
https://www.arduino.cc/en/tutorial/debounce

*/

#include "Wire.h"
#include <Adafruit_NeoPixel.h>
#include <SoftwareSerial.h>         // Ez a BlueTooth soros kommunikációjához kell

#define DS3231_I2C_ADDRESS 0x68

#define PIN_1            8          // Belső PL9823 LED sor ezen a pin -en van kezelve
#define PIN_2            9          // Külső PL9823 LED sor ezen a pin -en van kezelve
#define NUMPIXELS       60          // Hány darab PL9823 LED van kezelve. Számozásuk 0 -tól kezdődik

#define rxPin 2                     // Define SoftwareSerial rx data pin  , azaz a BlueTooth TX,  D2
#define txPin 3                     // Define SoftwareSerial tx data pin  , azaz a BlueTooth RX,  D3

Adafruit_NeoPixel pixels_1 = Adafruit_NeoPixel(NUMPIXELS, PIN_1, NEO_RGB + NEO_KHZ800);   // PL9823 LED -ekhez kell
Adafruit_NeoPixel pixels_2 = Adafruit_NeoPixel(NUMPIXELS, PIN_2, NEO_RGB + NEO_KHZ800);   // PL9823 LED -ekhez kell

SoftwareSerial BlueTooth(rxPin, txPin);  // create instance of SoftwareSerial  , ez a BlueTooth soros kommunikációjához kell

byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  
byte byteRead;     // Soros port (bluetooth) beolvasáskor ide olvasunk
byte ByteSorszam;  // Megadja, hogy hányadik byte beolvasásánál tartunk a BT modul felől

byte EV1;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott év első byte -ja
byte EV2;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott év második byte -ja
byte HON;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott hónap érték
byte NAP;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott nap érték
byte NA_;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott hét napja ( Appinventor: Weekday, Arduino: dayOfWeek )
byte ORA;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott óra érték
byte PER;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott perc érték
byte MAS;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott másodperc érték

int MODE = 0;      // A gombok kezelésénél használjuk. 0 - nem aktív, 1 - óra állítása, 2 - perc állítása, 3 - másodperc állítása

int A_buttonState = LOW;             // the current reading from the input pin. Az "=LOW" -t már én írtam hozzá
int A_lastButtonState = LOW;  
unsigned long A_lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long A_debounceDelay = 500;  // 1000 volt

int B_buttonState = LOW;             // the current reading from the input pin. Az "=LOW" -t már én írtam hozzá
int B_lastButtonState = LOW;  
unsigned long B_lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long B_debounceDelay = 20;  

int C_buttonState = LOW;             // the current reading from the input pin. Az "=LOW" -t már én írtam hozzá
int C_lastButtonState = LOW;  
unsigned long C_lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long C_debounceDelay = 20;  

byte A_gomb_fazis = 0;            // Megadja, hogy melyik óraállítási fázisban vagyunk:
                                    // 0 - egyik sem
                                    // 1 - órát állítunk
                                    // 2 - percet állítunk
                                    // 3 - másodpercet állítunk          

byte B_gomb_fazis = 0;            // Megadja, hogy felfelé vagy lefelé állítunk:
                                    // 0 - egyik sem
                                    // 1 - felfelé

byte C_gomb_fazis = 0;            // Megadja, hogy felfelé vagy lefelé állítunk:
                                    // 0 - egyik sem
                                    // 1 - felfelé
                                    
int gyors = 200;                  // PL9823 LED -ekhez

// byte masodperc_tomb[60][3] = {0};
byte tomb_1[61][3] = {0};     // A LED -eknek 61 elemű tömböket foglalunk, hogy könnyebb legyen címezni ( 1-60 )
byte tomb_2[61][3] = {0};
byte puffer[4] = {0};
int  second_buffer;
byte kismutato;                 // A 24 órás formátumú "hour" a megjelenítéskor ezt a LED sorszámot jelenti
byte km_korr;            // A percmutató állásától függő óramutató korrekció
// byte nm_korr;            // A nagymutató (percmutató) korrigált értéke ( 0 perc esetén 60 )

int MitAkar = 0;        /* Az Android eszköztől kapott byte, ami megadja, hogy mit szeretne tőlünk
                         *  - KÜLDHETI A PONTOS IDŐT                           ( a =  97 )
                         *  - LEKÉRDEZHETI A MI IDŐNKET                        ( b =  98 ) 
                         */
/* A következőkben megadjuk az óra egyes komponenseinek az R-G-B intenzitását ( 0 - 255 ) : */
byte OraMutato_R      = 150;
byte OraMutato_G      =   0;
byte OraMutato_B      =   0;
byte PercMutato_R     =   0;
byte PercMutato_G     =   0;
byte PercMutato_B     = 150;
byte MasPercMutato_R  =   0;
byte MasPercMutato_G  =  40;
byte MasPercMutato_B  =   0;
byte SegedOsztas_R    =   2;
byte SegedOsztas_G    =   2;
byte SegedOsztas_B    =   0;


/* ======================================================================================================================================================================= */
void setup()
{
  Wire.begin();
  Serial.begin(9600);
  BlueTooth.begin(9600);            // Ezen megy a BlueTooth kommunikáció
  // set the initial time here:
  // DS3231 seconds, minutes, hours, day, date, month, year
  
  pinMode(5, INPUT);  // Állító gombok
  pinMode(6, INPUT);    
  pinMode(7, INPUT);  
  
  pixels_1.begin(); // This initializes the NeoPixel library.  
  pixels_2.begin(); // This initializes the NeoPixel library.

}

/* ======================================================================================================================================================================= */
void loop()
{
  gomb_kezeles();   // Az A, B és C gombok alapján beállítja az RTC idejét
 
  bt_kezeles();                                      // Ha jön a soros porton valami, beolvassuk

  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year); 

// --------------- ITT KÖVETKEZIK A LED -EK KEZELÉSE ----------------------------------------------------------------------- :
// Elvileg elgendő volna csak akkor foglalkozni vele, ha másodperc váltás történt, de a gombokkal történő beállításnál azt szeretnénk,
// ha az éppen állítandó "mutató" gyorsan színt váltana, hogy jól látható legyen melyiket állítjuk

     for(int i=1; i<=60; i++)            // Először nullázunk mindent
     {
       tomb_1[i][0] = 0;
       tomb_1[i][1] = 0;
       tomb_1[i][2] = 0;       
       tomb_2[i][0] = 0;
       tomb_2[i][1] = 0;
       tomb_2[i][2] = 0;       
     }  

  // Azután beállítjuk az óra alapmintáit (osztásokat), tehát ami a mutatóktól függetlenül mindig a háttérben látható ( már ha épp egy mutató ki nem takarja )

     for(int i=5; i<=60; i=i+5)      
     {
      tomb_1[i][0]  = SegedOsztas_R ;
      tomb_1[i][1]  = SegedOsztas_G ; 
      tomb_1[i][2]  = SegedOsztas_B ; 
     } 

     for(int i=15; i<=60; i=i+15) 
     {
      tomb_2[i][0]  = SegedOsztas_R ;
      tomb_2[i][1]  = SegedOsztas_G ; 
      tomb_2[i][2]  = SegedOsztas_B ; 
     }
  
  // Ezután a mutatókat állítjuk be ( a másodpercet ( second ) a végére hagyjuk, hogy a másodperc mutató "eltakarja" a másik kettőt ha rájuk ér ).

  // A perc beállításánál az RTC 0-59 miatt speciális eset a 0 perc, mert annak a tömbünk 60 -as eleme felel meg. Ezt itt kezeljük le:
  if ( minute == 0 ) minute = 60;
  
  if ( MODE == 2 )    // Ha PERC változtatási üzemmódban vagyunk villogtatjuk a nagymutatót
     {
      tomb_2[minute][0] =  random ( 0, 200 );
      tomb_2[minute][1] =  random ( 0, 200 );
      tomb_2[minute][2] =  random ( 0, 200 );
//      pixels_2.setPixelColor(nm_korr-1, pixels_2.Color( random ( 0, 200 ), random ( 0, 200 ),random ( 0, 200 ))); 
      pixels_2.setPixelColor(minute-1, pixels_2.Color( random ( 0, 200 ), random ( 0, 200 ),random ( 0, 200 ))); 
      pixels_2.show();                                                                    
     }
  else
     {
      tomb_2[minute][0] = PercMutato_R;
      tomb_2[minute][1] = PercMutato_G;
      tomb_2[minute][2] = PercMutato_B;
     }

  // Az óra mutató speciálisan viselkedik, hiszen az RTC 0-23 órát számol amit le kell képeznünk a 0-12 felületre
  if (( hour ==  0 ) || ( hour == 12 )) kismutato = 60 ;
  if (( hour ==  1 ) || ( hour == 13 )) kismutato =  5 ;
  if (( hour ==  2 ) || ( hour == 14 )) kismutato = 10 ;
  if (( hour ==  3 ) || ( hour == 15 )) kismutato = 15 ;
  if (( hour ==  4 ) || ( hour == 16 )) kismutato = 20 ;
  if (( hour ==  5 ) || ( hour == 17 )) kismutato = 25 ;
  if (( hour ==  6 ) || ( hour == 18 )) kismutato = 30 ;
  if (( hour ==  7 ) || ( hour == 19 )) kismutato = 35 ;
  if (( hour ==  8 ) || ( hour == 20 )) kismutato = 40 ;
  if (( hour ==  9 ) || ( hour == 21 )) kismutato = 45 ;
  if (( hour == 10 ) || ( hour == 22 )) kismutato = 50 ;
  if (( hour == 11 ) || ( hour == 23 )) kismutato = 55 ;

  // Másrészt a számlapon két egész óra között 5 osztás van, és az a percmutató állásától függ, hogy ezek közül melyikben kell lennie.
  if (( minute >=  0 ) && ( minute < 12 )) km_korr = 0 ;  
  if (( minute >= 12 ) && ( minute < 24 )) km_korr = 1 ;  
  if (( minute >= 24 ) && ( minute < 36 )) km_korr = 2 ;    
  if (( minute >= 36 ) && ( minute < 48 )) km_korr = 3 ;
  if (( minute >= 48 ) && ( minute < 60 )) km_korr = 4 ;  
  if ( minute == 60 ) km_korr = 0 ;

  // Sajnos még mindig maradt speciális eset, mégpedig amikor legfelül a 12 -esen ( vagy 0 -n ) áll a kismutató. Ekkor ha megnövelnénk a korrekcióval, akkor 
  // 61, 62, 63, 64 adódna, ami nyilván megengedhetetlen az 1-60 tömbelemeknél. Ezt tahát külön kell lekezelnünk:
  if (( kismutato == 60 ) && ( km_korr != 0 )) kismutato =0;

  if ( MODE == 1 )     // Ha ÓRA változtatási üzemmódban vagyunk villogtatjuk a kismutatót
     {
      tomb_1[kismutato+km_korr][0] =  random ( 0, 200 );
      tomb_1[kismutato+km_korr][1] =  random ( 0, 200 );
      tomb_1[kismutato+km_korr][2] =  random ( 0, 200 );        
      pixels_1.setPixelColor(kismutato+km_korr-1, pixels_1.Color( random ( 0, 200 ), random ( 0, 200 ),random ( 0, 200 ))); 
      pixels_1.show();                                                                    
     }
  else
     {
      tomb_1[kismutato+km_korr][0] =  OraMutato_R;
      tomb_1[kismutato+km_korr][1] =  OraMutato_G;
      tomb_1[kismutato+km_korr][2] =  OraMutato_B;    
     }
     
  // Hasonlóan a percmutatónál levő megoldáshoz, a másodperc beállításánál az RTC 0-59 miatt speciális eset a 0 másodperc, 
  // mert annak a tömbünk 60 -as eleme felel meg. Ezt itt kezeljük le:
  if ( second == 0 ) second = 60;
  
  if ( MODE == 3 )        // Ha MÁSODPERC változtatási üzemmódban vagyunk villogtatjuk a másodperc mutatót
     {  
      tomb_1[second][0] =  random ( 0, 200 );
      tomb_1[second][1] =  random ( 0, 200 );
      tomb_1[second][2] =  random ( 0, 200 );  
      tomb_2[second][0] =  random ( 0, 200 );
      tomb_2[second][1] =  random ( 0, 200 );  
      tomb_2[second][2] =  random ( 0, 200 );  
      pixels_1.setPixelColor(second-1, pixels_1.Color( random ( 0, 200 ), random ( 0, 200 ),random ( 0, 200 ))); 
      pixels_1.show();        
      pixels_2.setPixelColor(second-1, pixels_2.Color( random ( 0, 200 ), random ( 0, 200 ),random ( 0, 200 ))); 
      pixels_2.show();        
      
     }
  else
     {   
      tomb_1[second][0] =  MasPercMutato_R;
      tomb_1[second][1] =  MasPercMutato_G;  
      tomb_1[second][2] =  MasPercMutato_B;  
      tomb_2[second][0] =  MasPercMutato_R;
      tomb_2[second][1] =  MasPercMutato_G;  
      tomb_2[second][2] =  MasPercMutato_B;
     }

 if ( second != second_buffer )          // Végül pedig kigyújtjuk a fentiekben beállított LED -eket, de csak akkor, ha lépett a másodperc
   { 
  for(int i=1; i<=60; i++)
     {
       pixels_1.setPixelColor(i-1, pixels_1.Color(tomb_1[i][0], tomb_1[i][1], tomb_1[i][2]));  // Azért kell -1, mert a LED -ek megjelenítési sorszáma 0 -val indul,
       pixels_1.show();                                                                        // a tömbjeink pedig 1 -el.
       pixels_2.setPixelColor(i-1, pixels_2.Color(tomb_2[i][0], tomb_2[i][1], tomb_2[i][2]));
       pixels_2.show();
     }  
   }
     
second_buffer = second;

}


/* ======================================================================================================================================================================= */
/* ====================================================== FÜGGVÉNYEK: ==================================================================================================== */
void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte
dayOfMonth, byte month, byte year)
{
  // sets time and date data to DS3231
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set next input to start at the seconds register
  Wire.write(decToBcd(second)); // set seconds
  Wire.write(decToBcd(minute)); // set minutes
  Wire.write(decToBcd(hour)); // set hours
  Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
  Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
  Wire.write(decToBcd(month)); // set month
  Wire.write(decToBcd(year)); // set year (0 to 99)
  Wire.endTransmission();
}

/* -------------------------------------------------------------------------------- */
void readDS3231time(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set DS3231 register pointer to 00h
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
  // request seven bytes of data from DS3231 starting from register 00h
  *second = bcdToDec(Wire.read() & 0x7f);
  *minute = bcdToDec(Wire.read());
  *hour = bcdToDec(Wire.read() & 0x3f);
  *dayOfWeek = bcdToDec(Wire.read());
  *dayOfMonth = bcdToDec(Wire.read());
  *month = bcdToDec(Wire.read());
  *year = bcdToDec(Wire.read());
}

/* -------------------------------------------------------------------------------- */
void displayTime()  // Csak tesztelésnél használjuk
{
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  // retrieve data from DS3231
  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
  // send it to the serial monitor
  Serial.print(hour, DEC);
  // convert the byte variable to a decimal number when displayed
  Serial.print(":");
  if (minute<10)
  {
    Serial.print("0");
  }
  Serial.print(minute, DEC);
  Serial.print(":");
  if (second<10)
  {
    Serial.print("0");
  }
  Serial.print(second, DEC);
  Serial.print(" ");
  Serial.print(dayOfMonth, DEC);
  Serial.print("/");
  Serial.print(month, DEC);
  Serial.print("/");
  Serial.print(year, DEC);
  Serial.print(" Day of week: ");
  switch(dayOfWeek){
  case 1:
    Serial.println("Sunday");
    break;
  case 2:
    Serial.println("Monday");
    break;
  case 3:
    Serial.println("Tuesday");
    break;
  case 4:
    Serial.println("Wednesday");
    break;
  case 5:
    Serial.println("Thursday");
    break;
  case 6:
    Serial.println("Friday");
    break;
  case 7:
    Serial.println("Saturday");
    break;
  }
}

/* -------------------------------------------------------------------------------- */
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return( (val/10*16) + (val%10) );
}

/* -------------------------------------------------------------------------------- */
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return( (val/16*10) + (val%16) );
}

/* -------------------------------------------------------------------------------- */
void bt_kezeles()
{

/* Ezt a függvényt kell gyúrnunk hogy az Android -os eszközzel megfelelő legyen a kommunikáció 
 *  
 * Először is meg kell állapítanunk, hogy az Android -os eszköz mit akar.  
 *  - KÜLDHETI A PONTOS IDŐT                           ( a =  97 )
 *  - LEKÉRDEZHETI A MI IDŐNKET                        ( b =  98 )
 *  
 *  Ezeket úgy jelzi, hogy elküldi a kívánt dolog kódját
*/

  MitAkar = 0;                          // Tiszta lappal indulunk

  if  ( BlueTooth.available() )         // Ha van mit olvasni
      { 
        MitAkar = BlueTooth.read();     // Beolvasunk egy karaktert 
        Serial.print ( " - Eszköztől kapott byte : " ); 
        Serial.print(MitAkar);
        switch(MitAkar)
        {
          case 97:  Serial.println ( " ( küldené a pontos időt )" );
                    break;
          case 98:  Serial.println ( " ( kéri a mi időnket )" );
                    break;          
          default:
          Serial.println(" ( ? )");
        }
      }

// Elvileg a MitAkar csak a fenti négy érték valamelyikét veheti fel mert azt kaphatjuk csak az Android eszköztől. 
// Ezeket az eseteket a következő négy if feltétellel kezeljük le:

if ( MitAkar == 97 )   // 'a', AZ ANDROID -OS ESZKÖZ KÜLDI A PONTOS IDŐT
{  
/*  A BT modul felől soros porton érkezik a pontos idő a következő formátumban:

    1. byte:  " + " a byte sorozat elejét jelző karakter
    2. byte:  ÉV első byte
    3. byte:  ÉV második byte. Hogy megkapjuk a helyes évet ezt 256 -tal kell szorozni, majd hozzáadni a 1. byte -ban kapott értéket
    4. byte:  HÓNAP
    5. byte:  NAP
    6. byte:  HÉT NAPJA
    7. byte:  ÓRA  
    8. byte:  PERC
    9. byte:  MÁSODPERC
   10. byte:  " - " a byte sorozat végét jelző karakter
    
*/  
      ByteSorszam = 1;                         // Megadja, hogy hányadik byte beolvasásánál tartunk a BT modul felől

      if  ( BlueTooth.available() )         // Ha van mit olvasni
          { 
            byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
            // Serial.write(byteRead);     
            if ( byteRead == 43 ) ByteSorszam = 2 ;   // Továbblépünk a ciklusból ha '+' jött
           }
     if ( ByteSorszam == 2 )                      // Ha az előbb beolvastuk az induló "+" karaktert, akkor beolvassuk a többi byte -ot is
     {
         while ( ByteSorszam == 2 )               // Ebben a ciklusban beolvassuk az ÉV érték első byte -ját
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     // Serial.write(byteRead);      // Vissza echo -zzuk
                     EV1 = byteRead;
                     ByteSorszam = 3 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 3 )               // Ebben a ciklusban beolvassuk az ÉV érték második byte -ját
               {                                  // A helyes évet úgy kapjuk, ha ezt 256 -tal kell szorozzuk, majd hozzáadjuk az EV1 -ben kapott értéket
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     // Serial.write(byteRead);      // Vissza echo -zzuk
                     EV2 = byteRead;
                     ByteSorszam = 4 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 4 )               // Ebben a ciklusban beolvassuk az HONAP értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     // Serial.write(byteRead);      // Vissza echo -zzuk
                     HON = byteRead;
                     ByteSorszam = 5 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 5 )               // Ebben a ciklusban beolvassuk a NAP értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     // Serial.write(byteRead);      // Vissza echo -zzuk
                     NAP = byteRead;
                     ByteSorszam = 6 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 6 )               // Ebben a ciklusban beolvassuk a "HÉT NAPJA" értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     // Serial.write(byteRead);      // Vissza echo -zzuk
                     NA_ = byteRead;
                     ByteSorszam = 7 ;            // Továbblépünk a ciklusból
                   }
               }               
         while ( ByteSorszam == 7 )               // Ebben a ciklusban beolvassuk az ÓRA értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     // Serial.write(byteRead);      // Vissza echo -zzuk
                     ORA = byteRead;
                     ByteSorszam = 8 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 8 )               // Ebben a ciklusban beolvassuk az PERC értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     // Serial.write(byteRead);      // Vissza echo -zzuk
                     PER = byteRead;
                     ByteSorszam = 9 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 9 )               // Ebben a ciklusban beolvassuk a MÁSODPERC értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     // Serial.write(byteRead);      // Vissza echo -zzuk
                     MAS = byteRead;
                     ByteSorszam = 10;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 10 )               // Ebben a ciklusban beolvassuk a záró " - " karaktert ( de nem csinálunk vele semmit )
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     // Serial.write(byteRead);      // Vissza echo -zzuk
                     ByteSorszam = 1 ;            // Továbblépünk a ciklusból
                     setDS3231time(MAS, PER, ORA, NA_, NAP, HON, EV2*256+EV1-2000)  ;    // Végül a beolvasott időt elküldjük az RTC -nek.
                   }       // Formátum: void setDS3231time(second, minute, hour, dayOfWeek, dayOfMonth, month, year)
               }           // A DS3231 az évnek csak a második 2 számjegyét várja, ezért kell kivonni a kiszámolt év értékéből ( EV2*256+EV1 ) 2000 -et.

       Serial.print ( " - Eszköztől kapott idő: " );       // Ezek csak akkor íródnak ki ha az Android eszköz küldi nekünk a 
       Serial.print ( " - EV_1=" ); Serial.print ( EV1 );                      // pontos időt, ritkán történik ezért benne hagytam a kiírásokat ... teszteléshez jól jöhet
       Serial.print ( " - EV_2=" ); Serial.print ( EV2 );       
       Serial.print ( " - HON=" );  Serial.print ( HON );
       Serial.print ( " - NAP=" );  Serial.print ( NAP );
       Serial.print ( " - NA_=" );  Serial.print ( NA_ );
       Serial.print ( " - ORA=" );  Serial.print ( ORA );
       Serial.print ( " - PER=" );  Serial.print ( PER );
       Serial.print ( " - MAS=" );  Serial.println ( MAS );
       // Serial.print ( " ----------- EV_szamolt=" ); Serial.println ( EV2*256+EV1 );

     }
} // Eddig tart az első funkció, azaz amikor az Android -os eszköz leküldi nekünk a pontos időt

if ( MitAkar == 98 )  // 'b', AZ ANDROID -OS ESZKÖZ LEKÉRDEZI A PONTOS IDŐT
{  
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);

  if ( hour > 12 ) hour = hour-12;    // Az óra kijelzése 0-12 -es, így célszerű ha az androidos eszközön sem 0-24 -ben van mutatva
  BlueTooth.write("20");
  BlueTooth.print(year, DEC);
  BlueTooth.write(".");  
  BlueTooth.print(month, DEC);
  BlueTooth.write(".");  
  BlueTooth.print(dayOfMonth, DEC);
  BlueTooth.write("  ");  
  BlueTooth.print(hour, DEC);
  BlueTooth.write(":");  
  BlueTooth.print(minute, DEC);
  BlueTooth.write(":");  
  BlueTooth.print(second, DEC);    // Eddig tartott a dátum és idő elküldése

  // A következő kiírást is bennehagytam, teszteléshez jó jöhet:
  Serial.print(" - Eszköznek elküldve : ");  
  Serial.print(hour, DEC);
  // convert the byte variable to a decimal number when displayed
  Serial.print(":");
  if (minute<10) Serial.print("0");
  Serial.print(minute, DEC);
  Serial.print(":");
  if (second<10) Serial.print("0");
  Serial.print(second, DEC);
  Serial.print(" ");
  Serial.print(dayOfMonth, DEC);
  Serial.print("/");
  Serial.print(month, DEC);
  Serial.print("/");
  Serial.print(year, DEC);
  Serial.println(" Day of week: none ");   // Ezt nem íratjuk ki mert külön számoló rész tartozna hozzá

  
} // Eddig tart a második funkció, azaz amikor az Android -os eszköz lekérdezi tőlünk a pontos időt és a memória adatokat

               
               // Csak a fenti két eset lehetséges, ha a négy kódon kívül valami más érkezik akkor nem reagálunk

}

/* -------------------------------------------------------------------------------- */

/* -------------------------------------------------------------------------------- */
void gomb_kezeles()
{
int  A_gomb = digitalRead( 5 );   // Ennek itt kell lennie, mert a függvény elején kell mindig beolvasnunk
int  B_gomb = digitalRead( 6 );   // Ennek itt kell lennie, mert a függvény elején kell mindig beolvasnunk
int  C_gomb = digitalRead( 7 );   // Ennek itt kell lennie, mert a függvény elején kell mindig beolvasnunk
  
  
  if (A_gomb != A_lastButtonState)    // Ha változott a gomb állapota ciklus legutóbbi futása óta, (újra)indítjuk a számlálót
       {
         A_lastDebounceTime = millis();
       }

  if ((millis() - A_lastDebounceTime) > A_debounceDelay)  // Ha a számláló elérte a beállított küszöböt
      {
     
        if (A_gomb != A_buttonState)    // Csak akkor foglalkozunk bármivel, ha a gomb állapota változott.
                                        // Tulajdonképpen a "buttonState" is "LastbuttonState", mert
                                        // ebben tároljuk a gomb legutóbbi állapotát
        {
          A_buttonState = A_gomb;       // itt
          if (A_buttonState == HIGH) 
          {
             if ( A_gomb_fazis < 3 )
               {
                 A_gomb_fazis++;
               }
             else
               {
                 A_gomb_fazis = 0;
               }
             MODE = A_gomb_fazis;  
          }
        }
     }
  A_lastButtonState = A_gomb;
    
if ( A_gomb_fazis != 0 )    // Ha az A gombbal ki lett választva valmelyik üzemmód, akkor figyeljük a B és C gombot
   {
     if (B_gomb != B_lastButtonState)                             //  Először nézzük a B gombot
       {
         B_lastDebounceTime = millis();
       }
     if ((millis() - B_lastDebounceTime) > B_debounceDelay)
      {
        if (B_gomb != B_buttonState)    
        {
          B_buttonState = B_gomb;
          if (B_buttonState == HIGH) 
            {
             B_gomb_fazis = 1;
            }
          else 
            {
             B_gomb_fazis = 0;
            }
        }
     }
     B_lastButtonState = B_gomb;
  
     if (C_gomb != C_lastButtonState)                             //  Utána nézzük a C gombot    
       {
         C_lastDebounceTime = millis();
       }
     if ((millis() - C_lastDebounceTime) > C_debounceDelay)
      {
        if (C_gomb != C_buttonState)    
        {
          C_buttonState = C_gomb;
          if (C_buttonState == HIGH) 
            {
             C_gomb_fazis = 1;
            }
          else
            {
             C_gomb_fazis = 0;
            }
        }
     }
     C_lastButtonState = C_gomb;
  
   }                                                                 // Eddig tartott maga a gomb kezelés
 
                                                    // Itt kezdődik a beolvasott gomb állapotok alapján az RTC beállítás
if ( (B_gomb_fazis == 1) or (C_gomb_fazis==1) )     // Csak akkor csinálunk bármit is, ha fel vagy le kell állítanunk az órát/percet/másodpercet
 {
  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);   // Kiolvassuk a pillanatnyi időt   
  if ( (A_gomb_fazis == 1) and (B_gomb_fazis==1) ) 
     {
       if ( hour < 23 ) hour++; else hour=0;         // A nem valós értékeket ki kell kiszöbölni, mert az RTC megesz mindent, csak persze hülyén működik
       B_gomb_fazis=0;
     }
  if ( (A_gomb_fazis == 1) and (C_gomb_fazis==1) ) 
     {
       if ( hour > 0 ) hour--; else hour=23;       
       C_gomb_fazis=0;
     }
  if ( (A_gomb_fazis == 2) and (B_gomb_fazis==1) ) 
     {
       if ( minute < 59 ) minute++; else minute=0;       
       B_gomb_fazis=0;
     }
  if ( (A_gomb_fazis == 2) and (C_gomb_fazis==1) ) 
     {
       if ( minute > 0 ) minute--; else minute=59;       
       C_gomb_fazis=0;
     }
  if ( (A_gomb_fazis == 3) and (B_gomb_fazis==1) ) 
     {
       if ( second < 59 ) second++; else second=0;       
       B_gomb_fazis=0;
     }
  if ( (A_gomb_fazis == 3) and (C_gomb_fazis==1) ) 
     {
       if ( second > 0 ) second--; else second=59;       
       C_gomb_fazis=0;
     }     
  setDS3231time(second, minute, hour, dayOfWeek, dayOfMonth, month, year)  ;                 // Visszaírjuk a megváltoztatott időt
 }  
 
}


A másik rész az Android -os alkalmazás, amit ezúttal is az App Inventor segítségével állítottam össze. Ezúttal nem kommentezem, de ha valami nem érthető ismétcsak javaslom átnézésre a Nyomógomb naplózó leírását. Vagy keress meg, szívesen elmagyarázom. A Designer -ben létrehozott fő képernyő így néz ki:

Ora_projekt

Működés közben az Android -os eszközön látható képernyőket mutatják az alábbi képek. A felső sorban van az Android -os eszköz saját ideje, alatta az óra ideje, tehát amit az óra bluetooth -on átküldött az Android -os eszköznek. Nyilván ha még nem jött létre a BT kapcsolat akkor itt csak kérdőjeleket láthatunk. Ha nincs bekapcsolva az Android -os eszközön a bluetooth akkor a felső gomb pirossal hívja fel erre a figyelmet. Az alkalmazásból nem lehet bekapcsolni ( még, de dolgozom a problémán :-). Ha engedélyezett a bluetooth ( felső gomb zöld ) akkor össze kell kapcsolódni az eszközzel. Ezt a második ( egyelőre piros ) gombra bökve lehet megtenni. A felkínált eszközök közül a HC-06 -ot kell választani. Ha minden jól megy ez is bezöldül, és fent a második sorban megjelenik az órától kapott idő ( ha nem látnánk magán az órán :-). Ha úgy ítéljük hogy ez az idő nem pontos, a harmadik gomb megnyomásával kezdeményezhetjük az Android -os eszköz ( nyilvánvalóan pontos ) idejének átküldését az órára. Az átküldés megtörténtét egy üzenet jelzi, ill. a felső második sorban ellenőrizhető.

Végezetül következzen maga a "program", vagyishát a program blokkok. Nem bonyolult ezért nem kommenteztem. A Nyomógomb naplózóéhoz hasonlóan itt is egy időzítő adja a lényegi részt ami 1 másodpercenként fut le. Az eszköz ennyi időközönként küld egy 98 ( azaz "b" ) karaktert az órának, ami abból tudja, hogy vissza kell küldenie az idejét. Ezáltal tud frissülni a második felső sorban látható óra idő ( a szemléletesség kedvéért 0-12 formában kiírva ). Ha a felhasználó az óra pontosítását szeretné, azazt megnyomja a 3. gombot, azt egy 97 kód ( azaz "a" karakter ) küldésével jelzi az Android eszköz. Ennek hatására az óra "vételi módba" áll, az Android küldi az időt, az óra pedig megkapva azt beírja az RTC -be és onnantól azt használja. Tehát a program:


Ora_projekt

Végezetül még egy kép, a - majdnem - kész óráról. Ha lesz végleges kép azt is felteszem, addig türelmet kérek :-)


Ora_projekt

Remélem hasznos és érdekes volt.


 JozsBiker, 2021