[138] Budujemy licznik (nie tylko) do magnetofonu cz. 4
![[138] Budujemy licznik (nie tylko) do magnetofonu cz. 4](/_next/image?url=https%3A%2F%2Farduino.pl%2Fimgproxy%2F4ZCL3zWD7U0_KXhgPtv8_ezpV61AiHvgR38DY5f1Zaw%2Ff%3Awebp%2Fw%3A1200%2FbG9jYWw6Ly8vaW1hZ2VzLzAtNjkyLzg3ODc2LzFhNWI1LzAtNjkyODc4NzYxYTViNTIyOTQ5NjU5Mi5wbmc%3D.webp&w=3840&q=75)
Liczniki czterocyfrowe mają sens w magnetofonach szpulowych. W kaseciakach nie bardzo, tam aż takich wielkich szpul nie używa się. Moją drugą propozycją jest tak zwany licznik z bajerem. Ograniczymy się do trzech pozycji tylko, a na pierwszej wyświetlimy animację, wskazującą na obracanie się talerzyka w wybranym kierunku. Lecz zanim zabierzemy się za zmiany w programie, najpierw mała zmiana sprzętowa:

Zerwę moją taśmę z talerzyka i przykleję ją na nowo, ale nieco inaczej: takimi ćwiartkami. W ten sposób powiększę rozdzielczość dwukrotnie. Po co? Po kolei.
#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.
const byte reset = 8; // Adres przycisku resetującego licznik.
const byte fotoLewy = A4; // Adresy portów fototranzystorów.
const byte fotoPrawy = A5;
volatile byte cyfry[4] = { 10, 0, 0, 0 }; // Tablica z cyframi, którą wyświetla przerwanie.
volatile byte aktywnaCyfra = 0; // Aktualnie świecąca się cyfra w multipleksie.
int licznik = 0; // Zmienna przechowująca stan licznika obrotów.
bool poprzedniFotoLewy; // Zmienne poprzedniego stanu fototranzystorów.
bool poprzedniFotoPrawy;
const byte ksztalty[14] = {
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
B0011100, // faza 1
B0001101, // faza 2
B0010101, // faza 3
B0011001 // faza 4
};
void setup() {
for (byte x = 0; x < 7; x++) // Deklaruj porty kolejnych segmentów wyświetlaczy.
pinMode(segmenty[x], OUTPUT);
for (byte x = 0; x < 4; x++) { // Dla każdej katody wyświetlacza...
pinMode(katody[x], OUTPUT); // Deklaruj porty.
digitalWrite(katody[x], HIGH); // Odłącz napięcie.
}
pinMode(reset, INPUT_PULLUP); // Deklaruj port przycisku resetującego licznik.
pinMode(fotoLewy, INPUT); // Deklaruj porty fototranzystorów.
pinMode(fotoPrawy, INPUT);
poprzedniFotoLewy = digitalRead(fotoLewy); // Załaduj stany fototranzystorów.
poprzedniFotoPrawy = digitalRead(fotoPrawy);
Timer1.initialize(1000); // Ustaw przerwania na milisekundę.
Timer1.attachInterrupt(przerwanie); // Włącz przerwania z określeniem miejsca lądowania.
}
void loop() {
if (digitalRead(reset) == HIGH) { // Jeśli wciśnięto przycisk resetu...
licznik = 0; // Resetuj licznik.
przeladuj(); // Odśwież stan wyświetlacza.
}
bool aktualnyFotoLewy = digitalRead(fotoLewy); // Załaduj stany fototranzystorów.
bool aktualnyFotoPrawy = digitalRead(fotoPrawy);
if (aktualnyFotoLewy != poprzedniFotoLewy || aktualnyFotoPrawy != poprzedniFotoPrawy) { // Jeśli nastąpiła jakakolwiek zmiana stanu fotorezystorów...
if ((poprzedniFotoLewy == LOW && aktualnyFotoLewy == HIGH && aktualnyFotoPrawy == LOW) || (poprzedniFotoLewy == HIGH && aktualnyFotoLewy == LOW && aktualnyFotoPrawy == HIGH)) {
licznik++; // Obrót w prawo.
} else if ((poprzedniFotoLewy == LOW && aktualnyFotoLewy == HIGH && aktualnyFotoPrawy == HIGH) || (poprzedniFotoLewy == HIGH && aktualnyFotoLewy == LOW && aktualnyFotoPrawy == LOW)) {
licznik--; // Obrót w lewo.
}
poprzedniFotoLewy = aktualnyFotoLewy; // Zapamiętaj stany fototranzystorów do kolejnej analizy.
poprzedniFotoPrawy = aktualnyFotoPrawy;
licznik = (licznik + 4000) % 4000; // Ogranicz zliczanie do przedziału 0...3999
przeladuj();
}
}
void przeladuj() {
cyfry[0] = (10 + licznik % 4); // Załaduj na pierwszą pozycję jedną z czterech animacji.
cyfry[1] = (licznik / 400) % 10; // Załaduj do tablicy cyfry reprezentujące wartość licznika.
cyfry[2] = (licznik / 40) % 10;
cyfry[3] = licznik / 4 % 10;
}
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 & (1 << (6 - x))));
digitalWrite(katody[aktywnaCyfra], LOW); // Zaświeć aktywny wyświetlacz.
aktywnaCyfra = (aktywnaCyfra + 1) % 4; // Zwiększ wartość aktywnego wyświetlacza modulo 4
}Do tablicy znaków dopisałem cztery pozycje, z czterema fazami animacji, którą zaraz zobaczymy. I już sprawa się wyjaśnia: jeśli na każdy skok licznika dostaniemy cztery animacje, będzie to oznaczać, że będziemy potrzebowali czterokrotnie wyższej rozdzielczości. Po to była zmiana na powierzchni talerzyka, ale tym podnieśliśmy rozdzielczość dwa razy tylko. Druga zmiana nastąpi w poniższym fragmencie:
if ((poprzedniFotoLewy == LOW && aktualnyFotoLewy == HIGH && aktualnyFotoPrawy == LOW) || (poprzedniFotoLewy == HIGH && aktualnyFotoLewy == LOW && aktualnyFotoPrawy == HIGH)) {
licznik++; // Obrót w prawo.
} else if ((poprzedniFotoLewy == LOW && aktualnyFotoLewy == HIGH && aktualnyFotoPrawy == HIGH) || (poprzedniFotoLewy == HIGH && aktualnyFotoLewy == LOW && aktualnyFotoPrawy == LOW)) {
licznik--; // Obrót w lewo.
}Jest on mocniej skomplikowany, lecz wykrywa kierunek i generuje impuls zmiany licznika zarówno przy przejściach z obszarów ciemnych na jasne jak i na odwrót. W ten sposób zyskamy potrzebny wzrost rozdzielczości. Ponieważ teraz licznik zlicza do czterech tysięcy, procedura konwersji wygląda inaczej:
cyfry[0] = (10 + licznik % 4); // Załaduj na pierwszą pozycję jedną z czterech animacji.
cyfry[1] = (licznik / 400) % 10; // Załaduj do tablicy cyfry reprezentujące wartość licznika.
cyfry[2] = (licznik / 40) % 10;
cyfry[3] = licznik / 4 % 10;Dzielimy nie przez setki, dziesiątki i jednostki, a przez czterysta, czterdzieści i cztery. Ale dla pierwszej pozycji, gdzie były tysiące, osadzamy inną regułę: do dziesięciu dodajemy resztę z dzielenia modulo cztery, co pozwala nam wysyłać periodycznie zmieniający się adres kolejnego wzorca animacji. Po kompilacji dostaniemy efekt „młynka” obracającego się na pierwszej pozycji wyświetlacza, zgodnie z kierunkiem obrotu szpulki. Jeśli ktoś zechce innych obrazków, może sobie pozamieniać wzory na pozycjach od 10 do 13 w tablicy ksztalt.
A ja postanowiłem raz jeszcze pogmerać w kodzie, wykorzystując nudzące się dotąd kropki. Wrócimy więc znowu do licznika czterocyfrowego, ale przesuniemy wszystkie segmenty o jeden pin w lewo, żeby kropki także były podłączone.

Teraz mamy 8 segmentów, więc trzeba zaktualizować wszędzie zakresy – zarówno w deklaracjach, jak w przerwaniach, które ósmy segment dotąd ignorowały.
#include <TimerOne.h> // Biblioteka obsługi przerwań.
const byte segmenty[8] = { 7, 6, 5, 4, 3, 2, 1, 0 }; // Adresy portów kolejnych segmentów: od A do P
const byte katody[4] = { A0, A1, A2, A3 }; // Adresy portów wspólnych wyprowadzeń (katod) kolejnych wyświetlaczy.
const byte reset = 8; // Adres przycisku resetującego licznik.
const byte fotoLewy = A4; // Adresy portów fototranzystorów.
const byte fotoPrawy = A5;
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.
unsigned long licznik = 0; // Zmienna przechowująca stan licznika obrotów.
bool poprzedniFotoLewy; // Zmienne poprzedniego stanu fototranzystorów.
bool poprzedniFotoPrawy;
const byte ksztalty[20] = {
B11111100, // 0, Tablica zawierająca wzorce znaków wyświetlacza siedmiosegmentowego.
B01100000, // 1, Schemat: P-A-B-C-D-E-F-G
B11011010, // 2
B11110010, // 3
B01100110, // 4
B10110110, // 5
B10111110, // 6
B11100000, // 7
B11111110, // 8
B11110110, // 9
B11111101, // 0, Te same cyfry z kropkami.
B01100001, // 1
B11011011, // 2
B11110011, // 3
B01100111, // 4
B10110111, // 5
B10111111, // 6
B11100001, // 7
B11111111, // 8
B11110111 // 9
};
void setup() {
for (byte x = 0; x < 8; x++) // Deklaruj porty kolejnych segmentów wyświetlaczy.
pinMode(segmenty[x], OUTPUT);
for (byte x = 0; x < 4; x++) { // Dla każdej katody wyświetlacza...
pinMode(katody[x], OUTPUT); // Deklaruj porty.
digitalWrite(katody[x], HIGH); // Odłącz napięcie.
}
pinMode(reset, INPUT_PULLUP); // Deklaruj port przycisku resetującego licznik.
pinMode(fotoLewy, INPUT); // Deklaruj porty fototranzystorów.
pinMode(fotoPrawy, INPUT);
poprzedniFotoLewy = digitalRead(fotoLewy); // Załaduj stany fototranzystorów.
poprzedniFotoPrawy = digitalRead(fotoPrawy);
Timer1.initialize(1000); // Ustaw przerwania na milisekundę.
Timer1.attachInterrupt(przerwanie); // Włącz przerwania z określeniem miejsca lądowania.
}
void loop() {
if (digitalRead(reset) == HIGH) { // Jeśli wciśnięto przycisk resetu...
licznik = 0; // Resetuj licznik.
przeladuj(); // Odśwież stan wyświetlacza.
}
bool aktualnyFotoLewy = digitalRead(fotoLewy); // Załaduj stany fototranzystorów.
bool aktualnyFotoPrawy = digitalRead(fotoPrawy);
if (aktualnyFotoLewy != poprzedniFotoLewy || aktualnyFotoPrawy != poprzedniFotoPrawy) { // Jeśli nastąpiła jakakolwiek zmiana stanu fotorezystorów...
if ((poprzedniFotoLewy == LOW && aktualnyFotoLewy == HIGH && aktualnyFotoPrawy == LOW) || (poprzedniFotoLewy == HIGH && aktualnyFotoLewy == LOW && aktualnyFotoPrawy == HIGH)) {
licznik++; // Obrót w prawo.
} else if ((poprzedniFotoLewy == LOW && aktualnyFotoLewy == HIGH && aktualnyFotoPrawy == HIGH) || (poprzedniFotoLewy == HIGH && aktualnyFotoLewy == LOW && aktualnyFotoPrawy == LOW)) {
licznik--; // Obrót w lewo.
}
poprzedniFotoLewy = aktualnyFotoLewy; // Zapamiętaj stany fototranzystorów do kolejnej analizy.
poprzedniFotoPrawy = aktualnyFotoPrawy;
licznik = (licznik + 40000) % 40000; // Ogranicz zliczanie do przedziału 0...9999
przeladuj();
}
}
void przeladuj() {
const int y[4] = { 4000, 400, 40, 4 }; // Wartości, którymi będziemy dzielić licznik na poszczególne cyfry.
for (byte x = 0; x < 4; x++) { // Załaduj do tablicy cyfry reprezentujące wartość licznika oraz kropki.
cyfry[x] = (licznik / y[x]) % 10 + 10 * (licznik % 4 == x);
}
}
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 < 8; x++) // Zaświeć odpowiednie segmenty aktywnej pozycji wyświetlacza.
digitalWrite(segmenty[x], (y & (1 << (7 - x))));
digitalWrite(katody[aktywnaCyfra], LOW); // Zaświeć aktywny wyświetlacz.
aktywnaCyfra = (aktywnaCyfra + 1) % 4; // Zwiększ wartość aktywnego wyświetlacza modulo 4
}
Obsługę kropek można zorganizować na różne sposoby. Ja to zrobiłem nieco naokoło: zadeklarowałem bliźniacze wzorce cyfr w tablicy kształty, ale tym razem ze świecącą się kropką. To ostatni bit i od połowy tablicy przyjmuje wartość jedynki.
Ponieważ znowu liczymy do 10 tysięcy, a licznik – jako że obsługuje także animację, sięga 40 tysięcy, pojawił się problem. Co prawda istnieje typ zmiennej int bez znaku, który może sięgnąć 65 tysięcy z hakiem, to nie działa tam poprawnie liczenie modulo. Nie wiem jeszcze jaka jest tego natura, ale wymiana typu na long problem rozwiązuje i rachunki znowu działają.
Cała animacja jest zorganizowana w zreformowanej procedurze konwersji stanu licznika na cztery cyfry.
const int y[4] = { 4000, 400, 40, 4 }; // Wartości, którymi będziemy dzielić licznik na poszczególne cyfry.
for (byte x = 0; x < 4; x++) { // Załaduj do tablicy cyfry reprezentujące wartość licznika oraz kropki.
cyfry[x] = (licznik / y[x]) % 10 + 10 * (licznik % 4 == x);
}Wygląda to tak: dzielniki siedzą teraz w tablicy, a w pętli wykonywanej dla każdej pozycji mamy regułę, która robi to, co robiło się dawniej, ale z alternatywnym przesunięciem wzorca o dziesięć, jeśli wypada akurat ta faza animacji. Bo o dziesięć są przesunięte wzorce liczb w wersji z zapaloną kropką.
I to wszystko. Teraz znów mamy czterocyfrowy licznik, ale na dole przesuwają się kropki – zgodnie z kierunkiem obracania się talerzyka. Ciekawe gdzie i czy w ogóle gdzieś zastosowano tego typu wskaźnik...












































