domingo, 15 de março de 2015

Como Utilizar a FFT com o PIC32MX - DSP Library

Neste post você encontra informações de como utilizar a Fast Fourier Transformer (FFT) em seu projeto com PIC32MX.

A família de microcontroladores PIC32MX não possui instruções de DSP. Operações de DSP estão disponíveis na família PIC32MZ. Aqui iremos apresentar a utilização da função mips_fft16 ( ) disponível na biblioteca DSP Library para o PIC32MX. Utilizaremos a linguagem C com o compilador MPLAB XC32 no ambiente de desenvolvimento MPLAB X.

Não entraremos em detalhes da teoria da Transformada Rápida de Fourier - FFT. Partiremos direto para a utilização da função mips_fft16 ( ). Para saber mais sobre a FFT você pode acessar estes posts: "Como Utilizar a FFT com o dsPIC - DSP Library" e "FFT-PIC - Um Analisador de Espectro para a Rede de Energia Elétrica ".


No início do nosso projeto devemos incluir os headers "dsplib_dsp.h" e "fftc.h" da biblioteca DSP, conforme mostra o Código 1. O arquivo "fftc.h" contém os coeficientes utilizados nos cálculos da FFT.


#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <p32xxxx.h>
#include <xc.h>         // comentar esta linha para usar o MPLAB C32
#include <plib.h>

#include "dsplib_dsp.h"
#include "fftc.h"

Código 1 - Incluindo headers da DSP Library ao projeto.

Para o nosso projeto temos que definir o número de "pontos" da FFT, conforme o Código 2.


/*         FFT com 128 pontos       */

#define N    128        // N = 2^7 = 128

#define fftc fft16c128  // from fftc.h, for N = 128

Código 2 - Define 128 pontos da FFT.

128 amostras no domínio do tempo (lidas pelo conversor analógico-digital, por exemplo), se transformarão em 64 pontos no domínio da frequência.

Dados da FFT deste exemplo:
- Frequência de amostragem (Fs): 7,68 ksps;
- Pontos da FFT: 128;
- log2 (N) = 7;
- Passo entre as frequências (Fo): 60 Hz.
A frequência de amostragem (Fs) do sinal de entrada irá determinar o espaçamento entre as frequências de cada ponto de saída (Fo).

A frequência de amostragem (Fs) do nosso sinal gerado (a onda quadrada) é de 7680 Hz, portanto Fo é 60 Hz, ou seja, 7680 sps / 128 pontos da FFT!

Isto quer dizer que temos na saída o valor do espectro das frequências múltiplas de 60 Hz, de 0 à 3840 Hz (60 Hz * 64 pontos no domínio da frequência).

Para o sinal de entrada que será aplicado a FFT iremos utilizar um vetor com dados de uma onda quadrada de 780 Hz e frequência de amostragem 7680 Hz. Esta onda foi gerada com o auxílio do Software dsPICworks, da Microchip. Para mais informações de como gerar este vetor de dados acesse "Como Utilizar a FFT com o dsPIC - DSP Library". O vetor sinal_Entrada [ ] do tipo int16, com 128 amostragens é visto no Código 3.



int16 sinal_Entrada [128]= {                   //vetor que ira armazenar os dados de entrada
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,        //valores iniciais declarados para simulacao
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,        //e teste do programa
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,        //square signal 780 Hz, 7680 sps, -1.0 a +1.0
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
0x8001, 0x8001, 0x8001};

 
Código 3 - Vetor com dados de entrada para a simulação e cálculo da FFT.

 A seguir precisamos declarar os vetores e variáveis utilizadas para o cálculo da função mips_fft16 ( ), conforme o Código 4.



    int log2N = 7;      // log2(128) = 7        N = 128 = (1<<log2N)     N = 2^7 = 128
    int16c din[N];
    int16c dout[N];
    int16c scratch[N];
    int16c *p_fftc = &fftc;
Código 4 - Declaração das variáveis utilizadas no cálculo da FFT.


Os vetores utilizados pela função mips_fft16 ( ) são do tipo int16c, ou seja, inteiro 16-bit complexo, com partes real e imaginária.

No início da função main ( ) iremos transferir os coeficientes para o cálculo da FFT, conhecidos como Twiddle Factors, da memória de programa para a memória RAM, para execução mais rápida do cálculo. Veja o Código 5.


    p_fftc = &fftc;                      //inicializa ponteiro

    for (i = 0; i < N / 2; i++)          //move twiddleFactors da flash para a RAM
    {
        twiddleFactors [i] = *p_fftc;
        *p_fftc++;                       //incrementa o ponteiro
    }


Código 5 - Movendo os coeficientes para a memória RAM.


Agora podemos partir para um loop while onde continuamente o PIC32 irá mover os dados contidos no vetor sinal_Entrada [ ] para o vetor din [ ] e limitar a escala entre -0.5 e +0.5. Note que sinal_Entrada [ ] pode conter dados vindos de um conversor analógico-digital ADC, por exemplo, e din [ ] é o vetor com dados complexos utilizados nos cálculos da FFT.

   while (TRUE) {

        //move buffer do sinal de entrada (real)para vetor da FFT (complexo)
        for (i = 0; i < N; i++)
        {
            din[i].re = sinal_Entrada [i];  //parte real
            din[i].im = 0;                  //parte imaginaria
            din[i].re = din[i].re >> 1;     //divide pela metade, escalona para -0.5 a +0.5
        }
          
        ...

  
Código 6 - Preparando o vetor de dados complexos de entrada para os cálculos da FFT.


A chamada para a função mips_fft16 ( ), que aplica a Transformada Rápida de Fourier ao sinal, é mostrada no Código 7. Detalhes sobre os argumentos desta função você encontra em "PIC32 DSP Library".


        // Executa o Calculo da FFT
        mips_fft16 (dout, din, twiddleFactors, scratch, log2N);


Código 7 - Cálculo da FFT.


O resultado da FFT está contido no vetor complexo dout [ ].

Para exibir o resultado gráfico da FFT comumente se computa o single sided FFT, a magnitude quadrática do espectro de frequência, ou o módulo do número complexo. O Código 8 apresenta as fórmulas que devem ser aplicadas a cada posição do vetor de dados.



        /*     Computa o single sided FFT    */
        // Calcula a soma do quadrado das partes real e imaginária transformando vetor complexo em um vetor real.
  
        square_Magnitude = parte_Real^2 + parte_Imaginaria^2
        

        /*          Calcula o Modulo         */
        // Extrai a raiz quadrada da soma dos quadrados da parte real e imaginaria
        
        modulo = sqrtf(parte_Real^2 + parte_Imaginaria^2)  

        
  
Código 8 - Cálculo da magnitude quadrática do resultado da FFT.


Por fim, podemos calcular o espectro da frequência de maior magnitude do sinal amostrado, conforme exemplificado no Código 9.



  
       // Encontra a frequency Bin ( indice da frequencia) que possui a maior energia
        picoFrequenciaBin = vectorMaxBin (PONTOS_FFT/2,squareMagnitude);

        // Calcula a frequencia em Hz com maior espectro
        picoFrequencia = picoFrequenciaBin*(SAMPLING_FREQ/PONTOS_FFT);

Código 9 - Cálculo do espectro da frequência de maior magnitude.


O programa agora pode ser testado.


Para confirmar se o seu projeto está correto, verifique se a variável picoFrequencia apresenta o valor de 780 [Hz] após os cálculos. Esta é a frequência da onda quadrada gerada no dsPICwork para a simulação dos cálculos da FFT.

A posição do vetor com o espectro de frequência de maior valor é SquareMagnitude [13].

Veja os cálculos finais:

Fs               /    PONTOS_FFT              =      Fo
7680 sps    /     128 pontos da FFT         =     60 Hz

60 Hz * [13]       =        780 Hz!


O Vídeo 1 mostra este código rodando no MPLAB X em modo simulação passo-a-passo.


Vídeo 1 - Simulação dos cálculos da FFT com o PIC32MX.



O Vídeo 2 mostra uma aplicação da FFT com o PIC32MX para a identificação da frequência de um sinal. O display gráfico mostra os dados do vetor squareMagnitude [ ] como resultado da FFT, o espectro das frequências.

Vídeo 2 - Aplicação da FFT para identificar a frequência de um sinal.


Para quem gosta de música e áudio, o Vídeo 3 mostra uma aplicação bem legal para a FFT: um Analisador de Espectro de Áudio para a animação de um MP3 Player com o PIC32MX!
 Vídeo 3 - PIC32 MP3 Player & Audio Spectrum Analyser com a FFT.


Foi utilizada a função mips_fft16 para o cálculo da FFT com 128 pontos.

A FFT é testada com o arquivo de áudio Sine3kHz.MP3 contendo uma onda senoidal variando de 20 Hz à 3 kHz.



Referências:

HAYKIN, Simon S.; VAN VEEN, Barry. Sinais e sistemas. Porto Alegre: Bookman, 2001. 668p.

MICROCHIP. Code Examples. CE018 - Using the Fast Fourier Transform (FFT) for Frequency Detection. Disponível em: http://ww1.microchip.com/downloads/en/DeviceDoc/CE018_FFT_DSPlib_101007.zip


MICROCHIP. Software Libraries. Disponível em: http://www.microchip.com/SoftwareLib.aspx



sábado, 7 de março de 2015

Como Utilizar a FFT com o dsPIC - DSP Library

Este post mostra um exemplo de como utilizar a Fast Fourier Transformer (FFT) da Microchip DSP Library da família dsPIC.


dsPICs  são Controladores Digitais de Sinais de 16-bit da Microchip, uma junção de Microcontrolador com Processador Digital de Sinais (DSP). Possui a memória RAM dividida em 2 partes: Memória X para acesso das instruções da parte do Microcontrolador e Memória Y para acesso das instruções de DSP. Os cálculos de DSP são executados com variáveis do tipo fracionária (fractional Q15), com range entre -1.0 e +1.0. 

As instruções de DSP são tipicamente utilizadas para o Processamento Digital de Sinais. Como todo DSP, o dsPIC também possui a instrução MAC!

A DSP Library da Microchip possui diversas funções como:
- operação com vetores: máximo, mínimo,  soma, subtração, multiplicação, convolução, correlação, etc.;
- operação com janelas: Bartlett, Blackman, Hamming, Kaiser, etc;
- operação com matrizes;
- filtros digitais FIR e IIR;
- transformadas FFT e IFFT;

Aqui iremos apresentar a utilização da função FFTComplexIP ( ) da DSP Library utilizando a linguagem C. O cálculo da FFT da DSP Library está escrito em Assembly, pois utiliza as instruções de DSP, para permitir o menor tempo de processamento.


Sinal de Entrada para o Cálculo

Para o sinal de entrada que será aplicado a FFT iremos gerar uma onda quadrada com o auxílio do Software dsPICworks, da Microchip.

No menu Generator encontramos a opção Square... para gerar uma onda quadrada.

A Figura 1 mostra os parâmetros que devemos preencher para o software gerar 128 amostras de uma onda quadrada com a frequência de 780 Hz, taxa de amostragem de 7680 sps, do tipo fracionário de 16 bit.

FIgura 1 - Configurações para gerar uma onda quadrada de 780 Hz e 7680 sps no dsPICworks.


A onda quadrada gerada, no domínio do tempo, é vista na Figura 2.

Figura 2 - Onda gerada no dsPICWork.


No menu File, opção Export File... podemos exportar os dados da onda gerada pelo dsPICworks.

O vetor com os dados gerados já adicionado em nosso programa está contido no Código 1. Repare que o vetor é do tipo fractional, 16-bit, com 128 dados de valores -1.0 e +1.0.



fractional sinal_Entrada [128]= {            //vetor que ira armazenar os dados de entrada
                                          //valores iniciais declarados para simulacao
                                          //e teste do programa
                                          //square signal 780 Hz 7.68 kSPS
                                          //sinal gerado com o dsPICWorks
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,    
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,    
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001};

Código 1 - Vetor de dados da onda quadrada exportada pelo dsPICworks para ser utilizado no MPLAB.


Dentro do arquivo header DSP.h da Biblioteca DSP podem ser encontrados detalhes dos argumentos da função FFTComplexIP ( ). Quem tiver interesse pode encontrá-lo no Código 2.


/****************************************************************************
*
* Interface to transform operations.
*
* A set of linear discrete signal transformations (and some of the inverse
* transforms) are prototyped below. The first set applies a Discrete Fourier
* transform (or its inverse) to a complex data set. The second set applies
* a Type II Discrete Cosine Transform (DCT) to a real valued sequence.
*
* A complex valued sequence is represented by a vector in which every pair
* of values corresponds with a sequence element. The first value in the pair
* is the real part of the element, and the second its imaginary part (see
* the declaration of the 'fractcomplex' structure at the beginning of this
* file for further details). Both, the real and imaginary parts, are stored
* in memory using one word (two bytes) each, and must be interpreted as Q.15
* fractionals.
*
* The following transforms have been designed to either operate out-of-place,
* or in-place. The former type populates an output sequence with the results
* of the transformation. In the latter, the input sequence is (physically)
* replaced by the transformed sequence. For out-of-place operations, the user
* must provide with enough memory to accept the results of the computation.
* The input and output sequences to the FFT family of transforms must be
* allocated in Y-Data memopry.
*
* The transforms here described make use of transform factors which must be
* supplied to the transforming function during its invokation. These factors,
* which are complex data sets, are computed in floating point arithmetic,
* and then transformed into fractionals for use by the operations. To avoid
* excessive overhead when applying a transformation, and since for a given
* transform size the values of the factors are fixed, a particular set of
* transform factors could be generated once and used many times during the
* execution of the program. Thus, it is advisable to store the factors
* returned by any of the initialization operations in a permanent (static)
* vector. The factors to a transform may be allocated either in X-Data or
* program memory.
*
* Additional remarks.
*
* A) Operations which return a destination vector can be nested, so that
*    for instance if:
*
*    a = Op1 (b, c), with b = Op2 (d), and c = Op3 (e, f), then
*
*    a = Op1 (Op2 (d), Op3 (e, f))
*
****************************************************************************/

/* Transform operation prototypes. */


extern fractcomplex* TwidFactorInit (    /* Initialize twiddle factors */
                    /* WN(k) = exp(i*2*pi*k/N) */
                    /* computed in floating point */
                    /* converted to fractionals */
   int log2N,                /* log2(N), N complex factors */
                       /* (although only N/2 are computed */
                       /* since only half of twiddle factors */
                    /* are used for I/FFT computation) */
   fractcomplex* twidFactors,        /* ptr to twiddle factors */
   int conjFlag                /* indicates whether to generate */
                    /* complex conjugates of twiddles */
                    /* 0 : no conjugates (default) */
                    /* 1 : conjugates */

                    /* twidfact returned */
                    /* only the first half: */
                    /* WN(0)...WN(N/2-1) */
                    /* (or their conjugates) */
);

/*...........................................................................*/

extern fractcomplex* BitReverseComplex (    /* Bit Reverse Ordering */
                    /* (complex) */
   int log2N,                /* log2(N), N is vector length */
   fractcomplex* srcCV            /* ptr to source complex vector */
                    /* MUST be N modulo aligned */

                    /* srcCV returned */
);

/*...........................................................................*/

extern fractcomplex* FFTComplex (    /* Fast Fourier Transform */
                    /* (complex, out-of-place) */
   int log2N,                /* log2(N), N-point transform */
   fractcomplex* dstCV,            /* ptr to destination complex vector */
                       /* with time samples */
                    /* in natural order */
                    /* MUST be N modulo aligned */
   fractcomplex* srcCV,            /* ptr to source complex vector */
                       /* with time samples */
                    /* in natural order */
   fractcomplex* twidFactors,        /* base address of twiddle factors */
                    /* either in X data or program memory */
                    /* if in X data memory, it points at */
                       /* WN(0).real */
                    /* if in program memory, base is the */
                    /* offset from program page boundary */
                    /* to address where factors located */
                    /* (inline assembly psvoffset ()) */
   int factPage                /* if in X data memory, set to */
                       /* defined value COEFFS_IN_DATA */
                    /* if in program memory, page number */
                    /* where factors are located */
                    /* (inline assembly psvpage ()) */

                    /* dstCV returned */
                       /* with frequency components */
                    /* in natural order */
                    /* and scaled by 1/(1<<log2N) */
);

/*...........................................................................*/

extern fractcomplex* FFTComplexIP (    /* Fast Fourier Transform */
                    /* (complex, in-place) */
   int log2N,                /* log2(N), N-point transform */
   fractcomplex* srcCV,            /* ptr to source complex vector */
                       /* with time samples */
                    /* in natural order */
   fractcomplex* twidFactors,        /* base address of twiddle factors */
                    /* either in X data or program memory */
                    /* if in X data memory, it points at */
                       /* WN(0).real */
                    /* if in program memory, base is the */
                    /* offset from program page boundary */
                    /* to address where factors located */
                    /* (inline assembly psvoffset ()) */
   int factPage                /* if in X data memory, set to */
                       /* defined value COEFFS_IN_DATA */
                    /* if in program memory, page number */
                    /* where factors are located */
                    /* (inline assembly psvpage ()) */

                    /* srcCV returned */
                       /* with frequency components */
                    /* in bit reverse order */
                    /* and scaled by 1/(1<<log2N) */
);
Código 2 - Parte do arquivo DSP.h da Microchip DSP Library com declaração das funções da FFT.


No início do nosso projeto devemos incluir o header DSP.h da biblioteca DSP, conforme mostra o Código 3.

O arquivo Header do dsPIC30F4013 também é incluso aqui, aliás, este é o DSC que iremos utilizar para a demonstração da FFT. Possui capacidade para 30 MIPS @117 MHz, 48 kB de Flash para memória de programa, 1 kB de memória RAM X e 1 kB de memória RAM Y, 1 kB de EEPROM e 13 canais ADC 12-bit com 200 ksps.

#include "dsp.h"
#include "p30f4013.h"
#include <stdio.h> 
Código 3 - Incluindo a biblioteca DSP.h ao projeto.

Os seguintes arquivos de biblioteca (.a) foram adicionados ao projeto:
- libc-coff.a;
- libdsp-coff.a;
- libm-coff.a;
- libp30f4013.a;
- libpic30-coff.a.


A Transformada Rápida de Fourier

Bom, agora temos que compreender alguns conceitos básicos da FFT. Para mais detalhes você pode acessar este outro post: http://mrgptu.blogspot.com.br/2015/01/analisador-de-espectro-fft-pic.html

A Transformada de Fourier (FT) é utilizada para converter uma função ou sinal do domínio do tempo para o domínio da frequência e vice-versa.

A Transformada Discreta de Fourier (DFT) é aplicada para sinais discretos no tempo (dados coletados por um conversor analógico digital por exemplo).

A Transformada Rápida de Fourier (FFT) foi desenvolvida para uma execução mais rápida dos cálculos da FT possibilitando o seu uso na computação. Dentre as suas principais aplicações estão a filtragem digital, reconhecimento de padrões, bargraph para sinais de áudio, equalizadores, eliminações de ruído e interferência de imagens.

A FFT representa a soma de uma série de ondas senoidais de diferentes frequências, fases e amplitudes.

Para entender a diferença do sinal analisado no domínio do tempo e no domínio da frequência vamos a um exemplo prático utilizando o software dsPICworks, da Microchip. Com ele geramos alguns sinais. 

Na Figura 3a temos um sinal senoidal de 60 Hz (domínio do tempo, eixo x do gráfico).
 

  Figura 3a - Sinal Senoidal de 60 Hz no tempo.


 Na Figura 3b um sinal senoidal de 600 Hz (domínio do tempo).


 Figura 3b - Sinal Senoidal de 600 Hz no tempo.


Efetuando a soma dos sinais senoidais A e B obtemos o sinal C mostrado na Figura 3c (domínio do tempo).



  Figura 3c - Soma dos Sinais Senoidais de 60 Hz e 600 Hz no tempo.


O resultado da Transformada Rápida de Fourier aplicada ao sinal C é exibido na Figura 3D. O sinal agora é visto no domínio da frequência (eixo x do gráfico). Observe 2 picos no gráfico, que representam a energia do espectro de 60 Hz e de 600 Hz.

 Figura 1d - Resultado da FFT aplicada ao sinal.


A análise de um sinal no domínio da frequência se torna muito interessante e/ou mais amigável conforme a aplicação, por exemplo, em um VU Meter, equalizador gráfico de áudio ou na análise das frequências harmônicas da rede de energia elétrica (múltiplas de 60 Hz).


Para o nosso projeto temos que definir o número de "pontos" da FFT, conforme o Código 4.


//---------------------- Define constantes -------------------------------
// constantes da FFT
#define PONTOS_FFT    128         //numero de pontos da FFT
#define LOG2_ESTAGIOS_BUTTERFLY 7    //numero de estagios "Butterfly" da FFT
#define TAXA_AMOSTRAGEM        7680    //taxa de amostragem do sinal de entrada 
Código 4 - Define 128 pontos da FFT.


128 amostras no domínio do tempo (lidas pelo conversor analógico-digital, por exemplo), se transformarão em 64 pontos no domínio da frequência.

Dados da FFT deste exemplo:
- Frequência de amostragem (Fs): 7,680 ksps;
- Pontos da FFT: 128;
- log2 (N) = 7;
- Passo entre as frequências (Fo): 60 Hz.
A frequência de amostragem (Fs) do sinal de entrada irá determinar o espaçamento entre as frequências de cada ponto de saída (Fo).

A frequência de amostragem (Fs) do nosso sinal gerado (a onda quadrada) é de 7680 Hz, portanto Fo é 60 Hz, ou seja, 7680 sps / 128 pontos da FFT!

Isto quer dizer que temos na saída o valor do espectro das frequências múltiplas de 60 Hz, de 0 à 3840 Hz (60 Hz * 64 pontos no domínio da frequência).


O Código Fonte

Retornando ao nosso projeto, iremos declarar as outras variáveis e vetores de dados que serão utilizados, Veja no Código 5 que o vetor sinalComplexo é do tipo fractcomplex (fracionário com partes real e parte imaginária). Este vetor irá conter os dados de entrada complexos para o cálculo da FFT. Este vetor é alocado na memória RAM Y, acessada pelas instruções de DSP contidas na função FFT.




int    picoFrequenciaBin = 0;                
unsigned long picoFrequencia = 0;

fractional *temp3_pnt, tempFrac;

// Entrada do sinal para a FFT declarada na memoria Y
fractcomplex sinalComplexo[PONTOS_FFT]__attribute__ ((section (".ydata, data, ymemory"),aligned (PONTOS_FFT * 2 *2))); 

fractional SquareMagnitude [PONTOS_FFT/2];
Código 5 - Declara variáveis e vetores.


Os cálculos da FFT utilizam coeficientes chamados Twiddle. Estes coeficientes podem ser calculados através da equação:

                                 WN (kn) = exp [-(j*2*pi*k*n)/N]

No nosso programa utilizamos os coeficientes declarados na memória de programa. A quantidade de coeficientes depende do número de pontos da FFT. O Código 6 contém os coeficientes Twiddle para 128 pontos da FFT. Os coeficientes também podem estar alocados na memória RAM, o que permite o processamento da FFT em menor tempo, mas nem sempre temos espaço em memória RAM disponível (1 kB para o dsPIC30F4013), então podemos optar por declarar os coeficientes na memória de programa (48 kB para o dsPIC30F4013).

// ------------- Declara constantes gravadas na flash -------------------------

// declara vetor dos coeficientes Twiddle da FFT na memoria de programa
// Define 128 constantes "twiddle" da FFT: WN (kn) = exp [-(j*2*pi*k*n)/N]
const fractcomplex twiddleFactors[] __attribute__ ((space(prog), aligned (PONTOS_FFT*2)))=
        {
        0x7FFF, 0x0000, 0x7FD9, 0xF9B8, 0x7F62, 0xF374, 0x7E9D, 0xED38,
        0x7D8A, 0xE707, 0x7C2A, 0xE0E6, 0x7A7D, 0xDAD8, 0x7885, 0xD4E1,
        0x7642, 0xCF04, 0x73B6, 0xC946, 0x70E3, 0xC3A9, 0x6DCA, 0xBE32,
        0x6A6E, 0xB8E3, 0x66D0, 0xB3C0, 0x62F2, 0xAECC, 0x5ED7, 0xAA0A,
        0x5A82, 0xA57E, 0x55F6, 0xA129, 0x5134, 0x9D0E, 0x4C40, 0x9930,
        0x471D, 0x9592, 0x41CE, 0x9236, 0x3C57, 0x8F1D, 0x36BA, 0x8C4A,
        0x30FC, 0x89BE, 0x2B1F, 0x877B, 0x2528, 0x8583, 0x1F1A, 0x83D6,
        0x18F9, 0x8276, 0x12C8, 0x8163, 0x0C8C, 0x809E, 0x0648, 0x8027,
        0x0000, 0x8000, 0xF9B8, 0x8027, 0xF374, 0x809E, 0xED38, 0x8163,
        0xE707, 0x8276, 0xE0E6, 0x83D6, 0xDAD8, 0x8583, 0xD4E1, 0x877C,
        0xCF04, 0x89BE, 0xC946, 0x8C4A, 0xC3A9, 0x8F1D, 0xBE32, 0x9236,
        0xB8E3, 0x9592, 0xB3C0, 0x9931, 0xAECC, 0x9D0E, 0xAA0A, 0xA129,
        0xA57E, 0xA57E, 0xA129, 0xAA0A, 0x9D0E, 0xAECC, 0x9931, 0xB3C0,
        0x9592, 0xB8E3, 0x9236, 0xBE32, 0x8F1D, 0xC3A9, 0x8C4A, 0xC946,
        0x89BE, 0xCF04, 0x877C, 0xD4E1, 0x8583, 0xDAD8, 0x83D6, 0xE0E6,
        0x8276, 0xE707, 0x8163, 0xED38, 0x809E, 0xF374, 0x8027, 0xF9B8
        } ;

Código 6 - Declara coeficientes Twiddle na memória de programa.


O Código 7 mostra o início da função main. De imediato criamos e declaramos algumas variáveis do tipo ponteiro.

int main (void)            //inicio do programa
{
    int i = 0;
    fractional *p_real = &sinalComplexo[0].real ;
    fractcomplex *p_complexo = &sinalComplexo[0] ;
    fractional *p_fract = &sinalComplexo[0].real ;

    ...

Código 7 - Início do programa main e declaração de ponteiros para os vetores de dados.


No Código 8 inicia-se o loop while. Encontramos 3 loops for:
1) movemos o vetor de entrada (a onda quadrada gerada pelo dsPICworks para teste do cálculo da FFT) para o vetor complexo utilizado pela FFT;
2) escalonamos o sinal de entrada de -1,0 a +1,0 para -0,5 a +0,5, para evitar overflow dos cálculos;
3) organizamos as partes real e imaginária dos dados do vetor complexo utilizado pela FFT. A parte real contém os dados do sinal gerado, ou de um ADC. A parte imaginária neste caso é zero.


  while (1)      //loop infinito
  {

     
    p_real = &sinalComplexo[0].real ;              //inicializa ponteiro
    p_complexo = &sinalComplexo[0];                //inicializa ponteiro
    
    /* Move buffer do sinal de entrada (real)para vetor da FFT    */
    for ( i = 0; i < PONTOS_FFT; i++ )             
    {
        *p_real = sinal_Entrada [i] ;        
        *p_real++;                                 //incrementa o ponteiro     
    }

    
    /* Escalona o vetor para o range [-0.5, +0.5]                */
    p_real = &sinalComplexo[0].real ;             //inicializa ponteiro
    for ( i = 0; i < PONTOS_FFT; i++ )         
    {
        *p_real = *p_real >>1 ;                     //desloca 1 bit para a direita
        *p_real++;            
    }                    


    /* Converte vetor real para vetor complexo                    */
    p_real = &sinalComplexo[(PONTOS_FFT/2)-1].real ;    //inicializa o ponteiro para parte real do vetor  
    p_complexo = &sinalComplexo[PONTOS_FFT-1] ; 

    for ( i = PONTOS_FFT; i > 0; i-- )         
    {
        (*p_complexo).real = (*p_real--);    
        (*p_complexo--).imag = 0x0000;            //Não possui parte imaginaria, escreve valor zero
    }

    ...

Código 8 - Prepara vetor de dados complexos de entrada para cálculos da FFT.


Agora podemos aplicar a Transformada Rápida de Fourier, a FFT, na onda quadrada de 780 Hz!

A função FFTComplexIP se encarrega desta tarefa!

Você encontra a chamada da FFT no Código 9. Após o cálculo da FFT é necessário organizar os dados de saída com a função BitReverseComplex.

    /* Cálcula a FFT - Transformada Rapida de Fourier             */
    FFTComplexIP (LOG2_ESTAGIOS_BUTTERFLY, &sinalComplexo[0], (fractcomplex *) __builtin_psvoffset(&twiddleFactors[0]), (int) __builtin_psvpage(&twiddleFactors[0]));


    /* Organiza dados da saída da FFT - Bit-reverso                */
    BitReverseComplex (LOG2_ESTAGIOS_BUTTERFLY, &sinalComplexo[0]);

Código 9 - Cálculo da FFT e organização do vetor de dados de saía.


No Código 10 você encontra um programa adicional para calcular a soma do quadrado das partes real e imaginária, encontrar a frequência com maior espectro e o cálculo do seu módulo. Isto é útil para criar um gráfico para mostrar o resultado da FFT.

    /* Calcula a soma do quadrado das partes real e imaginária 
       transformando vetor complexo em um vetor real 
            Sinal = real^2 + imaginario^2                      */
    SquareMagnitudeCplx(PONTOS_FFT, &sinalComplexo[0], &sinalComplexo[0].real);

    /* Move buffer do vetor complexo para vetor comum             */
    p_fract = &sinalComplexo[0].real ;                //inicia ponteiro
    for ( i = 0; i < PONTOS_FFT/2; i++ )             
    {
        SquareMagnitude [i] = *p_fract;        
        *p_fract++;                                     //incrementa o ponteiro                                               
    }


    /* Encontra a frequency Bin ( indice da frequencia) que possui a maior energia */
    VectorMax(PONTOS_FFT/2, &SquareMagnitude[0], &picoFrequenciaBin);

    /* Calcula a frequencia em Hz com maior espectro               */
    picoFrequencia = picoFrequenciaBin*(TAXA_AMOSTRAGEM/PONTOS_FFT);

    /* Encontra  o modulo da frequencia de pico                    */
    temp3_pnt = &SquareMagnitude[0] ;      //inicializa ponteiro    
    temp3_pnt = temp3_pnt + picoFrequenciaBin;
    tempFrac = *temp3_pnt;
    tempFloat = tempFrac;
    tempFloat = tempFloat / 32767;            // Converte fracionario para float
    modulo = sqrt (tempFloat);                // Extrai a raiz quadrada da soma dos quadrados da parte real/imaginaria

Código 10 - Calcula módulo e encontra frequência de maior energia.


A Simulação dos cálculos no MPLAB IDE

A Figura 4 mostra o projeto aberto no MPLAB IDE v8.83.

Figura 4 - Projeto no MPLAB IDE v8.83 com compilador MPLAB C30.


Na Figura 5 é visualizada a janela Watch da simulação no MPLAB contendo as variáveis e vetores do programa após serem realizados os cálculos.


Figura 5 - Tabela Watch com resultado do cálculo da FFT sobre o sinal quadrado gerado.


Observe que a variável picoFrequencia mostra o valor de 780 [Hz]. Esta é a frequência da nossa onda quadrada gerada no dsPICwork, lembra?

A posição do vetor com o espectro de frequência de maior valor é SquareMagnitude [13].

Veja os cálculos finais:

Fs               /    PONTOS_FFT              =      Fo
7680 sps    /     128 pontos da FFT         =     60 Hz

60 Hz * [13]       =        780 Hz!


 O código completo deste programa você encontra no Anexo A.

O Vídeo 1 mostra este projeto rodando no MPLAB IDE com o dsPIC30F4013. É simulado passo-a-passo a execução de cada função, incluindo a FFTComplexIP que efetua o cálculo da Fast Fourier Transform - FFT,  e as alterações das variáveis na tabela Watch.

Vídeo 1 - Exemplo da FFT rodando no MPLAB IDE


Para quem gosta de música, o Vídeo 2 mostra uma aplicação bem legal para a FFT: um Analisador de Espectro de Áudio para a animação do áudio com o dsPIC30F4013!

Vídeo 2 - VU Meter Audio Spectrum Analyser com o dsPIC30F.



Foi utilizada a função FFTComplexIP para o cálculo da FFT com 128 pontos.

A FFT é testada com um gerador de sinal aplicando uma onda senoidal variando de 20 Hz à 1,5 kHz.

Para desenvolver sua própria aplicação e não utilizar mais os dados gerados pelo dsPICwork, você deve configurar o conversor analógico-digital para amostrar o sinal em uma taxa de 7680 sps (7680 Hz) e no formato fracionário com sinal (ADC_FORMAT_SIGN_FRACT). 128 amostras do ADC serão copiadas para o vetor sinal_entrada [ ]. Pronto, agorá é só executar o código aqui sugerido.

Outros valores de Fs, pontos da FFT e Fo podem ser utilizados. Quanto maior Fs (frequencia de amostragem) mais rápido terá que ser seu ADC. Quanto mais pontos da FFT mais memória RAM é necessária para armazenar os vetores de dados e maior será o processamento da FFT (quantidade maior de cálculos).

Um exemplo para a aplicação da FFT você encontra neste post: http://mrgptu.blogspot.com.br/2015/01/analisador-de-espectro-fft-pic.html

Trata-se de um Analisador de Espectro que analisa as harmônicas da rede de energia elétrica (60 Hz). As funções da FFT são as mesmas utilizadas neste artigo, inclusive mesma Fs, pontos da FFT e Fo.


Anexo A - Código completo da FFT com o dsPIC30F4013

/************************************************************************************************
********************* DEMONSTRAÇÃO DO USO DA FFT NO dsPIC ***************************************
*************************************************************************************************
  
Caracteristicas:
- Microcontrolador & DSP dsPIC30F4013 trabalhando a 30 MIPS, com oscilador interno, WDT desligado
- Analise do espectro de frequencia atraves da Transformada Rapida de Fourier - FFT
- Calcula a frequencia com maior espectro e seu modulo 
- Sinal gerado para simulacao dos calculos: square signal, 780 Hz, 7680 SPS, amplitude -1.0 a +1.0

- Dados da FFT:
    Pontos: 128; 128 amostragens no tempo transformarao em 128 pontos de frequencia
     fs: 7,68 kHz; taxa de amostragem
     fo: 60 Hz; passo entre as frequencias       



Rev. 1.2 Criada aplicação de exemplo para uso da FFT


;##### M.R.G. ######################################
;##### MARÇO/2015 ##################################
;##### USADO MPLAB IDE V8.83 E MPLAB C30 v2.02 #####

;*********** CLOCK INTERNO DE 7.37 MHZ x PLL16 30 MIPS *******************************************

;***** DATA SPACE MEMORY MAP 2K MSB    LSB *******************************************************
;    X DATA RAM :             0X8001 0X0800           A        0X0BFF 0X0BFE instruçóes MCU e DSC
;    Y DATA RAM :             0X0C01 0X0C00           A        0X0FFF 0X0FFE instruções DSC
;***************** obs.: 0x0800 contém endereço do TOS (topo da pilha) ***************************
;*************************************************************************************************  
;***** EEPROM. 1 Kbytes of data EEPROM with an address globalrange from 0x7FFC00 to 0x7FFFFE *****
;*************************************************************************************************    
*/
 
//------------------ Inclui ficheiros auxiliares -------------------------
#include "dsp.h"
#include "p30f4013.h"
#include <stdio.h> 


/*-------------------------- FUSES ------------------------------*/
//--------- BITS DE CONFIGURACAO DO DISPOSITIVO -----------------
_FOSC  (0xBFE3); //(CSW_FSCM_OFF & FRC_PLL16);         //30 MIPS
//_FOSC  (0xB9E0); //(CSW_FSCM_OFF & FRC);             //1,8 MIPS
//_FWDT(WDT_ON & WDTPSA_512 & WDTPSB_4);     //WDT = 4096 ms, Prescaler A = 512, B = 4  
_FWDT(WDT_OFF);                                //WDT desligado
_FBORPOR(MCLR_EN & PBOR_OFF & PWRT_OFF);
_FGS(CODE_PROT_OFF); 


//---------------------- Define constantes -------------------------------
// constantes da FFT
#define PONTOS_FFT    128         //numero de pontos da FFT
#define LOG2_ESTAGIOS_BUTTERFLY 7    //numero de estagios "Butterfly" da FFT
#define TAXA_AMOSTRAGEM        7680    //taxa de amostragem do sinal de entrada 
                                        

//----------------- Declara variaveis globais-----------------------------------

fractional sinal_Entrada [128]= {            //vetor que ira armazenar os dados de entrada
                            //valores iniciais declarados para simulacao
                            //e teste do programa
                            //square signal 780 Hz 7.68 kSPS
                            //sinal gerado com o dsPICWorks
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,    
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,    
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001, 0x8001, 0x8001,
    0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF,
    0x8001, 0x8001, 0x8001};


unsigned char *TXPtr;
unsigned int n,dado;
float tempFloat, modulo;


int    picoFrequenciaBin = 0;                
unsigned long picoFrequencia = 0;

fractional *temp3_pnt, tempFrac;

// Entrada do sinal para a FFT declarada na memoria Y
fractcomplex sinalComplexo[PONTOS_FFT]__attribute__ ((section (".ydata, data, ymemory"),aligned (PONTOS_FFT * 2 *2))); 

fractional SquareMagnitude [PONTOS_FFT/2];


// ------------- Declara constantes gravadas na flash -------------------------

// declara vetor dos coeficientes Twiddle da FFT na memoria de programa
// Define 128 constantes "twiddle" da FFT: WN (kn) = exp [-(j*2*pi*k*n)/N]
const fractcomplex twiddleFactors[] __attribute__ ((space(prog), aligned (PONTOS_FFT*2)))=
        {
        0x7FFF, 0x0000, 0x7FD9, 0xF9B8, 0x7F62, 0xF374, 0x7E9D, 0xED38,
        0x7D8A, 0xE707, 0x7C2A, 0xE0E6, 0x7A7D, 0xDAD8, 0x7885, 0xD4E1,
        0x7642, 0xCF04, 0x73B6, 0xC946, 0x70E3, 0xC3A9, 0x6DCA, 0xBE32,
        0x6A6E, 0xB8E3, 0x66D0, 0xB3C0, 0x62F2, 0xAECC, 0x5ED7, 0xAA0A,
        0x5A82, 0xA57E, 0x55F6, 0xA129, 0x5134, 0x9D0E, 0x4C40, 0x9930,
        0x471D, 0x9592, 0x41CE, 0x9236, 0x3C57, 0x8F1D, 0x36BA, 0x8C4A,
        0x30FC, 0x89BE, 0x2B1F, 0x877B, 0x2528, 0x8583, 0x1F1A, 0x83D6,
        0x18F9, 0x8276, 0x12C8, 0x8163, 0x0C8C, 0x809E, 0x0648, 0x8027,
        0x0000, 0x8000, 0xF9B8, 0x8027, 0xF374, 0x809E, 0xED38, 0x8163,
        0xE707, 0x8276, 0xE0E6, 0x83D6, 0xDAD8, 0x8583, 0xD4E1, 0x877C,
        0xCF04, 0x89BE, 0xC946, 0x8C4A, 0xC3A9, 0x8F1D, 0xBE32, 0x9236,
        0xB8E3, 0x9592, 0xB3C0, 0x9931, 0xAECC, 0x9D0E, 0xAA0A, 0xA129,
        0xA57E, 0xA57E, 0xA129, 0xAA0A, 0x9D0E, 0xAECC, 0x9931, 0xB3C0,
        0x9592, 0xB8E3, 0x9236, 0xBE32, 0x8F1D, 0xC3A9, 0x8C4A, 0xC946,
        0x89BE, 0xCF04, 0x877C, 0xD4E1, 0x8583, 0xDAD8, 0x83D6, 0xE0E6,
        0x8276, 0xE707, 0x8163, 0xED38, 0x809E, 0xF374, 0x8027, 0xF9B8
        } ;



//----------------- Declara funcoes externas ---------------------------------

// calcula a magnitude quadrada do vetor complexo
extern void SquareMagnitudeCplx(int, fractcomplex*, fractional*); 





//****************************************************************************************
//*********************************** MAIN ***********************************************
//****************************************************************************************


int main (void)            //inicio do programa
{
    int i = 0;
    fractional *p_real = &sinalComplexo[0].real ;
    fractcomplex *p_complexo = &sinalComplexo[0] ;
    fractional *p_fract = &sinalComplexo[0].real ;


    
  while (1)      //loop infinito
  {

     
    p_real = &sinalComplexo[0].real ;              //inicializa ponteiro
    p_complexo = &sinalComplexo[0];                //inicializa ponteiro
    
    /* Move buffer do sinal de entrada (real)para vetor da FFT    */
    for ( i = 0; i < PONTOS_FFT; i++ )             
    {
        *p_real = sinal_Entrada [i] ;        
        *p_real++;                                 //incrementa o ponteiro     
    }

    
    /* Escalona o vetor para o range [-0.5, +0.5]                */
    p_real = &sinalComplexo[0].real ;             //inicializa ponteiro
    for ( i = 0; i < PONTOS_FFT; i++ )         
    {
        *p_real = *p_real >>1 ;                     //desloca 1 bit para a direita
        *p_real++;            
    }                    


    /* Converte vetor real para vetor complexo                    */
    p_real = &sinalComplexo[(PONTOS_FFT/2)-1].real ;    //inicializa o ponteiro para parte real do vetor  
    p_complexo = &sinalComplexo[PONTOS_FFT-1] ; 

    for ( i = PONTOS_FFT; i > 0; i-- )         
    {
        (*p_complexo).real = (*p_real--);    
        (*p_complexo--).imag = 0x0000;            //Não possui parte imaginaria, escreve valor zero
    }


    /* Calcula a FFT - Transformada Rapida de Fourier             */
    FFTComplexIP (LOG2_ESTAGIOS_BUTTERFLY, &sinalComplexo[0], (fractcomplex *) __builtin_psvoffset(&twiddleFactors[0]), (int) __builtin_psvpage(&twiddleFactors[0]));


    /* Organiza dados da saída da FFT - Bit-reverso                */
    BitReverseComplex (LOG2_ESTAGIOS_BUTTERFLY, &sinalComplexo[0]);


    /* Calcula a soma do quadrado das partes real e imaginária 
       transformando vetor complexo em um vetor real 
            Sinal = real^2 + imaginario^2                      */
    SquareMagnitudeCplx(PONTOS_FFT, &sinalComplexo[0], &sinalComplexo[0].real);

    /* Move buffer do vetor complexo para vetor comum             */
    p_fract = &sinalComplexo[0].real ;                //inicia ponteiro
    for ( i = 0; i < PONTOS_FFT/2; i++ )             
    {
        SquareMagnitude [i] = *p_fract;        
        *p_fract++;                                     //incrementa o ponteiro                                               
    }


    /* Encontra a frequency Bin ( indice da frequencia) que possui a maior energia */
    VectorMax(PONTOS_FFT/2, &SquareMagnitude[0], &picoFrequenciaBin);

    /* Calcula a frequencia em Hz com maior espectro               */
    picoFrequencia = picoFrequenciaBin*(TAXA_AMOSTRAGEM/PONTOS_FFT);

    /* Encontra  o modulo da frequencia de pico                    */
    temp3_pnt = &SquareMagnitude[0] ;      //inicializa ponteiro    
    temp3_pnt = temp3_pnt + picoFrequenciaBin;
    tempFrac = *temp3_pnt;
    tempFloat = tempFrac;
    tempFloat = tempFloat / 32767;            // Converte fracionario para float
    modulo = sqrt (tempFloat);                // Extrai a raiz quadrada da soma dos quadrados da parte real/imaginaria

 
    //tempFloat = Fract2Float (tempFrac);     //converte fracional para float, usando funcao da biblioteca DSP
    //modulo = sqrt (tempFloat);            // Extrai a raiz quadrada da soma dos quadrados da parte real/imaginaria


    // se o programa estiver calculando a FFT corretamente
    // na tabela Watch voce deve encontrar picoFrequencia = 780 Hz
    // de acordo com o sinal que foi gerado para esta simulacao
    
    asm    ("NOP");

   }    
}                        /*  Fim  */




Referências:

HAYKIN, Simon S.; VAN VEEN, Barry. Sinais e sistemas. Porto Alegre: Bookman, 2001. 668p.

MICROCHIP. Code Examples. CE018 - Using the Fast Fourier Transform (FFT) for Frequency Detection. Disponível em: http://ww1.microchip.com/downloads/en/DeviceDoc/CE018_FFT_DSPlib_101007.zip


MICROCHIP. Software Libraries. Disponível em: http://www.microchip.com/SoftwareLib.aspx

MICROCHIP. dsPIC DSC DSP Algorithm Library. Disponível em: http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=2680&dDocName=en023598






sábado, 14 de fevereiro de 2015

Programando um Filtro Digital IIR com a DSP Library do dsPIC30F


Este artigo demonstra os passos necessários para a elaboração de um Filtro Digital IIR Passa-Faixa  utilizando o Controlador Digital de Sinais  dsPIC30F4013 com a DSP Library da Microchip.


Filtros Digitais do tipo FIR (Resposta ao Impulso Finita) e IIR (Resposta ao Impulso Infinita) são muito utilizados por DSPs (Processadores Digitais de Sinais) para a filtragem de sinais.

Semelhantes aos filtros analógicos, os filtros digitais podem ser classificados em 4 tipos quanto à frequência:
- Passa Baixa;
- Passa Alta;
- Passa Faixa;
- Rejeita Faixa.

Funções de Janela são utilizadas em Filtros Digitais FIR para reduzir oscilações. Podem ser:
- janela Retangular;
- janela de Hamming;
- janela de Blackman;
- janela de Kaiser.

Para resolver problemas de aproximação em Filtros Digitais IIR podemos utilizar:
- filtro Butterworth
- filtro Tschebyscheff
- filtro Tschebyscheff inverso
- filtro Elliptic
- filtro Bessel

A Figura 1 mostra os 3 passos para você criar seu Filtro Digital.


 Figura 1 - Passos para criar um Filtro Digital

Para este artigo, foi escolhido a construção de um Filtro Digital IIR Passa-Faixa do tipo Blutterworth.

Vamos a algumas considerações do nosso projeto.

Objetivo do circuito: 
Filtrar um sinal entre 0 e 500 Hz e deixar "passar a faixa" de 60 à 180 Hz com 100 % da amplitude.

Especificações: 

- de acordo com o Teorema de Nyquist, a frequência de amostragem do ADC será portanto 1000 Hz (1 ksps).

- o microcontrolador escolhido é o dsPIC30F4013, um Controlador Digital de Sinais (DSC) da Microchip, que possui as funções para o Filtro Digital IIR disponíveis na DSP Library. O DSC possui as funções de um microcontrolador e de um DSP. Possui capacidade de processamento de 30 MIPS @117 MHz.
- será utilizado um programa de Digital Filter Design para definir os parâmetros do filtro digital passa-faixa a fim de obter o sinal filtrado especificado. O software fornece os coeficientes necessários para o cálculo do filtro.

- os coeficientes são utilizados no código fonte C do DSC (dsPIC). O programa consiste basicamente em declarar as variáveis (estruturadas), inicializar os ponteiros e variáveis, iniciar a estrutura do filtro com IIRLatticeInit ( ) e executar o cálculo do filtro digital com IIRLattice ( ).

- os dados resultantes do cálculo do filtro digital IIR executado em 117 pontos amostrados periodicamente são mostrados em um display gráfico GLCD para se ter uma ideia do que é "visto" pelo DSC.

- para teste do nosso circuito um gerador de sinais (ou software no computador) aplica um sinal senoidal com amplitude constante e frequência de 0 à 500 Hz à entrada analógica  do DSC que aplica o cálculo do filtro passa-faixa. O resultado é mostrado no display.


Definindo os parâmetros do Filtro Digital e gerando os coeficientes

No primeiro passo temos que definir alguns parâmetros para poder utilizar um software para gerar os coeficientes necessários para o cálculo do filtro digital pelo PIC.


 A Figura 2 traz graficamente a representação de cada parâmetro do filtro:
- frequência de amostragem;
- passband;
- stopband;
- máximo ripple no passband;
- mínima atenuação do stopband.


 
Figura 2 - Especificação do FIltro Digital passa-faixa

No segundo passo utilizaremos o QEDesign, um Digital Filter Design Software,  para modelar o nosso filtro digital com estes parâmetros e gerar os coeficientes para o programa do DSC.

No menu Design / IIR Design... iniciaremos o design para um Filtro Digital IIR. 

A Figura 3 mostra a primeira tela de configuração. Selecionaremos:
- método para o design do filtro: transformação bilinear;
- método de realização do filtro: estrutura Lattice;
- tipo de filtro: bandpass.

Figura 3 - Design para o Filtro Digital IIR

Na segunda tela, Figura 4, preencheremos os parâmetros do filtro passa-faixa conforme ilustrado anteriormente na Figura 2.

Figura 4 - Configurando os parâmetros para o filtro passa-faixa.

Na terceira tela selecionaremos a 8ª ordem para o filtro Butterworth, conforme Figura 5.


Figura 5 - Seleção da ordem do filtro.

A Figura 6 mostra o resultado para a modelagem do nosso filtro digital IIR passa faixa de acordo com os parâmetros fornecidos.


Figura 6 - Resultado da Modelagem do Filtro Digital IIR.

O gráfico da Magnitude vs Frequência da resposta do filtro é visto na Figura 7. Note que de 60 à 180 Hz teremos 100 % do sinal de entrada na saída. Na medida que distanciamos da faixa do filtro o sinal de entrada será atenuado.


Figura 7 - Gráfico Magnitude vs Frequência da resposta do filtro.

Clicando no botão "Save C File"  o software irá gerar um código fonte padrão na linguagem C para o filtro digital IIR construído, que pode ser visto na Figura 8. Com ele você pode construir seu filtro DIgital IIR em outro compilador C para computador ou outro modelo de microcontrolador.

O que nos interessa neste código são os valores dos coeficientes kappa e gamma, que iremos utilizar no programa do dsPIC.


/***************************************************************************
****************************************************************************
*   File: C:\Arquivos de programas\MDS\Filter Design\codigo C.c
*   Created by QEDesign 
*   C Code Generator  
****************************************************************************
*  Code Fragment to implement filter
*
*  The functions defined in 'qed_filt.c' must be compiled and linked in.
*    This can be accomplished by either #include "qed_filt.c"
*    or by separately compiling and linking 'qed_filt.c'
*
*** following is actual code fragment
*  extern LAT_filter LAT_codigo C;
*
*  init_lat_dbl (&LAT_codigo C);  // initialize filter structure 
*
*  LAT_codigo C.filter ( x, y, n, &LAT_codigo C);  // x is an array of input samples
*                                            // y is an array of output samples
*                                            // n is number of samples to process
*                                            // &LAT_codigo C is a pointer to the
*                                            //    filter structure
*****************************************************************************
*  This is a complete program which can be compiled and run to test the filter.
*  To change this to a subroutine only, just add in this program or add globally
*     in "qed_cgen.h" the line with the definition of DEFINE_SUBROUTINE as follows
*  #define DEFINE_SUBROUTINE
*****************************************************************************
****************************************************************************/

/* qed_cgen.h contains definitions of filter structures and function prototypes */
#include "qed_cgen.h"

/* filter functions are in files 'qed_filt.c'  */

double codigo C_kappa[9] = {
  -9.7948455815389446e-001,  /*    0 */
   9.7849500351160967e-001,  /*    1 */
  -8.6112175002729441e-001,  /*    2 */
   5.3530472709660060e-001,  /*    3 */
  -5.0440878979478099e-001,  /*    4 */
   3.9935338248048824e-001,  /*    5 */
  -1.2523348830583417e-001,  /*    6 */
   3.0071693969749614e-002,  /*    7 */
   0.0000000000000000e+000}; /*    8 */

double codigo C_gamma[9] = {
   5.1559577576911972e-004,  /*    0 */
   1.4085406081475862e-002,  /*    1 */
   3.6377697302576006e-002,  /*    2 */
  -1.1820731695711806e-001,  /*    3 */
  -3.7374705510515338e-001,  /*    4 */
  -1.4384357653258961e-001,  /*    5 */
   1.7693240143750988e-001,  /*    6 */
   1.7571299176925376e-001,  /*    7 */
   4.6649062145300228e-002}; /*    8 */

double codigo C_f[9];

double codigo C_b[9];

double codigo C_gain =  1.0000000000000000e+000; /* initial gain  */

LAT_filter LAT_codigo C = {

     1,     /* quantization: 0 off, 1 on   */
     1,     /* quantization type */
            /*   0  Floating point         */
            /*   1  Fixed point fractional */
   8,           /* lattice order */ 
   &codigo C_gain,   /* ptr to gain */
   codigo C_kappa,   /* ptr to kappa coefficients   */
   codigo C_gamma,   /* ptr to gamma coefficients */
   codigo C_f,       /* ptr to forward  function values */
   codigo C_b,       /* ptr to backward function values */
   lat_dbl};   /* ptr to filter routine */

/* call the following function first and normally only once */
/* init_lat_dbl (&LAT_codigo C)  */
/*   where &LAT_codigo C is a pointer to the LAT_filter */
/*   structure defining the filter */


/* call the following function to filter n samples */
/* LAT_codigo C.filter (pIn, pOut, int n, &LAT_codigo C); */

/*   where pIn  is a pointer to an array or buffer of samples to be filtered */
/*         pOut is a pointer to the array of filtered samples output by the filter */
/*         n    is the number of samples to filter */
/*         &LAT_codigo C is a pointer to the structure defining the filter */


#ifndef DEFINE_SUBROUTINE

/* The following main program can be used to test the filter.         */
/*   input is in file 'in' and the filtered samples are in file 'out' */
/*   The input and output files are ascii floating point values       */
/*    e.g 1.0342 with 1 sample per line                               */
/* The input files can be created in DSPworks and exported as         */
/*   ascii floating point or any other system capable of creating     */
/*   ascii files with floating point values.                          */
/* The filtered output file can be imported into DSPworks as an ascii */
/*    floating point file and an FFT can be run to validate           */
/*    the frequency response.                                         */

#include "qed_filt.c"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>


#define INSZ1  1000
#define OUTSZ1 1000

static    double x[INSZ1], y[OUTSZ1];


int main(int argc, char *argv[])

{
  int i, in_count, file_status, error;

  FILE *fin;              /* input file of samples */
  FILE *fout;             /* output file of filtered samples */ 

  fprintf (stderr," ***** start filter test *****\n");

  fprintf (stderr," this program accepts 0,1 or 2 command line arguments\n");
  fprintf (stderr," the first  argument is the filename of the input file\n");
  fprintf (stderr," the second argument is the filename of the output file\n");
  fprintf (stderr," if there are 0 arguments, input and output is respectively\n");
  fprintf (stderr,"     stdin and stdout\n");
  fprintf (stderr," if only one argument is specified, then output is stdout\n");
  fprintf (stderr," if input is stdin rather than a file, then fscanf expects input\n");
  fprintf (stderr,"     from the console which may be piped in or entered directly\n");

  fin  = stdin;
  fout = stdout;
  error = 0;

  if (argc == 1) {
        fprintf(stderr," ***** waiting for input *****\n");
  }
  if (argc >= 2) {
      fin = fopen (argv[1], "r");
      if (fin == NULL) {
          fprintf(stderr,"\n error - Cannot open file %s for input\n", argv[1]);
          error = 1;
      }
  }
  if (argc >= 3) {
      fout = fopen (argv[2], "w");
      if (fout == NULL) {
          fprintf(stderr,"\n error - Cannot open file %s for output\n", argv[2]);
          error = 1;
      }
  }
  if (error) {
      fprintf(stderr," ***** end filter test *****\n");
      return(0);
  }
  
  

  init_lat_dbl (&LAT_codigo C);


  do {

      /* get input samples */ 
      for (in_count = 0; in_count < INSZ1; in_count++) { 
          file_status = fscanf(fin,"%lf",&x[in_count]); 
          if (file_status != 1) 
              break; 
      }

      /* filter samples */ 

      if (in_count == 0) break;

      LAT_codigo C.filter( x, y, in_count, &LAT_codigo C);

      for (i = 0; i < in_count; i++)
          fprintf (fout,"%f\n",y[i]);

  } while (file_status == 1);

  fclose (fin); 
  fclose (fout);

  fprintf(stderr," ***** end filter test *****\n");
  return(1);

}
 
#endif 

Figura 8 - Código gerado pelo Digital Filter Design.


O Hardware

Utilizaremos o mínimo de componentes necessários para criar e testar o Filtro Digital IIR.

A Figura 9 mostra o diagrama simplificado do circuito.


Figura 9 - Diagrama simplificado do circuito.


A seguir a explicação para cada etapa:

SINAL ANALÓGICO: Deve estar compreendido dentro da faixa de medição do conversor analógico-digital (ADC) do microcontrolador: amplitude e frequência.

BIAS: Offset no sinal de entrada para metade da tensão de referência do ADC: os cálculos do filtro digital da biblioteca DSP necessitam que o sinal de entrada esteja na faixa de -1,0 à 1,0 (escala fracionária). Assim o zero será 2,5 V para uma tensão de referência do ADC de 0 Vcc (min) e 5 Vcc (max).

AMPLIFICADOR: para aumentar a amplitude do sinal de entrada.

FILTRO ANTI-ALIASING: é um filtro passa-baixa para evitar erros na medição. Assegura o Teorema de Nyquist: o sinal amostrado terá no máximo a metade da frequência de amostragem. Ex.: 500 Hz no máximo quando fs é 1000 sps.

dsPIC: Controlador Digital de Sinal (microcontrolador & DSP). Executa os cálculos matemáticos no sinal discreto amostrado através da biblioteca DSP da Microchip. Neste projeto utiliza os cálculos de um filtro digital IIR Lattice passa-faixa Butterworth de 8ª ordem.



O código fonte do PIC 

 No terceiro passo elaboraremos o programa do PIC no compilador MPLAB C30 da Microchip. Podem ser utilizadas as linguagens C ou Assembly. Optamos pela linguagem C.

Incluímos em nosso projeto a biblioteca "dsp.h" que possui as funções para o cálculo dos filtros digitais IIR, FIR e outras utilizadas em cálculos de processamentos digitais, como a FFT.

Na Figura 10 é visto parte do código em dsp.h relacionado à declaração da estrutura do Filtro Digital IIR Lattice.


 /*       Estrutura do Filtro Digital IIR Lattice                            */
typedef struct {
   int order;                           // filter order (M) 
                                        // M <= N (see IIRLattice for N) 
   fractional* kappaVals;               // ptr to lattice coefficients 
                                        // (k[m], 0 <= m <= M) 
                                        // either in X-Data or P-MEM 
   fractional* gammaVals;               // ptr to ladder coeficients 
                                        // (g[m], 0 <= m <= M) 
                                        // either in X-Data or P-MEM 
                                        // NULL for all pole implementation 
   int coeffsPage;                      // page number of program memory if 
                                        // coefficients are in program memory 
                                        // COEFFS_IN_DATA if not
   fractional* delay;                   // ptr to delay 
                                        // (d[m], 0 <= m <= M) 
                                        // only in Y-Data 
} IIRLatticeStruct;                     // IIR Lattice filter structure 

extern fractional* IIRLattice (         // IIR Lattice filtering 
   int numSamps,                        // number of input samples (N) 
   fractional* dstSamps,                // ptr to output samples 
                                        // (y[n], 0 <= n < N) 
   fractional* srcSamps,                // ptr to input samples 
                                        // (x[n], 0 <= n < N) 
   IIRLatticeStruct* filter             // filter structure 

                                        // returns dstSamps 
);

extern void IIRLatticeInit (            // Zero out dealy in filter structure 
   IIRLatticeStruct* filter             // Lattice filter structure 
);

Figura 10 - Estrutura do filtro IIR Lattice na biblioteca dsp.h.

No cógigo main.c do dsPIC iremos declarar:
-  a variável fooiir do tipo IIRLatticeStruct que irá conter a estrutura do nosso Filtro IIR Lattice;
- os vetores que irão conter os coeficientes do Filtro Digital IIR: kappa [ ], gamma [ ] e delayiir [ ];
- o vetor para os dados de entrada do filtro: adc_data [ ]. Os dados podem ser coletados pelo conversor analógico-digital do dsPIC, por exemplo;
- o vetor para os dados de saída do filtro: out_iir [ ].

O código contendo as declarações está contido na Figura 11.

/* declara estrutura Lattice filter structure com nome*/
IIRLatticeStruct fooiir;

fractional kappa[9] __attribute__ ((section (".xbss, bss, xmemory")));
fractional gamma[9] __attribute__ ((section (".xbss, bss, xmemory")));
fractional delayiir[9] __attribute__ ((section (".ybss, bss, ymemory")));

//fractional iniir [117];  ==> adc_data
fractional outiir [117];
int numero_amostra;


fractional adc_data [117]= {0,0,0,0,0,0,0,0,0,0,  //vetor de dados para o grafico de tendencia no GLCD
         0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0}; 

Figura 11 - Declaração das variáveis do filtro em main.c.

Após o início do programa, na função main ( ) temos que iniciar as variáveis e atribuir os coeficientes do filtro. Os valores para kappa e gamma foram retirados do código C gerado pelo Digital Filter Design Software.

A inicialização das variáveis é vista no código da Figura 12.


//------------- inicializa variaveis da estrutura filtro iir ---------------------------
//------------- filtro passa Faixa 40 A 180 Hz, amostragem 1 khz -----------------------
    fooiir.kappaVals = &kappa [0];        //inicia ponteiros 
    fooiir.gammaVals = &gamma [0];
    fooiir.delay = &delayiir [0];
    fooiir.coeffsPage = COEFFS_IN_DATA;     //coeficientes do filtro estao na memoria RAM     
    fooiir.order = 8;                       //seta a ordem do filtro
    numero_amostra =117;
// Coeficientes do filtro iir lattice
kappa[0] = Q15(-9.7948455815389446e-001); //passa faixa 40 a 180 hz 8 polos
kappa[1] = Q15( 9.7849500351160967e-001); 
kappa[2] = Q15(-8.6112175002729441e-001); 
kappa[3] = Q15( 5.3530472709660060e-001); 
kappa[4] = Q15(-5.0440878979478099e-001); 
kappa[5] = Q15( 3.9935338248048824e-001); 
kappa[6] = Q15(-1.2523348830583417e-001); 
kappa[7] = Q15( 3.0071693969749614e-002);
kappa[8] = Q15( 0.0000000000000000e+000);

gamma[0] = Q15( 5.1559577576911972e-004);
gamma[1] = Q15( 1.4085406081475862e-002);
gamma[2] = Q15(3.6377697302576006e-002);
gamma[3] = Q15(-1.1820731695711806e-001);
gamma[4] = Q15( -3.7374705510515338e-001);
gamma[5] = Q15( -1.4384357653258961e-001);
gamma[6] = Q15( 1.7693240143750988e-001);
gamma[7] = Q15(1.7571299176925376e-001);
gamma[8] = Q15( 4.6649062145300228e-002);  

Figura 12 -  Inicialização das variáveis e coeficientes do filtro.

Após o início do hardware do PIC e inicializadas as variáveis e coeficientes o programa pode passar por um loop infinito while (true), lendo dados do conversor analógico-digital e executando os cálculos do filtro.

A Figura 13, mostra o código necessário para executar o cálculo do filtro.

Considera-se que os dados de entrada (vindos do ADC) já estejam no vetor adc_data [ ].

2 funções são necessárias:

- IIRLatticeInit: inicializa a estrutura do Filtro Digital IIR Lattice;

- IIRLattice: executa o cálculo do Filtro Digital IIR Lattice;


    //inicializa estrutura do filtro digital IIR Lattice
    IIRLatticeInit (&fooiir); 


    //executa calculos do filtro iir lattice
    IIRLattice (numero_amostra, &outiir[0], &adc_data[0], &fooiir); //(numeroamostragem, *y (n), *x (n), *estrutura do filtro)

Figura 13 - Inicializando estrutura e executando o cálculo do Filtro Digital IIR Lattice.

O resultado final do nosso circuito é visto na Figura 14. Interligando um display gráfico GLCD 128x64 vemos o sinal de 60 Hz após ser aplicado ao Filtro Digital IIR.



Figura 14 - Sinal de 60 Hz aplicado ao circuito do Filtro DIgital IIR passa-faixa.

Observação: se no hardware não for considerado o filtro anti-aliasing (passa-baixa) pode-se ter um resultado desagradável na saída do filtro. Na Figura 15 fizemos este teste, retirando o filtro anti-aliasing do circuito e aplicando um sinal de entrada maior do que 500 Hz.


Figura 15 - Efeito de aliasing na amostragem digital.

O vídeo a seguir mostra o nosso circuito com o Filtro Digital IIR sendo elaborado e testado com um sinal de entrada variando de 0 à 500 Hz. O erro de aliasing também pode ser observado.



Video do circuito do Filtro Digital IIR com dsPIC


Referência Bibliográfica

SCANDELARI, L. Filtros Digitais. Cap. 5. Disponível em: http://paginapessoal.utfpr.edu.br/scandelari/laboratorio-de-pds/filtros.pdf/at_download/file

MICROCHIP. Filter Design for dsPIC. Digital Filter Design and Analysis System. Disponível em: http://ww1.microchip.com/downloads/en/DeviceDoc/SW300001%20manual.pdf

MICROCHIP. CE005 - Using FIR Filters from dsPIC Filter Design and DSP library. Disponível em: http://ww1.microchip.com/downloads/en/DeviceDoc/CE005_FIR_DSP_lib_Filter.zip