Operácie

Akordy s procesorom AVR

Z SensorWiki

Záverečný projekt predmetu MIPS / LS2024 - Kristián Pauliny


Zadanie

Naprogramujte nejakú pesničku aj s akordmi (využite skutočnosť, že procesor má až tri nezávislé časovače).

Vývojová doska Arduino UNO


Literatúra:


Analýza a opis riešenia

Zapojenie, vypracovanie je inšpirované cvičením 5 – Generovanie tónov, kde sme dostali funkčný program, v ktorom stačilo len “naprogramovať melódiu“.

Hranie tónov cez AVR je založené na vstavaných perifériách – počítadlá v stave CTC (Clear Timer on Compare Match). Budem používať všetky 3 nezávislé počítadlá (T0, T1, T2) – konkrétne ich výstupy OC0A, OC1A, OC2A (Output Compare Register) na riadenie výšky tónov. Nastavenie počítadiel vysvetlím konkrétne ako pre počítadle T0 – po pochopení jedného sa proces dá zopakovať aj pri ostatných.

Počítadlo T0 je 8-bitové. Cez registre TCCR0A a TCCR0B (Timer/Counter control register) možno nastaviť jeho preddeličku, PWM mód a hlavne režim generovania priebehu. Do TCCR0A a TCCR0B ide číslo ktorým nastavujeme počítadlo (8-miestny kód v binárnej sústave alebo to isté číslo prepočítané do inej sústavy). Viem, že potrebujem pre počítadlo nastaviť:

- Toggle OC0A on compare match – oživiť register OC0A

- Normal port operation, OC0B disconnected – register OC0B nechať vypnutý

- CTC – režim počítadla Clear Timer on Compare Match

- Preddelička 256 – ako “rýchlo“ má počítať


V tabuľkách DataSheetu dohľadám ako treba nastaviť podregistre COM0A1, COM0A0, COM0B1, COM0B0, WGM02, WGM01, WGM00, CS02, CS01, CS00 a zadám ich dokopy do registrov TCCR0A a TCCR0B podľa rozloženia:

Forma rozpoloženia podregistrov v registroch TCCR0A a TCCR0B.


Pre počítadlo T0 majú byť TCCR registre nastavené nasledovne:

TCCR0A = 0b01000010;
TCCR0A = 0b00000100;

Register OCR0A je priamo napojený na pozitívnu nožičku pasívneho meniča. Negatívna nožička meniča je pripojená na zem. Do registra OCR0A však nemôžem v AVR kóde napísať skutočnú frekvenciu zvuku ako mechanického vlnenia v prírode, ale treba ju prepočítať. Tento prepočet je daný jednoduchým vzťahom:

Vzorec na vypocet frekvencie tonu v AVR.png

Kde platí:

- fOCnx je skutočná frekvencia zvuku (napr. komorné “a“ = 440Hz)

- fclkIO je frekvencia oscilátora procesora – v prípade Arduina UNO (procesor ATmega328P) je to 16MHZ

- N je preddelička – 256

- OCRnx je hľadaná hodnota reprezentujúca skutočnú frekvenciu, pri konkrétnej preddeličke skompilovateľná cez AVR


Tento prevod za mňa dokáže urobiť jednoduchý, šikovný program AVRcalc (https://sourceforge.net/projects/avrcalc/). Stačí vybrať hore v záložkách 8-bit Timer, Mode nechať na Clear Timer on Compare Match, Prescaler zmeniť na 256, Clock zadať ako 16 000 000 a hýbať so sliderom v riadku Compare kým v riadku Frequency nebude moja želaná frekvencia. Hodnota v riadku Compare ide do registra OCR0A.

AVRcalc obrazok na ukazku vypoctu 440 Hz.jpg

Aby boli tieto tóny čitateľné pre ľudí na začiatku programu som im pridelil formou makier korešpondujúce mená tónov v štandardnom notopise

#define C4 118

Dĺžku tónov viem ovládať v main() programe funkciou delay(), ktorá zamrzne procesor na danú dobu v milisekundách a teda bude tú dobu hrať jeden tón, a potom prepísať OCR0A na iný tón.

Na ukážku funkčnosti tohto projektu som si vybral pieseň Happy Birthday. Je to jednoduchá kozonantná skladba ktorej som pre limitácie zvukovej kvality buzzerov musel mierne pomeniť harmóniu. Finálne noty vyzarajú takto:

Happy Birthday noty fotka.jpg

Schéma zapojenia:

Schema zapojenia 3 speakre s rezistormi.png

Repráčiky na pinoch D9 a D11 majú sériovo pred sebou zapojený rezistor (R = 220 Ω) aby ich zvuk bol o trošku tichší. Na pine D6 je pripojený repráčik, ktorý hrá hlavnú melódiu a na D9, D11 repráčiky, ktoré hrajú sprievodnú harmóniu (akordy), čiže potrebujem aby boli komplimentárne, trošku tichšie voči D6 a prepojím ich signál cez rezistor. Projekt funguje aj bez rezistorov napojením repráčikov priamo na signál, ale potom to znie akoby sa repráčiky prekrikovali.


Algoritmus a program

Celý kód je napísaný v jednom .c súbore – na spustenie netreba žiadne externé knižnice

#define F_CPU 16000000UL  // toto je lepsie vlozit do parametrov pre kompilator

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

#define O0 50000000000		// toto neexistuje teda nehra nic
// melodia:
#define C4 118
#define D4 106
#define E4 94
#define G4 79
#define A4 70
#define H4 62
#define C5 59
#define D5 52
#define E5 46
#define F5 44
#define G5 39

// bass:
#define A3 141
#define G3 158
#define F3 178
#define E3 189
#define D3 213
#define C3 238
#define H2 252


void InitTimer0(){
  DDRD   |= (1 << PD6);            // port D.6 pin ako vystup
  
  TCCR0A = 0b01000010;
  TCCR0B = 0b00000100;
  
  OCR0A = 70;
  
}

void InitTimer1(){
	DDRB   |= (1 << PB1);            // port D.9 pin ako vystup
  
	TCCR1A = 0b01000000;
	TCCR1B = 0b00001100;			// preddelicka 256
	
	OCR1A = 70;
  
}

void InitTimer2(){
 DDRB   |= (1 << PB3);            // port D.11 pin ako vystup
  
  TCCR2A = 0b01000010;
  TCCR2B = 0b00000110;
  
  OCR2A = 70;
  
}

void speaker0(vyska_tonu0){
	OCR0A = vyska_tonu0;
}

void speaker1(vyska_tonu1){
	OCR1A = vyska_tonu1;
	TCNT1 = 0x00;					// resetovanie pocitadla T1 aby nesekalo
}

void speaker2(vyska_tonu2){
	OCR2A = vyska_tonu2;
}

void delay(int delay)   // vlastna funkcia, lebo inak je max 16ms
{
	for (int i=1; i <= delay - 2; i++){
		_delay_ms(1);
	}

	OCR0A = O0;						// malinka pauza za kazkou notou melodie 
    for (int i=1; i <= 2; i++){		// ... ked za sebou idu dve rovnake noty aby nezneli
	  _delay_ms(1);					// ako jedna spojita
	}
}

int main(void)
{	
	InitTimer0();
	InitTimer1();
	InitTimer2();

	int tempo = 250;
		
	while(1){
		//	------------------------ Happy birthday ------------------------ //
		
		// 1 takt
		speaker0(G4);	speaker1(O0); speaker2(O0);			delay(tempo);			// ha-
		speaker0(G4);										delay(tempo);			// -ppy
		speaker0(A4);	speaker1(G3); speaker2(C3);			delay(tempo*2);			// Birth-
		speaker0(G4);										delay(tempo*2);			// -day
		
		// 2 takt
		speaker0(C5);	speaker1(O0); speaker2(O0);			delay(tempo*2);			// to
		speaker0(H4);	speaker1(G3); speaker2(H2);			delay(tempo*4);			// you
	
		// 3 takt
		speaker0(G4);	speaker1(O0); speaker2(O0);			delay(tempo);			// ha-
		speaker0(G4);										delay(tempo);			// -ppy
		speaker0(A4);	speaker1(G3); speaker2(H2);			delay(tempo*2);			// Birth-
		speaker0(G4);										delay(tempo*2);			// -day
		
		// 4 takt
		speaker0(D5);	speaker1(O0); speaker2(O0);			delay(tempo*2);			// to
		speaker0(C5);	speaker1(G3); speaker2(C3);			delay(tempo*4);			// you
		
		 // 5 takt
		speaker0(G4);	speaker1(O0); speaker2(O0);			delay(tempo);			// ha-
		speaker0(G4);										delay(tempo);			// -ppy
		speaker0(G5);	speaker1(C4); speaker2(E3);			delay(tempo*2);			// Birth-
		speaker0(E5);										delay(tempo*2);			// -day
		
		// 6 takt
		speaker0(C5);	speaker1(O0); speaker2(O0);			delay(tempo*2);			// dear
		speaker0(H4);	speaker1(C4); speaker2(F3);			delay(tempo*2);			// Ji-
		speaker0(A4);										delay(tempo*2);			// -mmy
		
		// 7 takt
		speaker0(F5);	speaker1(O0); speaker2(O0);			delay(tempo);			// Ha-
		speaker0(F5);										delay(tempo);			// -ppy
		speaker0(E5);	speaker1(C4); speaker2(E3);			delay(tempo*2);			// birth-
		speaker0(C5);										delay(tempo*2);			// -day
		
		// 8 takt
		speaker0(D5);	speaker1(G3); speaker2(D3);			delay(tempo*2);			// to
		speaker0(C5);	speaker1(G3); speaker2(C3);			delay(tempo*4);			// you
		
	}


}


Skript má 4 časti:

1 Definovanie tónov cez makrá. Každému použitému tónu som priradil “textový“ názov tónu aby som s nimi vedel pohodlnejšie pracovať a ľahšie sa orientovať. Je to prevod, ktorý som spomínal vyššie (použitím AVRcalc)

2 Inicializácia počítadiel. Pre každé počítadlo som nastavil ich príslušné piny ako výstupné, počítadlá do režimu CTC, oživiť registre OCR, preddelička 256.

3 Zavedenie funkcií na menenie tónov speaker(). Táto časť vznikla pretože s počítadlom T1 bol problém. Keďže je 16-bitové a chcem aby počítalo s rovnakou preddeličkou trvá mu dlhšie kým sa dostane po svoj strop. Tento problém sa prejavuje tak, že po zmene tónu v kóde nastane z repráčika napojeného na OCR1A ticho. Ošetril som to tak, že po každej zmene tónu v registri OCR1A sa nastaví počítadlo na nulu

TCNT1 = 0x00;

Tento jav sa deje aj pri ostatných dvoch počítadla, ale keďže sú 8-bitové ich strop je len 256 a tam sa dopočítajú veľmi rýchlo – ľudské ucho nezachytí toto malinké rytmické oneskorenie. V tejto časti je aj funkcia delay() ktorá len opakuje vstavanú funkciu _delay_ms() cez for() cyklus. Do funkcie ide len jeden parameter ktorý je doba v milisekundách, počas ktorej sa hrá jeden tón. Pre pohodlnejšie ovládanie som zaviedol premennú tempo ktorú len násobím. Násobok premennej tempo je teda počet dôb, koľko tón hrá. Ak dám do funkcie delay() len tempo, tón hrá jednu osminu doby, ak dám tempo*2, hrá jednu štvrtinu doby Navyše som do funkcie delay() pridal aj druhý kratučký for() cyklus, ktorý natvrdo dá do OVR0A registra neexistujúci tón, ktorý procesor interpretuje ako ticho. Toto je len ošetrenie ak by do registra OCR0A išli dva rovnaké tóny za sebou, aby nezneli ako jeden dlhý tón.

4 main() toto je časť do ktorej som len opisoval noty cez moje funkcie speaker() a delay(). Pre lepšiu prehľadnosť som pridal komentáre ku jednotlivým taktom a na koniec riadku aj slová spevu. Funguje to tak, že najprv zavolám funkciu speaker(), ktorá nastaví tón a potom ten tón podržím na určitú dobu funkciou delay(). Úplne prvý tón skladby, osminová nota tónu G4, viem teda naprogramovať ako:

speaker0(G4);		delay(tempo);

A potom tak treba napísať celú skladbu a šikovne doplniť harmóniu (akordy, sprievod) medzi tóny melódie.


Zdrojový kód: zdrojaky.zip


Overenie

Pre porovnanie funkčnosti som vo videu ukázal aj hranie nôt z MuseScore a potom z Arduina.

Video:

Po zapojení podľa schémy vyššie a spustení programu dostávame niečo takéto: