[110] Budujemy ze zrozumieniem - elektroniczny budzik cz. 4
![[110] Budujemy ze zrozumieniem - elektroniczny budzik cz. 4](/_next/image?url=https%3A%2F%2Farduino.pl%2Fimgproxy%2FkaEhS3rBxjBwmcBWcHHwdoYcF4QvZJ_7_5glVs3qauQ%2Ff%3Awebp%2Fw%3A1200%2FbG9jYWw6Ly8vaW1hZ2VzLzAtNjgzLzFlYTA2Lzg5OWFhLzAtNjgzMWVhMDY4OTlhYTI1MzI4NzUxOC5wbmc%3D.webp&w=3840&q=75)
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.