Święta z arduino, czyli gramy kolędy

W tym roku moja druga (ładniejsza) połówka wysłuchała moich próśb i zamiast kolejnych skarpetek, kapci czy kolejnych perfum, tudzież innych utensyliów znalazłem pod drzewkiem starter kit z arduino uno. Chyba się starzeję…

W związku z powyższym poczułem się zobowiązany (wobec drzewka), stąd pomysł na przetestowanie nowej zabawki w sposób odpowiedni do panującego nastroju 😉 Całość powstała w jakieś 30min jeszcze w wigilijny wieczór…

Zaczynamy!

Koncepcja prosta – ma grać i świecie, a najlepiej jak by grało świątecznie i świeciło choinkowo.

Zacznijmy od układu, oprócz arduino został wykorzystany 8-bitowy rejestr przesuwny z zatrzaskiem 74HC595, dzięki czemu dane do linijki LED możemy przesyłać szeregowo a co za tym idzie oszczędzamy wyjścia procesora. Inne wykorzystane elementy to trochę rezystorów, 8 LED`ów, buzzer piezo i dwa przyciski. Pełna lista potrzebnych elementów poniżej:

  • Arduino UNO
  • Płytka stykowa + kabelki połączeniowe
  • Rejestr 74HC595
  • 9 rezystorów 220OHM
  • 2 rezystory 10k
  • 2 przełączniki chwilowe
  • buzzer piezo

Dokładniejszy opis rejestru 74hc595 można znaleźć w nocie katalogowej np. Tutaj. Natomiast opis wyprowadzeń układu wygląda jak na poniższym rysunku:

wyprowadzenia 74hc595

wyprowadzenia 74hc595

Elektronika

Na poniższym rysunku widzimy schemat elektryczny układu. Wyjścia rejestru połączone z diodami led (D1 – D8) poprzez rezystory ograniczające prąd (R1 – R8). Pin 10 master reset w negacji, podłączony do plusa zasilania (czyli reset nie aktywny – negacja), Pin 13 output enable, ustawiony na off czyli podany stan niski (minus zasilania), a co za tym idzie wyjście Qn (pin 9) nie podłączone.

Dalej mamy dwa przyciski (S1 i S2) dające poziom wysoki po wciśnięciu oraz dwa rezystory 10k (R9 i R10) które utrzymują stan niski na wejściu mikrokontrolera w momencie gdy przyciski nie są naciśnięte. Jest jeszcze buzzer piezo (U2), podłączony bezpośrednio przez rezystor (R11) ograniczający prąd do wyjścia (PWM) mikrokontrolera.

Schemat układu

Schemat układu

Działa to wszystko tak, że na wejście ST_CP rejestru (storage register clock input, letchPin w kodzie, pin 12 układu) podawany jest stan niski, co powoduje przejście rejestru (zatrzasku) w stan nasłuchiwania nowej porcji danych (taki globalny zegar, raz, raz, raz), która jest podawana szeregowo bit po bicie na wejście DS (serial data input, pin 14 układu, w kodzie jako dataPin), w rytm sygnału zegarowego podawanego na wejście SH_CP (shift register clock input, pin 11 układu, w kodzie jako clockPin). Następnie na wejście ST_CP podawany jest z powrotem stan wysoki co powoduje zamknięcie się zatrzasku i „pamiętanie” podanego bajtu danych przesłanego do układu.

Przyciski (służące do zmiany szybkości odgrywanej melodyjki) S1 i S2 są podłączone bezpośrednio do wejść cyfrowych mikrokontrolera i po naciśnięciu dają stan wysoki natomiast w momencie gdy nie są używane dają stan niski na wejściach poprzez rezystory bocznikujące R9 i R10.

Głośniczek piezzo (U2) jest natomiast podłączony bezpośrednio do wyjścia PWM mikrokontrolera.
Cały zmontowany układ na płytce stykowej prezentuje się tak:

uklad

uklad

Program

Program obsługujący jest bardzo prosty. Funkcje odgrywające poszczególne tony (nutki) zostały pożyczone z tutoriala Melody z strony arduino.cc. Zostały jednak one troszkę przerobione, a mianowicie została dodana obsługa nowej nutki! Jest do dźwięk b z niższej oktawy.

Cały kod wygląda jak poniżej:

//Pin connected to ST_CP of 74HC595
int latchPin = 8;
//Pin connected to SH_CP of 74HC595
int clockPin = 12;
//Pin connected to DS of 74HC595
int dataPin = 11;
//Pin connected to UP button
int btnA = 2;
//Pin connected to DOWN button
int btnB = 3;
//pin connected to speaker
int speakerPin = 6;

// the number of notes
int length = 62;
// a space represents a rest
char notes[] = "cBcdedefgagcBcdedefgagCggagfefffgfedefgedcCggagfefffgfedefgedc";
//note length: 1 - 1/8, 2 - 1/4, 4 - 1/2
int beats[] = {1,1,1,1,1,1,1,1,2,2,4,1,1,1,1,1,1,1,1,2,2,4,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,2,4,2,2,4,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,2,4,2,2,4};
int tempo = 300; //tempo

void setup() {
//set pin mode, 4 output and 2 input
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(btnA, INPUT);
pinMode(btnB, INPUT);
pinMode(speakerPin, OUTPUT);
}

void loop() {
for (int i = 0; i < length; i++) {
if(digitalRead(btnA)==HIGH)
tempo = tempo + 10;
else if(digitalRead(btnB)==HIGH)
tempo = tempo - 10;

if (notes[i] == ' ') {
delay(beats[i] * tempo); // rest
} else {
playNote(notes[i], beats[i] * tempo);
}

// pause between notes
delay(tempo / 2);
}

}

void setData(int j)
{
byte dataA = 1;
dataA = 1 << j;
//ground latchPin and hold low for as long as you are transmitting
digitalWrite(latchPin, LOW);
//move 'em out
shiftOut(dataPin, clockPin, MSBFIRST, dataA);
//return the latch pin high to signal chip that it
//no longer needs to listen for information
digitalWrite(latchPin, HIGH);
}

void playTone(int tone, int duration) {
for (long i = 0; i < duration * 1000L; i += tone * 2) {
digitalWrite(speakerPin, HIGH);
delayMicroseconds(tone);
digitalWrite(speakerPin, LOW);
delayMicroseconds(tone);
}
}

void playNote(char note, int duration) {
char names[] = {'B','c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };
int tones[] = { 2028, 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956 };

// play the tone corresponding to the note name
for (int i = 0; i < 9; i++) {
if (names[i] == note) {
setData(i);
playTone(tones[i], duration);
}
}
}

Na samym początku mamy inicjalizację zmiennych które reprezentują poszczególne (wykorzystywane przez nas) piny mikrokontrolera.

//Pin connected to ST_CP of 74HC595
int latchPin = 8;
//Pin connected to SH_CP of 74HC595
int clockPin = 12;
//Pin connected to DS of 74HC595
int dataPin = 11;
//Pin connected to UP button
int btnA = 2;
//Pin connected to DOWN button
int btnB = 3;
//pin connected to speaker
int speakerPin = 6;
  • latchPin – pin do którego podłączona jest ‘blokada zatrzasku’
  • clockPin – pin sygnału zegarowego dla przesyłanych danych szeregowych
  • dataPin – pin danych (tutaj przesyłamy 8 bitów danych)
  • btnA – pin do którego jest podłączony pierwszy przycisk
  • btnB – pin do którego podłączony jest drugi przycisk
  • speakerPin – pin do którego podłączony jest przetwornik piezoelektryczny

Dalej mamy tablicę która difiniują graną melodyjkę (tak, tak, to są nutki!) notes, oraz drugą tablicę beats która definiuje długość nutki, i dalej zmienna tempo która określa jak szybko ma być grany „utwór”. Zmienna length którą widać na początku opisywanego bloku kodu określa ilość nutek które mają zostać zagrane z tablicy notes.

// the number of notes
int length = 62;
// a space represents a rest
char notes[] = "cBcdedefgagcBcdedefgagCggagfefffgfedefgedcCggagfefffgfedefgedc";
//note length: 1 - 1/8, 2 - 1/4, 4 - 1/2
int beats[] = {1,1,1,1,1,1,1,1,2,2,4,1,1,1,1,1,1,1,1,2,2,4,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,2,4,2,2,4,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,2,4,2,2,4};
int tempo = 300; //tempo

Dalej znajduję się funkcja setup() (dla tych którzy nie wiedzą, jest ona uruchamiana zanim program wejdzie w swoją główną pętle), w której ustawiamy piny jako odpowiednie wejścia i wyjścia

  • latchPin, clockPin, dataPin oraz speakerPin ustawione jako wyjścia
  • btnA i btnB ustawione jako wejścia

Następnie mamy główną pętle programu która znajduję się w funkcji loop(). Kod wygląda tak:

void loop() {
for (int i = 0; i < length; i++) {
if(digitalRead(btnA)==HIGH)
tempo = tempo + 10;
else if(digitalRead(btnB)==HIGH)
tempo = tempo - 10;

if (notes[i] == ' ') {
delay(beats[i] * tempo); // rest
} else {
playNote(notes[i], beats[i] * tempo);
}

// pause between notes
delay(tempo / 2);
}

}

Znajduje się tutaj pętla for która staruje od wartości 0 do wielkości równej ilości nutek length. W ten sposób odgrywamy po kolei wszystkie dźwięki zapisane w tablicy notes. Dodatkowo w pętli znajdują się warunki sprawdzające czy jeden z przycisków został naciśnięty i jeśli tak się stało to zostaje odpowiednio zwiększona lub zmniejszona wartość zmiennej tempo która jest odpowiedzialna za to jak szybko „utwór” jest grany. Dalej jest jeszcze jeden warunek, sprawdzający czy aktualna nutka nie jest pauzą, i jeśli tak jest (pauza == pusty znak) wówczas pauzujemy na czas określony jako iloczyn tempa i długości nutki, jeśli jednak aktualna wartość nie jest pauzą, zostaje odtworzony odpowiedni dźwięk za pomocą funkcji playNote() której (oraz playTone()) nie będę opisywać ponieważ zostały one wyczerpująco opisane w tutorialu na stronie arduino o którym wspominałem na wstępie. Funkcja playNote() została jedynie troszkę zmodyfikowana, a mianowicie została dodana nowa nutka B (dolne b), której wartość jest równa ½ wartości normalnego b, oraz zostało dodane wywołanie funkcji setData()

void setData(int j)
{
byte dataA = 1;
dataA = 1 << j;
//ground latchPin and hold low for as long as you are transmitting
digitalWrite(latchPin, LOW);
//move 'em out
shiftOut(dataPin, clockPin, MSBFIRST, dataA);
//return the latch pin high to signal chip that it
//no longer needs to listen for information
digitalWrite(latchPin, HIGH);
}

która wysyła odpowiedni bajt danych do rejestru zapalając diodę reprezentującą dany (aktualnie grany dźwięk). Jak widać na powyższym kodzie, najpierw tworzymy odpowiedni bajt który ma zostać wysłany. Zmienna lokalna dataA której przypisujemy wartość 1 (binarnie 00000001), a następnie shiftujemy ją o odpowiednią przekazaną parametrem wartość w lewo. I tak np. Jeśli na wejściu będziemy mieli wartość 3 to po shifcie bajt zapisany w zmiennej dataA będzie wyglądał tak: 00001000.

Dalej zapisujemy stan niski na wyjściu latchPin który zwalnia zatrzask, zapisujemy dane szeregowo do dataPin podając sygnał zegarowy dla danych na wyjście clockPin. Po przesłaniu całych danych, ustawiamy z powrotem stan wysoki na wyjściu latchPin i tym samym blokujemy zatrzask.

Efekt końcowy

Efekt końcowy prezentuje się jak na poniższym filmiku.

Jak widać melodyjka jest odgrywana, diody zapalają się od powiedniej kolejności w stosunku do granych dźwięków (od lewej b,c,d,e,f,g,a,h), a po naciśnięciu jednego z przycisków tempo utworu jest zwiększane lub zmniejszane o 10ms.

Możliwości

Opisany układzik, można bardzo łatwo zmodyfikować tak aby zagrał jakąkolwiek inną melodyjkę. Wszystko co musimy zrobić aby tego dokonać to wprowadzić nową definicję dla nutek, ich wartości rytmicznych oraz długość całości (ilość użytych nutek) a także tempo wyrażone w milisekundach na nutę. Wartości znajdujące się w tablicy beats to tak naprawdę mnożnik długości trwania dźwięku. Przykładowo jeśli mamy taką definicję:

int length = 1;
char notes[] = "c";
int beats[] = {1};
int tempo = 300;

Wówczas zostanie odegrany dźwięk C trwający 300ms. W ten sposób możemy wprowadzić dowolną melodyjkę 😉

Na zakończenie nie pozostaje mi nic innego jak tylko życzyć wam Wesołych Świąt i owocnego arduinowania w nowym roku!

Jedna odpowiedź do “Święta z arduino, czyli gramy kolędy”

  1. […] tym, jak działa rejestr 74HC595, pisałem już w tym miejscu, więc nie będę przytaczał tego samego tekstu tutaj. Jedyna różnica między naszym […]

Skomentuj Budujemy „cyfrowy zasilacz” cz. 3 – przetwornik DAC oparty na R-2R, arduino » EE & IT blog Anuluj pisanie odpowiedzi

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *