![]() |
Wyjście Spis treści Poprzedni Następny
Autor artykułu: mgr Jerzy Wałaszek Konsultacja: Wojciech Grodowski, mgr inż. Janusz Wałaszek |
©2015 mgr
Jerzy Wałaszek
|
| Tematy pokrewne | Podrozdziały | |
| (w budowie) |
Generacja impulsów APP005 – prosty instrument Podsumowanie |
Generacja impulsów |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
W poprzednim rozdziale
poznaliśmy licznik w ATTiny, który pozwalał zliczać impulsy. Nie
jest to jedyna jego funkcja. Można go również wykorzystać do
generowania sygnałów o zadanej częstotliwości. Operacja ta
wykonywana jest sprzętowo w trybie CTC, w którym zawartość
licznika jest porównywana z rejestrem OCR0A. Jeśli licznik
osiągnie stan równy zawartości rejestru OCR0A, to jest zerowany
przy następnym impulsie zegarowym. W tym momencie można zmieniać
stan linii PB0
(zwanej tutaj OC0A), jeśli ustawi się
ją jako wyjście w rejestrze kierunku DDRB oraz ustawi się
odpowiednio bity COM0A1 i COM0A0 w rejestrze TCCR0A:
Rejestr sterowania licznikiem TCCR0A
Bity COM0B1 i COM0B0 sterują rejestrem OCR0B i wyjściem OC0B (PB1) w identyczny sposób. Przy generacji ciągu impulsów najbardziej interesujący dla nas będzie stan drugi, COM0A1 = 0 i COM0A2 = 1. Częstotliwość impulsów na wyjściu OC0A (PB0) wyraża sie wzorem:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
APP005 – prosty instrument |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Zaprojektujemy teraz prostą płytkę aplikacyjną
APP005, za pomocą której będziemy mogli pobawić się tymi nowymi
funkcjami licznika w ATTiny13. Płytka będzie zawierała
wzmacniacz na jednym tranzystorze BC547, mały głośniczek oraz
cztery przyciski. Podobny wzmacniacz projektowaliśmy dla
generatora przerywanego. Wykorzystamy go również tutaj.
Schemat jest następujący:
Przyciski wykorzystamy do "grania" na naszym instrumencie lub do innych funkcji. Uruchom program Eagle, utwórz nowy projekt APP005, a w nim nowy schemat. Do schematu wstaw elementy:
frames A5L-LOC
(ramka)
supply1 GND x 4 (masa) pinhead PINHD-2X4 PINHD-2X4/90 x 1 (goldpiny kątowe męskie do połączenia z płytką bazową, element obróć o 180º i odbij lustrzanie) switch-omron 10-XX x 4 (przycisk W) rlc R-EU R-EU_0204/7 x 2 (oporniki) transistor *-NPN- BC547-NPN-TO92-CBE x 1 (tranzystor wzmacniacza) wirepad 1,6/0,8 x 2 (pole lutownicze do głośnika)
Elementy ułóż na schemacie i połącz przewodami narzędziem Net:
Kształt i wielkość płytki PCB będą uzależnione od posiadanego głośniczka. Mi udało się zakupić głośniczki w obudowach plastikowych (po 4zł sztuka) z wyprowadzonymi nóżkami, które bardzo łatwo przylutuję do płytki i osadzę na kropelce kleju.
Dla innych głośniczków należy odpowiednio rozmieścić na płytce pola lutownicze SPK1/SPK2 oraz pozostawić wolne miejsce na sam głośniczek. Utwórz w edytorze PCB płytkę o poniższym układzie ścieżek:
Pierwszy program generuje wybrane dźwięki gamy po naciśnięciu przycisku na płytce. Ponieważ przycisków tych jest tylko 4, nasz "instrument" potrafi odegrać jedynie cztery różne dźwięki. Do wkurzania otoczenia to zupełnie wystarczy.
Drobnego wyjaśnienia wymaga końcówka programu. Po zakończeniu generacji dźwięku linia PB0 jest ustawiana w stan niski 0. Linia ta steruje bazą tranzystora. W stanie 1 przez tranzystor przepływa dosyć duży prąd, który powoduje jego nagrzewanie. Aby zatem oszczędzić tranzystor, linię PB0 ustawiamy w stan 0, co spowoduje zablokowanie tranzystora. Przez zablokowany tranzystor nie płynie prawie żaden prąd i tranzystor się nie będzie niepotrzebnie nagrzewał. Następny program generuje 4 różne efekty dźwiękowe wyzwalane naciśnięciem jednego z czterech przycisków na płytce APP005. Przytrzymanie przycisku powoduje powtarzanie wybranego efektu.
W programie wykorzystano prosty generator pseudolosowy, który daje liczby z przedziału od 0 do 249. Więcej na temat takich generatorów dowiesz się z tego materiału. Teraz pobawimy się odtwarzaniem nut. Każdy dźwięk gamy charakteryzuje określona częstotliwość. Częstotliwości te można uzyskać w ATTINY13 z pewnym przybliżeniem. Nie zagłębiając się zbytnio w teorie muzyczne, możemy powiedzieć, że gama składa się oktaw po 12 dźwięków, które oznaczamy literowo:
C Cis D Dis E F Fis G Gis A Ais H
Kolejny, trzynasty dźwięk, jest dźwiękiem C następnej oktawy. W następnych oktawach częstotliwości dźwięków są dwa razy wyższe. Np. jeśli C0 ma częstotliwość 32,705 Hz, to C1 w kolejnej oktawie ma częstotliwość 65,41 Hz = 2 x 32,705. Częstotliwości dźwięków wewnątrz oktawy otrzymamy mnożąc częstotliwość poprzedzającego dźwięku przez pierwiastek dwunastego stopnia z dwóch, czyli przez 1,05946... Otrzymamy wtedy tzw. gamę temperowaną. Kolejne dźwięki tej gamy są oddalone od siebie o równe półtony:
Do generacji dźwięku potrzebujemy dwóch ustawień: stanu rejestrów OCR0A oraz TCCR0B. W rejestrze OCR0A określamy stan licznika, przy którym nastąpi zmiana stanu wyjścia PB0 na przeciwny. W trybie CTC licznik jest zerowany po osiągnięciu tego stanu i zaczyna zliczać od początku. Dzięki temu na wyjściu PB0 otrzymujemy sygnał prostokątny o częstotliwości zależnej od zawartości rejestru porównawczego OCR0A. W rejestrze TCCR0B ustalamy dzielnik preskalera, przez który jest dzielona częstotliwość taktowania procesora - normalnie 1000000 Hz, będąca źródłem impulsów, które licznik zlicza. Wartości te wyliczymy ze wzoru:
Wartość N musi być tak dobrana, aby OCR0A mieściło się w zakresie do 255 (1 bajt). Obliczenia po zaokrągleniu do najbliższej wartości całkowitej dają poniższe wyniki:
Największe niedokładności częstotliwości dźwięku występują tam, gdzie rejestr OCR0A przyjmuje małą wartość. Są to gamy 2, 5 i 8. Staraj się unikać dźwięków z tych oktaw, jeśli będzie to możliwe, ponieważ tony są nieco zafałszowane. Nic na to nie poradzimy z licznikiem 8-bitowym. Przypomnijmy, podziałem częstotliwości zegarowej sterujemy poprzez rejestr:
Rejestr sterowania licznikiem TCCR0B
CS02 |
CS01 |
CS00 |
Opis |
0 |
0 |
0 |
Brak impulsów zegarowych, licznik jest
zatrzymany. |
0 |
0 |
1 |
Licznik zlicza impulsy o częstotliwości FCPU
(1000000 Hz). |
0 |
1 |
0 |
Licznik zlicza impulsy o częstotliwości FCPU/8
(125000 Hz) |
0 |
1 |
1 |
Licznik zlicza impulsy o częstotliwości FCPU/64
(15625 Hz) |
1 |
0 |
0 |
Licznik zlicza impulsy o częstotliwości FCPU/256
(3906,25 Hz) |
1 |
0 |
1 |
Licznik zlicza impulsy o częstotliwości
FCPU/1024
(976,5625 Hz) |
1 |
1 |
0 |
Licznik zlicza impulsy z linii PB2 pracującej
jako wejście T0. Zliczenie następuje przy opadającym
zboczu impulsu. |
1 |
1 |
1 |
Licznik zlicza impulsy z linii PB2 pracującej
jako wejście T0. Zliczenie następuje przy
narastającym zboczu impulsu. |
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
W naszym przypadku do TCCR0B będziemy wprowadzać wartości: 3 (0b011 – podział przez 64), 2 (0b010 – podział przez 8) i 1 (ob001 – podział przez 1, czyli pełna częstotliwość taktowania). Wyłączenie zliczania następuje po wprowadzeniu do TCCR0B wartości 0.
Wypróbujmy tę wiedzę w kolejnym programie, który będzie odgrywał dźwięki poszczególnych gam.
/*
* main.c
*
* Created on: 12 gru 2015
* Author: Jerzy
* Opis:
* Program wygrywa kolejne dźwięki gam 0...5
*/
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
const uint8_t nt[36] PROGMEM = {238,224,212,200,189,178,168,158,149,141,133,126,
118,112,105, 99, 94, 88, 83, 79, 74, 70, 66, 62,
59, 55, 52, 49, 46, 44, 41, 39, 37, 35, 33, 31};
int main(void)
{
uint8_t i,s,n;
DDRB = (1<<PB0); // Ustawiamy PB0 jako wyjście, reszta jako wejścia
TCCR0A = (1<<COM0A0)|(1<<WGM01); // Tryb CTC ze zmianą PB0 na przeciwną
while (1)
{
s = 0; // stan OCR0A dla dźwięku
n = 3; // stan TCCR0B, preskaler, podział przez 64
for(i = 0; i < 108; i++)
{
OCR0A = pgm_read_byte(&nt[s++]);
TCCR0B = n;
if(s == 36) // po odegraniu wszystkich dźwięków z tablicy
{
s = 0; // rozpoczynamy następny zestaw
n--; // z innym podziałem przez preskaler (8, 1)
}
_delay_ms(100);
}
}
}
|
W programie zastosowaliśmy tablicę nt[], która jest umieszczona w pamięci FLASH, nie w RAM. Pamięć FLASH posiada większą pojemność (1024 B) w porównaniu z pamięcią RAM (64 B). Dlatego dane, które się nie zmieniają, umieszcza się właśnie w pamięci FLASH. Aby to zrobić, należy dołączyć do programu plik nagłówkowy avr/pgmspace.h. Tablica musi być zdefiniowana jako:
const typ nazwa[rozmiar] PROGMEM = {zawartość};
Dostęp do poszczególnych komórek tej tablicy (tylko odczyt) uzyskuje się za pomocą makra:
pgm_read_byte(&nazwa[indeks]);
Efektu działania tego programu nie daje się zbyt długo słuchać, dlatego napiszemy kolejny program, w którym nasz maluch będzie wygrywał melodyjkę. Program nie będzie korzystał z przycisków, melodia będzie odgrywana w kółko (możesz dopisać sobie kod, który czeka na naciśnięcie przycisku). Kolejne nuty będą zapisane w tablicy, jednakże w celu oszczędzenia miejsca zastosujemy specjalny sposób kodowania. Zapis muzyki będzie swojego rodzaju "programem muzycznym" złożonym z poleceń. Polecenia te będą kolejno odczytywane przez procesor i interpretowane. Każde polecenie będzie miało długość 1 bajtu. Bajt polecenia będzie się składał z dwóch pól bitowych (grupa sąsiadujących ze sobą bitów). Cztery najmłodsze bity określą rodzaj polecenia (daje to 16 rozkazów). 4 najstarsze bity określą argument. Polecenia będą następujące:
| Polecenie | Mnemonik | Opis |
| 0bnnnn0000 | C | odgrywa nutę C przez 0bnnnn jednostek czasu |
| 0bnnnn0001 | Cis | odgrywa nutę Cis przez 0bnnnn jednostek czasu |
| 0bnnnn0010 | D | odgrywa nutę D przez 0bnnnn jednostek czasu |
| 0bnnnn0011 | Dis | odgrywa nutę Dis przez 0bnnnn jednostek czasu |
| 0bnnnn0100 | E | odgrywa nutę E przez 0bnnnn jednostek czasu |
| 0bnnnn0101 | F | odgrywa nutę F przez 0bnnnn jednostek czasu |
| 0bnnnn0110 | Fis | odgrywa nutę Fis przez 0bnnnn jednostek czasu |
| 0bnnnn0111 | G | odgrywa nutę G przez 0bnnnn jednostek czasu |
| 0bnnnn1000 | Gis | odgrywa nutę Gis przez 0bnnnn jednostek czasu |
| 0bnnnn1001 | A | odgrywa nutę A przez 0bnnnn jednostek czasu |
| 0bnnnn1010 | Ais | odgrywa nutę Ais przez 0bnnnn jednostek czasu |
| 0bnnnn1011 | H | odgrywa nutę H przez 0bnnnn jednostek czasu |
| 0bnnnn1100 | P | pauza przez 0bnnnn jednostek czasu |
| 0bxxxx1101 | END | koniec, powrót na początek melodii |
| 0bnnnn1110 | O0...O8 | ustawia oktawę od 0 do 8 |
| 0bxxxx1111 | brak | nieużywane |
Przez jednostkę czasu rozumiemy czas odgrywania najkrótszej nuty. Na przykład, jeśli pole nnnn ma stan 0000, to będzie odegrana nuta szesnastka. Dla stanu 0010 będą to trzy szesnastki (w muzyce taką nutę oznacza się z kropką). Pole nnnn zawiera wartość binarną czasu minus 1.
Pierwszym poleceniem każdej melodii powinno być Gn, które ustawi właściwą gamę oraz preskaler.
/*
* main.c
*
* Created on: 13 gru 2015
* Author: Jerzy
* Opis:
* Program wygrywa zaprogramowaną melodię
*/
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
// Stale określają wartości poleceń
// Dźwięki gam
#define C 0b0000
#define Cis 0b0001
#define D 0b0010
#define Dis 0b0011
#define E 0b0100
#define F 0b0101
#define Fis 0b0110
#define G 0b0111
#define Gis 0b1000
#define A 0b1001
#define Ais 0b1010
#define H 0b1011
#define P 0b1100
// Koniec melodii
#define END 0b1101
// Oktawy
#define O0 0b00001110
#define O1 0b00011110
#define O2 0b00101110
#define O3 0b00111110
#define O4 0b01001110
#define O5 0b01011110
#define O6 0b01101110
#define O7 0b01111110
#define O8 0b10001110
// argumenty
#define N1 0b00000000
#define N2 0b00010000
#define N3 0b00100000
#define N4 0b00110000
#define N5 0b01000000
#define N6 0b01010000
#define N7 0b01100000
#define N8 0b01110000
#define N9 0b10000000
#define N10 0b10010000
#define N11 0b10100000
#define N12 0b10110000
#define N13 0b11000000
#define N14 0b11010000
#define N15 0b11100000
#define N16 0b11110000
// Tablica przechowuje wartości OCCR0A dla poszczególnych nut w 3 oktawach
const uint8_t nt[] PROGMEM = {238,224,212,200,189,178,168,158,149,141,133,126,
118,112,105, 99, 94, 88, 83, 79, 74, 70, 66, 62,
59, 55, 52, 49, 46, 44, 41, 39, 37, 35, 33, 31,};
// Tablica przechowuje polecenia melodii
// Dla Elizy, L. Van Beethoven
const uint8_t m[] PROGMEM = {
O4,E|N1,Dis|N1, // Takt 1
E|N1,Dis|N1,E|N1,O3,H|N1,O4,D|N1,C|N1, // Takt 2
O3,A|N3,C|N1,E|N1,A|N1, // Takt 3
H|N3,E|N1,Gis|N1,H|N1, // Takt 4
O4,C|N3,O3,E|N1,O4,E|N1,Dis|N1, // Takt 5
E|N1,Dis|N1,E|N1,O3,H|N1,O4,D|N1,C|N1, // Takt 6
O3,A|N3,C|N1,E|N1,A|N1, // Takt 7
H|N3,E|N1,O4,C|N1,O3,H|N1, // Takt 8
A|N2,P|N2,O4, E|N1,Dis|N1, // Takt 9
E|N1,Dis|N1,E|N1,O3,H|N1,O4,D|N1,C|N1, // Takt 10
O3,A|N3,C|N1,E|N1,A|N1, // Takt 11
H|N3,E|N1,Gis|N1,H|N1, // Takt 12
O4,C|N3,O3,E|N1,O4,E|N1,Dis|N1, // Takt 13
E|N1,Dis|N1,E|N1,O3,H|N1,O4,D|N1,C|N1, // Takt 14
O3,A|N3,C|N1,E|N1,A|N1, // Takt 15
H|N3,E|N1,O4,C|N1,O3,H|N1, // Takt 16
A|N3,H|N1,O4,C|N1,D|N1, // Takt 17
E|N3,O3,G|N1,O4,F|N1,E|N1, // Takt 18
D|N3,O3,F|N1,O4,E|N1,D|N1, // Takt 19
C|N3,O3,E|N1,O4,D|N1,C|N1, // Takt 20
O3,H|N3,E|N1,O4,E|N1,O3,E|N1, // Takt 21
O4,E|N3,O3,E|N1,O4,E|N1,O3,E|N1, // Takt 22
O4,E|N1,Dis|N1,E|N1,Dis|N1,E|N1,Dis|N1, // Takt 23
E|N1,Dis|N1,E|N1,O3,H|N1,O4,D|N1,C|N1, // Takt 24
O3,A|N3,C|N1,E|N1,A|N1, // Takt 25
H|N3,E|N1,Gis|N1,H|N1, // Takt 26
O4,C|N3,O3,E|N1,O4,E|N1,Dis|N1, // Takt 27
E|N1,Dis|N1,E|N1,O3,H|N1,O4,D|N1,C|N1, // Takt 28
O3,A|N3,C|N1,E|N1,A|N1, // Takt 29
H|N3,E|N1,O4,C|N1,O3,H|N1, // Takt 30
A|N3,H|N1,O4,C|N1,D|N1, // Takt 31
E|N3,O3,G|N1,O4,F|N1,E|N1, // Takt 32
D|N3,O3,F|N1,O4,E|N1,D|N1, // Takt 33
C|N3,O3,E|N1,O4,D|N1,C|N1, // Takt 34
O3,H|N3,E|N1,O4,E|N1,O3,E|N1, // Takt 35
O4,E|N3,O3,E|N1,O4,E|N1,O3,E|N1, // Takt 36
O4,E|N1,Dis|N1,E|N1,Dis|N1,E|N1,Dis|N1, // Takt 37
E|N1,Dis|N1,E|N1,O3,H|N1,O4,D|N1,C|N1, // Takt 38
O3,A|N3,C|N1,E|N1,A|N1, // Takt 39
H|N3,E|N1,Gis|N1,H|N1, // Takt 40
O4,C|N3,O3,E|N1,O4,E|N1,Dis|N1, // Takt 41
E|N1,Dis|N1,E|N1,O3,H|N1,O4,D|N1,C|N1, // Takt 42
O3,A|N3,C|N1,E|N1,A|N1, // Takt 43
H|N3,E|N1,O4,C|N1,O3,H|N1, // Takt 44
A|N10, // Takt 45/46 - koniec
P|N14,P|N12|P|N12,END
};
// Procedura wprowadza zadane opóźnienie
void wait(uint8_t x)
{
uint8_t i;
for(i = 0; i < x; i++) _delay_ms(180); // określa szybkość odtwarzania melodii
}
int main(void)
{
uint8_t s,n,t,b;
uint16_t i; // indeks m[] musi być 16-bitowy!!!
DDRB = (1<<DDB0); // Ustawiamy PB0 jako wyjście, reszta jako wejścia
while (1)
{
i = 0; // Ustawiamy indeks pierwszego polecenia
do
{
b = pgm_read_byte(&m[i++]); // odczytujemy polecenie
t = b >> 4; // w t argument
b &= 0b1111; // w b kod polecenia
if(b == O0) // gama
{
s = 12 * (t % 3); // indeks bazowy w nt[]
n = 3 - t / 3; // wartość dla OCCR0B
}
else if(b == P) // pauza
{
TCCR0B = 0; // zatrzymujemy licznik
TCCR0A = 0; // przywracamy tryb normalny
PORTB = 0b11110; // linia PB0 w stan niski
wait(t+1); // czekamy odpowiedni czas
}
else if(b != END) // nuta
{
TCCR0A = (1<<COM0A0)|(1<<WGM01); // Tryb CTC ze zmianą PB0 na przeciwną
OCR0A = pgm_read_byte(&nt[s + b]);
TCCR0B = n; // włączamy generację dźwięku
wait(t+1); // czekamy odpowiedni czas
}
} while(b != END);
}
}
|
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.