[110] Budujemy ze zrozumieniem - elektroniczny budzik cz. 4

[110] Budujemy ze zrozumieniem - elektroniczny budzik cz. 4

Skoro mamy już działający na przerwaniach zegar, trzeba stworzyć jakąś wirtualną śrubkę do jego… może nie nakręcania, ale regulacji. Tak na marginesie, gdy młody człowiek słyszy „nakręcić zegarek”, myśli raczej o próbie wzbudzenia w zegarku jakiejś manii, co brzmi co najmniej dziwnie. Acz sformułowanie „nakręcić się na coś” weszło do codzienności, zaś nakręcanie zegarów z niej wyszło.


Poprzedni sposób regulacji – dwoma przyciskami, gdzie jeden zwiększał godziny, a drugi – minuty – był popularny w różnego rodzaju aplikacjach budzików i zarazem niewygodny. Przewinięcie 60 minut trwało wieki albo wymagało klikania bez końca. Lata praktyki przyniosły mi interfejs szybki i wygodny: nadal mamy dwa przyciski, ale jednym zwiększamy bieżące wskazania, drugim – zmniejszamy. Przy czym wciśnięcie przycisku zmienia stan o jeden, pozostając zamrożonym przez 100 ms, a następnie pojawia się szalona autorepetycja z szybkością dwieście jednostek na sekundę. Może się to wydawać dziwne, ale jest naprawdę wygodne. Przewinięcie całej doby trwa siedem sekund, a zwykle potrzeba mniejszych skoków. Oczywiście teraz już nie regulujemy osobno godzin i minut, więc procedura wymaga użycia szeregu warunków pilnowania przepełnień i to dla każdej liczby składowej osobno, bo jak mówiłem, czas jest zapisywany w kodzie BCD, na czterech bajtach.

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

const byte segmentA = 5;  // Adresy portów kolejnych segmentów: od A do G oraz dwukropek.
const byte segmentB = 6;
const byte segmentC = 9;
const byte segmentD = 10;
const byte segmentE = 11;
const byte segmentF = 12;
const byte segmentG = 13;
const byte dwukropek = 3;

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 plus = 7;  // Adresy portów klawiatury.
const byte minus = 4;

const byte opoznienieDlugie = 100;  // Współczynniki autorepetycji klawiatury.
const byte opoznienieKrotkie = 5;

const byte tablica[11] = {
  B1000000,  // 0, Tablica zawierająca wzorce znaków wyświetlacza siedmiosegmentowego.
  B1111001,  // 1, Schemat: G-F-E-D-C-B-A
  B0100100,  // 2
  B0110000,  // 3
  B0011001,  // 4
  B0010010,  // 5
  B0000010,  // 6
  B1111000,  // 7
  B0000000,  // 8
  B0010000,  // 9
};

int licznikPrzerwan = 0;     // Licznik ramek odmierzających 2,5 ms
byte aktywnaCyfra = 1;       // Numer aktywnej cyfry na wyświetlaczu.
byte sekundy = 57;           // Sekundnik.
byte minutaJednostka = 9;    // Jednostki minut.
byte minutaDziesiatka = 5;   // Dziesiątki minut.
byte godzinaJednostka = 3;   // Jednostki godzin.
byte godzinaDziesiatka = 2;  // Dziesiątki godzin.

void setup() {
  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(dwukropek, OUTPUT);

  pinMode(wspolne1, OUTPUT);  // Deklaruj porty wspólnych wyprowadzeń kolejnych wyświetlaczy.
  pinMode(wspolne2, OUTPUT);
  pinMode(wspolne3, OUTPUT);
  pinMode(wspolne4, OUTPUT);

  pinMode(plus, INPUT_PULLUP);  // Deklaruj porty pstryczków jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
  pinMode(minus, INPUT_PULLUP);

  digitalWrite(dwukropek, HIGH);       // Włącz dwukropek.
  Timer1.initialize(2500);             // Ustaw licznik na 2500 mikrosekund.
  Timer1.attachInterrupt(przerwanie);  // Włącz przerwanie z określeniem miejsca lądowania.
}

void loop() {

  if (digitalRead(plus) == HIGH) {       // Jeśli wciśnięto pstryczek plus...
    zwiekszCzas();                       // Zwiększ czas.
    delay(opoznienieDlugie);             // Opóźnienie przed autorepetycją.
    while (digitalRead(plus) == HIGH) {  // Dopóki przycisk plus jest trzymany...
      zwiekszCzas();                     // Zwiększaj czas.
      delay(opoznienieKrotkie);          // Opóźnienie autorepetycji.
    }
    licznikPrzerwan = 0;      // Zeruj licznik przerwań.
    sekundy = 0;              // Zeruj sekundnik.
    delay(opoznienieDlugie);  // Opóźnienie przed wyjściem z procedury.
  }

  if (digitalRead(minus) == HIGH) {  // Obsługa pstryczka minus.
    zmniejszCzas();
    delay(opoznienieDlugie);
    while (digitalRead(minus) == HIGH) {
      zmniejszCzas();
      delay(opoznienieKrotkie);
    }
    licznikPrzerwan = 0;
    sekundy = 0;
    delay(opoznienieDlugie);
  }
}

void przerwanie() {                                    // Tutaj lądujemy za każdym razem, gdy wewnętrzny timer odliczy 2500 mikrosekund.
  licznikPrzerwan++;                                   // Zwiększ licznik ramek odmierzających 2,5 ms
  if (licznikPrzerwan == 400) {                        // Jeśli osiągnął 400 (2,5 ms * 400 = 1 sekunda)...
    licznikPrzerwan = 0;                               // Zeruj go oraz...
    digitalWrite(dwukropek, !digitalRead(dwukropek));  // Zmień stan dwukropka.
    sekundy++;                                         // Zwiększaj liczbę sekund.
    if (sekundy == 60) {                               // Jeśli osiągnął 60...
      sekundy = 0;                                     // Zeruj go oraz...
      zwiekszCzas();                                   // Zwiększ czas.
    }
  }
  digitalWrite(wspolne1, LOW);  // Wygaś wszystkie cyfry.
  digitalWrite(wspolne2, LOW);
  digitalWrite(wspolne3, LOW);
  digitalWrite(wspolne4, LOW);

  switch (aktywnaCyfra) {           // Załaduj kształty aktywnej cyfry z tablicy...
    case 1:                         // Dla dziesiątek godzin.
      if (godzinaDziesiatka > 0) {  // Ale tylko, jeśli większe od zera.
        digitalWrite(segmentA, bitRead(tablica[godzinaDziesiatka], 0));
        digitalWrite(segmentB, bitRead(tablica[godzinaDziesiatka], 1));
        digitalWrite(segmentC, bitRead(tablica[godzinaDziesiatka], 2));
        digitalWrite(segmentD, bitRead(tablica[godzinaDziesiatka], 3));
        digitalWrite(segmentE, bitRead(tablica[godzinaDziesiatka], 4));
        digitalWrite(segmentF, bitRead(tablica[godzinaDziesiatka], 5));
        digitalWrite(segmentG, bitRead(tablica[godzinaDziesiatka], 6));
        digitalWrite(wspolne1, HIGH);  // Włącz aktywną cyfrę.
      }
      aktywnaCyfra = 2;  // Ustaw numer aktywnej cyfry dla kolejnego wejścia w przerwanie.
      break;
    case 2:  // Dla jednostek godzin.
      digitalWrite(segmentA, bitRead(tablica[godzinaJednostka], 0));
      digitalWrite(segmentB, bitRead(tablica[godzinaJednostka], 1));
      digitalWrite(segmentC, bitRead(tablica[godzinaJednostka], 2));
      digitalWrite(segmentD, bitRead(tablica[godzinaJednostka], 3));
      digitalWrite(segmentE, bitRead(tablica[godzinaJednostka], 4));
      digitalWrite(segmentF, bitRead(tablica[godzinaJednostka], 5));
      digitalWrite(segmentG, bitRead(tablica[godzinaJednostka], 6));
      digitalWrite(wspolne2, HIGH);
      aktywnaCyfra = 3;
      break;
    case 3:  // Dla dziesiątek minut.
      digitalWrite(segmentA, bitRead(tablica[minutaDziesiatka], 0));
      digitalWrite(segmentB, bitRead(tablica[minutaDziesiatka], 1));
      digitalWrite(segmentC, bitRead(tablica[minutaDziesiatka], 2));
      digitalWrite(segmentD, bitRead(tablica[minutaDziesiatka], 3));
      digitalWrite(segmentE, bitRead(tablica[minutaDziesiatka], 4));
      digitalWrite(segmentF, bitRead(tablica[minutaDziesiatka], 5));
      digitalWrite(segmentG, bitRead(tablica[minutaDziesiatka], 6));
      digitalWrite(wspolne3, HIGH);
      aktywnaCyfra = 4;
      break;
    case 4:  // Dla jednostek minut.
      digitalWrite(segmentA, bitRead(tablica[minutaJednostka], 0));
      digitalWrite(segmentB, bitRead(tablica[minutaJednostka], 1));
      digitalWrite(segmentC, bitRead(tablica[minutaJednostka], 2));
      digitalWrite(segmentD, bitRead(tablica[minutaJednostka], 3));
      digitalWrite(segmentE, bitRead(tablica[minutaJednostka], 4));
      digitalWrite(segmentF, bitRead(tablica[minutaJednostka], 5));
      digitalWrite(segmentG, bitRead(tablica[minutaJednostka], 6));
      digitalWrite(wspolne4, HIGH);
      aktywnaCyfra = 1;
      break;
  }
}

void zwiekszCzas() {                 // Procedura zwiększająca czas z uwzględnieniem przeładowań jednostek.
  minutaJednostka++;                 // Zwiększ licznik jednostek minut.
  if (minutaJednostka == 10) {       // Jeśli osiągnęły 10...
    minutaJednostka = 0;             // Zeruj je oraz...
    minutaDziesiatka++;              // Zwiększ liczbę dziesiątek minut.
    if (minutaDziesiatka == 6) {     // Jeśli osiągnęły 6...
      minutaDziesiatka = 0;          // Zeruj je oraz...
      godzinaJednostka++;            // Zwiększ licznik jednostek godzin.
      if (godzinaJednostka == 10) {  // Jeśli osiągnęły 10...
        godzinaJednostka = 0;        // Zeruj je oraz...
        godzinaDziesiatka++;         // Zwiększ liczbę dziesiątek godzin.
      }
    }
  }
  if (godzinaDziesiatka == 2 && godzinaJednostka == 4) {  // Jeśli mamy godzinę 24...
    godzinaDziesiatka = 0;                                // Zeruj dziesiątki godzin oraz...
    godzinaJednostka = 0;                                 // Zeruj jednostki godzin.
  }
}

void zmniejszCzas() {                    // Procedura zmniejszająca czas z uwzględnieniem przeładowań jednostek.
  minutaJednostka--;                     // Zmniejsz licznik jednostek minut.
  if (minutaJednostka == 255) {          // Jeśli osiągnęły 255...
    minutaJednostka = 9;                 // Ustaw dziewiątkę oraz...
    minutaDziesiatka--;                  // Zmniejsz liczbę dziesiątek minut.
    if (minutaDziesiatka == 255) {       // Jeśli osiągnęły 255...
      minutaDziesiatka = 5;              // Ustaw piątkę oraz...
      godzinaJednostka--;                // Zmniejsz licznik jednostek godzin.
      if (godzinaJednostka == 255) {     // Jeśli osiągnęły 255...
        godzinaJednostka = 9;            // Ustaw dziewiątkę  oraz...
        godzinaDziesiatka--;             // Zmniejsz liczbę dziesiątek godzin.
        if (godzinaDziesiatka == 255) {  // Jeśli osiągnęły 255...
          godzinaDziesiatka = 2;         // Ustaw dwójkę dla dziesiątki godzin oraz...
          godzinaJednostka = 3;          // Ustaw trójkę dla jednostki godzin.
        }
      }
    }
  }
}

Ale przecież procedura zegarowa na przerwaniach już to robi, więc wystarczy ten fragment wyjąć i osadzić w podprogramie, by służył obu blokom. Z tym, że oczywiście w przerwaniach odwołujemy się tam raz na minutę, a w bloku regulacyjnym – zgodnie z algorytmem obsługi klawiatury.

void zwiekszCzas() {                 // Procedura zwiększająca czas z uwzględnieniem przeładowań jednostek.
  minutaJednostka++;                 // Zwiększ licznik jednostek minut.
  if (minutaJednostka == 10) {       // Jeśli osiągnęły 10...
    minutaJednostka = 0;             // Zeruj je oraz...
    minutaDziesiatka++;              // Zwiększ liczbę dziesiątek minut.
    if (minutaDziesiatka == 6) {     // Jeśli osiągnęły 6...
      minutaDziesiatka = 0;          // Zeruj je oraz...
      godzinaJednostka++;            // Zwiększ licznik jednostek godzin.
      if (godzinaJednostka == 10) {  // Jeśli osiągnęły 10...
        godzinaJednostka = 0;        // Zeruj je oraz...
        godzinaDziesiatka++;         // Zwiększ liczbę dziesiątek godzin.
      }
    }
  }
  if (godzinaDziesiatka == 2 && godzinaJednostka == 4) {  // Jeśli mamy godzinę 24...
    godzinaDziesiatka = 0;                                // Zeruj dziesiątki godzin oraz...
    godzinaJednostka = 0;                                 // Zeruj jednostki godzin.
  }
}

Drugi z przycisków potrzebuje procedury lustrzanej, to znaczy zmniejszającej wartości i pilnującej zejść poniżej zera.

void zmniejszCzas() {                    // Procedura zmniejszająca czas z uwzględnieniem przeładowań jednostek.
  minutaJednostka--;                     // Zmniejsz licznik jednostek minut.
  if (minutaJednostka == 255) {          // Jeśli osiągnęły 255...
    minutaJednostka = 9;                 // Ustaw dziewiątkę oraz...
    minutaDziesiatka--;                  // Zmniejsz liczbę dziesiątek minut.
    if (minutaDziesiatka == 255) {       // Jeśli osiągnęły 255...
      minutaDziesiatka = 5;              // Ustaw piątkę oraz...
      godzinaJednostka--;                // Zmniejsz licznik jednostek godzin.
      if (godzinaJednostka == 255) {     // Jeśli osiągnęły 255...
        godzinaJednostka = 9;            // Ustaw dziewiątkę  oraz...
        godzinaDziesiatka--;             // Zmniejsz liczbę dziesiątek godzin.
        if (godzinaDziesiatka == 255) {  // Jeśli osiągnęły 255...
          godzinaDziesiatka = 2;         // Ustaw dwójkę dla dziesiątki godzin oraz...
          godzinaJednostka = 3;          // Ustaw trójkę dla jednostki godzin.
        }
      }
    }
  }
}

Jak wiadomo, zmniejszone o jeden zero dla zmiennej typu byte daje 255, więc takie wartości wstawiamy w warunkach. Nie musimy teraz badać osobliwości typu godzina 24, bo takie zjawisko nie wystąpi. Ale w przypadku zejścia poniżej zera dziesiątek godzin musimy ustawić nie tylko dwójkę dla nich, ale także trójkę dla jednostek godzin – żeby po godzinie zero pojawiała się dwudziesta trzecia.

Wartości opóźnień wyniosłem na początek, w blok deklaracji, by każdy sobie je dobrał do własnego widzimisię.

const byte opoznienieDlugie = 100;  // Współczynniki autorepetycji klawiatury.
const byte opoznienieKrotkie = 5;

Po puszczeniu przycisków wypada jeszcze dołożyć dłuższą pętlę opóźniającą, by przyciski się nam nie „kleiły”, podczas końcowych, pojedynczych zmian. Teraz już możemy sobie regulować czas i właściwie podstawowa forma zegara jest już gotowa.

A co z naszym statycznym dotąd dwukropkiem? W zegarach zwykł on migać więc… niech miga. Na początek klasycznie, sekundę ciemny, sekundę – jasny. W tym celu dołożymy nową zmienną – sekundy.

void przerwanie() {                                    // Tutaj lądujemy za każdym razem, gdy wewnętrzny timer odliczy 2500 mikrosekund.
  licznikPrzerwan++;                                   // Zwiększ licznik ramek odmierzających 2,5 ms
  if (licznikPrzerwan == 400) {                        // Jeśli osiągnął 400 (2,5 ms * 400 = 1 sekunda)...
    licznikPrzerwan = 0;                               // Zeruj go oraz...
    digitalWrite(dwukropek, !digitalRead(dwukropek));  // Zmień stan dwukropka.
    sekundy++;                                         // Zwiększaj liczbę sekund.
    if (sekundy == 60) {                               // Jeśli osiągnął 60...
      sekundy = 0;                                     // Zeruj go oraz...
      zwiekszCzas();                                   // Zwiększ czas.
    }
  }

Licznik przerwań nie będzie już liczyć 24000 odcinków 2,5 milisekundowych, a jedynie 400. Bo 400 razy 2 i pół daje 1000, czyli sekundę. Gdy minie właśnie sekunda, dwukropek zaprzeczy sobie. Ale trzeba jeszcze dołożyć brakujące 60 sekund na obsługę zmiany minut i reszty zegara, więc należy dopisać kolejny warunek, analogiczny do już poznanych, o przepełnieniu równym 60. Tak na marginesie, jeśli ktoś chciałby mieć zegarek z sekundnikiem, musiałby dołożyć jeszcze dwie cyfry i ich obsługę, a zmienną już ma.

Po skompilowaniu programu możemy się cieszyć mrugającym sekundnikiem. Czas więc na zrobienie z zegarka budzika, bo dopiero coś takiego będzie cieszyć. Ale o tym 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