2010-II/es/2010Digital2
ANALIZADOR DE ESPECTRO DE AUDIO
Contents |
[edit] Calificación
| Porcentaje | Presentación 20% | Informe 30% | Funcionamiento 50% |
|---|---|---|---|
| 1 Entrega 10% | 0.5 | 0.5 | 0.5 |
| 2 Entrega 30% | 1.7 | 1.7 | 1.7 |
| 3 Entrega 20% | 3 | 4 | 3 |
| 4 Entrega 40% | 4.5 | 4.5 | 4.5 |
Es en esencia un dispositivo que a partir de una entrada de audio toma diferentes rangos de frecuencias, los separa y exhibe su intensidad en un gráfico como el que muestran varios reproductores de computador; existen tanto análogos como digitales, en este caso desarrollaremos un analizador digital.
Para implementarlo utilizaremos un proceso matemático llamado Transformada Rapida de Fourier (FFT), por medio del cual se transforma un señal continua (como el audio) en los diversos componentes de su espectro en frecuencia; este análisis se visualizara en el display gráfico del SIE, cuya interfaz mostrara 3 canales incluyendo la frecuencia máxima.
Cuando se habla de separar en canales se trata de dividir el espectro de frecuencias en varios rangos de frecuencias ya que en realidad eso es lo que obtenemos del filtro.
[edit] ESPECIFICACIONES
Físicas
El proyecto se implementara en la tarjeta de desarrollo SIE, con la ayuda de una tarjeta hija que contiene la matriz de leds (con su correpondiente amplificación), la memoria y el circuito análogo cambiador de nivel, las dimensiones de las tarjetas hijas es de 9cm x 8.1cm que es mismo tamaño que el sie.
Funcionales
Diagrama de Flujo
[edit] PARTICIONAMIENTO
[edit] Diagrama de bloques
[edit] Mapa de Memoria
| Periferico | Dirección |
|---|---|
| BRAM | 15'h0000 |
| UART | 15'h7000 |
| Timer | 15'h7001 |
| GPIO | 15'h7002 |
| Uniont | 15'h7003 |
[edit] Circuitos implementados
La siguiente galería muestra el diseño de la PCB que vamos a utilizar en este proyecto:
[edit] Cambio de Nivel
Se refiere a montar la señal de audio en una señal DC. Vamos a utilizar una señal de audio de salida del computador. Esta señal se encuentra alrededor de 2 voltios pico a pico, y la idea sería montarla en una de 1 voltio DC para poder tener los datos en un rango de 0 y 2 voltios.
[edit] Matriz de Leds
esta parte se refiere a la comunicación con la matriz de leds para graficar la frecuencia contra ganancia de la señal de audio; para el desarrollo del PCB se debe tener en cuenta los requerimientos de corriente y potencia propios de la matriz, así como también la configuración de la matriz, en este caso usaremos una matriz de 5x7 (5 Columnas - 7 Filas) con los ánodos ubicados en las columnas y los cátodos ubicados en las filas.
El circuito de la matriz de leds consta de varios amplificadores, para las columnas se utilizaron transistores tipo PNP, con una resistencia de 1k ohmios en la base y otra resistencia de 100 ohmios en el emisor. Al colector se le conectó una fuente de 3.3 voltios, que corresponde al pin numero 27 del sie; a la base se conectaron las correspondientes salidas digitales del sie.
Para las filas se utilizó un arreglo de amplificadores de tipo Darlington que corresponden al circuito integrado ULN2803, los cuales van conectados de la siguiente manera: las bases de los transistores van conectadas a una fuente de 3.3 voltios que corresponde al pin 27 del sie, y los colectores van a las salidas digitales del sie y a las filas del la matriz de leds.
[edit] Hardware
[edit] ADC
Básicamente en esta etapa lo que se hace es discretizar la señal de entrada. El ADC con el que vamos a trabajar es el que viene integrado con la tarjeta SIE que es el de referencia TLV1548C. Este ADC es serial, de 10 bits, con 8 canales de entrada análogas, con tiempo de conversión de 10us y frecuencia de muestreo es de 10kHz.
* 0x00: En el cambio de nivel de alto a bajo de ~CS, se habilitan las señales DATA_IN, DATA_OUT y I/O CLK. Mientras se recibe
DATA_IN; DATA_OUT está transmitiendo los datos de la anterior conversión al Wishbone.
* 0x04: EOC pasa de nivel alto a bajo cuando se completa el décimo ciclo de reloj, que es cuando se termina el muestreo y
comienza la conversión, y luego pasa a alto cuando se ha completado la conversión y ~CS pasa a nivel alto de nuevo.
Descripción de funcionamiento del periferico hardware: En los primeros 4 ciclos de reloj después que se ha puesto ~CS en nivel bajo, se lee la señal de entrada DATA_IN y se transmiten los datos de la señal DATA_OUT de la conversión anterior; los siguientes 6 ciclos de reloj proveen el control de tiempo para muestrear la señal análoga que se ha escogido con la señal DATA_IN. En ese momento ya se completan 10 ciclos de reloj, momento en el cual se pone EOC en 0 y comienza la conversión. EOC vuelve a 1 cuando se completa la conversión y ~CS puede volver a se 1. En este momento el proceso vuelve a empezar cuando se ponga de nuevo ~CS en nivel bajo.
El primer paso a realizar para comenzar a usar el ADC es inicializarlo, para ello debemos enviarle datos específicos en la entrada ADC_DIN.
Lista completa de comandos que recibe el ADC:
Para el correcto funcionamiento del ADC se debe seguir cierta secuencia de tiempos, para ellos observamos la siguiente imagen:
Para poder manejar el ADC es necesario manejar la siguiente interfez:
- Interfaz Serial (SPI): es un estándar de comunicación que utiliza un bus de 4 líneas para interconectar dispositivos periféricos de baja y media velocidad, la comunicación se realiza siguiendo un modelo maestro/esclavo donde el maestro selecciona al esclavo y comienza el proceso de transmisión/recepción de información; SPI constituye un bus full duplex, es decir, que se puede enviar y recibir información de manera simultánea, lo cual, eleva la tasa de transferencia de los datos.
Los nombres de las señales son definidos dentro del estándar como:
* SCLK (reloj del bus) * MOSI (Master Output Slave Input): salida de datos del maestro y entrada de datos a los esclavos * MISO (Master Input Slave Output): salida de datos de los esclavos y entrada de datos al maestro * SS (Slave Select): habilitación del esclavo por parte del maestro
- Código en Verilog del ADC
module SPI_slave_ADC(
input clk,
input reset,
input start,
output wire miso,
output wire led,
output wire led2,
output reg flagmem,
input [3:0] datain,
// ADCinput ADC_DOUT,
input ADC_EOC,
output reg ADC_SCLK,
output reg ADC_CS,
output reg ADC_DIN,
output wire ADC_CSTART
);
parameter count_c=17'd100000; //Counts 10us
reg miso_r;
reg [4:0] temp;
reg [16:0] temp_1;
reg [3:0] buffer_in;
reg [4:0] pulsos;
reg [2:0] state,next_state;
reg bajoSCLK=0;
reg altoSCLK=0;
reg [8:0] counter;
reg buff_start;
reg init;
initial begin
temp=0;
temp_1=0;
ADC_CS=1;
ADC_DIN=1'b0;
miso_r=1'd0;
buffer_in=4'd0;
pulsos=4'd0;
state=3'd0;
next_state=3'd0;
counter=9'd0;
ADC_SCLK=1'd0;
buff_start=1'd0;
init=1'd0;
ADC_CSTART=1;
end//datain=4'b1011 ---> la salida es 10'b1000000000//conversión Rápida ----> ADC_DIN=4'b1001always@(posedge clk)
beginif(~start) buff_start=1'd1;
if(reset)
beginbuff_start=1'd0;
ADC_DIN=1'b0;
miso_r=1'd0;
buffer_in=4'd0;
pulsos=4'd0;
state=3'd0;
next_state=3'd0;
counter=9'd0;
ADC_SCLK=1'd0;
init=1'd0;
endif(buff_start)
beginif(counter<9'd500)
begincounter=counter+1'd1;
endelse if (counter==9'd500)
beginpulsos=pulsos+1'd1;
counter=9'd0;
ADC_SCLK=~ADC_SCLK;
endstate=next_state;
case(state)
3'd0:begin
if(ADC_EOC==1'd1 && init==1'd0)
beginADC_CS=1'd0;
next_state=3'd1;
buffer_in=4'b1001;
endelse if(ADC_EOC==1'd1 && init==1'd1)
beginADC_CS=1'd0;
next_state=3'd2;
buffer_in=datain;
endend3'd1:begin
if(ADC_SCLK==1'd0 && counter==9'd250)
beginmiso_r=ADC_DOUT;
flagmem<=1'b0;
if(ADC_SCLK==1'd0 && pulsos<=5'd8)
beginADC_DIN=buffer_in[3];
buffer_in = buffer_in << 1;
endendelse if (pulsos>5'd20)
beginnext_state=3'd3;
init=1'd1;
flagmem<=1'b0;
endend3'd2:begin
if(ADC_SCLK==1'd0 && counter==9'd250)
beginmiso_r=ADC_DOUT;
flagmem<=1'b1;
if(ADC_SCLK==1'd0 && pulsos<=5'd8)
beginADC_DIN=buffer_in[3];
buffer_in = buffer_in << 1;
endendelse if (pulsos>5'd20)
beginnext_state=3'd3;
flagmem<=1'b0;
endend3'd3:begin
if (ADC_EOC==1'd0)
beginpulsos=4'd0;
endelse if (ADC_EOC==1'd1 && pulsos==4'd0)
beginADC_CS=1'd1;
endif(ADC_CS==1'd1 && pulsos==4'd7)
beginnext_state=3'd0;
pulsos=4'd0;
endendendcaseendendassign miso=miso_r;
endmodule
- Diagramas RTL
- Simulación
Luego de realizar el código en Verilog se obtuvo la siguiente simulación, en donde se observan los valores de inicialización (1001 ---> Conversión Rápida), el ciclo de conversión que es de 10us (para efectos de simulación se uso un tiempo de 40ns) y el cambio de las señales de control propias del ADC, en este punto ahí que aclarar que algunas variables, como ADC_EOC (que se le asigna a la señal wb_dat_o) las genera el ADC físico, por lo tanto en la simulación no van a presentar ningún cambio. Además se ha asignado que la señal ADC_CS sea la quinta posición del arreglo wb_dat_i y que la entrada ADC_DIN sea las posiciones [3:0] de wb_dat_i. También para efectos de simulación se colocó la dirección 0 para el arreglo wb_adr_i.
- Prueba
La anterior foto nos muestra el correcto funcionamiento del ADC del SIE. En esta se puede observar la señal EOC que nos que se mantiene en 1 mientras se muestrean los datos, se pone en 0 cuando se realiza la conversión y luego vuelve a 1 cuando se ha realizado el fin de la conversión.
En la anterior foto se logra ver en la parte superior la señal ADC_DOUT, con esto podemos comprobar que realmente se esta recibiendo la señal de audio digitalizada. Además en la parte inferior se esta mostrando la señal ADC_EOC que indica el tiempo de conversión.
[edit] Memoria
La idea principalmente es guardar en una pila los datos que provienen del ADC, para luego poder procesarlos en el módulo de FFT y asi poder visualizar en tiempo real en los leds. Para realizar esto se tienen dos opciones, la primera es hacer uso de los bloques de RAM disponibles en la fpga, con lo cual contamos con 360K de memoria, pero este espacio esta sujeto a disponibilidad dependiendo cuanto espacio ocupe el procesador y el programa como tal; por esta razón, la opción 2 es usar un SRAM externa de 32K.
- Opción 1
module memoria #(
parameter mem_file_name = ("none")
)(
input clk,
input ADC_SCLK,
input miso,
input flagmem,
input reset,
input start,
output reg [9:0]infft0,
output reg [9:0]infft1,
output reg [9:0]infft2,
output reg [9:0]infft3,
output reg [9:0]infft4,
output reg [9:0]infft5,
output reg [9:0]infft6,
output reg [9:0]infft7
);
reg [9:0]mem[63:0];
reg [3:0]count10;
reg [6:0]count64;
reg [9:0]infft_r0;
reg [9:0]infft_r1;
reg [9:0]infft_r2;
reg [9:0]infft_r3;
reg [9:0]infft_r4;
reg [9:0]infft_r5;
reg [9:0]infft_r6;
reg [9:0]infft_r7;
reg [9:0]memtemporal;
reg bajoSCLK=0;
reg altoSCLK=0;
reg [2:0] countmemoria;
reg reset_pass;
reg pass;
reg buff_start;
wire [9:0] buff_dout;
reg [3:0] count_fft;
reg [14:0] counter;
reg clk_frec;
initialbegincount10=4'd10;
count64=7'b0;
countmemoria=4'd1;
reset_pass=0;
count_fft=0;
infft_r0=0;
infft_r1=0;
infft_r2=0;
infft_r3=0;
infft_r4=0;
infft_r5=0;
infft_r6=0;
infft_r7=0;
counter=0;
clk_frec=0;
endreg [5:0] addr;
wire [5:0] addr_1;
memoria_ram #(
.mem_file_name(mem_file_name)
)memoria_m (.clk(clk),
.we(we),
.addr(addr_1),
.din(din),
.dout(buff_dout)
);
initial begin
addr=0;
end// Este código usa una memoria inicializada a traves de un archivo .ramalways@(posedge clk)
beginif(reset)
beginbuff_start=1'd0;
count_fft=0;
infft_r0=0;
infft_r1=0;
infft_r2=0;
infft_r3=0;
infft_r4=0;
infft_r5=0;
infft_r6=0;
infft_r7=0;
pass=0;
infft0=0;
infft1=0;
infft2=0;
infft3=0;
infft4=0;
infft5=0;
infft6=0;
infft7=0;
counter=0;
clk_frec=0;
endif(~start) buff_start=1'd1;
if(buff_start)
beginif(counter<15'd15000)
begincounter=counter+1'd1;
clk_frec=0;
endelse if (counter==15'd15000)
beginclk_frec=1;
counter=15'd0;
endif(clk_frec)
beginif(addr==64)
beginaddr=0;
endelse addr=addr+1;
if(count_fft<=7)
begincase(count_fft)
4'd0: begin infft_r0=buff_dout; count_fft=1; pass=0; end
4'd1: begin infft_r1=buff_dout; count_fft=2; end
4'd2: begin infft_r2=buff_dout; count_fft=3; end
4'd3: begin infft_r3=buff_dout; count_fft=4; end
4'd4: begin infft_r4=buff_dout; count_fft=5; end
4'd5: begin infft_r5=buff_dout; count_fft=6; end
4'd6: begin infft_r6=buff_dout; count_fft=7; end
4'd7: begin infft_r7=buff_dout; count_fft=0; pass=1; end
default: begin count_fft=0; pass=0; end
endcaseendif(pass)
begincount_fft=0;
infft0=infft_r0;
infft1=infft_r1;
infft2=infft_r2;
infft3=infft_r3;
infft4=infft_r4;
infft5=infft_r5;
infft6=infft_r6;
infft7=infft_r7;
endendendendassign addr_1=addr;
//------------------------------------------------------------------------------------// Este código llena una memoria a partir de las salidas del ADCalways@(ADC_SCLK)
beginif(ADC_SCLK)
beginbajoSCLK=0;
altoSCLK=1;
endelse begin
bajoSCLK=1;
altoSCLK=0;
endendalways@(negedge clk)
beginif(flagmem)
beginmemtemporal[count10]=miso;
mem[count64]=memtemporal;
endendalways@(posedge bajoSCLK)
beginif(~start) begin
buff_start=1'd1;
count10=4'd10;
count64=7'b0;
countmemoria=3'd0;
endif(reset)
begincount10=4'b0;
count64=7'b0;
buff_start=1'd0;
countmemoria=3'd0;
endif(buff_start)
beginif(count10==4'b0)
begincase(countmemoria)
3'd1:infft_r0= begin mem[count64]; reset_pass=1'b0; end
3'd2:infft_r1= mem[count64];
3'd3:infft_r2= mem[count64];
3'd4:infft_r3= mem[count64];
3'd5:infft_r4= mem[count64];
3'd6:infft_r5= mem[count64];
3'd7:infft_r6= mem[count64];
3'd0:infft_r7= mem[count64];
default:reset_pass=1'b1;
endcaseif(countmemoria==3'd0)
pass<=1'd1;
else if(countmemoria!=3'd0)
pass<=1'd0;
endif (flagmem)
begincount10=count10-1'b1;
if(count10==4'd15)
begincount64=count64+1'b1;
count10=4'd9;
if(reset_pass)
countmemoria=0;
elsecountmemoria=countmemoria+1'b1;
endif(count64==7'd64)
count64=7'b0;
endif (pass==1'd1)
begininfft1=infft_r1;
infft2=infft_r2;
infft3=infft_r3;
infft4=infft_r4;
infft5=infft_r5;
infft6=infft_r6;
infft7=infft_r7;
infft0=infft_r0;
endendendendmodule
Del anterior código se puede ver que se ha hecho una pequeña memoria de 640 bits, en la cual almacenamos cada uno de los datos que nos entrega el ADC. En cada una de las filas de 10 bits se ha guardado un dato y todos estos posteriormente serán entregados al modulo de la FFT; esto se hace de manera paralela ya que es necesario entregarle 8 arreglos de 10 bits a la FFT.
Inicialización de la RAM de prueba
module memoria_ram #(
parameter mem_file_name = "none"
)(
input wire clk,
input wire we,
input wire [5:0] addr,
input wire [9:0] din,
output wire [9:0] dout
);
// signal declarationreg [9:0] ram [0:63];
initialbeginif (mem_file_name != "none")
begin$readmemb(mem_file_name, ram);
endend// bodyalways @(posedge clk)
if (we) // write operation
ram[addr] <= din;
// read operationassign dout = ram[addr];
endmodule
- Opción 2
En nuestro circuito impreso agregamos una memoria tipo SRAM (Static Random Access Memory) con capacidad de 32K palabras de 8 bits cada una. Esta memoria tiene un rendimiento de alta velocidad y al tiempo consume poca potencia. Utiliza una señal de chip select (~CS) y una interfaz de periférico serial (SPI).
Lectura:
La memoria funciona con el flanco positivo del reloj serial. La memoria se habilita poniendo ~CS en estado bajo. Los primeros 8 bits que recibe el dispositivo en el pin SI (serial input) son para seleccionar la instrucción deseada que en este caso sería 0000 0011 como se indica en la tabla anterior; luego de recibir estos bits, la memoria recibe la dirección que se desea leer que consta de 16 bits. Luego de estos lo que se reciba por el pin SI sera un estado don't care. Después, la información almacenada en la dirección seleccionada pasa al pin SO (serial output). Después que la palabra inicial se ha puesto en SO, el dato almacenado en la siguiente posición de memoria se lee y así sucede secuencialmente, esto sucede porque el apuntador interno para la dirección de memoria se incrementa automáticamente luego que una palabra ha sido leida, esto puede suceder hasta que la se ha leido la palabra 32 y en ese momento el apuntador vuelve a ser cero y continua enviando los datos a SO.
Escritura:
Luego que se ha puesto ~CS se ha puesto en bajo, SI recibe en los 8 primeros bits la instrucción que en este caso es 0000 0010 y en los siguientes 16 bits recibe la dirección de memoria en la que se desea escribir. Luego de los 16 bits SI comienza a recibir los datos que de van a almacenar; en este momento SO se encuentra en alta impedancia. Cuando se termina de escribir una palabra, el apuntador interno de la dirección se incrementa automáticamente a la siguiente posición de memoria esto puede seguir por las 32 palabras de la memoria y cuando de terminan las 32 palabras, el apuntador vuelve a la posición 0 de memoria. El ciclo se termina cuando ~CS se pone en alto de nuevo.
[edit] UNIÓN
Este módulo contiene al ADC, la memoria y la FFT.
[edit] FFT
Transformada rápida de Fourier: en esta etapa se realiza el análisis del frecuencia de la señal discreta que ya tenemos.
Las señales que recibamos las vamos a clasificar en 3 canales de frecuencia principalmente:
* Graves: son las señales que estan entre 20Hz y 256Hz.
* Medios: son las señales que estan entre 256Hz y 2KHz.
* Agudos: son las señales que estan entre 2KHz y 20KHz.
El algoritmo de la FFT a trabajar, descompone la DFT de N puntos en transformadas más pequeñas; una DFT de N puntos es descompuesta en dos DFT’s de N/2 puntos, cada DFT de N/2 puntos se descompone a su vez en dos DFT’s de N/4 puntos y así sucesivamente. Al final de la descomposición se obtienen N/2 DFT´s de 2 puntos cada una; la transformada más pequeña viene determinada por la base de la FFT, para una FFT de base 2, N debe ser una potencia de 2 y la transformada más pequeña es la DFT de 2 puntos.
Para implementar la FFT existen dos procedimientos: diezmado en el tiempo (DIT del inglés Decimation In Time) y diezmado en frecuencia (DIF del inglés Decimation In Frequency); el primero, trabaja con los coeficientes desordenados y al final como resultado de las operaciones en estructura mariposa resultan ordenados, en cambio, el segundo trabaja de forma inversa, inicia ordenado y termina desordenado.
Algoritmo FFF en Base 2 y Diezmado en el tiempo:
Consideremos el cálculo de la DFT de N = 2v a partir de dividir la secuencia de datos de N puntos, en dos secuencias de N/2, correspondientes a las muestras pares e impares de x[n], respectivamente, esto es:
Obsérvese, que se realizó el diezmado de la secuencia x[n], una vez. La DFT de N puntos puede expresarse ahora en términos de las DFTs de las secuencias diezmadas como sigue:
Pero
. Sustituyendo esta igualdad en la expresión anterior se obtiene:
donde F1(k) y F2(k) son las DFTs de N/2 puntos de las secuencias f1[n] y f2[n].
Puesto que F1(k) y F2(k) son periódicas, de periodo N/2, tenemos F1(k+N/2)= F1(k) y F2(k+N/2)= F2(k); por otro lado, se cumple que
, por lo que se puede rescribir la expresión anterior de la siguiente manera:
Se observa que el calculo directo de F1(k) requiere (N/2)2 multiplicaciones complejas al igual que F2(k). Además, se requieren N/2 multiplicaciones más para calcular
. De aquí que el calculo de X(k) requiere N2/2 + N/2 multiplicaciones complejas. El primer paso realizado de una reducción en el número de multiplicaciones de N2 a N2/2 + N/2, lo que equivale aproximadamente a dividir por dos el número de multiplicaciones cuando N es grande.
Habiendo realizado el diezmado en tiempo una vez, podemos repetir el proceso para cada una de las secuencias f1[n] y f2[n]. Por lo tanto, se obtendrá dos secuencias de N/4 puntos:
Calculando las DFTs de N/4 puntos se obtienen las DFTs de N/2 puntos F1(k) y F2(k) a partir de las
siguientes relaciones:
donde Vij(k) son las DFTs de N/4 puntos de las secuencias vij[n].
Se observa que el calculo de Vij(k) requiere 4(N/4)2 multiplicaciones y por lo tanto el calculo de F1(k) y F2(k) puede realizarse con N2/4 + N/2 multiplicaciones complejas. Se requieren N/2 multiplicaciones complejas más para calcular X(k) a partir de F1(k) y F2(k). Consecuentemente, el número total de multiplicaciones necesarias N2/4 + N/2 se reduce otra vez aproximadamente por un factor de dos.
El diezmado de la secuencia de datos se repite v = log2 N veces, ya que se tienen N = 2v datos. Por lo tanto el número total de multiplicaciones complejas se reduce a (N/2) log2 N , mientras que el número de sumas complejas es N log2 N .
Como puede observarse, el calculo que se realiza en cada etapa, el cual consiste en aplicar las operaciones de una transformada DFT de dos puntos o “mariposa”; en general, cada mariposa implica una multiplicación y dos sumas complejas. Para N puntos, tenemos N/2 mariposas por cada etapa del proceso y log2N etapas de mariposas. Por lo tanto podemos guardar el resultado de cada operación de la mariposa (A, B), en las mismas posiciones de sus operandos (a, b). En consecuencia, necesitamos una cantidad fija de memoria, en concreto 2N registros de almacenamiento para guardar los resultados de N números complejos.
Simplificando aun más el proceso del algoritmo mediante la mariposa, se tiene lo siguiente:
dando como resultado un algoritmo aun más sencillo:
Ahora, para realizar la transformada de acuerdo a la estructura mariposa es necesario ser cuidadoso en el orden de las operaciones, así como también con el orden de los coeficientes, para ello se siguen las siguientes consideraciones:
Operaciones:
Coeficientes:
El orden de los coeficientes al inicio de la mariposa depende del número de de puntos y bits a manejar, por ejemplo, en este caso tenemos N=2n, en donde N=8, por lo tanto el número de bits a usar son n=3:
En la gráfica se puede ver claramente que la clave al momento de ordenar los coeficientes iniciales se encuentra en invertir el orden de los bits correspondientes a dicho coeficiente.
- Código de la Función Mariposa en Verilog:
function [17:0] mariposa;
input [17:0] wn;
input [17:0] s1,s2;
input n; //signo entre factores
reg [8:0] m1,m2,m3,m4,m5,m6,m7,m8;
begin//------------------ PARA EL FACTOR Wn*x --------------------------m1=wn[16:9]*s2[16:9]; //Real
m2=wn[16:9]*s2[7:0]; //Complejo
m3=wn[7:0]*s2[16:9]; //Complejo
m4=wn[7:0]*s2[7:0]; //Real (j*j)
//Signos parte realif((wn[17]==1 && s2[17]==1)||(wn[17]==0 && s2[17]==0)) m1[8]=0;
else if((wn[17]==0 && s2[17]==1)||(wn[17]==1 && s2[17]==0)) m1[8]=1;
//Signos parte complejaif((wn[17]==1 && s2[8]==1)||(wn[17]==0 && s2[8]==0)) m2[8]=0;
else if((wn[17]==0 && s2[8]==1)||(wn[17]==1 && s2[8]==0)) m2[8]=1;
//Signos parte complejaif((wn[8]==1 && s2[17]==1)||(wn[8]==0 && s2[17]==0)) m3[8]=0;
else if((wn[8]==0 && s2[17]==1)||(wn[8]==1 && s2[17]==0)) m3[8]=1;
//Signos parte real (ahi q tener en cuenta el q j*j=-1)if((wn[8]==1 && s2[8]==1)||(wn[8]==0 && s2[8]==0)) m4[8]=1;
else if((wn[8]==0 && s2[8]==1)||(wn[8]==1 && s2[8]==0)) m4[8]=0;
//Sumas parte complejaif(m2[8]==0 && m3[8]==0)
if (n==0) m6={1'b0,m2[7:0]+m3[7:0]};
else m6={1'b1,m2[7:0]+m3[7:0]};
else if(m2[8]==1 && m3[8]==1)
if (n==0) m6={1'b1,m2[7:0]+m3[7:0]};
else m6={1'b0,m2[7:0]+m3[7:0]};
else if((m2[8]==0 && m3[8]==1)&&(m2[7:0]>=m3[7:0]))
if (n==0) m6={1'b0,m2[7:0]-m3[7:0]};
else m6={1'b1,m2[7:0]-m3[7:0]};
else if((m2[8]==0 && m3[8]==1)&&(m2[7:0]<m3[7:0]))
if (n==0) m6={1'b1,m3[7:0]-m2[7:0]};
else m6={1'b0,m3[7:0]-m2[7:0]};
else if((m2[8]==1 && m3[8]==0)&&(m2[7:0]>m3[7:0]))
if (n==0) m6={1'b1,m2[7:0]-m3[7:0]};
else m6={1'b0,m2[7:0]-m3[7:0]};
else if((m2[8]==1 && m3[8]==0)&&(m2[7:0]<=m3[7:0]))
if (n==0) m6={1'b0,m3[7:0]-m2[7:0]};
else m6={1'b1,m3[7:0]-m2[7:0]};
//Sumas parte realif(m1[8]==0 && m4[8]==0)
if (n==0) m5={1'b0,m1[7:0]+m4[7:0]};
else m5={1'b1,m1[7:0]+m4[7:0]};
else if(m1[8]==1 && m4[8]==1)
if (n==0) m5={1'b1,m1[7:0]+m4[7:0]};
else m5={1'b0,m1[7:0]+m4[7:0]};
else if((m1[8]==0 && m4[8]==1)&&(m1[7:0]>=m4[7:0]))
if (n==0) m5={1'b0,m1[7:0]-m4[7:0]};
else m5={1'b1,m1[7:0]-m4[7:0]};
else if((m1[8]==0 && m4[8]==1)&&(m1[7:0]<m4[7:0]))
if (n==0) m5={1'b1,m4[7:0]-m1[7:0]};
else m5={1'b0,m4[7:0]-m1[7:0]};
else if((m1[8]==1 && m4[8]==0)&&(m1[7:0]>m4[7:0]))
if (n==0) m5={1'b1,m1[7:0]-m4[7:0]};
else m5={1'b0,m1[7:0]-m4[7:0]};
else if((m1[8]==1 && m4[8]==0)&&(m1[7:0]<=m4[7:0]))
if (n==0) m5={1'b0,m4[7:0]-m1[7:0]};
else m5={1'b1,m4[7:0]-m1[7:0]};
//------------------ PARA EL FACTOR x ------------------------------//Sumas parte complejaif(s1[8]==0 && m6[8]==0) m8={1'b0,s1[7:0]+m6[7:0]};
else if(s1[8]==1 && m6[8]==1) m8={1'b1,s1[7:0]+m6[7:0]};
else if((s1[8]==0 && m6[8]==1)&&(s1[7:0]>=m6[7:0])) m8={1'b0,s1[7:0]-m6[7:0]};
else if((s1[8]==0 && m6[8]==1)&&(s1[7:0]<m6[7:0])) m8={1'b1,m6[7:0]-s1[7:0]};
else if((s1[8]==1 && m6[8]==0)&&(s1[7:0]>m6[7:0])) m8={1'b1,s1[7:0]-m6[7:0]};
else if((s1[8]==1 && m6[8]==0)&&(s1[7:0]<=m6[7:0])) m8={1'b0,m6[7:0]-s1[7:0]};
//Sumas parte realif(s1[17]==0 && m5[8]==0) m7={1'b0,s1[16:9]+m5[7:0]};
else if(s1[17]==1 && m5[8]==1) m7={1'b1,s1[16:9]+m5[7:0]};
else if((s1[17]==0 && m5[8]==1)&&(s1[16:9]>=m5[7:0])) m7={1'b0,s1[16:9]-m5[7:0]};
else if((s1[17]==0 && m5[8]==1)&&(s1[16:9]<m5[7:0])) m7={1'b1,m5[7:0]-s1[16:9]};
else if((s1[17]==1 && m5[8]==0)&&(s1[16:9]>m5[7:0])) m7={1'b1,s1[16:9]-m5[7:0]};
else if((s1[17]==1 && m5[8]==0)&&(s1[16:9]<=m5[7:0])) m7={1'b0,m5[7:0]-s1[16:9]};
mariposa={m7,m8};
endendfunction
La función anteriormente descrita es la base de nuestra FFT, ya que el desarrollo de la misma no es más que el llamado a está función de forma recursiva para cada etapa del algoritmo mariposa de la FFT.
- Simulación:
Para la simulación se tomo como salida del ADC 8 muestras de 10 bits, que para efectos prácticos tomará valores entre 8 y 1.
Para los arreglos de salida X0 a X7 la distribución de bits es:
| X[17] | X[16:9] | X[8] | X[7:0] |
|---|---|---|---|
| Signo | # Real | Signo | # Complejo |
- Para la simulación de la FFT de 64 puntos se tomaron todos los coeficientes de entrada como 2 y se uso un contador para la realización de cada etapa de la mariposa, por lo cual, se observa claramente que la salida final de la FFT se obtiene en la etapa 6:
Magnitud
Debido a que el espectro de audio se representa como "Magnitud contra Frecuencia" se desarrollo el siguiente código basados en dos conceptos:
- La magnitud de un número complejo es:
- Aproximación de la raíz cuadrada mediante el Algoritmo Babilónico: este se centra en el hecho de que cada lado de un cuadrado es la raíz cuadrada del área.
- Pasos para hallar la Raíz(x):
- Escoja dos números b y h tales que bh = x
- Si h≈b vaya al paso 6, si no, vaya al paso 3
- Asigne b=(b+h)/2
- Asigne h=x/b
- Vaya al paso 2
- Escriba (x)1/2≈b
- Código:
`timescale 1ns / 1ps
module Magnitud(clk,X2,X4,X6,M2,M4,M6);
input clk;
input [21:0] X2,X4,X6;
output wire [20:0] M2,M4,M6;
function [20:0] suma_cuadrados;
input [21:0] X;
reg [9:0] m1,m2;
beginm1=X[20:11]; //X
m2=X[9:0]; //Y
suma_cuadrados=(m1*m1)+(m2*m2); //X^2+Y^2
endendfunctionassign M2=suma_cuadrados(X2);
assign M4=suma_cuadrados(X4);
assign M6=suma_cuadrados(X6);
endmodule
- Simulación:
Para la simulación se evaluaron valores extremos tales como: 1023+j1023 y 0+j0
[edit] RESOLUCIÓN
Para saber que frecuencias podemos llegar a visualizar se tienen las siguientes consideraciones matemáticas:
En donde fs es la frecuencia de muestreo (50KHz) y N es el número de puntos de la FFT (8), así pues, podemos obtener de forma sencilla la resolución frecuencial, que en nuestro caso es de 6.25KHz.
Considerando la resolución frecuencial y el numero de puntos de la FFT podemos obtener la siguiente tabla:
| Coeficiente k | Frecuencia |
|---|---|
| 0 | 0 |
| 1 | 6.25KHz |
| 2 | 12.5KHz |
| 3 | 18.75KHz |
| 4 | 25KHz |
| 5 | 31.25KHz |
| 6 | 37.5KHz |
| 7 | 43.75KHz |
Los coeficientes elegidos para la visualización son 1,2 y 3.
[edit] VISUALIZACIÓN
El control de la matriz de leds se realizo como una maquina de estados, habilitando las columnas de acuerdo a la frecuencia deseada y así mismo, evaluando la magnitud correspondiente para definir el número de leds que se encenderan las filas; además de esto, fue necesario realizar un buffer de tal manera que los datos visualizador se encontraran constantes el tiempo suficiente en la variables de magnitud evaluadas, esto con el fin de obtener una vizualización adecuada, ya que los datos varian muy rápido y no es posible observarlos por mucho tiempo en la matriz, lo que causaba que los leds estuviesen todo el tiempo en el dato que se presentaba mayor número de veces.
Código:
module Matriz(
input clk,
input start,
input reset,
input [20:0] M1,M2,M3,
output reg [4:0] columna,
output reg [6:0] fila
);
reg clk_frec;
reg [23:0] counter;
reg buff_start;
reg [20:0] buff_M1;
reg [20:0] buff_M2;
reg [20:0] buff_M3;
reg [31:0] cont1;
reg [31:0] cont2;
reg [31:0] cont3;
reg [31:0] cont4;
initialbegincolumna=1;
fila=1;
counter<=32'd0;
buff_M1=0;
buff_M2=0;
buff_M3=0;
clk_frec=0;
cont1=0;
cont2=0;
cont3=0;
cont4=0;
endalways@(posedge clk)
beginif(reset)
beginbuff_start=1'd0;
clk_frec=0;
counter=0;
cont1=0;
cont2=0;
cont3=0;
cont4=0;
endif(~start)
beginbuff_start=1'd1;
endif(buff_start)
beginif(counter<24'd500)
begincounter=counter+1'd1;
clk_frec=0;
endelse if (counter==24'd500)
beginclk_frec=1;
counter=24'd0;
endif(clk_frec)
begincont4=cont4+1'd1;
if(cont4==32'd1000)
begincont4=0;
buff_M1=M1;
buff_M2=M2;
buff_M3=M3;
endcase(cont3)
32'd0:begin
if(buff_M1==21'd0) begin cont1=0; cont2=0; cont3=1; end
else if(21'd0<buff_M1 && buff_M1<=21'd348843) begin cont1=1; cont2=1; cont3=1; end
else if(21'd348843<buff_M1 && buff_M1<=21'd697686) begin cont1=2; cont2=1; cont3=1; end
else if(21'd697686<buff_M1 && buff_M1<=21'd1046529) begin cont1=3; cont2=1; cont3=1; end
else if(21'd1046529<buff_M1 && buff_M1<=21'd1395372) begin cont1=4; cont2=1; cont3=1; end
else if(21'd1395372<buff_M1 && buff_M1<=21'd1744215) begin cont1=5; cont2=1; cont3=1; end
else if(21'd1744215<buff_M1 && buff_M1<=21'd2093058) begin cont1=6; cont2=1; cont3=1; end
end32'd1:begin
if(buff_M2==21'd0) begin cont1=0; cont2=0; cont3=2; end
else if(21'd0<buff_M2 && buff_M2<=21'd348843) begin cont1=1; cont2=2; cont3=2; end
else if(21'd348843<buff_M2 && buff_M2<=21'd697686) begin cont1=2; cont2=2; cont3=2; end
else if(21'd697686<buff_M2 && buff_M2<=21'd1046529) begin cont1=3; cont2=2; cont3=2; end
else if(21'd1046529<buff_M2 && buff_M2<=21'd1395372) begin cont1=4; cont2=2; cont3=2; end
else if(21'd1395372<buff_M2 && buff_M2<=21'd1744215) begin cont1=5; cont2=2; cont3=2; end
else if(21'd1744215<buff_M2 && buff_M2<=21'd2093058) begin cont1=6; cont2=2; cont3=2; end
end32'd2:begin
if(buff_M3==21'd0) begin cont1=0; cont2=0; cont3=0; end
else if(21'd0<buff_M3 && buff_M3<=21'd348843) begin cont1=1; cont2=3; cont3=0; end
else if(21'd348843<buff_M3 && buff_M3<=21'd697686) begin cont1=2; cont2=3; cont3=0; end
else if(21'd697686<buff_M3 && buff_M3<=21'd1046529) begin cont1=3; cont2=3; cont3=0; end
else if(21'd1046529<buff_M3 && buff_M3<=21'd1395372) begin cont1=4; cont2=3; cont3=0; end
else if(21'd1395372<buff_M3 && buff_M3<=21'd1744215) begin cont1=5; cont2=3; cont3=0; end
else if(21'd1744215<buff_M3 && buff_M3<=21'd2093058) begin cont1=6; cont2=3; cont3=0; end
enddefault: begin cont3=0; cont1=0; cont2=0; end
endcaseendendendalways@(cont1)
begincase(cont1)
32'd0: fila=7'b1111111;
32'd1: fila=7'b1111100;
32'd2: fila=7'b1111000;
32'd3: fila=7'b1110000;
32'd4: fila=7'b1100000;
32'd5: fila=7'b1000000;
32'd6: fila=7'b0000000;
endcaseendalways@(cont2)
begincase(cont2)
32'd0: columna=5'b11111;
32'd1: columna=5'b01111;
32'd2: columna=5'b10111;
32'd3: columna=5'b11011;
endcaseendendmodule
[edit] Software
- Inicializar el ADC: la principal tarea software que se va a realizar con este proyecto es mandarle la informacion del canal que se va a utilizar en el ADC.
void adc_init() { uniont0->channel=0x00; return; }
- Puerto serial
Para realizar algunas pruebas sobre el funcionamiento del proyecto, fue necesario trabajar con el puerto serial. En este se verificó que realmente los datos que entran a la memoria, son los mismos datos que llegan a la FFT. El siguiente es el código realizado para manejar la uart:
#include "soc-hw.h" volatile uint32_t *p; int main() { irq_disable(); adc_init(); unsigned char j=0; for(;j<1;){ *p=uniont0->miso; switch(*p){ case(0x00):uart_putchar(*p+0x30);break; case(0x01):uart_putchar(*p+0x31);break; default: uart_putchar(*p+0x3D);break; } } return(0); }
[edit] Análisis Económico
ANÁLISIS DE COSTOS DEL PROYECTO COSTO DEL PROTOTIPO
Elementos de circuitos varios $42,000
Pcb $26,000
SIE $195,000
Total $263,000
En el Cuadro anterior se observan los costos de insumos utilizados en la realizacin del proyecto. En elementos de circuitos varios, incluimos el costo de resistencias, cables, conectores, transistores, entre otros. No se incluyeron costos de software debido a que se utilizó software libre para la realización de todo el proyecto. Decidimos cobrar $30.000 por cada hora de trabajo en este proyecto basándonos en la dificultad del proyecto, la cantidad de conocimientos específicos necesarios para abordar un problema como el planteado; trabajamos alrededor de 448 horas, durante todo el semestre, por lo tanto el trabajo tiene un costo de $13.440.000. en total. Si tuvieramos que cobrar por este prototipo, cobraríamos alrededor de $15.000.000 teniendo en cuenta algunos gastos demás con la energía del sitio de trabajo, el costo de internet, entre otros.
COSTOS DE PRODUCIR 100 UNIDADES
Matrices 239 dólares
ULN2803 35 dólares
Resistencias, condensadores y diodos 39 dólares
Pcb 300 dólares
Conectores 500 dólares
Otros costos 100 dólares
Total 1213 dólares
Hecho por Carolina y Alejandra





