[158] Gadający woltomierz cz. 4

Skoro już opanowaliśmy generację dźwięków z plików znajdujących się na karcie SD, wróćmy do edytora audio, załadujmy plik z poprzedniego artykułu i mozolnie wyeksportujemy wszystkie 38 plików – przeczytanych liczebników - zaznaczając uprzednio podzielone fragmenty. Przypomnę całą listę dla liczb od zera do tysiąca:


0.afm - zero

1.afm - jeden

2.afm - dwa

3.afm - trzy

4.afm - cztery

5.afm - pięć

6.afm - sześć

7.afm - siedem

8.afm - osiem

9.afm - dziewięć

10.afm - dziesięć

11.afm - jedenaście

12.afm - dwanaście

13.afm - trzynaście

14.afm - czternaście

15.afm - piętnaście

16.afm - szesnaście

17.afm - siedemnaście

18.afm - osiemnaście

19.afm - dziewiętnaście

20.afm - dwadzieścia

30.afm - trzydzieści

40.afm -czterdzieści

50.afm - pięćdziesiąt

60.afm - sześćdziesiąt

70.afm - siedemdziesiąt

80.afm - osiemdziesiąt

90.afm - dziewięćdziesiąt

100.afm - sto

200.afm - dwieście

300.afm - trzysta

400.afm - czterysta

500.afm - pięćset

600.afm - sześćset

700.afm - siedemset

800.afm - osiemset

900.afm - dziewięćset

1000.afm - tysiąc

Ważne, by miejsca podziału pliku, który nagrałem na potrzeby poprzedniego artykułu, miały zerową głośność, inaczej usłyszymy stuki między cyframi. Zbiorek taki nazwałem według widocznego wyżej klucza, za pomocą cyfr i można go ściągnąć z załącznika pod artykułem. Jest w tym chyba tak oczywista logika, że tłumaczyć tego nie muszę. A równocześnie bardzo to uprości działania algorytmu wybierającego składowe do wypowiadania fraz.

Skoro tylko przegramy wszystko na kartę, pozostanie nam stworzyć nowy szkic. I – nie ukrywam – jest on dość skomplikowany, ale przez to w miarę niewielki na to, co robi. Bierzmy się więc za analizę. Najpierw wstęp i główna pętla.

#include <SimpleSDAudio.h>  // Biblioteka odtwarzania sampli z karty SD.

int numberToSpeak;          // Liczba, którą należy odtworzyć.
byte digitIndex = 0;        // Pozycja aktualnie odtwarzanej cyfry odtwarzanej liczby (0...3)
bool playing = false;       // Ustawiony, jeśli trwa odtwarzanie.
bool pressed = false;       // Ustawiony, jeśli przycisk wyzwolił odtwarzanie.
const byte button = 8;      // Port przycisku.
const byte voltmeter = A1;  // Adres portu pomiaru napięcia.
char file[12];              // Tablica dla bieżącej nazwy odtwarzanego pliku.

void setup() {
  pinMode(button, INPUT_PULLUP);                                            // Deklaruj port przycisku jako wejście.
  SdPlay.init(SSDA_MODE_FULLRATE | SSDA_MODE_MONO | SSDA_MODE_AUTOWORKER);  // Inicjacja biblioteki.
}
void loop() {
  if (digitalRead(button) == HIGH && !pressed) {  // Jeśli wciśnięto przycisk i nie był on wciśnięty wcześniej...
    speakNumber(analogRead(voltmeter));           // Przejdź do procedury przeczytania zmierzonej wartości.
    pressed = true;                               // Ustaw flagę wciśnięcia przycisku.
  }
  if (digitalRead(button) == LOW) {  // Jeśli przycisk został puszczony...
    pressed = false;                 // Czyść flagę wciśnięcia przycisku.
  }
  if (playing && SdPlay.isStopped()) {  // Jeśli zakończyło się odtwarzanie kolejnej cyfry z liczby oraz...
    if (++digitIndex < 4) {             // Jeśli wciąż pozostały jakieś liczby do odtworzenia...
      playNextDigit();                  // Odtwórz kolejne cyfry.
    } else {                            // Jeśli wszystko zostało odtworzone...
      playing = false;                  // Zeruj flagę odtwarzania.
    }
  }
}

Oto lista zmiennych, kolejno: numberToSpeak – liczba, którą Arduino będzie miało wypowiedzieć. digitIndex – Kolejna cyfra z tej liczby (od jednej, dla liczb mniejszych od dziesięciu do czterech, dla liczb większych lub równych tysiąc). Dwie flagi: playing – ustawiona podczas odtwarzania sampli i pressed – po wciśnięciu przycisku odtwarzania. W końcu port przycisku button – tutaj jest to środkowy mikroswitch na płytce edukacyjnej TME i voltmeter – wejście napięcia mierzonego. Na potrzeby testowania programu będę mierzył napięcie ślizgacza pokładowego potencjometru. Na końcu mamy tablicę file, która przechowuje nazwy wybieranych do odtwarzania plików.

W części wstępnej nie ma nic ciekawego, inicjacja biblioteki i deklaracja portu przycisku. Główna pętla rozpoczyna się od analizy przycisku button. Jeśli go wciśnięto, a był wcześniej puszczony, należy zmierzyć napięcie i z wynikiem udać się do podprogramu speakNumber obsługującego odtwarzanie dźwięków (listing znajduje się niżej). Zaraz tam przejdziemy, dokończmy analizę pętli głównej. Najpierw ustawia się flagę wciśnięcia przycisku pressed, żeby nie doszło do lawinowego autopowielania, gdyby przycisk był trzymany dłużej.

Jeśli odtwarzanie dźwięku skończy się – o czym informuje flaga playing oraz funkcja biblioteki, należy przejść do odtworzenia kolejnej cyfry liczby zmierzonej, których mogą być cztery (od zera do trzech). Jeśli wszystko zostanie odtworzone, możemy mogli wyzerować flagę zezwolenia obsługi przycisku pressed, czyli pozwolić na kolejny odczyt.

void speakNumber(int x) {
  numberToSpeak = x;  // Liczba, którą należy odtworzyć.
  digitIndex = 0;     // Pozycja aktualnie odtwarzanej cyfry to setki.
  playNextDigit();    // Odtwórz aktualną cyfrę.
  playing = true;     // Ustaw flagę odtwarzania.
}
void playNextDigit() {
  SdPlay.stop();                             // Zatrzymaj bieżące odtwarzanie.
  file[0] = '\\0';                            // Wyczyść tablicę z nazwą odtwarzanego pliku.
  byte digit000 = numberToSpeak / 100;       // Wyciągnij z liczby do odtworzenia: setki...
  byte digit00 = (numberToSpeak / 10) % 10;  // Dziesiątki...
  byte digit0 = numberToSpeak % 10;          // Jednostki.

  switch (digitIndex) {                       // Dopasuj plik do cyfry i pozycji w liczbie do odtworzenia.
    case 0:                                   // Setki.
      if (digit000)                           // Jeśli są...
        sprintf(file, "%u00.afm", digit000);  // Odtwarzaj odpowiedni plik.
      break;
    case 1:                                                                // Dziesiątki oraz "nastki".
      if (digit00) {                                                       // Jeśli są...
        byte val = (digit00 == 1 && digit0) ? 10 + digit0 : digit00 * 10;  // Ustaw pułapkę na tzw. "nastki" (11-19).
        sprintf(file, "%u.afm", val);                                      // Odtwarzaj odpowiedni plik.
      }
      break;
    case 2:                                     // Jednostki.
      if (digit0 && (digit00 > 1 || !digit00))  // Jeśli liczba >20 lub <10
        sprintf(file, "%u.afm", digit0);        // Odtwarzaj odpowiedni plik.
      break;
    case 3:                     // Zero
      if (!numberToSpeak)       // Jeśli liczba do otworzenia to zero...
        strcpy(file, "0.afm");  // Odtwórz zero.
      break;
  }
  if (!file[0] || !SdPlay.setFile(file)) {  // Jeśli nie ma pliku lub pojawia się błąd jego odczytu...
    if (++digitIndex < 4)                   // I jeśli jest coś do odczytania...
      playNextDigit();                      // Odczytaj kolejną cyfrę.
    return;
  }
  SdPlay.play();  // Odtwarzaj bieżący plik.
}

Jak wygląda konwersja liczby na składowe? Podprogram odzyskuje liczbę, ładując ją do zmiennej x, przy czym wchodzi tu tylko na początku analizy. Raz jeszcze zerowany jest adres cyfry digitIndex, gdyby ktoś wciskał przycisk zbyt często. Dzięki temu czytanie komunikatu jest przerywane natychmiast, a nowy czytany jest od początku. Ustawiamy flagę aktywności odtwarzania playing i przechodzimy do wspólnego podprogramu playNextDigit dla kolejnych cyfr. Ten zaczyna się zatrzymaniem odtwarzania – gdyby przycisk został wciśnięty przed czasem. Następnie czyści się tablicę z nazwą pliku file, by nie zostały w niej żadne śmieci. Teraz tworzą się trzy zmienne zaczynające się od słowa digit, w które wchodzą cyfry setek, dziesiątek i jednostek. Przechodzimy do algorytmu dobierającego pliki do liczby. Mamy tu cztery etapy.

switch (digitIndex) {                       // Dopasuj plik do cyfry i pozycji w liczbie do odtworzenia.
  case 0:                                   // Setki.
    if (digit000)                           // Jeśli są...
      sprintf(file, "%u00.afm", digit000);  // Odtwarzaj odpowiedni plik.
    break;

Pierwszy to odczyt setek. Jak widać, plik pobieramy, manipulując jego nazwą, czyli podmieniając pierwszy znak pliku, który jest zgodny z wyłuskaną przed chwilą cyfrą. To jest proste, schody zaczynają się na kolejnym etapie.

case 1:                                                                // Dziesiątki oraz "nastki".
  if (digit00) {                                                       // Jeśli są...
    byte val = (digit00 == 1 && digit0) ? 10 + digit0 : digit00 * 10;  // Ustaw pułapkę na tzw. "nastki" (11-19).
    sprintf(file, "%u.afm", val);                                      // Odtwarzaj odpowiedni plik.
  }
  break;

Tutaj musimy ustawić zawiły wzór, który osobno potraktuje dziesiątki od dwudziestu w górę, a inaczej zakres od 11 do 19, odpowiednio przekształcając nazwę pliku do pobrania i odtworzenia.

case 2:                                     // Jednostki.
  if (digit0 && (digit00 > 1 || !digit00))  // Jeśli liczba >20 lub <10
    sprintf(file, "%u.afm", digit0);        // Odtwarzaj odpowiedni plik.
  break;

W końcu jednostki czytane są, o ile nie ma zera ani liczb od 11 do 19.

case 3:                     // Zero
  if (!numberToSpeak)       // Jeśli liczba do otworzenia to zero...
    strcpy(file, "0.afm");  // Odtwórz zero.
  break;

Ostatnim etapem jest czytanie zera i ma to miejsce tylko wtedy, gdy liczba do przeczytania równa jest zero.

if (!file[0] || !SdPlay.setFile(file)) {  // Jeśli nie ma pliku lub pojawia się błąd jego odczytu...
  if (++digitIndex < 4)                   // I jeśli jest coś do odczytania...
    playNextDigit();                      // Odczytaj kolejną cyfrę.
  return;
}
SdPlay.play();  // Odtwarzaj bieżący plik.

Ostatnimi elementami są: pułapka na popsuty bądź nieistniejący plik. Dzięki temu program nie zawiesi się, gdyby nie mógł czegoś odczytać. W końcu zwiększa się adres bieżącej pozycji w liczbie do przeczytania i odczytuje ją – jeśli to nie koniec. Ostatnia instrukcja to uruchomienie właściwego odczytywania uprzednio załadowanego pliku.

Uff, po uruchomieniu możemy sobie kręcić potencjometrem i słuchać, jakie napięcie zostało wybrane. Jeśli załadujemy zestaw z załącznika, zauważmy, że tempo odczytów jest niespieszne. Jeśli ktoś chciałby to przyspieszyć, można oczywiście nagrać szybsze komunikaty, ale można też włączyć „tryb krasnala” zamieniając słowo MONO w STEREO w linii inicjacji biblioteki. Oczywiście nie będzie stereofonii, ale biblioteka rozdzieli strumień na dwa porty, wysyłając na nasz co drugi bajt, co da nam efekt czytającego szybko krasnoludka. Miłej zabawy.

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