[101] Wyświetlacze multipleksowane cz. 2
![[101] Wyświetlacze multipleksowane cz. 2](/_next/image?url=https%3A%2F%2Farduino.pl%2Fimgproxy%2FPR8GP00xEWD2YIXMHTZQRu-dr1B95US6OwGFtcFLAqw%2Ff%3Awebp%2Fw%3A1200%2FbG9jYWw6Ly8vaW1hZ2VzLzAtNjdlL2FmZmMyL2U0YjdlLzAtNjdlYWZmYzJlNGI3ZTU5NjA0OTU5My5wbmc%3D.webp&w=3840&q=75)
Skoro już zbudowaliśmy zegar z naszego retro – albo zupełnie współczesnego wyświetlacza – czas na ożywienie cyferek i zrozumienie idei multipleksu. Na wstępie dodam, że oczywiście jak każda ze standaryzowanych czynności, multipleksowanie doczekało się własnych bibliotek i można ich użyć, nie zawracając sobie głowy szczegółami. Idea słuszna, ale nieedukacyjna. W tej wersji zdecydowałem się napisać własną procedurę – w celach poznawczych, lecz nie tylko. W przyszłości planuję używać bardzo nietypowych wyświetlaczy, do których bibliotek nie znajdziemy, a tę z łatwością będzie się dało przerobić.
#include <TimeLib.h> // To jest biblioteka obsługi zegara.
const byte wspolne1 = A0; // Adresy portów wspólnych wyprowadzeń kolejnych wyświetlaczy.
const byte wspolne2 = A1;
const byte wspolne3 = A2;
const byte wspolne4 = A3;
const byte wspolne5 = A4;
const byte wspolne6 = A5;
const byte segmentA = 10; // Adresy portów kolejnych segmentów: od A do kropki.
const byte segmentB = 3;
const byte segmentC = 12;
const byte segmentD = 6;
const byte segmentE = 9;
const byte segmentF = 2;
const byte segmentG = 5;
const byte segmentP = 11;
const byte pstryczekGodzina = 4; // Adres pstryczka zmiany godzin.
const byte pstryczekMinuta = 8; // Adres pstryczka zmiany minut.
const byte pstryczekSekunda = 7; // Adres pstryczka zerowania sekund.
const byte tablica[11] = {
B00111111, // 0, Tablica zawierająca wzorce znaków wyświetlacza siedmiosegmentowego.
B00000110, // 1, Schemat: P-G-F-E-D-C-B-A
B01011011, // 2
B01001111, // 3
B01100110, // 4
B01101101, // 5
B01111101, // 6
B00000111, // 7
B01111111, // 8
B01101111, // 9
B00000000, // spacja
};
byte x; // Zmienna pomocnicza.
void setup() {
pinMode(wspolne1, OUTPUT); // Deklaruj porty wspólnych wyprowadzeń kolejnych wyświetlaczy.
pinMode(wspolne2, OUTPUT);
pinMode(wspolne3, OUTPUT);
pinMode(wspolne4, OUTPUT);
pinMode(wspolne5, OUTPUT);
pinMode(wspolne6, OUTPUT);
pinMode(segmentA, OUTPUT); // Deklaruj porty kolejnych segmentów wyświetlaczy.
pinMode(segmentB, OUTPUT);
pinMode(segmentC, OUTPUT);
pinMode(segmentD, OUTPUT);
pinMode(segmentE, OUTPUT);
pinMode(segmentF, OUTPUT);
pinMode(segmentG, OUTPUT);
pinMode(segmentP, OUTPUT);
pinMode(pstryczekGodzina, INPUT_PULLUP); // Deklaruj porty pstryczków jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
pinMode(pstryczekMinuta, INPUT_PULLUP);
pinMode(pstryczekSekunda, INPUT_PULLUP);
setTime(23, 59, 57, 01, 01, 2000); // Ustaw przykładowy czas.
}
void loop() {
wyswietl(); // Wyświetl czas.
if (digitalRead(pstryczekGodzina) == HIGH) { // Jeśli wciśnięto pstryczek zmiany godzin, to...
x = hour(); // Pobierz aktualną godzinę.
x++; // Zwiększ jej wartość.
if (x > 23) { // Jeśli przekroczono maksymalną legalną wartość, to...
x = 0; // Zeruj jej wartość.
}
setTime(x, minute(now()), second(now()), 01, 01, 2000); // Ustaw nową wartość godzin.
while (digitalRead(pstryczekGodzina) == HIGH) {} // Czekaj, aż pstryczek pozostanie puszczony.
}
if (digitalRead(pstryczekMinuta) == HIGH) { // Jeśli wciśnięto pstryczek zmiany minut, to...
x = minute(); // Pobierz aktualną minutę.
x++; // Zwiększ jej wartość.
if (x > 59) { // Jeśli przekroczono maksymalną legalną wartość, to...
x = 0; // Zeruj jej wartość.
}
setTime(hour(now()), x, second(now()), 01, 01, 2000); // Ustaw nową wartość minut.
while (digitalRead(pstryczekMinuta) == HIGH) {} // Czekaj, aż pstryczek pozostanie puszczony.
}
if (digitalRead(pstryczekSekunda) == HIGH) { // Jeśli wciśnięto pstryczek zerowania sekund, to...
setTime(hour(now()), minute(now()), 0, 01, 01, 2000); // Zeruj sekundy.
}
}
void wyswietl() { // Procedura wyświetlająca czas.
x = second() % 10; // Młodsza cyfra sekund (reszta z dzielenia przez 10).
segmenty(); // Zaświeć segmenty według wzorców z tablicy.
digitalWrite(wspolne6, LOW); // Włącz ostatni wyświetlacz.
wygas(); // Wyłącz wszystkie wyświetlacze.
x = second() / 10; // Starsza cyfra sekund.
segmenty();
digitalWrite(wspolne5, LOW);
wygas();
x = minute() % 10; // Młodsza cyfra minut.
segmenty();
digitalWrite(wspolne4, LOW);
wygas();
x = minute() / 10; // Starsza cyfra minut.
segmenty();
digitalWrite(wspolne3, LOW);
wygas();
x = hour() % 10; // Młodsza cyfra godzin.
segmenty();
digitalWrite(wspolne2, LOW);
wygas();
x = hour() / 10; // Starsza cyfra godzin.
if (x == 0) { // Wyświetl spację, jeśli godzina < 10
x = 10; // Pod adresem dziesiątym znajduje się spacja.
}
segmenty();
digitalWrite(wspolne1, LOW);
wygas();
}
void segmenty() { // Zaświeć segmenty według wzorców z tablicy.
digitalWrite(segmentA, bitRead(tablica[x], 0));
digitalWrite(segmentB, bitRead(tablica[x], 1));
digitalWrite(segmentC, bitRead(tablica[x], 2));
digitalWrite(segmentD, bitRead(tablica[x], 3));
digitalWrite(segmentE, bitRead(tablica[x], 4));
digitalWrite(segmentF, bitRead(tablica[x], 5));
digitalWrite(segmentG, bitRead(tablica[x], 6));
digitalWrite(segmentP, bitRead(tablica[x], 7));
}
void wygas() { // Wyłącz wszystkie wyświetlacze.
delayMicroseconds(2500); // Opóźnienie - dobierz, by nie było widać mrugania, a jasność była wystarczająco duża
digitalWrite(wspolne1, HIGH);
digitalWrite(wspolne2, HIGH);
digitalWrite(wspolne3, HIGH);
digitalWrite(wspolne4, HIGH);
digitalWrite(wspolne5, HIGH);
digitalWrite(wspolne6, HIGH);
}
Zapraszam zatem do analizy. TimeLib już znamy, to biblioteka zegara. Pracuje w tle, zliczając sekundy, minuty, godziny, dni, miesiące i lata. Skorzystamy tylko z trzech pierwszych zmiennych. Tak na marginesie, biblioteka współpracuje z różnymi wzorcami czasów, ale na tym etapie będzie grała solo – co nie obiecuje wysokiej dokładności zegara, lecz do zabawy wystarczy.
Poniżej znajduje się cała litania wyjść połączonych z wyświetlaczem. Katody – wspólne – podłączyłem sobie do wyjść, które mogą pracować jako wejścia analogowe, anody, czyli zwarte ze sobą kolejne segmenty – do wybranych portów cyfrowych. Kolejność wydaje się nieco chaotyczna, ale wynika z kolejnych wyprowadzeń na krawędzi wyświetlacza.
Na portach 4, 7 i 8 znajdują się przyciski, więc te piny ominąłem. Jednym będziemy zwiększać ilość godzin, drugim – minut, a trzecim – zerować sekundnik. To najprostsza możliwość edycji, mało wygodna, ale nie na tym chciałem się skupić w ćwiczeniu, a taki sposób nastawy czasu jest dość popularny w zegarach fabrycznych.
Poniżej znajdziemy tablicę ze wzorcami kształtów cyfr. Po szczegóły zapraszam do artykułu o wyświetlaczu segmentowym, w którym wystąpił ten element solo. Nadmienię tylko, że każdy może sobie tutaj zaprojektować własne wersje cyferek, np. siódemkę z zawiniętym daszkiem.
X będzie zmienną przydatną do bieżących obliczeń. Poniżej powtórzymy zestawienie pinów, deklarując dla nich zadania. Nieustawiony zegar startuje od północy, ciekawiej będzie jednak, gdy wystartuje trzy sekundy wcześniej, dając nam po resecie efekciarską zmianę dnia – stąd deklaracja czasu startowego: setTime(23, 59, 57, 01, 01, 2000);
W głównej pętli mamy cztery procedury: wyświetlanie czasu, zwiększanie godzin, zwiększanie minut i zerowanie sekund. Wyświetlanie od razu prowadzi do procedury, więc tam się przenieśmy.
void wyswietl() { // Procedura wyświetlająca czas.
x = second() % 10; // Młodsza cyfra sekund (reszta z dzielenia przez 10).
segmenty(); // Zaświeć segmenty według wzorców z tablicy.
digitalWrite(wspolne6, LOW); // Włącz ostatni wyświetlacz.
wygas(); // Wyłącz wszystkie wyświetlacze.
x = second() / 10; // Starsza cyfra sekund.
segmenty();
digitalWrite(wspolne5, LOW);
wygas();
x = minute() % 10; // Młodsza cyfra minut.
segmenty();
digitalWrite(wspolne4, LOW);
wygas();
x = minute() / 10; // Starsza cyfra minut.
segmenty();
digitalWrite(wspolne3, LOW);
wygas();
x = hour() % 10; // Młodsza cyfra godzin.
segmenty();
digitalWrite(wspolne2, LOW);
wygas();
x = hour() / 10; // Starsza cyfra godzin.
if (x == 0) { // Wyświetl spację, jeśli godzina < 10
x = 10; // Pod adresem dziesiątym znajduje się spacja.
}
segmenty();
digitalWrite(wspolne1, LOW);
wygas();
}
Cała procedura wyświetlania czasu będzie się odbywać od tyłu, czyli najpierw sekundy, dziesiątki sekund, minuty, dziesiątki minut, godziny i dziesiątki godzin. Biblioteka TimeLib zwraca nam godziny, minuty i sekundy, ale w postaci liczb naturalnych. My jednak potrzebujemy dwucyfrową liczbę zamienić na dwie cyfry. Trzeba użyć pewnego fortelu.
Pojedyncze sekundy uzyskamy dzięki reszcie z dzielenia. Jeśli weźmiemy resztę z dzielenia sekund, na przykład 37 przez 10, otrzymamy 7 – i to jest interesująca nas pozycja pojedynczych sekund.
Dziesiątki sekund uzyskać już łatwiej, należy wartość sekund podzielić przez 10. A że są to liczby naturalne, reszta przepadnie, ale nie jest nam już potrzebna. Identycznie będzie z minutami i godzinami.
Wróćmy na początek tej procedury. W tym momencie wszystkie wyświetlacze są wyciemnione – zaraz powiem dlaczego, a wyciemnia się je w ten sposób, że na wszystkich katodach należy ustawić stan wysoki. Wtedy prąd nie będzie miał jak płynąć bez względu na to, co się będzie działo na anodach. A dziać się będzie…
Na początku w zmiennej X mamy pojedyncze sekundy. Pędzimy do procedury, która wydobywa kształty cyfry z tablicy, gdzie X jest adresem tej konkretnej cyfry, która ma się wyświetlić.
void segmenty() { // Zaświeć segmenty według wzorców z tablicy.
digitalWrite(segmentA, bitRead(tablica[x], 0));
digitalWrite(segmentB, bitRead(tablica[x], 1));
digitalWrite(segmentC, bitRead(tablica[x], 2));
digitalWrite(segmentD, bitRead(tablica[x], 3));
digitalWrite(segmentE, bitRead(tablica[x], 4));
digitalWrite(segmentF, bitRead(tablica[x], 5));
digitalWrite(segmentG, bitRead(tablica[x], 6));
digitalWrite(segmentP, bitRead(tablica[x], 7));
}
Po przeładowaniu stanów na wszystkich segmentach wracamy na górę i aktywujemy tę dokładnie szóstą, czyli ostatnią cyfrę. Na katodach pojawia się stan niski, w ustawionych stanem wysokim segmentach pojawia się światło. Przechodzimy teraz do kolejnej procedury.
void wygas() { // Wyłącz wszystkie wyświetlacze.
delayMicroseconds(2500); // Opóźnienie - dobierz, by nie było widać mrugania, a jasność była wystarczająco duża
digitalWrite(wspolne1, HIGH);
digitalWrite(wspolne2, HIGH);
digitalWrite(wspolne3, HIGH);
digitalWrite(wspolne4, HIGH);
digitalWrite(wspolne5, HIGH);
digitalWrite(wspolne6, HIGH);
}
Tutaj mamy kluczową instrukcję opóźniającą. Należy ją dobrać tak, by nie było widać efektu mrugania, ale też nie może być zbyt krótka, bo wtedy stosunek czasu, gdy wyświetlacz jest wyciemniony do tego, gdy świeci, jest niekorzystny i jasność mocno spada. Czas ten ustaliłem na 2,5 milisekundy.
Po opóźnieniu wszystkie wyświetlacze są wyciemniane – taki stan musi się pojawić na początku manipulacji przy każdej z cyfr. W przeciwnym razie widzielibyśmy tzw. duchy, czyli przypadkowo słabo świecące się segmenty, przeszkadzające zwłaszcza wieczorem i w nocy. Tak na marginesie, gdybyśmy mieli wyświetlacze ze wspólną anodą, zamiast ustawiać te porty, należałoby je zerować.
No i znowu wracamy na górę, tym razem do pozycji dziesiątek sekund, a potem minut, dziesiątek minut i tak dalej. Jedyną różnicą jest tutaj aktywacja kolejnych pozycji – w stronę lewą, aż do dziesiątek godzin. Na końcu pojawił się jeszcze pewien wyjątek.
x = hour() / 10; // Starsza cyfra godzin.
if (x == 0) { // Wyświetl spację, jeśli godzina < 10
x = 10; // Pod adresem dziesiątym znajduje się spacja.
}
Jeśli godzina bieżąca ma wartość niższą niż 10, ładniej będzie nie wyświetlać zera, tylko wygasić tę pozycję, czyli wyświetlić spację. Spacja w tablicy ma adres dziesiąty, więc na sztywno ustawiam takie właśnie X, a reszta zrobi się już sama. Na końcu wrócimy do głównej pętli i przyjrzyjmy się modułom regulacji zegara.
if (digitalRead(pstryczekGodzina) == HIGH) { // Jeśli wciśnięto pstryczek zmiany godzin, to...
x = hour(); // Pobierz aktualną godzinę.
x++; // Zwiększ jej wartość.
if (x > 23) { // Jeśli przekroczono maksymalną legalną wartość, to...
x = 0; // Zeruj jej wartość.
}
setTime(x, minute(now()), second(now()), 01, 01, 2000); // Ustaw nową wartość godzin.
while (digitalRead(pstryczekGodzina) == HIGH) {} // Czekaj, aż pstryczek pozostanie puszczony.
}
Jeśli naciśnięto przycisk zwiększania godzin, należy pozyskać aktualną wartość godziny i załadować ją do zmiennej X. Następnie zwiększamy ją i badamy, czy nie przekroczyła 23. Jeśli tak, to zerujemy ją, bowiem 24 jest już wartością nielegalną.
Na koniec w taki nieco pokręcony sposób ładujemy nową wartość do funkcji TimeLib, przy czym wartość minut i sekund ładujemy wartością równocześnie odczytaną, a dni, miesiące i lata wrzucamy jakiekolwiek, bo nie biorą udziału w zabawie. Nie da się niestety uaktualnić samych godzin, stąd tak złożona operacja.
Na koniec czekamy, aż przycisk zostanie puszczony. Inaczej przybywałoby nam godzin w tempie rekordowym i nie dałoby się ich ustawić. W tym czasie wyświetlacz staje się ciemny – jest to pewna niedogodność uproszczonej procedury regulacji, ale też wskaźnik potwierdzenia zwiększania godzin.
Regulacja minut wygląda bliźniaczo, a sekund – jest uproszczona i po prostu zeruje je po naciśnięciu przycisku.
To jest oczywiście tylko propozycja wyjściowa urządzenia. Można tutaj napisać funkcje budzika, rozbudować regulacje i tak dalej, ale o tym napiszę w kolejnych artykułach.
