[135] Budujemy licznik (nie tylko) do magnetofonu cz. 1

[135] Budujemy licznik (nie tylko) do magnetofonu cz. 1

Niedawno usłyszałem prośbę, i to niejedną, o przedstawienie projektu licznika do magnetofonu. Myślę sobie: magnetofon? Zapomniane, odkładane w pawlacze i wynoszone na strychy, a jednak z powrotem stamtąd wracają. Po co? Nie odpowiem na to pytanie i nie będę poruszał tego tematu; fakty są takie, że taśma znowu zaczyna się kręcić, ale wracają też chęci do poprawiania tego czy owego.


Dawno, dawno temu, gdy magnetofonów używało się na co dzień, wielu z nas z zazdrością spoglądała na modele z licznikami cyfrowymi, obiecując sobie, że kiedyś przerobimy nasze mechaniczne cyferki na takie, modne. Lecz jakoś brakowało części, inicjatywy i zdolności. Dziś pomysł powraca i najlepszym tego dowodem są właśnie takie prośby. I to właśnie jest właściwa platforma we właściwym miejscu. Zbudujemy taki licznik wykorzystując oczywiście Arduino, wyświetlacz i aż siedem rezystorów – nic nadto. Dlaczego wystarczy tylko tyle?

Po pierwsze, wykorzystamy wyświetlacz pracujący w multipleksie. Przypomnę: to idea, gdzie łączy się razem wszystkie segmenty i naraz świeci się tylko jedna cyfra. Ale bardzo krótko, zaraz się ona wyłącza i świeci się sąsiad – i tak po kolei. Dzięki temu zamiast 29 przewodów będziemy potrzebować tylko 11. W szufladzie ze wszystkim wygrzebałem takie cyferki, które gdzieś już kiedyś pracowały, ale oczywiście za kilka złotych możemy kupić podobne. Ważne jest to, by były one przeznaczone do pracy multipleksowej właśnie, czyli by wszystkie segmenty kolejnych cyfr były połączone ze sobą. Dzięki temu nie będziemy musieli tego krosować na zewnątrz, oszczędzając sobie lutowania. Taki zespół ma z tyłu dwanaście wyprowadzeń: osiem dla segmentów i cztery dla kolejnych katod cyfr. Bo to jest wersja ze wspólną katodą, ale możemy użyć także wyświetlaczy ze wspólną anodą. Będzie to wymagało jedynie niewielkich zmian w programie.

Po drugie, wykorzystamy nowoczesny wyświetlacz, który świeci jasno już przy prądzie jednego miliampera. Jeśli na segmenty wstawimy rezystory 1 kΩ, maksymalny prąd jaki popłynie przy świecącej ósemce będzie wynosił 20 mA. Za to nie będą nam potrzebne żadne wzmacniacze, bufory i dodatkowe lutowania.

Po trzecie w końcu, jak byśmy nie podłączyli wyświetlacza, w programie możemy go dokładnie zdefiniować, uzyskując w każdej konfiguracji poprawne kształty znaków. Zaraz zresztą zrobimy z tego praktyczny użytek. Dzięki temu nie będziemy musieli zachowywać jakiejś narzuconej kolejności podłączania przewodów do Arduino. Skoro już wiemy dlaczego będzie łatwo, szybko i przyjemnie, czas na prace ręczne.

Jak widać, do każdego wyprowadzenia połączonego wewnątrz z segmentami, podłączyłem rezystor – jak mówiłem 1 kΩ, ale dobrze przetestować jasność jeszcze bez lutowania i ewentualnie dobrać wartości większe. Trzeba tylko pamiętać, że testy będziemy wykonywać statycznie, a wyświetlacz będzie pracować w multipleksie, więc jego jasność nieco się zmniejszy. Mówiłem o siedmiu segmentach, a tu widać osiem rezystorów. Podłączyłem także kropki, które na razie nie przydadzą się nam, ale może później coś dla nich wymyślimy.

Radzę wyrobić sobie pewien nawyk: jak widać połączyłem wszystko kolorowymi przewodami, lecz nieprzypadkowo. Zawsze zestawiam kolejność widmową, a więc: brąz, czerwień, pomarańcz, żółć, zieleń, błękit, fiolet… kolorów brakło, więc jeszcze szary. Taką kolejność mamy w tęczy, ale też w kodzie rezystorów, więc łatwo będzie zidentyfikować segmenty: A to brąz, G – szary. Do kompletu mielibyśmy jeszcze turkus, reszta barw nie jest już tak jednoznaczna. Pozostają jeszcze biały i czarny, który zwykle ustawiałem: czarny przed brązem, biały za szarym – ale to już moje preferencje. Tak czy inaczej, brakłoby jednego odcienia, więc dla katod powtórzyłem barwy, nakładając innego koloru koszulki.

Z drugiej strony osadziłem goldpiny, ponieważ wyświetlacz będę podłączał do mojej płytki edukacyjnej TME. Docelowo będzie on pracował z Arduino Nano albo Mini. Nano jest jakby małym Uno, natomiast Mini nie ma gniazdka USB i potrzebuje programatora. Oba są malutkie i w każdym magnetofonie znajdzie się dla nich miejsce.

Na razie podłączymy całość tak, jak tu widać: i tu wrócę do tego wspomnianego praktycznego użytku. Każdy, kto się bawi Arduino wie, że porty zero i jeden służą do łączenia się z komputerem i generalnie nie używa się ich. Ale wcale nie ma takiego zakazu. Trzeba tylko uważać, by tych portów nie zwierać podczas programowania Arduino, bo procesu tego nie uda się przeprowadzić. Wyświetlacz zasilany przez kiloomowy rezystor nie stanowi zwarcia, więc po zaprogramowaniu płytki podłączone tam segmenty będą świecić bez problemów. Piny segmentów wydają się być wstawione z przesunięciem – tak ma być, nie chciałem podłączać kropek i one sobie wiszą w powietrzu. Katody natomiast podłączyłem do pinów zwykle używanych jako wejścia analogowe. Ale każdy z nich może być cyfrowym wyjściem i tak też tu będzie.

Zużyliśmy 11 pinów. Nie ma co płakać, pozostało jeszcze 9, a w przypadku Nano bądź Mini – 11. Do licznika wystarczy aż za dość. Cóż, skoro wszystko zostało podłączone, czas na programowanie. Zaczniemy jednak od modułu samego wyświetlacza, jeszcze bez licznika, choć nie do końca – dla wizualizacji licznik będzie się zmieniał co 100 ms. Nie ma to sensu stricte, ale posłuży dwóm sprawom: pierwsza to weryfikacja połączeń i sprawności wyświetlacza, druga – edukacja.

#include <TimerOne.h>  // Biblioteka obsługi przerwań.

const byte segmenty[7] = { 6, 5, 4, 3, 2, 1, 0 };  // Adresy portów kolejnych segmentów: od A do G
const byte katody[4] = { A0, A1, A2, A3 };         // Adresy portów wspólnych wyprowadzeń (katod) kolejnych wyświetlaczy.

volatile byte cyfry[4] = { 0, 0, 0, 0 };  // Tablica z cyframi, którą wyświetla przerwanie.
volatile byte aktywnaCyfra = 0;           // Aktualnie świecąca się cyfra w multipleksie.

const byte ksztalty[10] = {
  B1111110,  // 0, Tablica zawierająca wzorce znaków wyświetlacza siedmiosegmentowego.
  B0110000,  // 1, Schemat: A-B-C-D-E-F-G
  B1101101,  // 2
  B1111001,  // 3
  B0110011,  // 4
  B1011011,  // 5
  B1011111,  // 6
  B1110000,  // 7
  B1111111,  // 8
  B1111011   // 9
};
int licznik = 0;  // Zmienna przechowująca wyświetlaną liczbę.

void setup() {
  for (byte x = 0; x &lt; 7; x++)  // Deklaruj porty kolejnych segmentów wyświetlaczy.
    pinMode(segmenty[x], OUTPUT);

  for (byte x = 0; x &lt; 4; x++) {    // Dla każdej katody wyświetlacza...
    pinMode(katody[x], OUTPUT);     // Deklaruj porty.
    digitalWrite(katody[x], HIGH);  // Odłącz napięcie.
  }
  Timer1.initialize(1000);             // Ustaw przerwania na milisekundę.
  Timer1.attachInterrupt(przerwanie);  // Włącz przerwania z określeniem miejsca lądowania.
}
void loop() {
  licznik = (licznik == 9999) ? 0 : licznik + 1;  // Jeśli licznik osiągnął 9999, ustaw 0, w przeciwnym razie zwiększ go.
  // licznik = (licznik == 0) ? 9999 : licznik - 1;   // Jeśli licznik osiągnął 0, ustaw 9999, w przeciwnym razie zmniejsz go.

  cyfry[0] = (licznik / 1000) % 10;  // Załaduj do tablicy cyfry reprezentujące wartość licznika.
  cyfry[1] = (licznik / 100) % 10;
  cyfry[2] = (licznik / 10) % 10;
  cyfry[3] = licznik % 10;
  delay(100);
}
void przerwanie() {             // Obsługa przerwania.
  for (byte x = 0; x < 4; x++)  // Odłącz napięcie od wszystkich katod wyświetlaczy.
    digitalWrite(katody[x], HIGH);

  byte y = ksztalty[cyfry[aktywnaCyfra]];  // Wyprowadź kształt do wyświetlenia.
  for (byte x = 0; x < 7; x++)             // Zaświeć odpowiednie segmenty aktywnej pozycji wyświetlacza.
    digitalWrite(segmenty[x], (y &amp; (1 << (6 - x))));

  digitalWrite(katody[aktywnaCyfra], LOW);  // Zaświeć aktywny wyświetlacz.
  aktywnaCyfra = (aktywnaCyfra + 1) % 4;    // Zwiększ wartość aktywnego wyświetlacza modulo 4
}

W tym momencie powinienem cofnąć się do artykułu, który już omawiał multipleks i właściwie można by skorzystać z tamtego rozwiązania. Ale skorzystamy zeń tylko ideą, pisząc wszystko od początku. Dlaczego? Bo niedawno pisałem, że po setnym odcinku wypada już być bardziej „profi”. Dlatego szkic będzie się wydawać nieco trudniejszy w analizie, lecz dzięki temu będzie krótszy i bardziej efektywny. Wykorzystamy w nim jedną tylko bibliotekę TimerOne, bez której właściwie moglibyśmy się obejść, bo ona nie robi nic nadzwyczajnego, tylko porządkuje sprawy związane z przerwaniami. Bez niej należałoby ustawić kilka rejestrów i wyliczyć sobie współczynniki, z nią – musimy tylko podać interwał i nazwać podprogram, do którego przerwanie będzie nas wrzucać zgodnie z tym interwałem. A więc…

Na początek zaczytujemy wspomnianą bibliotekę. Tę samą, której już kiedyś użyłem w wymienionym artykule. Poniżej zobaczymy cztery tablice i odtąd będziemy ich używać częściej niż kiedyś, redukując długie wyliczanki. Pierwsza, o nazwie segmenty, będzie zawierać adresy pinów, które staną się portami kolejnych segmentów: od A do G. Druga, katody, to bliźniak, zawierający adresy katod. Jak mówiłem, wstawiamy tam rzeczywiste adresy pinów, do których podłączyliśmy kabelki. U mnie wyszło po kolei, ale nie jest to konieczne.

Trzecia tablica: cyfry zawiera cztery cyfry w takiej kolejności, w jakiej pojawią się na wyświetlaczu. Będzie to potrzebne, by wymieniać dane między główną pętlą, a przerwaniami. Z tego powodu na początku umieściłem wyrażenie volatile, które nie jest może niezbędne, ale przydatne dla tych zmiennych, które wykorzystuje zarówno jawna część programu, ta z głównej pętli i część schowana w przerwaniach. Ze względu na pewne optymalizacje kompilatora, należy dodać dla niego wiadomość, by zawsze korzystał z komórek pamięci, które reprezentują te zmienne, a nie na przykład z buforów, których przerwania mogą, że tak powiem, nie szanować.

Zaraz potem mamy zmienną aktywnaCyfra, z której przerwanie bierze informację o tym, która to aktualnie cyfra się świeci – zgodnie z ideą multipleksu. W końcu jest i czwarta tablica o nazwie kształty. Siedzą w niej kształty cyfr. Jeśli ktoś chce na przykład amerykańskiej szóstki i dziewiątki – bez daszków, może sobie wyzerować odpowiednie bity. Ostatnią zmienną jest licznik, tutaj będziemy przechowywać stan licznika, który będzie się wyświetlał.

W części setup, czyli wykonującej się jednorazowo, będziemy deklarować piny segmentów, potem katod – wraz z odłączeniem napięć, czyli wyciemnieniem wszystkich pozycji. W końcu ustawimy interwał przerwań na milisekundę i włączymy je. Powinna to być ostatnia operacja w tej części programu.

Pętla główna będzie mieć tylko znaczenie szkoleniowe. Licznik będzie się zwiększał co 100 ms i jeśli osiągnie 10 tysięcy, wyzeruje się. I tu – nowość, bo takiego zapisu jeszcze nie przedstawiałem.

licznik = (licznik == 9999) ? 0 : licznik + 1;

To skrócona, taka zawodowa forma wyrażenia if - else, które dobrze znamy. Tłumaczy się to tak: jeśli licznik osiągnął już 9999, ustaw zero, jeśli nie – zwiększ jego wartość o jeden. Może jest to mało czytelne i dziwne, ale ile tu oszczędności!

Pod spodem znajdziemy zakomentowane bliźniacze wyrażenie zmniejszające wartość licznika, również z przewinięciem po zerze do czterech dziewiątek – jak w prawdziwym liczniku z magnetofonu ZK140. Będziemy sobie mogli sprawdzić zaraz oba kierunki liczenia – w górę i w dół. Potem zrobimy w tym miejscu logikę pracy już z prawdziwym magnetofonem.

Następnie siedzi sobie blok przygotowywania danych dla przerwania. W tym celu stan naszego licznika musimy rozbić na tysiące, setki, dziesiątki i jednostki i to wszystko powsadzać w tablicę cyfry. Najłatwiej – za pomocą reszty z dzielenia, co już nieraz prezentowałem. Pozostało wyjaśnić obsługę przerwań, a będzie tu sporo kruczków.

void przerwanie() {             // Obsługa przerwania.
  for (byte x = 0; x < 4; x++)  // Odłącz napięcie od wszystkich katod wyświetlaczy.
    digitalWrite(katody[x], HIGH);

  byte y = ksztalty[cyfry[aktywnaCyfra]];  // Wyprowadź kształt do wyświetlenia.
  for (byte x = 0; x < 7; x++)             // Zaświeć odpowiednie segmenty aktywnej pozycji wyświetlacza.
    digitalWrite(segmenty[x], (y &amp; (1 << (6 - x))));

  digitalWrite(katody[aktywnaCyfra], LOW);  // Zaświeć aktywny wyświetlacz.
  aktywnaCyfra = (aktywnaCyfra + 1) % 4;    // Zwiększ wartość aktywnego wyświetlacza modulo 4
}

Zaczyna się od odłączenia napięć od wszystkich katod, czyli od wyciemnienia wyświetlacza. Dlaczego tak? Bo gdybyśmy tylko przełączali katody, ten krótki czas, gdy gmera się przy segmentach, byłby widoczny w postaci delikatnie żarzących się tak zwanych duchów. Zasada jest prosta: przy przeładowywaniu danych wyświetlacz musi być zgaszony.

Proces wyłuskiwania kształtów znaków i ładowania ich na odpowiednią pozycję wyświetlacza jest złożony. Powołujemy na chwilę do życia zmienną y, w której siedzi wyłuskany kształt związany z wartością cyfry i jej pozycją. Następnie bit po bicie wstawiamy to, co w niej siedzi, na kolejne segmenty wyświetlacza. Gdy już cały komplet siedmiu bitów – segmentów będzie załadowany właściwymi danymi, będzie można zaświecić tę pozycję, czyli wystawić niski stan. Prąd popłynie, cyfra się pojawi. Na końcu trzeba będzie zmienić numer aktywnej cyfry, by w kolejnym przerwaniu tę zgasić i zaświecić sąsiada. Gdy będzie to już ostatnia, czwarta pozycja, licznik cyfr trzeba będzie wyzerować.

Po kompilacji powinniśmy ujrzeć coś w rodzaju stopera, odmierzającego dziesiąte części sekundy. O tym co dalej napiszę w kolejnym artykule.

Powiązane tematy

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2Sprawdź tutaj

Przeczytaj również

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2025 arduino.pl