Witam Was ponownie!
Zachęcony pozytywnymi komentarzami oraz ocenami pierwszej części artykułu dotyczącego budowy „analogowego” zegarka (do postawienia np. na biurku w pracy) niezwłocznie przystąpiłem do realizacji i opisywania części drugiej. A w niej – podłączenie naszych woltomierzy do mikrokontrolera, podłączenie zewnętrznego układu zegara RTC – DS1307 i próba (miejmy nadzieję że skuteczna) złożenia tego wszystkiego do kupy. Zaczynamy!
Po poprzedniej części jesteśmy w posiadaniu dwóch woltomierzy, przeskalowanych na napięcia przyjazne dla naszego mikrokontrolera, z zamontowanymi diodami ułatwiającymi odczyt godziny po ciemku i z wymienionymi podziałkami.
Jako, że niestety nie jestem szczęśliwym posiadaczem płytki Arduino, muszę wykonać parę kroków zanim będę mógł zacząć programować. Pierwsze co należy zrobić to prawidłowe podłączenie mikrokontrolera ATmega 328P-PU do zasilania. Jak zapewne każdy z Was wie (a jeżeli nie wie to zaraz się dowie) od prawidłowego podłączenia mikrokontrolera do zasilania bardzo często zależy jego prawidłowa praca. Artykułów w tzw. „Internetach” traktujących o prawidłowym sposobie podłączania zasilania do mikrokontrolerów jest bez liku, także nie rozwodząc się przedstawiam schemat wg którego ja będę zasilał swój układ.
Jak widać na wejściu układu znajduje się stabilizator napięcia 7805. Cały układ będzie zasilany ze starej ładowarki od telefonu pewnej znanej fińskiej firmy, który to zasilacz ma na wyjściu napięcie około 8V, które stabilizujemy do 5V przy pomocy (niespodzianka ;) ) stabilizatora napięcia. Umiejscowienie i wielkości pozostałych elementów znajdujących się w układzie (kondensatorów, dławika oraz rezystora) wynika z danych wyczytanych z datasheet’a mikrokontrolera.
Mając już taki układ na płytce stykowej możemy podłączyć do niego woltomierze. Z tyłu obudowy każdego woltomierza znajdują się dwa zaciski – „+” oraz „-”. Jak łatwo się domyśleć – zacisk „-” podłączamy do masy układu, natomiast zacisk „+” podłączamy do pinu pozwalającego na podanie sygnału PWM – w przypadku mikrokontrolera atmega328 w obudowie do montażu przewlekanego są to piny 5,11,12,15,16,17 – ja, mając na uwadze dalsze projektowanie płytki drukowanej do całego układu podłączyłem woltomierz wskazujący godzinę pod pin 5 (który odpowiada pinowi 3 Arduino), natomiast ten od minut – pod pin 12 (pin 6 Arduino), zgodnie z poniższym schematem:
Uwaga – jeżeli nie jesteście pewni, który pin ma jaką nazwę/funkcjonalność polecam zerknąć albo do datasheet’a mikrokontrolera albo na tą grafikę.
Dlaczego piszę o tym, który jest to pin w Arduino? Ponieważ grzechem byłoby nie skorzystać z dobrodziejstw i ułatwień, które niesie ze sobą zastosowanie oprogramowania Arduino IDE, które bez przeszkód możemy zmusić do używania programatorów dostępnych na rynku jak USBasp i programowania zwykłych mikrokontrolerów. (po więcej informacji jak to zrobić odsyłam do tego artykułu i tego wątku na forum)
A więc mamy już gotowe środowisko programistyczne oraz prawidłowo zasilony układ – czas coś napisać!
Pierwszy program, który wgramy na mikrokontroler będzie miał za zadanie sprawdzić, czy nasz woltomierz jest dobrze przeskalowany.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const int HOUR_PIN=3; //numer pinu Arduino pod który podpięty jest woltomierz pokazujący godziny const int MINUTE_PIN=6; //numer pinu Arduino pod który podpięty jest woltomierz pokazujący minuty void setup() { pinMode(HOUR_PIN,OUTPUT); pinMode(MINUTE_PIN,OUTPUT); //ustawienie obu pinów jako wyjścia } void loop() { analogWrite(HOUR_PIN,255); analogWrite(MINUTE_PIN,255); //podanie sygnału PWM o wypełnieniu 100% na oba piny delay(5000); //odczekaj 5 sekund analogWrite(HOUR_PIN,0); analogWrite(MINUTE_PIN,0); //podanie sygnału PWM o wypełnieniu 0% na oba piny delay(5000); //odczekaj 5 sekund } |
Program nie jest skomplikowany, ale wyjaśnijmy szybko jego działanie. Najpierw definiujemy używane przez nas piny jako wyjścia układu. Następnie w nieskończonej pętli ustawiamy sygnał PWM o wypełnieniu 100% (wartość wypełnienia sygnału PWM mieści się w przedziale 0-255, gdzie 0 oznacza cały czas stan niski, a 255 – stan wysoki), a po odczekaniu 5 sekund zmieniamy jego wypełnienie na 0%. Jeżeli przeskalowaliśmy nasz woltomierz prawidłowo, jego igła powinna przez 5 sekund być zatrzymana dokładnie na wartości maksymalnej, by następnie przez 5 sekund opaść dokładnie na 0. Jeżeli igła nie osiąga dokładnie którejś ze skrajnych wartości – musimy wprowadzić poprawki:
– jeżeli igła nie zatrzymuje się idealnie na 0 (czyli zatrzymuje się powyżej lub poniżej zera) – musimy delikatnie obrócić cały mechanizm odpowiadający za wychylanie igły – w moim przypadku najbezpieczniej to zrobić używając jako „dźwigni” odchodzącej pionowo w dół blaszki, którą zaznaczyłem na poniższym zdjęciu – wystarczy delikatnie obrócić ją zgodnie, bądź przeciwnie do ruchu wskazówek zegara żeby przestawić położenie zerowe igły.
– jeżeli igła nie zatrzymuje się idealnie na wartości maksymalnej – musimy zmienić nastawę naszego potencjometru wieloobrotowego tak, aby igła osiągnęła dokładnie zadaną wartość.
Możliwie dokładna kalibracja pozwoli nam uzyskać dość dokładne odczyty godziny z gotowego zegara, także warto się do tego przyłożyć!
Następnie zajmiemy się przekazaniem do naszego układu informacji o tym, która jest godzina. W tym celu użyjemy bardzo popularnego układu DS1307. Jako, że od urodzenia byłem leniem – zamiast kupić układ i wszystkie inne potrzebne do niego rzeczy osobno i zmontować je samemu, wydałem parę złotych więcej i kupiłem gotową, niewielką płytkę zawierającą w sobie układ zegara RTC (akronim angielskiego określenia Real Time Clock – zegar czasu rzeczywistego), kwarc zegarkowy, kondensatory filtrujące zasilanie układu oraz złącze baterii zegarkowej, służącej do podtrzymania liczenia czasu przez układ w przypadku zaniku podstawowego napięcia zasilania. Co ważne, na płytce znajdują się także dwa rezystory o wartości 4,7kΩ każdy, podciągające linie sygnałowe do napięcia zasilania – utrzymują one stan wysoki na liniach w czasie, gdy nie ma na nich transmisji danych.
Płytkę podłączamy do układu zestawem 6 goldpinów – 5V, GND, SQW, SCL, SDA, VBAT. Bardziej spostrzegawczy z Was po zauważeniu pinów opisanych jako SCL oraz SDA wiedzą już jak nasze układy będą się ze sobą komunikować – chodzi oczywiście o interfejs I²C (ze względów licencyjnych występujący czasami pod alternatywną nazwą TWI – Two Wire Interface). W swoim projekcie, ze względu na jego prostotę będę używał tylko 4 z 6 pinów – piny SQW oraz VBAT nie będą mi potrzebne (choć można by np. pin VBAT podpiąć pod przetwornik ADC atmegi i kontrolować, czy aby bateria nam się nie rozładowała). Wpinamy płytkę, podłączamy ją kabelkami zgodnie z poniższym schematem:
W celu obsłużenia układu DS1307 pobieramy jedną z dziesiątek bibliotek, które mają za zadanie ułatwiać obsługę tego układu. Ja zdecydowałem się na bibliotekę o wiele mówiącej nazwie DS1307RTC, którą możecie pobrać stąd. W celu prawidłowego działania biblioteka ta potrzebuje także obecności biblioteki Time, jeżeli jeszcze jej nie macie możecie się w nią zaopatrzyć tutaj. Pobrane biblioteki wrzucamy do folderu Arduino/libraries (wrzucamy tutaj całe foldery z bibliotekami!).
Aby móc cokolwiek odczytać z naszego zegara, musimy mu najpierw powiedzieć która jest godzina – ja w pierwszym przykładzie wpisałem do układu godzinę 12:00, którą na późniejszym etapie poprawimy tak, aby była ona zgodna z prawdą. Wgranie godziny do układu zrealizujemy krótkim programem napisanym na podstawie jednego z przykładów dostarczonych z biblioteką:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <DS1307RTC.h> #include <Time.h> #include <Wire.h> void setup() { tmElements_t tm; tm.Hour=12; tm.Minute=0; if(RTC.write(tm)) { //tutaj możemy np. zapalić jakąś diodę //sygnalizującą poprawność wgrania godziny } else { //tutaj możemy np. zapalić jakąś diodę //sygnalizującą błąd wgrania godziny } } void loop() { } |
Zanim zabierzemy się za odczytywanie godziny musimy napisać funkcję, która zamieni nam dwie wartości (np. 12 oraz 30, oznaczające godzinę 12:30) na sygnały PWM podane na piny, pod które podpięte są nasze woltomierze. Będziemy w tym celu wykorzystywali głównie wbudowaną w Arduino funkcję map. Funkcja ta pozwala łatwo przekształcić wartość będącą w jednym przedziale na odpowiednią wartość w innym przedziale (przykładowo wartość 10 z przedziału 0-100 przekształcona na wartość z przedziału 0-10 będzie wynosiła 1). Napisanie takiej funkcji dla minut nie stanowi większego problemu – będzie ona miała postać:
1 |
analogWrite(MIN_PIN,map(tm.Minute,0,59,0,255)); |
Natomiast analogiczna funkcja dla godzin wymaga już pewnego zastanowienia się. Można oczywiście zrobić to po prostu w ten sposób:
1 2 3 |
int godzina=tm.Hour; godzina=godzina%12; analogWrite(HOUR_PIN,map(tm.Hour,0,11,0,255)); |
Jednak mi taki sposób wyświetlania godziny się nie podoba. Dlaczego? Ponieważ dysponujemy analogowym woltomierzem, a ten kod wyświetla godzinę w sposób dość „cyfrowy”. O co mi chodzi? Chodzi mi o to, że część „godzinowa” godziny 12:00 zostanie wyświetlona tak samo jak godziny 12:59. Po krótkim zastanowieniu się postanowiłem stworzyć wartość do mapowania, uwzględniającą zarówno godzinę jak i informację o minutach. Najpierw przedstawię kod, a następnie go omówię:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void show(int h,int m) {//funkcja wyświetlająca godzinę h=h%12; h=h*10; h=h+(map(m,0,60,0,10)); h-=5; if(h<0) { h+=5; h=abs(h); h+=115; } analogWrite(HOUR_PIN,map(h,0,120,0,255)); analogWrite(MIN_PIN,map(m,0,60,0,255)); } |
Funkcja ta dostaje dwie wartości całkowite – godzinę oraz minuty i wyświetla je na pinach HOUR_PIN oraz MIN_PIN. Wartość dla pinu MIN_PIN jest tworzona w sposób chyba dość zrozumiały, natomiast wyjaśnienia wymaga wartość dla godzin. W wyniku wykonania kodu z linijek 3-5 w zmiennej h znajduje się wartość z przedziału 0-119, gdzie ostatnia cyfra powstaje na podstawie minut (funkcją „map(m,0,60,0,10)” – mała uwaga – minuty są tak naprawdę z przedziału 0-59, dlatego nigdy ta funkcja nie zwróci 10), a to co jest przed nią powstaje w wyniku pomnożenia godziny przez 10. Wyświetlając taką wartość mamy już wyraźne rozróżnienie między 12:00 a 12:59. Wiersze 6-12 są konsekwencją projektu tarczy woltomierza z godzinami – godziny 12:00-12:29 wyświetlane są na skrajnej prawej części podziałki, natomiast 12:30-12:59 na skrajnej lewej.
Jeżeli mamy już jak wyświetlić godzinę, możemy zabrać się za jej odczytywanie. Drugi przykład załączony do biblioteki DS1307RTC pokazuje nam, jak to zrobić. Po chwili otrzymujemy następujący kod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#include <Wire.h> #include <DS1307RTC.h> #include <Time.h> //biblioteki do obsługi zegara RTC const int HOUR_PIN=3; //pin, pod który podłączony jest woltomierz pokazujący godzinę const int MIN_PIN=6; //pin, pod który podłączony jest woltomierz pokazujący minuty void setup() { pinMode(HOUR_PIN,OUTPUT); pinMode(MIN_PIN,OUTPUT); } void loop() { update(); //wyświetl aktualną godzinę } void update(void) {//funkcja pobiera godzinę z RTC i przekazuje ją do funkcji show do wyświetlenia tmElements_t tm; if (RTC.read(tm)) { show(tm.Hour,tm.Minute); } } void show(int h,int m) {//funkcja wyświetlająca godzinę h=h%12; h=h*10; h=h+(map(m,0,60,0,10)); h-=5; if(h<0) { h+=5; h=abs(h); h+=115; } analogWrite(HOUR_PIN,map(h,0,120,0,255)); analogWrite(MIN_PIN,map(m,0,60,0,255)); } |
Wgrywamy kod na układ i cieszymy się pierwszym namacalnym efektem na to, że ta kupa części i pomysłów walająca się po biurku od jakiegoś czasu ma szanse stać się faktycznie zegarem!
Do zrobienia w tej części zostały dwie rzeczy – oprogramowanie diod doświetlających oraz podpięcie i oprogramowanie przycisków służących do zmieniana wyświetlanej godziny. Zaczniemy od łatwiejszej sprawy, czyli od diod oświetlających.
Ze względu na niski prąd na wyjściach mikrokontrolera (ok. 20mA) diody będziemy załączali poprzez tranzystor PNP. Dla tych, którzy dopiero zaczynają przygodę z elektroniką krótkie wyjaśnienie – w najprostszym ujęciu, tranzystor działa niczym przycisk, jednak jest to przycisk, który załączamy/rozłączamy podając na niego prąd, a nie wciskając go. Użyjemy więc tranzystora jako klucza „dopuszczającego” napięcie do diod zwartych na stałe do masy, a żeby wykorzystać możliwości układu bazę tranzystora podłączymy pod wejście pozwalające na podanie sygnału PWM, tak aby móc dowolnie ściemniać i rozjaśniać diody. Wiemy już, w jaki sposób podłączymy diody do układu, pozostaje jeszcze kwestia wartości które będziemy na nie podawać. Można by oczywiście uzależnić jasność świecenia diod wyłącznie od tego, która jest godzina, ale jest to mało eleganckie rozwiązanie. Lepszym pomysłem jest wykorzystanie fotorezystora. Jest to rezystor, którego rezystancja (oporność) zmienia się wraz z ilością światła na niego padającą. Podłączyć go musimy oczywiście pod konwerter analogowo-cyfrowy (ponieważ zwykły pin nie byłby w stanie rozróżnić takich zmian). Poniższy schemat prezentuje sposób, w jaki połączymy tranzystor z mikrokontrolerem (nie zapominamy o rezystorze na bazie tranzystora!) i dalej tranzystor z diodami znajdującymi się już w woltomierzach, a także fotorezystor pod przetwornik ADC (dodatkowy rezystor tworzy z fotorezystorem dzielnik napięcia, o którym więcej możecie przeczytać tutaj).
Kod, który będzie odpowiadał za zapalanie diody w zależności od oświetlenia będzie wyglądał następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const int PHOTORES_PIN=A0; //pin, pod który podłączony jest fotorezystor const int LIGHT_PIN=5; //pin, pod który podłączona jest baza tranzystora PNP const int DAYLIGHT_VALUE=850; //do jakiego odczytu traktujemy jako "dzień" - czyli nie zapalamy diod const int NIGHTLIGHT_VALUE=600;//jaką wartość minimalną osiąga odczyt - wartość w całkowicie ciemnym pomieszczeniu void setlight(void) {//funkcja ustalająca podświetlenie zegarów int light=analogRead(PHOTORES_PIN); if(light<DAYLIGHT_VALUE) {//jeżeli odczytana wartość jest poniżej progu zapalającego diody - wylicz wartość PWM i podaj ją na diody analogWrite(LIGHT_PIN,map(light,NIGHTLIGHT_VALUE,DAYLIGHT_VALUE,0,200)); } else {//jeżeli wartość jest powyżej progu - wyłącz diody digitalWrite(LIGHT_PIN,HIGH); } } |
Użyłem tutaj dwóch stałych – DAYLIGHT_VALUE oraz NIGHTLIGHT_VALUE – dzięki nim diody nie świecą w ogóle przy dużym oświetleniu fotorezystora (czyli przy odczycie przekraczającym wartość DAYLIGHT_VALUE) i rozświetlają się z maksymalną jasnością dla wartości odczytu większej niż 0 (ponieważ przy dzielniku napięcia nie osiągniemy wartości zerowej odczytu). Dlaczego w funkcji map mapuję wartość do zakresu 0-200 a nie 0-255? Ponieważ chciałem, aby (jeżeli jest już to potrzebne) diody zapalały się z jakąś konkretną jasnością, a nie ledwo się żarzyły. Tutaj drobna uwaga – ze względu na fakt, że użyliśmy tranzystora PNP pamiętamy o tym, że „zapalamy” diody stanem niskim (czyli diody zapalone na stałe to PWM o wypełnieniu = 0), a gasimy stanem wysokim (PWM o wypełnieniu = 255).
Podłączając do układu przyciski, które posłużą do przestawiania godziny wykorzystam możliwości zastosowanego przeze mnie mikrokontrolera. Mam na myśli dostępne w atmega328 PCI, czyli Pin Change Interrupt – przerwania wyzwalane zmianą stanu pinu. Standardowo, w atmega328 oraz płytkach Arduino zawierających ten mikrokontroler mamy do dyspozycji dwa piny wywołujące przerwanie zewnętrzne – jest to pin 2 i 3 Arduino (widać to po opisach INT0 oraz INT1). Oprócz tego każdy pin układu (oczywiście oprócz pinów związanych z zasilaniem) opisany jest jako PCINTXX, gdzie XX to kolejna liczba oznaczające odwołanie do tego konkretnego pinu. Jak łatwo się domyśleć pozwala to na “podpięcie” przerwania pod dowolny z pinów mikrokontrolera. Niestety, mimo wszelkich swoich dobrodziejstw oprogramowanie Arduino nie pozwala domyślnie na korzystanie z użytecznej możliwości podpięcia zewnętrznego przerwania pod dowolny pin mikrokontrolera. Na szczęście jest na świecie wiele osób, które poświęcają swój wolny czas na tworzenie tego typu dodatków, z czego my zaraz skorzystamy. Podobnie jak w przypadku biblioteki dla układu DS1307, tak w przypadku PCINT również mamy do wyboru wiele różnych bibliotek oferujących zbliżoną funkcjonalność. Ja zdecydowałem się na użycie biblioteki PinChangeInt, którą pobrałem z tej strony. Zdecydowałem się akurat na tą bibliotekę, ponieważ nie dość, że implementuje ona potrzebne nam przerwania to dodaje ona do nich bardzo użyteczną funkcjonalność. Normalnie, przerwania typu Pin Change Interrupt wywoływane są, jak sama nazwa wskazuje, poprzez zmianę stanu na pinie, niezależnie od tego czy jest to zmiana typu falling, czy rising (zbocze opadające sygnału, czy zbocze narastające) i to do programisty należy rozróżnienie który przypadek miał właśnie miejsce, jednak dzięki użyciu tej biblioteki możemy jedną prostą komendą doprecyzować, czy chcemy aby przerwanie wywoływało się na zbocze opadające, zbocze narastające, czy też chcemy wykrywać każdą zmianę sygnału na pinie. Po przeanalizowaniu paru przykładów i wczytaniu się w dokumentację znajdującą się na stronie biblioteki doszedłem do następującego kodu obsługującego 5 przycisków (jeden odpowiedzialny za włączenie trybu pozwalającego na przestawienie godziny oraz dwie pary – jedna zwiększająca/zmniejszająca godzinę, a druga zwiększająca/zmniejszająca minuty):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#define NO_PORTB_PINCHANGES // to indicate that port b will not be used for pin change interrupts #define NO_PORTC_PINCHANGES // to indicate that port c will not be used for pin change interrupts #include <PinChangeInt.h> //biblioteka przerwań pin change const int BUT1_PIN=0; //przycisk +1H const int BUT2_PIN=1; //przycisk -1H const int BUT3_PIN=2; //przycisk +1M const int BUT4_PIN=4; //przycisk -1M const int BUT5_PIN=7; //przycisk włączenia trybu zmiany godziny void but1() { //kod dla 1 przycisku } void but2() { //kod dla 2 przycisku } void but3() { //kod dla 3 przycisku } void but4() { //kod dla 4 przycisku } void but5() { //kod dla 5 przycisku } void setup() { pinMode(BUT1_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT1_PIN, &but1, FALLING); pinMode(BUT2_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT2_PIN, &but2, FALLING); pinMode(BUT3_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT3_PIN, &but3, FALLING); pinMode(BUT4_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT4_PIN, &but4, FALLING); pinMode(BUT5_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT5_PIN, &but5, FALLING); } void loop() { } |
Pozostaje jedynie kwestia podłączenia przycisków do mikrokontrolera – jak widzicie w powyższym kodzie przyciski podpiąłem pod piny 0,1,2,4,7 i zadeklarowałem je jako wejścia z włączonym rezystorem podciągającym (pullup). Zaoszczędzi mi to konieczności umieszczania rezystorów podciągających osobno na płytce, wobec czego nasz układ z dołożonymi przyciskami będzie wyglądał tak:
Składając do kupy wszystkie powyższe elementy kodu otrzymałem następujący kod obsługujący cały zegar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
#define NO_PORTB_PINCHANGES // to indicate that port b will not be used for pin change interrupts #define NO_PORTC_PINCHANGES // to indicate that port c will not be used for pin change interrupts #include <PinChangeInt.h> //biblioteka przerwań pin change #include <Wire.h> #include <DS1307RTC.h> #include <Time.h> //biblioteki do obsługi zegara RTC const int PHOTORES_PIN=A0; //pin, pod który podłączony jest fotorezystor const int LIGHT_PIN=5; //pin, pod który podłączona jest baza tranzystora PNP const int HOUR_PIN=3; //pin, pod który podłączony jest woltomierz pokazujący godzinę const int MIN_PIN=6; //pin, pod który podłączony jest woltomierz pokazujący minuty const int BUT1_PIN=0; //przycisk +1H const int BUT2_PIN=1; //przycisk -1H const int BUT3_PIN=2; //przycisk +1M const int BUT4_PIN=4; //przycisk -1M const int BUT5_PIN=7; //przycisk włączenia trybu zmiany godziny const int DAYLIGHT_VALUE=850; //do jakiego odczytu traktujemy jako "dzień" - czyli nie zapalamy diod const int NIGHTLIGHT_VALUE=600;//jaką wartość minimalną osiąga odczyt - wartość w całkowicie ciemnym pomieszczeniu bool change_time=false; //flaga trybu zmiany czasu bool updatetoRTC=false; //flaga przesłania nowej godziny do zegara bool getfromRTC=false; //flaga odczytania godziny z zegara do zmiennej int h_temp=0; //tymczasowa godzina - do trybu zmiany czasu int m_temp=0; //tymczasowa minuta - do trybu zmiany czasu void wait(int miliseconds) {//funkcja do debounce, nie korzystająca z przerwań for(int i=0;i<miliseconds;i++) { delayMicroseconds(1000); } } void enabletimechange() {//obsługa przycisku 5 wait(50);//debounce if(change_time) {//jeżeli jesteśmy w trybie zmiany godziny change_time=false; //wyłącz tryb zmiany godziny digitalWrite(LIGHT_PIN,HIGH); updatetoRTC=true; //ustaw flagę sygnalizującą wgranie nowej godziny do układu } else {//jeżeli nie jesteśmy w trybie zmiany godziny change_time=true; //włącz tryb zmiany godziny getfromRTC=true; //ustaw flagę sygnalizującą konieczność odczytania godziny z układu do zmiennej lokalnej } } void plushour() {//obsługa przycisku 1 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu h_temp++; //zmień godzinę if(h_temp>23) {//zapewnia zapętlenie, 22<->23<->0<->1 h_temp=0; } } } void minushour() {//obsługa przycisku 2 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu h_temp--; //zmień godzinę if(h_temp<0) {//zapewnia zapętlenie, 22<->23<->0<->1 h_temp=23; } } } void plusminute() {//obsługa przycisku 3 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu m_temp++; //zmień godzinę if(m_temp>59) {//zapewnia zapętlenie, 58<->59<->0<->1 m_temp=0; } } } void minusminute() {//obsługa przycisku 4 wait(50);//debounce if(change_time) {//jesteśmy w trybie zmiany czasu m_temp--; //zmień godzinę if(m_temp<0) {//zapewnia zapętlenie, 58<->59<->0<->1 m_temp=59; } } } void setup() {//deklaracje pinów, podpięcia przerwań pod piny przycisków pinMode(PHOTORES_PIN,INPUT); pinMode(LIGHT_PIN,OUTPUT); pinMode(HOUR_PIN,OUTPUT); pinMode(MIN_PIN,OUTPUT); pinMode(BUT1_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT1_PIN, &plushour, FALLING); pinMode(BUT2_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT2_PIN, &minushour, FALLING); pinMode(BUT3_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT3_PIN, &plusminute, FALLING); pinMode(BUT4_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT4_PIN, &minusminute, FALLING); pinMode(BUT5_PIN,INPUT_PULLUP); PCintPort::attachInterrupt(BUT5_PIN, &enabletimechange, FALLING); digitalWrite(LIGHT_PIN,HIGH); } void loop() { if(getfromRTC) {//jeżeli należy pobrać godzinę z układu do zmiennej lokalnej getfromRTC=false; //reset flagi tmElements_t tm; RTC.read(tm); //pobranie godziny h_temp=tm.Hour; m_temp=tm.Minute; //podstawienie jej do zmiennych lokalnych } if(updatetoRTC) {//jeżeli należy wysłać nową godzinę do układu updatetoRTC=false; //reset flagi tmElements_t tm; tm.Hour=h_temp; tm.Minute=m_temp; //przygotowanie danych do wysłania RTC.write(tm); //wysłanie danych do układu } if(change_time) {//jesteśmy w trybie zmiany czasu digitalWrite(LIGHT_PIN,LOW); //zapal diodę - sygnalizacja trybu zmiany godziny show(h_temp,m_temp); //wyświetl tymczasową godzinę } else {//jeżeli jesteśmy w trybie wyświetlania czasu update(); //wyświetl aktualną godzinę setlight(); //dostosuj podświetlenie int count=15; for(int i=0;i<count;i++) {//odczekaj 15 sekund, ale przerwij czekanie jeżeli w trakcie tych 15 sekund układ wejdzie w tryb ziany godziny if(change_time) { break; } else { delay(1000); } } } } void update(void) {//funkcja pobiera godzinę z RTC i przekazuje ją do funkcji show do wyświetlenia tmElements_t tm; if (RTC.read(tm)) { show(tm.Hour,tm.Minute); } } void show(int h,int m) {//funkcja wyświetlająca godzinę h=h%12; h=h*10; h=h+(map(m,0,60,0,10)); h-=5; if(h<0) { h+=5; h=abs(h); h+=115; } analogWrite(HOUR_PIN,map(h,0,120,0,255)); analogWrite(MIN_PIN,map(m,0,60,0,255)); } void setlight(void) {//funkcja ustalająca podświetlenie zegarów int light=analogRead(PHOTORES_PIN); if(light<DAYLIGHT_VALUE) {//jeżeli odczytana wartość jest poniżej progu zapalającego diody - wylicz wartość PWM i podaj ją na diody analogWrite(LIGHT_PIN,map(light,NIGHTLIGHT_VALUE,DAYLIGHT_VALUE,0,200)); } else {//jeżeli wartość jest powyżej progu - wyłącz diody digitalWrite(LIGHT_PIN,HIGH); } } |
Cały kod jest bogato opatrzony w komentarze, a jego większość jest pobrana z wcześniej już wyjaśnionych kawałków kodu, także myślę, że nie powinno być trudności ze zrozumieniem sposobu jego działania. Jeżeli jednak jakieś by się pojawiły – proszę o info w komentarzach, postaram się rozwiać wszelkie wątpliwości.
Ostatecznie, nauczony doświadczeniem postanowiłem do układu dołożyć także złącze pozwalające na zaprogramowanie układu bez wyjmowania go z płytki, co zmodyfikowało schemat do ostatecznej wersji:
No, to by było na tyle w tej części. Trochę tego wyszło, mam nadzieję że nie przejechaliście całości tylko po to żeby dać jakąś słabą ocenę, tylko znaleźliście chwilę czasu na przeczytanie ;) W następnej części zaprojektujemy płytkę drukowaną, wytrawimy ją, zaprojektujemy obudowę, oddamy ją do wycięcia laserowego i złożymy wszystko razem, otrzymując (miejmy nadzieję ;) ) estetyczny i fajny zegarek na biurko. Z racji tego, że wolę pisać o rzeczach które już zrobiłem następna część może powstać z lekkim opóźnieniem – nie wiem ile czasu zajmie firmie wycięcie dla mnie obudowy. Cześć!
Końcowe schematy troszeczkę się poplątały… Jak robię coś na swój użytek to przy takich kombinacjach często nazywam połączenie i daję tylko oznaczenie obok. Mimo iż trzeba skakać wzrokiem trochę, to i tak schemat staję się bardziej czytelny. Spróbuj tak przy następnym.
Wg mnie 5/5 :) Ogólnie cały pomysł bardzo ciekawy :).
zebrałem się w sobie dzięki temu komentarzowi i narysowałem schematy od nowa, na porządnie. teraz są (mam nadzieję :P) czytelne :)
Moja uwaga: jeśli robisz schematy tak, jak tutaj, nie zmieniaj położenia konkretnego fragmentu schematu (ostatnie schematy świetnie, wcześniejsze są “przesuwane”).
Szkoda, że nie ma nagrania pokazującego pracę zegara. W takiej formie, jak jest na chwilę obecną.
Poza tym naprawdę świetny artykuł. Czekam na pozostałą część.
Specjalnie we wcześniejszych schematach poprzesuwałem elementy, bo tak to 80% grafiki byłoby puste :P
Nie spodziewałem się tak szybko drugiej odsłony :)
Super projekt – chyba spróbuję sam coś takiego popełnić…
Brawo! Twój post jest najlepiej ocenionym postem na Majsterkowie!!! ;)
Ale niestety już nie jest :/
Bardzo pomocne i ciekawe, ale aby wszystko zrozumieć muszę się jeszcze raz zagłębić w lekturze, dzięki za wpis! pozdrawiam :)
Nie ma to jak dobry pomysł. Tyle rzeczy często mamy w piwnicy, z których coś można zrobić ciekawego. Potrzeba tylko czasu, chęci no i pomysłu. Z drugiej strony dzisiaj sklepów z elementami elektronicznymi jest dużo mniej niż kiedyś i ta dziedzina grzebania w elektronice odchodzi do lamusa.
Sklepów tego typu faktycznie jest mniej, ale moim zdaniem nie tyle wynika to ze spadku popularności “grzebania w elektronice” co z popularyzacji internetu i zakupów przez internet – na Allegro, nawet z uwzględnieniem przesyłki duperele elektroniczne kupi się taniej, a sklepy stacjonarne nie dość że mają wyższe koszty to jeszcze żeby się utrzymać w biznesie muszą mieć dużo różnorodnego towaru na stanie, nierzadko zalegającego dość długo aż ktoś przyjdzie i będzie szukał AKURAT tego elementu.
To mówiłem ja – Jarząbek Wacław, trener drugiej klasy ;)
Myślę, że oprócz tego dochodzi “modułowość” urządzeń. Prościej jest wymienić cały panel na przykład w telewizorze niż wziąć lutownicę i wymieniać jakieś drobne uszkodzone podzespoły
To na pewno w jakimś stopniu też, choć świadomość wśród ludzi procesu planned obsolescence (przejawiająca się chociażby przez artykuł tu, na majsterkowie) i nasza polska mentalność nakazująca nam wycisnąć ze wszystkiego ostatnie soki i reanimować sprzęt, mimo że nierzadko taniej byłoby wymienić go na nowy nie pozwoli całkiem umrzeć dziedzinie “grzebania w elektronice”, moim zdaniem oczywiście ;)
Składam powoli całość, a że mój pierwszy DS1307 wariuje to użyłem DS3231 do którego mam większe przekonanie. Myślałem, że będę musiał zmienić bibliotekę ale na szczęście wygląda, że ten RTC działa równie dobrze na tej samej. Nie wiem czy oczekiwać jakichś problemów?
Zauważyłem też drobny wpływ załączenia podświetlenia. Ja z braku tranzystora PNP zrobiłem na NPN. Po włączeniu podświetlenia wskazówka lekko opada. Nie wiem czy to wpływ przewodów zasilania diod na cewkę miernika i czy ewentualnie można to wyeliminować?
Nie widzę na schemacie zewnętrznego rezonatora. Czyżby układ chodził na wewnętrznym? Jeśli tak to jak ustawić ArduinoIDE, żeby zaprogramować uC na wewnętrznym oscylatorze? Udało mi się znaleźć bootloader dla ATmega88P ale dla 328P nie znalazłem i bez zewnętrznego kwarcu 16MHz nie mogę go zaprogramować.
Jaki dokładnie tranzystor PNP został zastosowany?