3.2 Processamento de ´ audio em tempo real em Arduino
3.2.5 Implementa¸c˜ao
Para juntar todos os elementos descritos na se¸c˜ao anterior e obter um sistema de processa- mento de ´audio em tempo real que inclui amostragem e emiss˜ao de sinal, basta apenas escolher os parˆametros certos para configurar as diferentes partes do microcontrolador.
Parˆametros ADC
Segundo a especifica¸c˜ao do microcontrolador, o processo completo de convers˜ao ADC demora cerca de 14.5 ciclos do rel´ogio ADC. Se a frequˆencia do rel´ogio da CPU ´e de 16 MHz e o valor do pr´e-escalonador ADC ´e p, ent˜ao o per´ıodo do rel´ogio ADC ´e de p/16 µs e o per´ıodo de convers˜ao ´e de
3.2 PROCESSAMENTO DE ´AUDIO EM TEMPO REAL EM ARDUINO 45
Figura 3.4: Evolu¸c˜ao temporal dos valores dos registros do mecanismo PWM funcionando no modo Fast PWM. O registro TCNTn corresponde ao valor do contador, e o registro OCnx cont´em o valor do pino de sa´ıda. Note como mudan¸cas no valor de referˆencia determinam o duty-cycle de cada per´ıodo de onda.
Tconv= 14.5 × p/16 µs. Os valores te´oricos para o per´ıodo de convers˜ao Tconv (para todos os valores de pr´e-escalonador dispon´ıveis) e os resultados ˜Tconv da medi¸c˜ao do per´ıodo de convers˜ao podem ser vistos na tabela abaixo. A ´ultima coluna corresponde `as frequˆencias de convers˜ao medidas
˜
fconv = 1/ ˜Tconv.
Pr´e-escalonador ADC Tconv (µs) T˜conv(µs) f˜conv(≈KHz)
2 1.8125 12.61 79.302 4 3.625 16.06 62.266 8 7.25 19.76 50.607 16 14.5 20.52 48.732 32 29 34.80 28.735 64 58 67.89 14.729 128 116 114.85 8.707
Estas medi¸c˜oes foram feitas utilizando a fun¸c˜ao micros() da API do Arduino, que tem uma resolu¸c˜ao de cerca de 4 µs. Isto pode explicar parte da diferen¸ca entre os valores medidos e os valores esperados para os valores mais baixos de pr´e-escalonador. Na medi¸c˜ao foi utilizada uma aproxima¸c˜ao de 8 bits, e para uma aproxima¸c˜ao de 10 bits pode-se esperar um aumento significativo (por volta de 25%) no tempo de convers˜ao.
PWM
A partir do que foi visto na Se¸c˜ao 3.2.3, em uma CPU operando a 16 MHz, um contador de 8 bits e valor de pr´e-escalonador igual a p possui uma frequˆencia de overflow de foverflow = 106/p × 24 Hz. Abaixo podemos ver uma tabela com as frequˆencias de incremento fincr e de interrup¸c˜ao por overflow foverflow para todos os valores poss´ıveis de pr´e-escalonador de um contador de 8 bits:
TOVn Interrupt Flag Set OCnx Interrupt Flag Set
1 2 3 TCNTn Period OCnx OCnx (COMnx1:0 = 2) (COMnx1:0 = 3) OCRnx Update
Figura 3.5: Evolu¸c˜ao temporal dos valores dos registros do mecanismo PWM funcionando no modo Phase Correct. O registro TCNTn corresponde ao valor do contador, e o registro OCnx cont´em o valor do pino de sa´ıda. Note como mudan¸cas no valor de referˆencia determinam o duty-cycle de cada per´ıodo de onda.
Pr´e-escalonador PWM fincr (KHz) foverflow(Hz)
1 16.000 62500 8 2.000 7812 32 500 1953 64 250 976 128 125 488 256 62,5 244 1024 15,625 61
A escolha dos valores de pr´e-escalonador PWM e ADC determinam a frequˆencia de amostragem do sistema DSP implementado. Se o pr´e-escalonador ADC for configurado de forma que o per´ıodo de convers˜ao ADC seja menor do que a frequˆencia de interrup¸c˜ao por overflow do PWM, e se a leitura da entrada for sincronizada com a escrita na sa´ıda, ent˜ao a frequˆencia de interrup¸c˜ao por overflow do PWM corresponde exatamente `a frequˆencia de amostragem do sistema DSP. Isto ser´a visto com mais detalhes na pr´oxima se¸c˜ao.
Para o mecanismo PWM, a escolha foi utilizar o modo Fast-PWM, com um contador de 8-bits com valor de pr´e-escalonador igual a 1. Isto fornece uma taxa de amostragem de 62500 Hz, suficiente para representar o espectro aud´ıvel. ´E poss´ıvel diminuir artificialmente esta frequˆencia executando o ciclo DSP completo (amostragem, processamento e emiss˜ao do sinal) n˜ao em todas mas apenas em uma fra¸c˜ao das interrup¸c˜oes. Nos testes executados, a escolha foi de cortar a frequˆencia de amostragem pela metade, para 31250 Hz, correspondendo a um per´ıodo de amostra de 32 µs. Esta escolha privilegia a vantagem de se ter dispon´ıvel o dobro do tempo de computa¸c˜ao, vantagem esta aparentemente maior do que a desvantagem de se perder os ´ultimos 4,4 KHz do espectro aud´ıvel. Juntando os peda¸cos
Tendo escolhido o tamanho do contador e do pr´e-escalonador PWM, falta apenas escolher os valores dos parˆametros ADC. Como j´a foi dito, ´e suficiente escolher valores que garantam que o per´ıodo de convers˜ao ADC ´e menor do que o per´ıodo desejado de uma amostra. Para os testes,
3.2 PROCESSAMENTO DE ´AUDIO EM TEMPO REAL EM ARDUINO 47 foi escolhido utilizar a convers˜ao de 8 bits para obter uma resolu¸c˜ao igual `a da sa´ıda PWM e um per´ıodo menor de convers˜ao (comparado com o per´ıodo da convers˜ao de 10 bits). Tamb´em foi escolhido um valor de pr´e-escalonador ADC igual a 8, correspondendo a um per´ıodo de convers˜ao medido de 19, 76µs que, quando comparado com o per´ıodo de amostra igual a 32 µs garante que a convers˜ao terminar´a antes que o mecanismo ADC tenha que iniciar uma nova convers˜ao, deixando tempo livre para outros processamentos.
Abaixo, pode-se ver uma reprodu¸c˜ao do peda¸co de c´odigo correspondente `a fun¸c˜ao de inter- rup¸c˜ao por overflow, executada sempre que o contador ´e reiniciado, que corresponde ao mecanismo controlador do ciclo DSP no sistema implementado. O vetor x ´e o buffer de entrada, ADCH ´e uma vari´avel que aponta para o registro ADC que cont´em a amostra de entrada, OCR2A aponta para o registro de sa´ıda PWM e y ´e o vetor de sa´ıda. Note que boa parte do c´odigo corresponde a c´alculos de ´ındices dos vetores.
1 /***************************************************************************** 2 * Funcao de interrupcao por overflow do Timer 2.
3 *
4 * As variaveis ‘writeind‘ e ‘readind‘ sao inteiros de 8 bits, portanto seu 5 * valor maximo e’ 255. Essa propriedade e’ utilizada para fazer a leitura e 6 * escrita circular dos vetores de entrada e saida, ‘x‘ e ‘y‘, cujo tamanho e’ 7 * exatamente 256. 8 * 9 ****************************************************************************/ 10 ISR(TIMER2_OVF_vect) 11 { 12 /*
13 * Divisao da frequencia de amostragem por 2.
14 */
15 static boolean div = false;
16 div = !div;
17 if (div){
18
19 /*
20 * Leitura da entrada com conversao de tipo. ADCH e’ o registrador que
21 * contem o resultado da ultima conversao ADC.
22 */
23 x[writeind] = (int16_t) 127 - ADCH; 24
25 /*
26 * Escrita da amostra de saida no registrador PWM, com ajuste de
27 * offset e conversao de tipo.
28 */
29 OCR2A = (uint8_t) 127 + y[(writeind-BLOCK_SIZE)]; 30
31 /*
32 * Verifica se um novo bloco de amostras esta cheio, calculando a
33 * seguinte condicao: (writeind mod BLOCK_SIZE) == 0.
34 */
35 if ((writeind & (BLOCK_SIZE - 1)) == 0) {
36 readind = writeind - BLOCK_SIZE;
37 dsp_block = true;
38 }
39
40 /*
41 * Incremento do indice de escrita.
42 */
43 writeind++;
44
45 /*
46 * Inicia a proxima conversao.
47 */
49 } 50 }
Note que no passo 3 (linhas 11-14) ´e feito um teste para determinar se o valor do ´ındice de entrada ´e um m´ultiplo do tamanho do bloco e, em caso positivo, o valor do ´ındice de leitura rind e da vari´avel dsp block s˜ao atualizados para sinalizar que existe um novo bloco de amostras dispon´ıvel para processamento. Enquanto isso, a fun¸c˜ao loop() ´e executada concorrentemente e eventualmente iniciar´a o trabalho nas amostras. Ao final, o ´ındice dos buffers ´e incrementado (linhas 16 e 17) e um registro ´e alterado para iniciar uma nova convers˜ao ADC (linha 19).
O c´odigo a seguir mostra uma implementa¸c˜ao de algoritmo simples de convolu¸c˜ao no dom´ınio do tempo dentro da fun¸c˜ao loop(). Cada implementa¸c˜ao substitui somente o peda¸co de c´odigo referente ao processamento do bloco de amostras.
1 /** 2 * LIMIT(x) 3 * Limita ‘x‘ ao intervalo [-127,127]. 4 */ 5 #define LIMIT(x) \ 6 if (x < -127) \ 7 x = -127; \ 8 if (x > 127) \ 9 x = 127; 10 11 /**
12 * TMOD(x, index, len)
13 * Leitura circular do indice ‘index‘ de um vetor ‘x‘ de tamanho ‘len‘, 14 * potencia de 2.
15 */
16 #define TMOD(x, index, len) x[(index)&(len-1)] 17
18 void loop() 19 {
20 /* aguarda um novo bloco de amostras */ 21 while (!dsp_block);
22
23 /* variaveis para contagem de tempo */ 24 static unsigned long elapsed_time = 0; 25 unsigned long start_time = micros(); 26
27 /* processamento do bloco */
28 uint16_t maxind = readind+BLOCK_SIZE;
29 for (uint8_t n = readind; n < maxind; n++) { 30
31 int16_t yn = 0; 32 uint8_t order = 0;
33 for (uint8_t i = 0; i <= order; i++) { 34 yn += TMOD(x, n, BUFFER_SIZE) * 0.33;
35 }
36
37 LIMIT(yn);
38 TMOD(y, n, BUFFER_SIZE) = yn;
39 }
40 elapsed_time += micros() - start_time; 41 count++;
42
43 dsp_block = false; 44 }