
Mając do dyspozycji jedną matrycę 8x8 czerwonych diod LED o boku 60,2mm oraz mikrokontroler ATtiny861 pozostałe po innych projektach postanowiłem zbudować to, co każdy elektronik chociaż raz w życiu zbudować musi: zegar

W trybie binarnym wyświetlane są zawsze wszystkie możliwe informacje: w górnej linii (czterech pikselach) godzina, minuta, sekunda i pomiar z termometru "wewnętrznego", a w dolnej rok (bez stulecia), miesiąc, dzień i pomiar z termometru "zewnętrznego" lub, jeśli go nie ma, numer dnia tygodnia.
Piksele odpowiadające bitom nieużywanym (np. dwa starsze dla dziesiątek godzin) są wygaszone, a bitom wyzerowanym - mają minimalną jasnośc, co ułatwia odczyt.

W trybie BIT.TRIP przez pierwsze 30 sekund każdej minuty wyświetlana jest godzina i minuta, następnie przez 10 sekund miesiąc i dzień, a przez ostatnie 20 sekund temperatury/-a. Wartości w górnej i dolnej linii są względem siebie różnie przesunięte, aby można było rozróżnić, co jest w danej chwili wyświetlane. Dodatkowo temperatury jednocyfrowe mają większy znak niż dwucyfrowe.
Wzory wszystkich używanych znaków można znaleźć w załączonym arkuszu.




Myślę, że o czytelności nie ma co dyskutować. Każdy zegar binarny ma ją z założenia małą. Mógłbym zastosować więcej matryc lub inny wyświetlacz, ale nie takie były założenia. Ja z czytelności w obu trybach jestem zadowolony.
Podczas programowania wykorzystywany jest tryb binarny. Wartości, które jeszcze nie zostały zmienione przez użytkownika są na bieżąco odczytywane z RTC, a więc można szybko przestawić zegar pomiędzy czasem zimowym a letnim.
Zegar miganiem matrycy informuje użytkownika, że jest nieustawiony.
Prawy przycisk wchodzi/wychodzi z trybu programowania po długim naciśnięciu lub przełącza pomiędzy trybem binarnym a BIT.TRIP po krótkim.
Lewy przycisk inkrementuje wybraną wartość podczas programowania lub przełącza jasność wyświetlacza pomiędzy sześcioma poziomami.
Zależność prądu [A] przepływającego przez diodę LED od współczynnika wypełnienia PWM [%] jest liniowa. Zależność światłości [cd], a więc i luminancji [cd/m2] diody LED od przepływającego przez nią prądu również jest liniowa. Natomiast zależność ludzkiego postrzegania jasności od luminancji opisuje funkcja potęgowa (J = 1.0 * L ^ (1 / 2.2)). Z tego względu wykonuje się korekcję gamma, w przypadku tego zegara bardzo prostą: każdy z ośmiu poziomów jasności tablicowany jest na jedną z 16 wartości współczynnika wypełnienia PWM.
Zegar oparty jest o mikrokontroler Atmel ATtiny861. Ze względu na małą liczbę wyprowadzeń wykorzystałem dekoder 74HC138, a magistrala 1-Wire współdzieli wyprowadzenie z prawym przyciskiem (niemożliwe jest zasilanie pasożytnicze). Odmierzaniem czasu zajmuje się PCF8563 z podtrzymaniem bateryjnym. Ponieważ chciałem zachować symetrię rozłożenia elementów pod wyświetlaczem, to jeden czujnik DS18x20 w obudowie TO-92 przylutowany jest od strony druku. Drugi można podłączyć do polaryzowanego złącza Molex KK. Zasilanie dostarczane jest przez standardowe współosiowe gniazdo 5,5/2,1mm. Ja wykorzystują starą transformatorową ładowarkę Nokii bez stabilizacji napięcia (12V bez obciążenia, 3,7V pod obciążeniem nominalnym

Matryca LED to Foryard FYM-23881DUHR-21 (kolor czerwony ultrajasny, kolumny to katody, wiersze to anody) z Seguro. Niestety okazuje się, że ma ona niestandardowy rozkład wyprowadzeń, inny niż wszystkie matryce dostępne w TME, Farnellu czy Maritexie...
Rezystory 470R ograniczają prąd diod LED do ok. 5mA i jest to aż nadto pomimo multipleksowania.
Na zdjęciach włączone piksele wyglądają na białe, ale w rzeczywistości oczywiście mają ładny czerwony kolor.
Płytka drukowana (jednowarstwowa z trzema zworami drutowymi) została wykonana na frezarce LPKF Protomat S62 (niestety bez użycia narzędzia end mill, więc powierzchnie miedzi niepodłączone do żadnego potencjału pozostały na laminacie) i po lutowaniu zabezpieczona plastikiem w sprayu.

"Obudowa" to pleksiglas o grubości 4mm. Większy kawałek z marketu budowlanego przyciąłem wyrzynarką na najwolniejszych obrotach i równie wolno nawierciłem otwory. Następnie miejsce zgięcia naciąłem nożem do tapet i grzałem lutownicą na gorące powietrze, aż pleksiglas ugiął się pod własnym ciężarem do wymaganego kąta. Na koniec dodałem cztery samoprzylepne stopki o średnicy 11,1mm i wysokości 5,0mm zakupione na Allegro.


Ja praktycznie wszystkie elementy miałem, ale myślę, że zakupy powinny się zamknąć w kwocie 30-40zł.
W załączonym projekcie płytki drukowanej są poprawki w stosunku do mojej wersji:
- dodane zostały pady do przylutowania przewodów z programatora,
- "uszy" nie wystają z płytki wzdłuż przekątnych, tylko z góry i z dołu, dzięki czemu jest ona węższa i łatwiejsza w domowym wykonaniu, a prawe dolne ucho i "obudowa" nie kolidują z kątowym wtykiem zasilającym.
"Gęstość zaludnienia" jest na tyle mała, że po zmianie padów matrycy z podłużnych na okrągłe możliwe byłoby zmniejszenie szerokości płytki tak, by zrównała się z matrycą.
Firmware napisany jest w C i zajmuje 98,5% z 8kB pamięci flash. Nie było jednak żadnych kompromisów (wręcz przeciwnie), po prostu tyle wyszło

Najwyższy priorytet ma przerwanie od timera, w którym następuje multipleksowanie matrycy LED. Jego częstotliwość to 9kHz ((16 poziomów wypełnienia PWM - 1) * 8 kolumn * odświeżanie 75Hz). Zastosowane zostało potrójne buforowanie (jeden bufor jest aktualnie wyświetlany, drugi jest przygotowany i zostanie wyświetlony, gdy zacznie się kolejny cykl odświeżania, a trzeci edytujemy). Dzięki temu część wyświetlająca i zapisująca bufor nie muszą być ze sobą zsynchronizowane, co ułatwia programowanie kosztem zużycia pamięci.
Przerwanie to może zagnieżdżać się przerwaniu od drugiego timera wywoływanym z częstotliwością 100Hz, w którym jest odczytywany stan zegara po I2C (RTC nie przytrzymuje SCL, a AVR jest jedynym urządzeniem nadrzędnym na magistrali, więc transmisja jest deterministyczna czasowo), są obsługiwane przyciski i jest aktualizowany wyświetlacz.
W pętli głównej są wykonywane najbardziej czasochłonne operacje, czyli komunikacja z termometrami przez 1-Wire oraz aktualizacja EEPROM-u. Ponieważ 1-Wire wymaga blokowania przerwań ze względu na wymagania czasowe, komunikacja została na poziomie bitowym zsynchronizowana z multipleksowaniem matrycy LED, aby zapobiec wyraźnie dostrzegalnemu migotaniu. Program uwzględnia współdzielenie wyprowadzenia 1-Wire z przyciskiem i generalnie nie stanowi to większego problemu. Identyfikatory czujników są zapamiętywane automatycznie.
Warte uwagi i łatwe do wykorzystania w innych projektach są w szczególności biblioteki do obsługi przycisków oraz EEPROM-u.
Ta pierwsza (btn) zapewnia duży stopień abstrakcji od sprzętu (użytkownik sam implementuje funkcje, które odczytują stan wejść; przeniesienie kodu na jakiegoś ARM-a to tylko kwestia przygotowania odpowiednika <util/atomic.h> z avr-libc) i ma duże możliwości (debouncing, wykrywanie krótkich i długich naciśnięć, powtarzanie akcji dla przytrzymanego przycisku ze zmienną częstotliwością, np. im dłużej trzymamy przycisk, tym szybciej inkrementuje się parametr, a gdy zbliżamy się do granicy, to inkrementacja zwalnia).
Ta druga (eecb) traktuje EEPROM jak bufor kołowy i zamiast zapisywać dane ciągle do tych samych komórek wykorzystuje równomiernie całą pamięć. Dodatkowo dane są zabezpieczone CRC, więc nie trzeba ręcznie sprawdzać ich poprawności oraz obsługiwana jest sytuacja, gdy aktualizacja danych zostanie przerwana przez zanik zasilania. W przypadku tego zegara użyteczność tej biblioteki jest dosyć dyskusyjna, ale miałem ją już gotową.
C-iekawostki:
LTO to optymalizacja programu w czasie linkowania. Jest to w GCC następca optymalizacji -fwhole-program, która też odbywa się na całości programu. Według mnie jej najlepszym efektem jest wstawienie w miejsce wywołania funkcji, które są wywoływane tylko raz, a przy których kompilator w normalnych warunkach nie zrobiłby tego ze względu na ich rozmiar.
Niestety dodanie flagi -flto w tradycyjnym makefile'u powodowało przekroczenie rozmiaru sekcji .text i błąd, tak więc musiałem go tak zmodyfikować, żeby kompilowanie i konsolidacja odbywały się w jednym kroku, bez generowania pośrednich plików obiektowych.
static_assert to słowo kluczowe z C++11, które umożliwia sprawdzanie na etapie kompilacji różnych warunków, które nie są możliwe do sprawdzenia za pomocą dyrektywy preprocesora #if. W C11 jego odpowiednikiem jest _Static_assert, a dla poprzednich standardów można stworzyć makro o tej samej funkcjonalności, którego również można używać w dowolnym miejscu programu:
Code: c
Przykład wykorzystania:
Code: c
Jeśli static_assert jest obsługiwany natywnie to zobaczymy błąd kompilacji:
error: static assertion failed: "struct s musi zawsze mieć dwa bajty!"
error: static assertion failed: "enum ma złe wartości!"
W przeciwnym przypadku niespełniony warunek jest wykrywany, ale komunikat ignorowany:
error: size of array 'static_assert_at_line_<n>' is negative
error: size of array 'static_assert_at_line_<n+1>' is negative
Cool? Ranking DIY