Witam Was w moim kolejnym wpisie na majsterkowie.
Tematem dzisiejszego wpisu będzie… zegar. Tak, wiem – mam coś z zegarami, delikatnie licząc w ciągu ostatniego roku zrobiłem chyba ze 4. Ale nic na to nie poradzę :P
Tak jak w przypadku mojego poprzedniego zegara, ten również będzie wyświetlał aktualną godzinę w bardzo nietypowy sposób. Zegar ten będzie pokazywał zdanie (w języku angielskim), które odczytane powie nam, która jest godzina. 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.
Zegar oparty będzie oczywiście o mój ulubiony mikrokontroler ATmega328, z wgranym bootloaderem Arduino i zaprogramowany 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 trzy linie wejściowe i 10 linii wyjściowych.
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.
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 (rysunek za wikipedią):
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: http://pl.wikipedia.org/wiki/Uk%C5%82ad_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:
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 „0”, a gdy wtyczka będzie podłączona – „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:
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.
Przy użyciu bardzo ciekawego pluginu do programu EAGLE, pozwalającego na eksport płytki do programu Sketchup stworzyłem model 3D płytki, który pozwala z jednej strony sprawdzić, czy różne elementy płytki nie będą ze sobą kolidowały, a z drugiej – stworzyć np. taką animację procesu jej powstawania. Jeżeli będzie zainteresowanie tym tematem to mogę stworzyć jakiś artykuł opisujący z czym i jak to się je.
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ą.
Po pierwsze – obsługa magistrali I2C w celu uzyskiwania informacji od układu RTC.
Po drugie – obsługa magistrali SPI w celu podawania danych na rejestry przesuwające.
Po trzecie – obsługa fotorezystora oraz podawanie sygnału PWM sterującego jasnością wyświetlacza.
I po czwarte – obsługa pilota na podczerwień.
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 atmedze sprzętowo. W Arduino istnieją dwie klasy, które służą do obsługi tych magistral. W przypadku magistrali I2C klasa ta to „Wire” (http://arduino.cc/en/reference/wire), a w przypadku SPI – klasa „SPI” (http://arduino.cc/en/Reference/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ń, oraz ze względu na 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:
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 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 |
#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; IRRecieved=false; return -1; } //jeżeli adres zanegowany i niezanegowany się różnią to odebraliśmy coś nie tak, odrzucamy if(addr_normal!=IR_DEVICE_ADDR){ IRbitcount=0; IRRecieved=false; return -1; } //jeżeli to co odebraliśmy nie pochodzi z naszego pilota, odrzucamy 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; IRRecieved=false; return -1; } //jeżeli dane zanegowane i niezanegowane się różnią, to coś nie tak, odrzucamy 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()) { 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); //konsola do debudowania delay(2000); pinMode(IRdetect,INPUT); if(digitalRead(IRdetect)==HIGH) {//jeżeli odbiornik jest podłączony IRsetup(); //inicjalizacja pilota } RTCsetup(); //inicjalizacja RTC PWMsetup(); PWMset(); } 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 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(); } |
Zamieszczam także filmik prezentujący pracę zegara. Wgrany jest tutaj program, w którym jedna minuta została przyspieszona do 1 sekundy, co pozwala zaobserwować dokładnie w jaki sposób wyświetlany jest czas na zegarze.
https://www.youtube.com/watch?v=UmP2rT1qSX8
Pomyslalem wow ale fajne ciekawe czy trudne po zobaczeniu płytki pomyslałem.. pozna godzina (01:46) ide spac. 5 :)
Gdyby nie bawić się w dodatki typu pilot, fotorezystor itp to projekt trudny nie jest. “Trudnym” elementem jest połączenie części elektronicznej z fizyczną.
Świetne !
Klasa.
Zegary słowne (dlaczego słowowy a nie słowny? Która forma jest poprawna bo “słowny” brzmi lepiej) posiadają z reguły też dodatkowe litery między wyrazami, aby tarcza była wypełniona znakami bez odstępów. Dlaczego zdecydowałeś się bez nich? Tylko kwestia gustu czy np. przełożyło by się to na znaczące koszty cięcia folii?
Nie podoba mi się ten “blob” kleju (pewnie termoglut?) przy kątowniku, ale reszta to pierwsza liga.
Zrobiłem wersję bez nich tylko ze względów estetycznych – moim zdaniem w takiej wersji wygląda to estetyczniej. Kosz wycięcia folii byłby bardzo podobny.
Termoglut, termoglut – problem z tymi kątownikami jest taki, że są one niewielkie i ciężko je estetycznie skleić, choć faktycznie mogłem tam popracować nożem po klejeniu żeby to odciąć. Na szczęście ten element nie dość, że jest z tyłu zegara to jeszcze u góry, także go nie widać jak zegar wisi.
A zegar nazywam słowowym, ponieważ “słowny” jest człowiek, który dotrzymuje słowa. Ale oczywiście mogę się mylić :)
Prof. Miodek by się tutaj przydał :D
może i coś w tym jest? :P
Prof. Miodek by się tutaj przydał :D
Według mnie powinno być słowny bo są np. “gry słowne” czy też “zabawy słowne”. Czyli gry ze słowami to gry słowne a zegar ze słowami to zegar słowny. Taka ot logika :)
Też bym obstawiał za “słowny”, ale że od zawsze byłem ścisłowcem, to się nie wtrącam ;)
Po za tym “słowowy” nie występuje w języku polskim. Zajrzyjcie do słownika http://sjp.pl/s%B3owowy
a to już powinno nam wyjaśnić wszystko ;)
dobra, wygrałeś – niech będzie że słowny :P
hehe po prostu jakoś kuło mnie to w oczy :D chociaż z polskiego średnio mi szło :D
Zegar z wyrazami :)
Trochę nie rozumiem jak zrobiłeś tarczę. Ta folia czarna jest naklejona na przezroczysty kawałek plexi? Wtedy te słowa na tej folii są powycinane? Dzięki z odp.
Już nieważne ;) Doczytałem i zrozumiałem. Sorry za zamieszanie
Widziałem chyba taki sam zegarek na instructables :D Fajny ale mogłeś go spolszczyć ;) Ooo dzięki Tobie wykorzystam swoje stare odbiorniki IR od kart TV :D Dzięki :)
niestety spolszczenie byłoby bardzo trudne, jeżeli nie nie możliwe, ze względu na specyfikę naszego języka – jest wpół do trzeciEJ, a już za piętnaście trzeciA…
Rozumiem ale tutaj ktoś sobie poradził :D http://www.elektroda.pl/rtvforum/topic1669546.html
Super! Od dawna przymierzałem się, żeby sobie taki zrobić, a teraz mam to opisane (dużo lepiej niż sam planowałem :P).
Btw. Ile kosztowały Cię wszystkie materiały do tego zegara?
ciężko by policzyć, ponieważ zegar powstawał sporo czasu i się rozłożyło. pleksę miałem akurat z odzysku, także głównymi kosztami było wycięcie folii (tak jak pisałem 20zł), zakup lasek kleju (poszły chyba ze trzy) plus część elektroniczna (około 25-35zł).
Jaki byłby koszt wykonania całej płytki ?
Pierwsze zdjęcie ukradzione z jakiejś strony, wstyd.
http://goo.gl/0pvVDl
i
https://www.facebook.com/dougswordclock
więc autorem tego zdjęcia na pewno nie jesteś.
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. …
“czytanie ze zrozumieniem” – polecam, bardzo się przydaje w życiu
Znalazłem bardzo podobny zegar
http://www.instructables.com/id/The-Wordclock-Grew-Up/
tak jak wyraźnie napisałem – pomysł na zegar nie jest mój, pomysł zaczerpnąłem z internetu. natomiast zegar wykonałem w całości samodzielnie.
Nie mówię, że to nie twój zegar. Ale tak na marginesie dobra robota.
całkowicie na marginesie – wczoraj przy ~45 głosach wpis miał średnią 4,8. dzisiaj przy 58 ma 4,1. oznacza to ni mniej ni więcej tylko 13 głosów o średniej oscylującej w okolicach 1,5 pkt, przy poprzednich 45 głosach ze średnią prawie 5,0. ktoś ma ze mną jakiś problem?
jestem całkowicie otwarty na krytykę, ale tutaj (w komentarzach) brak takiej…
@Łukasz Więcek – może należałoby rozważyć ograniczenie głosowania tylko dla zalogowanych użytkowników?
Wg statystyk zalogowanych jest tylko 0,6% wszystkich czytelników, więc mogłoby być z tym ciężko. Gdyby to było chociaż z 5%…
Obecnie można oddać tylko jeden głos z danego IP. Dodatkowo przy głosowaniu zapisywane jest ciacho, które również blokuje oddawanie kolejnych głosów na ten sam post. Rozwiązanie idealne nie jest, ale chyba na razie nic lepszego nie wymyślimy…
W dobie, gdy wszyscy mają zmienne IP a ciasteczka czyści się dwoma kliknięciami myszki niestety nie jest to skuteczne zabezpieczenie…
istnieje również TOR dzięki któremu można głosować ile i jak tylko się komuś chce :|
Jak ograniczę głosowanie tylko dla zalogowanych, to próg wyjścia postu z Poczekalni będzie trzeba obniżyć do dwóch głosów ;)
dobre było by zabezpieczenie które by prosiło o adres mailowy a następnie wysyłało maila z linkiem aktywacyjnym ale takiego gotowego rozwiązania chyba nie ma
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?
Witam, choć temat już trochę stary, może mi ktoś wyjaśni co oznaczają linie kodu w funkcji IRDecode() a mianowicie:
data_inv|=(B1<<(7-i));
Ni jak nie mogę załapać, co to jest to B1…
Był bym bardzo wdzięczny za podpowiedź.
bardzo fajny projekt i świetnie wygląda
mam pytanie czy mógłbym zlecic wykonanie prostego ukladu właczania napisu A lub B (takich napisów jak w twoim zegarze) za pomocą przycisku zdalnego?
pozdrawiam
siwy2411 na początek, szcun za ogrom pracy. Jeżeli mogę coś poradzić, to może nie warto się fiksować na pewne rozwiązania. Mam na myśli atmegi. Sporo wysiłku kosztowało Cię ustawianie czasu. Obsługa pilotem, lepsza niż przyciski ale jak padnie pilot? A jak mam inny pilot, to będę musiał rozkminiać od początku jego kodowanie. Poza tym zmiana czasu, letni zimowy? (póki co jeszcze). Z powodzeniem używam nodeMcu, programuje się prawie identycznie jak atmegi, a pamięć większa, szybsze taktowanie, czas możesz pobrać na bieżąco z internetu (nie potrzebujesz żadnych modułów zegara), parametry zmienić za pomocą przeglądarki www.