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

0.png

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.

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2

Inne artykuły z tej kategorii

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2026 arduino.pl