Programació en C del PIC 16F690

Referència Trucs Perifèrics   Recursos CITCEA
Tutorial Exemples Projectes   Inici

Brunzidor piezoelèctric

Un brunzidor piezoelèctric és un element capaç de generar un so d'una determinada freqüència quan se l'alimenta amb un senyal oscil·lant de la freqüència desitjada. En el nostre cas emprem un brunzidor CUI o un CAF4. El connexionat és molt simple. Només cal connectar el positiu del brunzidor a la sortida escollida i el negatiu a la pota VSS (negatiu de l'alimentació).

Tenim dues opcions per fer sonar les notes:

Al final de la pàgina hi podeu trobar alguns consells d'ús.

Atès que quan emprem PWM hem de fer servir la pota corresponent (RC5), en tots els casos i exemples utilitzarem la mateixa sortida.

Els brunzidors són elements petits i molt senzills de fer servir però el seu so no és de gaire qualitat. Si volem un so més agradable, podem emprar un altaveu.

Emprant PWM

El PWM ha de tenir la freqüència de la nota desitjada i un temps d'activació proper al 50 %.

Imaginem que volem fer un la (freqüència de 440 Hz) i amb un temps d'activació del 50 %.

Amb la configuració bàsica del microcontrolador, el rellotge treballa a 4 MHz que dona una freqüència de 1 MHz al rellotge del programa, amb el que es controla el Timer 2. Per tant ens caldria que el Timer 2 comptés fins a:

2273

Però el Timer 2 és de 8 bits, per tant només pot comptar fins a 256 valors. Per tant, ens cal posar una prescala a Timer 2. Aquesta prescala ha de ser superior a:

8,9

Com el Timer 2 pot tenir prescales d'1, 4 i 16, agafarem aquesta darrera. Així el nombre que ens cal comptar serà:

142

Però atès que el comptador comença a 0, caldrà comptar de 0 a 141 per tenir 142.

El Timer 2 es configura a T2CON. En el mode PWM els cinc bits més significatius no tenen utilitat. El bit 2 engega el Timer quan està activat, si està desactivat el Timer no treballa. Els bits 0 i 1 configuren la prescala, segons la següent taula:

Bit 1 Bit 0 Prescala
0 0 1
0 1 4
1 0 16
1 1

Així, doncs, T2CON haurà de ser 00000111 on els dos darrers bits són els que permeten triar la prescala.

Ara ens cal trobar el valor de CCP per aconseguir el temps d'activació desitjat. Com treballem amb 10 bits, el valor de CCP pot anar de 0 a quatre cops PR2; és a dir 568.

568

Per a un 50 %, el valor corresponent serà 284.

199

Hem de separar els dos bits de menys pes i quedar-nos amb la resta. Els vuit bits de més pes seran la part entera de dividir per 4:

71

I els dos bits de més pes seran:

0

Per al mode normal, la configuració del registre CCP1CON ha de ser 00001100; on els dos bits marcats en color són els que corresponen a DC1B.

Malauradament, el microcontrolador no té prou precisió i les notes reals tindran una freqüència lleugerament diferent. En aquest cas, serà:

0

La taula següent indica els valors que cal configurar en el PWM per obtenir dues notes extremes:

Nota Freqüència teòrica (Hz) Prescala PR2 CCPR1L DC1B Freqüència real (Hz)
si2 B2 246,942 16 253 126 2 247,036
do7 C8 4186,01 1 239 119 2 4184,10

A la velocitat de 4 MHz del microcontrolador no podem obtenir una nota de menor freqüència que el si2. Per obtenir freqüències més baixes caldria reduir la velocitat del rellotge.

La taula següent indica els valors que cal configurar en el PWM per obtenir les notes de l'escala central del piano:

piano
Nota Freqüència teòrica (Hz) Prescala PR2 CCPR1L DC1B Freqüència real (Hz)
do3 C4 261,626 16 238 119 2 261,506
do#3 re b3 C#4 Db4 277,183 16 224 112 2 277,778
re3 D4 293,665 16 212 106 2 293,427
re#3 mi b3 D#4 Eb4 311,127 16 200 100 2 310,945
mi3 E4 329,628 16 189 95 0 328,947
fa3 F4 349,228 16 178 89 2 349,162
fa#3 sol b3 F#4 Gb4 369,994 16 168 84 2 369,822
sol3 G4 391,995 16 158 79 2 393,082
sol#3 la b3 G#4 Ab4 415,305 16 149 75 0 416,667
la3 A4 440,000 16 141 71 0 440,141
la#3 si b3 A#4 Bb4 466,164 16 133 67 0 466,418
si3 B4 493,883 16 126 63 2 492,126
do4 C5 523,251 16 118 59 2 525,210

La funció següent toca la nota que se li indica durant 0,2 s i després deixa un silenci de 0,2 s més. Per especificar la nota, cal escriure els valors corresponents a PR2, CCPR1L i DC1B a les respectives variables ValPR2, ValCCPR1L i ValDC1B.

A l'exemple NM la podeu veure en funcionament.

#pragma config FOSC = INTRCIO, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = OFF
#pragma config CPD = OFF, BOREN = OFF, IESO = OFF, FCMEN = OFF
#include <xc.h>					// Carrega el fitxer de funcions necessari per al compilador XC8
#define _XTAL_FREQ  4000000			// La freqüència del rellotge és 4 MHz
void main (void){
	TRISC = 0b00100000;			// Definim com volem les E/S del port C
						// RC5 (sortida del PWM), de moment, com a entrada
	PORTC = 0;				// Desactiva les sortides del port C
	CCP1CON = 0b00001100;			// Configura el PWM, bits P1M (bits 7-6) a 00 mode senzill
						// DC1B = 11 (bits 5-4) els dos bits de menys pes són 0
						// CCP1M = 11xx en mode senzill els bit 0 i 1 no afecten
						// Ho posa com a configuració del PWM
	PIR1bits.TMR2IF = 0;			// Desactiva el bit d'interrupció del Timer 2
	T2CON = 0b00000011;			// Configura el Timer 2
						// bits T2KCPS (bits 1-0) a 11 prescalat de 16
						// bit 2 (TMR2ON) a 0, Timer aturat
						// Postscaler TOUTPS (bits 6-3) no afecten al PWM
...
	TocaNota(141, 71, 0);			// Valor que correspon aproximadament a 440 Hz (LA3)
...
void TocaNota(char ValPR2, char ValCCPR1L, char ValDC1B) {
	TRISC = 0b00100000;			// Definim com volem les E/S del port C
						// RC5 (sortida del PWM), de moment, com a entrada
	PR2 = ValPR2;				// Carrega PR2
	CCP1CON = CCP1CON & 0b11001111;		// Posa a zero els bits que corresponen a DC1B
	ValDC1B = ValDC1B % 4;			// DC1B va de 0 a 3
	ValDC1B = ValDC1B * 16;			// Desplaça els bits a la posició que els correspon a CCP1CON
	CCP1CON = CCP1CON | ValDC1B;		// Coloca DC1B al seu lloc
	CCPR1L = ValCCPR1L;			// Carrega CCPR1L, registre que ens dona l'amplada de tON
	PIR1bits.TMR2IF = 0;			// Desactiva el bit d'interrupció del Timer 2
	T2CON = 0b00000111;			// Configura el Timer 2
						// bits T2KCPS (bits 1-0) a 11 prescalat de 16
						// bit 2 (TMR2ON) a 1, Timer activat
						// Postscaler TOUTPS (bits 6-3) no afecten al PWM
	while (PIR1bits.TMR2IF == 0)		// Espera l'activació del bit d'interrupció del Timer 2
		;    				// Esperem
	TRISC = 0b00000000;			// Posem RC5 (sortida del PWM) com a sortida
	__delay_ms(200);	   	 	// Retard de 0,2 s
	TRISC = 0b00100000;			// Posem RC5 (sortida del PWM) com a entrada
						// O sigui, silenci
	__delay_ms(200);	   	 	// Retard de 0,2 s
}

Fent servir interrupcions

Aquest mètode és conceptualment més complicat però té l'avantatge que la gestió del so es fa en un segon pla i, per tant, el programa es continua executant mentre sonen les notes. Això, però, implica que la gestió de la durada de la nota (i, si escau, del temps entre notes) no es pot gestionar amb retards.

La funció TocaNota configurarà el timer 2 per tal que faci interrupcions periòdicament. A cada interrupció invertirem la sortida RC5 (corresponent al brunzidor). Per poder comptar la durada de la nota, hi haurà un comptador que permetrà acabar el so quan s'hagin fet un nombre predeterminat d'interrupcions. Un cop acabat el so, es comptaran un altre cop les interrupcions per establir la durada del silenci que, si es desitja, pot ser inexistent.

Atès que la gestió del temps la fa la pròpia interrupció, podria passar que es torni a cridar la funció TocaNota mentre encara està tocant la nota anterior. Si això passa, la funció s'espera a que hagi finalitzat.

Imaginem que volem fer un la (freqüència de 440 Hz) i amb un temps d'activació del 50 %. El període corresponent és:

2,273 ms

El temps d'activació serà la meitat del període:

1,136 ms

Aquest és el temps que hi haurà d'haver entre una interrupció i la següent. El càlcul del temps del timer 2 el podem fer multiplicant el període del rellotge del microcontrolador (1 μs si no hem canviat la velocitat) per l'invers del postescalador, l'invers del prescalador i el valor que posem a PR2.

durada

Podem escollir qualsevol combinació de valors que ens doni els 1136 μs però els valors que tenen més opcions de variació són PR2 i PostEs, per tant és recomanable buscar la combinació que ens doni el valor més gran possible (però inferior a 255) per a PR2 i, seguidament, el més gran possible per a PostEs. Si agafem 1 com a prescala, el valor més gran de postescala possible és 1/5 i obtindrem el següent valor de PR2:

PR2

Ara ens queda definir la durada de la nota. Imaginem que volem que sigui de 0,2 s, això voldrà dir que haurem de repetir aquest semiperíode el següent nombre de cops:

176

Si volem que després hi hagi un silenci del mateix valor, farem el mateix nombre de cicles. Si no volem silenci farem servir un zero.

La taula següent indica els valors que cal configurar en el PWM per obtenir les notes de l'escala central del piano:

piano

Nota Freqüència teòrica (Hz) Prescalat Postescalat PR2 NS Freqüència real (Hz)
PreEs PS PostEs TOUTPS
do3 C4 261,626 1 0 1/8 7 239 105 261,506
do#3 re b3 C#4 Db4 277,183 1 0 1/8 7 225 111 277,778
re3 D4 293,665 1 0 1/7 6 243 118 293,945
re#3 mi b3 D#4 Eb4 311,127 1 0 1/7 6 230 124 310,559
mi3 E4 329,628 1 0 1/6 5 253 132 329,381
fa3 F4 349,228 1 0 1/6 5 239 139 348,675
fa#3 sol b3 F#4 Gb4 369,994 1 0 1/6 5 225 148 370,370
sol3 G4 391,995 1 0 1/6 5 213 156 391,236
sol#3 la b3 G#4 Ab4 415,305 1 0 1/5 4 241 166 414,938
la3 A4 440,000 1 0 1/5 4 227 176 440,529
la#3 si b3 A#4 Bb4 466,164 1 0 1/5 4 215 186 465,116
si3 B4 493,883 1 0 1/4 3 253 198 494,071
do4 C5 523,251 1 0 1/4 3 239 209 523,013

La taula següent indica els valors que cal configurar per obtenir dues notes extremes:

Nota Freqüència teòrica (Hz) Prescalat Postescalat PR2 NS Freqüència real (Hz)
PreEs PS PostEs TOUTPS
do0 C1 32,7032 1/4 1 1/16 15 239 13 32,6883
do7 C8 4186,01 1 0 1 0 119 1681 4201,68

La funció següent toca la nota que se li indica durant el temps especificat seguida d'un silenci també especificat. Per seleccionar la nota i la durada, cal escriure els valors corresponents a valPre, valPos, valPR2 i numbuc. Per especificar el silenci posem un valor (calculat igual que numbuc) a numsil. Si hi posem el mateix valor, el silenci durarà el mateix que la nota. Si hi posem un zero no hi haurà silenci

A l'exemple NT la podeu veure en funcionament.

#pragma config FOSC = INTRCIO, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = OFF
#pragma config CPD = OFF, BOREN = OFF, IESO = OFF, FCMEN = OFF
#include <xc.h>					// Carrega el fitxer de funcions necessari per al compilador XC8
#define _XTAL_FREQ  4000000			// La freqüència del rellotge és 4 MHz
#define clrbit(var, bit) ((var) &= ~(1 << (bit)))
#define flipbit(var, bit) ((var) ^= (1<<(bit)))
char port;					// Variable auxiliar del port C
char control;					// Variable de control de la funció TocaNota
						// Si és 0 nota, si és 1 silenci
int bucles;					// Comptador d'iteracions de la nota
int silenci;					// Durada del silenci
void main (void){
	TRISC = 0;				// Tot el port C és de sortida
	port = 0;
	PORTC = 0;				// Desactiva les sortides del port C
	INTCON = 0b11000000;			// Activa GIE i PEIE
...
	TocaNota(0, 7, 239, 105, 105);	// Valor que correspon aproximadament a do3
...
}
void __interrupt() temporit(void){
// void interrupt temporit(void) {		// Línia alternativa
	if (PIR1bits.TMR2IF) {			// Comprovem que hi ha interrupció per Timer2
		PIR1bits.TMR2IF = 0;		// Desactiva el bit que indica interrupció pel Timer2
		if (control == 0){		// Si estem tocant una nota
			flipbit(port, 5);	// Inverteix la sortida del brunzidor
			PORTC = port;		// Ho copia al port C
		}
		bucles--;			// Comptador per a la durada de la nota
		if (bucles == 0){		// Si és zero, ja ha passat la durada de la nota
			if ((control == 1) || (silenci == 0)){	// Si s'acaba el silenci
				T2CONbits.TMR2ON = 0;		// Desactiva el Timer2
				PIE1bits.TMR2IE = 0;		// Desactiva les interrupcions per Timer2
			}
			if (control == 0){			// Si estem tocant una nota
				clrbit(port, 5);		// A l'acabar, deixa la sortida desactivada
				PORTC = port;			// Ho copia al port C
				if (silenci > 0){		// Si es preveu silenci
					bucles = silenci;	// Agafem la durada del silenci
					control = 1;		// Toca silenci
				}
			}
		}
	}
}
void TocaNota(char valPre, char valPos, char valPR2, int numbuc, int numsil) {
						// Funció per tocar una nota
	while(T2CONbits.TMR2ON)			// Si està tocant una nota
		;				// Esperem que acabi
	valPos &= 0b00001111;			// Per precaució, posem a zero els bits no emprats
	valPre &= 0b00000011;			// Per precaució, posem a zero els bits no emprats
	T2CON = ((valPos<<3) | valPre);		// Ho posa a la configuració del temporitzador
	PR2 = valPR2;				// Preselecció del Timer2
	bucles = numbuc;			// Comptador d'iteracions
	silenci = numsil;			// Durada del silenci
	control = 0;				// Comencem tocant la nota
	PIE1bits.TMR2IE = 1;			// Activem les interrupcions per Timer2
	T2CONbits.TMR2ON = 1;			// Activem el Timer2
}

Consells d'ús

Quan s'han de tocar diverses notes el programa es pot tornar força llarg si es va copiant de manera seqüencial cada un dels quatre valors corresponents a la nota i després es crida a la funció TocaNota. Pot ser una bona idea guardar les notes en un o més vectors i anar-les llegint d'allà.

També és possible guardar els valors a la memòria de programa i fer una funció que (indicant la posició del primer valor) els llegeixi seqüencialment de la memòria de programa i els guardi als registres corresponents. Com a referència, es pot consultar l'exemple MP.

En el cas de voler tocar una melodia ens trobarem que cada una de les notes es pot tocar diverses vegades. Llavors es pot guardar la melodia a base d'indicar la posició de cada una de les notes guardades. La seqüència de la melodia també es pot guardar en una taula o a la memòria de programa.

 

 

Llicència de Creative Commons
Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.