Zegar cyfrowy LED z bajerami – Część IV: Tablica czcionek i pomiar czasu.

Zegar cyfrowy LED z bajerami – Część IV: Tablica czcionek i pomiar czasu.

No to zaczynamy kolejny odcinek cyklu “nie dla lalusiów”, gdzie spróbujemy dodać kilka nowych funkcji do naszej biblioteki, i pobawić się już trochę w wyświetlanie i pomiar czasu. Przypominam tym, którzy przez ostatnie dwa odcinki zapomnieli, że końcowym efektem ma być zegar :) Wybaczcie, że tak się szeroko rozpisałem o programowaniu, a mało o majsterkowaniu, ale pomyślałem sobie, że wielu z Was – podobnie jak ja – zagląda tutaj też po to, żeby się czegoś nauczyć, a nie tylko pooglądać zdjęcia fajnych gadżetów. A w programowaniu na razie czuję się mocniej, niż w majsterkowaniu, więc dzielę się tym co potrafię najlepiej. Może komuś przyda się to w jego projekcie i to nie tylko zegara ;)

Śmiało, podsumujmy co już potrafi nasza biblioteka:

  • Wyświetla obraz automatycznie z zadanego obszaru pamięci (elegancko w przerwaniu i baaaardzo szybko, bo przez SPI)
  • mamy funkcję która ładuje nam obrazek z tablicy do pamięci obrazu
  • mamy funkcję która robi nam negatyw obrazka

To trochę mało, przecież chcielibyśmy wyświetlać 4 różne cyfry:

rysunek matrycy

No i oczywiście kropeczka nad matrycą ma mrugać co sekunde ;)

Cała reprezentacja zawartości naszej matrycy w pamięci to 5 bajtów na lewy(L) wyświetlacz oraz 5 bajtów na prawy(P), zapisane kolejno:

1L , 1P , 2L ,2P , 3L , 3P , 4L , 4P , 5L ,5P

  I jak być może pamiętacie, w ten właśnie sposób do tej pory zapisywaliśmy wyświetlane obrazki zajmujące całą matrycę:

Do wyświetlania 4 różnych cyfr raczej słabo się to sprawdzi. Na szybko policzyłem mi, że gdyby zapisać w ten sposób wszystkie możliwe pozycje dla zegara 24 godzinnego mielibyśmy 24*60 = 1440 obrazków do zaprojektowania :)  W dodatku zajęlibyśmy 14KB pamięci. Na taką rozrzutność to sobie nie możemy pozwolić :) Musimy to zrobić inaczej.

Zdefiniujemy sobie  każdą cyfrę z osobna i będziemy ją wrzucać do odpowiednich kolumn matrycy, przecież już wiemy jak przesuwać bity w lewo i w prawo. Mam także śliczny odręczny projekt cyferek mojego autorstwa:

szkic czcionek

No to kodujemy je pojedynczo jako 3 najmłodsze bity i pakujemy w strukturę tablicy:

No i mamy tablicę znaków. A dla niezorientowanych w zawiłych meandrach zapisu binarnego, wrzucam obrazek poglądowy z konwersji cyfry 9. Ponoć czasem jeden obraz więcej wart więcej niż tysiąc słów:

konwersja dla opornych

Przypomniał mi się taki “cyfrowy” suchar z brodą:

Ile jest typów ludzi?
10 – tacy którzy rozumieją zapis binarny i tacy którzy nie.

Jeżeli nie zrozumiałeś żartu, to należysz do tych drugich :D

Cyfry w tablicy znaki[] celowo ułożyłem zaczynając od zera, bo przecież wszyscy pamiętamy, że tablice w języku C indeksowane są od zera, prawda? I korzystając właśnie z tej cechy, indeks pozycji naszej tablicy jednocześnie wskazuje nam jaka cyfra jest zawarta w tej pozycji. No i cudownie. Ale zanim przejdziemy dalej, znów pojawi się chwila nudnej teorii, która jednak może się Wam kiedyś przydać.

Mnie już dopadł problem o którym za chwilę opowiem, a i to w zasadzie w pierwszym poważnym projekcie na Arduino. A chodzi dokładnie o pamięć RAM, której niestety mamy w procesorze atmega328P tylko 2KB. A w atmega168, na którym właśnie robiłem ów pechowy projekt, mamy jej tylko 1KB. Ale może od początku.

Zrobiłem prototyp kontrolera MIDI na Arduno Uno(Atmega328P). Wszystko pięknie działało na płytce stykowej, dumny jak paw postanowiłem z prototypu przejść w fazę upojnego lutowania i przeniesienia oprogramowania właśnie na kość atmega168. Po kompilacji kod wynikowy miał niecałe 10Kb, więc nie spodziewałem się żadnych problemów, w końcu 168 ma jak sama nazwa wskazuje 16KB pamięci programu. A tu lipa panie. Soft kontrolera po przeniesieniu na inny procek działa kilka, czasem kilkanaście sekund i przestaje reagować. Uparcie. I to w przeróżnych odstępach czasu. Bez sensu. Pierwsze podejrzenie zimny lut. Przemierzyłem wszystko wszerz i wzdłuż, wymieniałem kości na inne przy czym, w przypływach rozpaczy coraz częściej czyniłem swój język prawdziwie plugawym, złorzecząc haniebnie na swe próżne starania. Ciągle nic. Ciągle ruletka. Raz działa nawet prawie minute. A potem znów ledwo rusza.

W końcu zacząłem wywalać fragmenty kodu i też bez zmian. Wczytałem szkic w wersji z przedwczoraj – HULA! Porównuję co się zmieniło, a tutaj niespodzianka. Praktycznie nic. Zmieniłem tylko w definicjach na początku kodu ilość programowalnych presetów z 2 na 4. Wynikiem tego,  i tak już dosyć spora struktura danych przechowująca ustawienia dla presetów całego urządzenia, puchła na tyle, że gdy tylko procesor próbował zrobić cokolwiek mądrzejszego, to 1KB ramu w Atmega168 przestawało wystarczać. I pac. Totalna zwiecha. Myślałem, że będę musiał zrezygnować z presetów całkiem, ale poszukałem po internetach i okazało się, że jednak jest jeszcze nadzieja.

Domyślnie wartości wszystkich zmiennych globalnych, które zadeklarujemy w naszym projekcie, przechowywane są właśnie w pamięci RAM, a ta ze względu na rozmiar kiepsko nadaje się do przechowywania większych struktur. A szczególnie jeżeli nasz procesor ma jeszcze dodatkowo robić cokolwiek innego. Ale mamy jeszcze pamięć flash przeznaczoną na kod programu, której zwykle nie wykorzystujemy do końca. A przecież jest jej kilka a nawet kilkanaście razy więcej niż RAMu!

Aby przechowywać zawartość zmiennej w pamięci programu, musimy zadeklarować ją, używając dyrektywy PROGMEM.

lub alternatywnie:

Oczywiście jak zwykle, są pewne obostrzenia i limity.

  • w pamięci PROGMEM możemy przechowywać tylko określone typy danych o specjalnych nazwach (o tym za moment)
  • nie możemy w pamięci PROGMEM przechowywać liczb zmiennoprzecinkowych (pośrednio się da, ale to osobna bajka)
  • do dostępu do danych w pamięci PROGMEM musimy używać specjalnych funkcji (mamy je w bibliotece  “avr/pgmspace.h”)

Niewielki to koszt, za dodatkowe kilka KB pamięci na nasze drogocenne dane. A te “specjalne” typy danych, to w zasadzie nic innego jak 3 podstawowe typy liczb całkowitych znane nam już z C, tylko trochę inaczej się nazywają:

Spokojnie wystarcza. A o tych specjalnych funkcjach dostępu można więcej poczytać na stronie: http://www.arduino.cc/en/Reference/PROGMEM.

Nic specjalnie trudnego ;) zaraz zobaczycie na przykładzie. Postaram się tak skomentować kod, aby wszystko było jasne ;).

To wrzućmy nasze struktury danych do pamięci programu, dodajmy kilka pożytecznych funkcji, które zbliżą nasz projekt o krok od bycia pełnoprawnym zegarem i zobaczmy co z tego wyjdzie:

I co nam z tego wyszło? (ktoś narzekał na brak filmów)

Jak widać w kodzie pozwoliłem sobie umieścić czcionki i napis test w pamięci PROGMEM. A sam odczyt niewiele się zmienił. Używamy niemal tej samej funkcji memcpy, tyle, że z przyrostkiem  _P, oznaczającym że odczytujemy dane z pamięci programu. Jedyne co się zmienia, to to, że drugi parametr, czyli źródło musi wskazywać na zmienną w pamięci PROGMEM. Reszta bez zmian.

Jeszcze jedna rzecz wymaga chyba wyjaśnienia początkującym:

taka konstrukcja nazywana jest “operatorem warunkowym“:

zmienna = warunek_0 ? wyrażenie_1 : wyrażenie_2;

i działa tak:

Jeżeli spełniony jest warunek_0, zmiennej przypisywana jest wartość wyrażenia_1, jeżeli nie jest – wyrażenie 2.

Czyli zapis:

jest równoważny zapisowi:

ale zajmuje dużo mniej miejsca.

W naszym przypadku wyrażenie jest spełnione gdy reszta z dzielenia  (% – operator modulo) przez dwa wynosi 1, czyli wartość w nawiasie jest prawdą dla liczb nieparzystych, a fałszem dla parzystych. Trochę to może dziwne na pierwszy rzut oka, ale działa to dlatego, że w języku C wartość zero interpretowana jest jako fałsz, każda inna jako prawda. Czyli zapis (pozycja % 2) jest tożsamy z  ((pozycja % 2)== 0). Jak jeszcze coś jest niejasne – śmiało pytajcie w komentarzach.

Teraz jeszcze chwila zabawy z wyświetlaniem cyferek. Podmieniamy funkcję loop() i zrobimy pseudo-stoperek liczący do 100 sekund z dokładnością setnej części sekundy:

No i efekt:

Teoretycznie dwie lewe cyfry wyświetlacza powinny wskazywać ilość upływających sekund. Ale niestety – tylko teoretycznie. Porównując pomiar z “lepszym” stoperem, już w przeciągu dwóch minut nasz stoper spóźnia się około sekundy. Specjalnie nagrałem całe 100 sekund, żeby niedowiarki sami mogli sobie zmierzyć. A dlaczego tak?

Pomiędzy wywołaniami funkcji delay(10) wykonujemy także operacje wyświetlania znaków. Dodatkowo każdy delay(10) mający podobno trwać 10 milisekund jest przynajmniej 5 razy przerywany naszym ukrytym przerwaniem zegarowym (przypominam – 500Hz), w którym odświeżana jest zawartość matrycy. Niby przez SPI – więc migiem, ale też nie dzieje się to zupełnie natychmiast. 

Zatem – już wiadomo: używając funkcji delay() dokładnego zegara nie zrobimy. Pewnie gdyby zmniejszyć częstotliwość odświeżania i aktualizować cyfry tylko raz na sekundę, precyzja by nieco wzrosła, ale góra do poziomu wystarczającego na zgrubny pomiar w krótkich odstępach czasu. Minutnik do jajek dałoby radę :) Ale przecież nie o to nam chodzi.

Inną (kiepską) alternatywą może być użycie funkcji millis(), która zwraca nam (nawet dosyć dokładnie) ilość milisekund która upłynęła od startu naszego programu. Spróbujmy:

Nie będę już wrzucał kolejnego, prawie identycznego filmiku, ale jest teraz duuuużo dokładniej. Prawie idealnie. Więc dlaczego napisałem, że jest to kiepska metoda do zrobienia zegara? Po pierwsze dlatego, że wartość zwracana przez funkcję millis() po jakimś czasie (kilku dniach, się nam przepełni) i wyzeruje. Można też próbować nie zliczać milisekund tylko same sekundy i na upartego wydłużylibyśmy ten czas do paru tygodni. Niby tak. Ale co jak odłączymy zasilanie? Albo wciśniemy reset?

Za każdym razem musielibyśmy ustawiać na nowo bieżący czas. Kompletnie bez sensu.

I tutaj czas na peryferia ;) Podłączymy do Arduino moduł zegara czasu rzeczywistego oparty na układzie DS1307. Sam moduł jest bardzo prosty w konstrukcji, i co ważne – posiada opcje potrzymania zegara baterią. Tutaj poradnik jak poskładać takowy samemu: http://tronixstuff.wordpress.com/2010/05/28/lets-make-an-arduino-real-time-clock-shield/

Pewnie sprawi to niejednemu frajde. Ja jednak – jako urodzony – leń poszedłem na łatwiznę i kupiłem za mniej niż 10 pln – gotowy moduł. W dodatku od razu z baterią.

Sam moduł komunikuje się z mikrokontrolerem za pomocą magistrali I2C, która w Arduino Uno znajduje się na pinach ANALOG4 i ANALOG5. Do jego obsługi są też gotowe biblioteki, co czyni jego wykorzystanie banalnie prostym.

Podłączymy DS1307, oraz parę innych rzeczy, już w następnym odcinku. Oj, będzie dużo kabelków. Przeniesiemy też projekt na docelową płytkę i będzie też trochę lutowania.

A tymczasem kończę część czwartą i życzę udanego weekendu.

Ocena: 4.73/5 (głosów: 33)

Podobne posty

5 komentarzy do “Zegar cyfrowy LED z bajerami – Część IV: Tablica czcionek i pomiar czasu.

  • Kawał dobrej roboty :) Ja bym tą jedną diodę zamienił na dwie połączone pomiędzy tymi wyświetlaczami bo tak z jedną mrugającą nad to trochę głupio wygląda :D

    Odpowiedz
  • O ile dobrze widzę dokumentację Arduino, można korzystać z binarnej formy zapisu, np: B1111011 – to ułatwia zapis (i późniejszą poprawę) tablic fontów lub obrazków. Dodatkowo nie trzeba przeliczać nic na dziesiętny, co jest sporą oszczędnością czasu.

    Odpowiedz
  • Jest inny sposób na wykorzystanie millis() , wystarczy dać operator reszty z dzielenia % i jeśli jest on równy 0 to niech progrm doda sekundę, po czym wyzeruje tą wartosć :>

    Odpowiedz

Odpowiedz

anuluj

Masz uwagi?