Licznik – "muzyka"


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:

 

bit 7 6 5 4 3 2 1 0
nazwa COM0A1 COM0A0 COM0B1 COM0B0 WGM01 WGM00
R/W R/W R/W R/W R/W R R R/W R/W
stan 0 0 0 0 0 0 0 0

Rejestr sterowania licznikiem TCCR0A

 
COM0A1 COM0A0 Opis
0 0 PB0 pracuje w normalny sposób jako wyjście.
0 1 Przy stanie licznika równym OCR0A na wyjściu OC0A (PB0) pojawia się stan przeciwny do poprzedniego.
1 0 Przy stanie licznika równym OCR0A na wyjściu OC0A (PB0) pojawia się stan 0.
1 1 Przy stanie licznika równym OCR0A na wyjściu OC0A (PB0) pojawia się stan 1.

 

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:

obrazek

fOC0A  – częstotliwość impulsów wyjściowych
fCLK  – częstotliwość zegara mikrokontrolera
N  – dzielnik preskalera (1,8,64,256 lub 1024)
OCR0A  – rejestr porównawczy

 

 

 

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:
obrazek obrazek
Spis elementów
Element Ilość Opis
opornik 330Ω/0,125W 1 –(                )–
opornik 390Ω/0,125W 1 (                )
głośnik 8Ω/0,25W 1  
tranzystor BC547 1  
głośnik 8Ω/0,25W 1  
tranzystor BC547 1  
Złącze kątowe męskie 2 x 4 Goldpin 1  
przycisk Omron 4 do "gry?"

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)

obrazek

Elementy ułóż na schemacie i połącz przewodami narzędziem Net:

obrazek

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.

obrazek

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:

obrazek

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.

/*
 * main.c
 *
 *  Created on: 11 gru 2015
 *      Author: Jerzy
 *  Opis:
 *  Program wygrywa różne dźwięki gamy
 *  za pomocą przycisków na płytce APP005
 */

#include <avr/io.h>

uint8_t ocr[] = {238,189,158,125};
//               C   E   G   H'

int main(void)
{
    uint8_t i,pbi;
    DDRB   = (1<<PB0); // Ustawiamy PB0 jako wyjście, reszta jako wejścia
    PORTB  = 0b11110;  // Oporniki podciągające do PB1...PB4

    while (1)
    {
       pbi = (~PINB & 0b11110) >> 1; // Stan wejść
       if(pbi)
       {
           for(i = 0; i < 4; i++)
           {
               if(pbi & 1) break;
               pbi >>= 1;
           }
           OCR0A = ocr[i];
           TCCR0A = (1<<COM0A0)|(1<<WGM01); // Tryb CTC ze zmianą PB0 na przeciwną
           TCCR0B = (1<<CS01); // Preskaler: podział przez 8
       }
       else
       {
           TCCR0B = 0;         // Licznik stop
           TCCR0A = 0;         // Linia PB0 w stan normalny
           PORTB = 0b11110;    // PB0 w stan 0, aby odciążyć tranzystor na płytce
       }
    }
}

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.

/*
 * main.c
 *
 *  Created on: 12 gru 2015
 *      Author: Jerzy
 *  Opis:
 *  Program wygrywa różne efekty dźwiękowe
 *  za pomocą przycisków na płytce APP005
 */

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    uint8_t i,j,pbi;
    uint16_t x = 0;     // Ziarno generatora pseudolosowego
    DDRB   = (1<<PB0);  // Ustawiamy PB0 jako wyjście, reszta jako wejścia
    PORTB  = 0b11110;   // Oporniki podciągające do PB1...PB4

    while (1)
    {
        pbi = (~PINB & 0b11110) >> 1;  // Stan wejść
        if(pbi)
        {
            for(i = 0; i < 4; i++)
            {
                if(pbi & 1) break;
                pbi >>= 1;
            }
            TCCR0A = (1<<COM0A0)|(1<<WGM01); // Tryb CTC ze zmianą PB0 na przeciwną
            TCCR0B = (1<<CS01);              // Preskaler: podział przez 8
            switch(i)
            {
                case 0: for(j = 254; j > 64; j--)
                        {
                            OCR0A = j;
                            _delay_ms(3);
                        }
                        break;
                case 1: for(j = 32; j < 128; j++)
                        {
                            OCR0A = j;
                            _delay_ms(3);
                        }
                        break;
                case 2: for(j = 2; j < 128; j <<= 1)
                        {
                            OCR0A = j;
                            _delay_ms(50);
                        }
                        break;
                case 3: for(j = 0; j < 10; j++)
                        {
                            x = (251 * x + 101) % 250;  // Następna liczba pseudolosowa
                            OCR0A += x;
                            _delay_ms(20);
                        }
                        break;
            }
        }
        else
        {
            TCCR0B = 0;        // Licznik stop
            TCCR0A = 0;        // Linia PB0 e stan normalny
            PORTB = 0b11110;   // PB0 w stan 0, aby odciążyć tranzystor
        }
    }
}

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:

Oktawa 0 Oktawa 1 Oktawa 2 Oktawa 3 Oktawa 4 Oktawa 5 Oktawa 6 Oktawa 7 Oktawa 8
C 32,70 Hz 65,41 Hz 130,81 Hz 261,63 Hz 523,25 Hz 1046,50 Hz 2093,00 Hz 4186,01 Hz 8372,02 Hz
Cis 34,65 Hz 69,30 Hz 138,59 Hz 277,18 Hz 554,37 Hz 1108,73 Hz 2217,46 Hz 4434,92 Hz 8869,84 Hz
D 36,71 Hz 73,42 Hz 146,83 Hz 293,66 Hz 587,33 Hz 1174,66 Hz 2349,32 Hz 4698,64 Hz 9397,27 Hz
Dis 38,89 Hz 77,78 Hz 155,56 Hz 311,13 Hz 622,25 Hz 1244,51 Hz 2489,02 Hz 4978,03 Hz 9956,06 Hz
E 41,20 Hz 82,41 Hz 164,81 Hz 329,63 Hz 659,26 Hz 1318,51 Hz 2637,02 Hz 5274,04 Hz 10548,08 Hz
F 43,65 Hz 87,31 Hz 174,61 Hz 349,23 Hz 698,46 Hz 1396,91 Hz 2793,83 Hz 5587,65 Hz 11175,30 Hz
Fis 46,25 Hz 92,50 Hz 185,00 Hz 369,99 Hz 739,99 Hz 1479,98 Hz 2959,95 Hz 5919,91 Hz 11839,82 Hz
G 49,00 Hz 98,00 Hz 196,00 Hz 392,00 Hz 783,99 Hz 1567,98 Hz 3135,96 Hz 6271,93 Hz 12543,85 Hz
Gis 51,91 Hz 103,83 Hz 207,65 Hz 415,30 Hz 830,61 Hz 1661,22 Hz 3322,44 Hz 6644,88 Hz 13289,75 Hz
A 55,00 Hz 110,00 Hz 220,00 Hz 440,00 Hz 880,00 Hz 1760,00 Hz 3520,00 Hz 7040,00 Hz 14080,00 Hz
Ais 58,27 Hz 116,54 Hz 233,08 Hz 466,16 Hz 932,33 Hz 1864,66 Hz 3729,31 Hz 7458,62 Hz 14917,24 Hz
H 61,74 Hz 123,47 Hz 246,94 Hz 493,88 Hz 987,77 Hz 1975,53 Hz 3951,07 Hz 7902,13 Hz 15804,27 Hz

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:

obrazek

OCR0A  – rejestr porównawczy
fCLK  – częstotliwość zegara mikrokontrolera
N  – dzielnik preskalera (1,8,64,256 lub 1024)
f  – częstotliwość dźwięku gammy

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:

Oktawa 0 OCR0A N Oktawa 1 OCR0A N Oktawa 2 OCR0A N
C 32,70 Hz 238 64 65,41 Hz  118 64 130,81 Hz 59 64
Cis 34,65 Hz  224 64 69,30 Hz  112 64 138,59 Hz  55 64
D 36,71 Hz  212 64 73,42 Hz  105 64 146,83 Hz  52 64
Dis 38,89 Hz  200 64 77,78 Hz  99 64 155,56 Hz 49 64
E 41,20 Hz  189 64 82,41 Hz  94 64 164,81 Hz  46 64
F 43,65 Hz  178 64 87,31 Hz  88 64 174,61 Hz  44 64
Fis 46,25 Hz  168 64 92,50 Hz  83 64 185,00 Hz  41 64
G 49,00 Hz  158 64 98,00 Hz  79 64 196,00 Hz  39 64
Gis 51,91 Hz  149 64 103,83 Hz  74 64 207,65 Hz  37 64
A 55,00 Hz  141 64 110,00 Hz  70 64 220,00 Hz  35 64
Ais 58,27 Hz  133 64 116,54 Hz  66 64 233,08 Hz  33 64
H 61,74 Hz  126 64 123,47 Hz  62 64 246,94 Hz 31 64
  Oktawa 3 OCR0A N Oktawa 4 OCR0A N Oktawa 5 OCR0A N
C 261,63 Hz 238 8 523,25 Hz 118 8 1046,50 Hz 59 8
Cis 277,18 Hz  224 8 554,37 Hz  112 8 1108,73 Hz  55 8
D 293,66 Hz  212 8 587,33 Hz  105 8 1174,66 Hz  52 8
Dis 311,13 Hz  200 8 622,25 Hz  99 8 1244,51 Hz 49 8
E 329,63 Hz  189 8 659,26 Hz  94 8 1318,51 Hz  46 8
F 349,23 Hz  178 8 698,46 Hz  88 8 1396,91 Hz  44 8
Fis 369,99 Hz  168 8 739,99 Hz  83 8 1479,98 Hz  41 8
G 392,00 Hz  158 8 783,99 Hz  79 8 1567,98 Hz  39 8
Gis 415,30 Hz  149 8 830,61 Hz  74 8 1661,22 Hz  37 8
A 440,00 Hz  141 8 880,00 Hz  70 8 1760,00 Hz  35 8
Ais 466,16 Hz  133 8 932,33 Hz  66 8 1864,66 Hz  33 8
H 493,88 Hz  126 8 987,77 Hz  62 8 1975,53 Hz 31 8
  Oktawa 6 OCR0A N Oktawa 7 OCR0A N Oktawa 8 OCR0A N
C 2093,00 Hz 238 1 4186,01 Hz 118 1 8372,02 Hz 59 1
Cis 2217,46 Hz  224 1 4434,92 Hz  112 1 8869,84 Hz  55 1
D 2349,32 Hz  212 1 4698,64 Hz  105 1 9397,27 Hz  52 1
Dis 2489,02 Hz  200 1 4978,03 Hz  99 1 9956,06 Hz 49 1
E 2637,02 Hz  189 1 5274,04 Hz  94 1 10548,08 Hz  46 1
F 2793,83 Hz  178 1 5587,65 Hz  88 1 11175,30 Hz  44 1
Fis 2959,95 Hz  168 1 5919,91 Hz  83 1 11839,82 Hz  41 1
G 3135,96 Hz  158 1 6271,93 Hz  79 1 12543,85 Hz  39 1
Gis 3322,44 Hz  149 1 6644,88 Hz  74 1 13289,75 Hz  37 1
A 3520,00 Hz  141 1 7040,00 Hz  70 1 14080,00 Hz  35 1
Ais 3729,31 Hz  133 1 7458,62 Hz  66 1 14917,24 Hz  33 1
H 3951,07 Hz  126 1 7902,13 Hz  62 1 15804,27 Hz 31 1

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:

bit 7 6 5 4 3 2 1 0
nazwa FOC0A FOC0B WGM02 CS02 CS01 CS00
R/W W W R R R/W R/W R/W R/W
stan 0 0 0 0 0 0 0 0

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

 

 

Podsumowanie

 
   

Generacja fali prostokątnej na wyjściu PB0

  1. Ustawiamy linię PB0 jako wyjście, wpisując do bitu DDB0 w rejestrze DDRB bit o wartości 1:
    DDRB = (1<<DDB0); lub DDRB |= (1<<DDB0); jeśli nie chcemy zmieniać stanu pozostałych bitów rejestru DDRB.
  2. W rejestrze OCR0A umieszczamy wartość graniczną dla licznika. Licznik będzie zliczał od 0 do tej wartości.
  3. W rejestrze TCCR0A ustawiamy na 1 bity COM0A0 oraz WGM01. Włączą one tryb CTC, w którym stan licznika jest porównywany z rejestrem OCR0A i przy zgodności następuje wyzerowanie licznika oraz zmiana na przeciwny stanu linii PB0.
  4. W rejestrze TCCR0B ustawiamy CS02, CS01 i CS00, które ustawiają odpowiedni podział impulsów taktujących i włączają ich zliczanie w liczniku. Licznik działa do momentu, aż go zatrzymamy przez wpisanie do TCCR0B wartości 0.

Tablica w pamięci programu

  1. Należy dołączyć plik nagłówkowy:
    #include <avr/pgmspace.h>
    .
  2. Tablicę definiujemy poza funkcjami jako stałą i globalną:
    const typ nazwa[ ] PROGMEM = {zawartość};
  3. Dostęp do zawartości komórek odbywa się poprzez:
    pgm_read_byte(&nazwa[indeks]); lub pgm_read_word(&nazwa[indeks]);

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

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.
Informacje dodatkowe