[109] Budujemy ze zrozumieniem - elektroniczny budzik cz. 3

[109] Budujemy ze zrozumieniem - elektroniczny budzik cz. 3

Przypomnę, że kontynuujemy temat rozpoczęty w poprzednim artykule. Na początek chciałbym rzec słówko o pewnej pułapce przerwań. Jak wiadomo, wpada się tutaj cyklicznie – u mnie co dwie i pół milisekundy. Należy bezwzględnie zadbać o to, by program siedzący wewnątrz wyrabiał się w czasie krótszym i to znacznie, bo po pierwsze – musi pozostać rezerwa na obsługę głównej pętli, po drugie – zegarek napędzany jest licznikiem wejść w przerwaniach, więc jeśli któreś zostaną pominięte, licznik będzie pracował wolniej i zegar będzie się spóźniał. Wykluczone są na przykład jakiekolwiek pętle opóźniające w obsłudze przerwań, a także rozbudowane warunki, których wykonywanie trwałoby zbyt długo, choćby wejścia w nie odbywały się regularnie.


Jak sprawdzić czy aby nie zbliżamy się do punktu krytycznego? Można policzyć jak długo będzie się wykonywał kod, ale w przypadku Arduino nie jest to takie proste. Jest inny sposób, zadziwiająco miły, choć zgrubny. Użyjemy do tego oscyloskopu.

#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 test = A5;

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 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);

  pinMode(test, OUTPUT);

  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ń (a więc też licznik sekund).
    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;
    delay(opoznienieDlugie);
  }
}

void przerwanie() {  // Tutaj lądujemy za każdym razem, gdy wewnętrzny timer odliczy 2500 mikrosekund.
  digitalWrite(test, LOW);

  licznikPrzerwan++;               // Zwiększ licznik ramek odmierzających 2,5 ms
  if (licznikPrzerwan != 24000) {  // Jeśli osiągnął 24000 (2,5 ms * 400 * 60 sekund)...
    licznikPrzerwan = 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;
  }
  digitalWrite(test, HIGH);
}

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.
        }
      }
    }
  }
}

Użyję tutaj niewykorzystany dotąd pin A5, który nazwę test. Podczas pracy w pętli przerwań będzie mieć stan niski, a poza nią – wysoki. Stosunek stanu wysokiego do niskiego będzie świadczył o rezerwie mocy obliczeniowej. Jeśli teraz spojrzymy na przebieg sygnału występującego na tym pinie, zobaczymy, że stan niski jest tylko ledwo zaznaczony, a więc zapasu jest ogrom.

Ale żeby mieć bardziej obiektywy osąd, sprawmy by wszystkie warunki procedury zegarowej za każdym razem były sprawdzane. Tymczasowo wystarczy zanegować ich badanie i trzeba zmniejszyć licznik ramek do minimum. Coś tam lekko drgnęło, ale nadal poziom niski to praktycznie punkcik.

Dopisałem jak najbardziej zabronioną pętlę opóźniającą o wartości 100 us:

void przerwanie() {  // Tutaj lądujemy za każdym razem, gdy wewnętrzny timer odliczy 2500 mikrosekund.
  digitalWrite(test, LOW);
  delayMicroseconds(100);

I oczywiście można ją teraz zobaczyć – zajmuje 1/25 wypełnienia.

Po zwiększeniu jej do milisekundy proces niebezpiecznie zawładnie znaczną częścią mocy.

Przy 3 ms pacjent umarł, pozostając w nieskończonej pętli przerwań. I do tego nigdy nie wolno dopuścić.

Oczywiście nie ma potrzeby używania tak starożytnego narzędzia, jednak przy okazji chciałem pokazać, że stare oscyloskopy bywają jeszcze przydatne, tym bardziej, że tu nie interesują nas jednostki, a zgrubna wizualizacja. W razie potrzeby można użyć też zwykłej diody świecącej, która będzie świecić coraz słabiej w ramach ubywania rezerw mocy. Ale tu należy być ostrożnym, bo wzrok potrafi oszukiwać. W kolejnym artykule przedstawię propozycję wygodnego interfejsu regulacji zegarów, bo ten aspekt bardzo często jest traktowany po macoszemu.

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