A PROGRAMAR
Arduino e o cálculo da FFT
Introdução
É objectivo deste artigo mostrar quais são as capacidades e limitações do uso da plataforma de desenvolvimento
Arduino, num caso concreto, o cálculo da FFT (Fast Fourier
Transform) para diferentes frequências do sinal de entrada.
Para tal vão ser efectuadas algumas medições de carácter
prático utilizando o modelo Arduino Duemilinove.
Como segundo objectivo, e não menos importante,
espera-se fazer uma comparação entre os microcontroladores utilizados nos modelos Arduino Mega com ATmega1280,
Arduino Duemilinove com ATmega168 e Arduino Duemilinove com ATmega328. Tentando assim dar uma perspectiva
das suas capacidades e limitações neste campo.
Variáveis vs Memória disponível
Como primeiro passo para o início de uma análise
pormenorizada do tema, torna-se necessário ter em consideração a quantidade de memória SRAM (Static Random Access Memory) disponível nos diversos modelos de microcontroladores
utilizados.
Flash
ATmega168
ATmega328
ATmega1280
16 KBytes
32 KBytes
128 KBytes
(2 KBytes Bootloader)
(2 KBytes Bootloader)
(4 KBytes Bootloader)
SRAM
1024 Bytes
2048 Bytes
8192 Bytes
EEPROM
512 Bytes
1024 Bytes
4096 Bytes
Tabela 1 – Quantidades de memória disponíveis
O Arduino Duemilinove está equipado com o ATMega168, podendo ser “actualizado” facilmente substituindo o
microcontrolador actual (ATmega168), por um ATMega328
pré-programado (com o respectivo Bootloader).
Fazendo uma breve análise à tabela 1 (valores retirados do datasheet de cada microcontrolador), é possível verificar que a versão Arduino Mega com o ATmega1280 leva
clara “vantagem” em termos de capacidade, mas resta analisar até que ponto nesta aplicação concreta será suficiente
essa “vantagem”.
Ao ser declarada (utilizada) uma variável do tipo float
estão a ser reservados 4 bytes (32 bits) na memória SRAM.
Se considerarmos que ao efectuar a FFT vamos obter resultados com parte real e imaginária, vamos ter de reservar dois
“espaços” de um array (vector)do tipo float para cada número. O que nos leva a que ao ser declarado p.ex. um array de
f[i] com i=1 a N com N=128 , teremos um array que corres-
14
ponderá a 64 números do tipo “a+jb)”. Em que p.ex. corresponderá à parte real (“a”), e f[2] corresponderá a (“b”) do
primeiro número. Esta conclusão vai ser importante nos cálculos efectuados, para achar a capacidade máxima de armazenamento em SRAM dos microcontroladores referidos na
Tabela 1.
Numa situação ideal, em que mais nenhuma variável
necessitaria de ser usada, a capacidade de armazenamento
(para este caso concreto) seria a seguinte:
Arduino Duemilinove (ATMega168)
Arduino Duemilinove (ATMega328)
Arduino Mega (ATMega1280)
Pela análise dos valores acima obtidos, podemos constatar
que poderíamos armazenar 128 números do tipo “a+jb” num
ATMega168, 256 num ATMega328 e 1000 recorrendo ao
uso de um ATMega1280.
Mas como é óbvio é necessário recorrer a variáveis
para o próprio cálculo da FFT, bem como a variáveis
“intermédias” para obter e guardar os valores obtidos nas
diferentes entradas analógicas presentes no Arduino
(qualquer que seja a versão a considerar). O que nos leva
desde já a tomar os valores acima descritos como um exercício meramente teórico.
Após uma abordagem sobre os valores teóricos de armazenamento em SRAM, dos diversos modelos de microcontrolador utilizado na plataforma de desenvolvimento Arduino, vai
ser tomado em consideração o tempo de conversão A/D
levado pelo ADC (Analog-to-digital-converter ou conversor
analógico-digital). Vai também ser tomado em consideração
o tempo necessário para a aplicação do algoritmo de cálculo
da FFT utilizado.
A PROGRAMAR
ARDUINO E O CÁLCULO DA FFT
Arduino e o ADC
void loop()
{
b = micros();); //Atribui à variável “b” o
//tempo actual de execução do programa actual
a = analogRead(0);
c = micros(); // Atribui à variável “c” o
//tempo actual de execução do programa actual
Serial.print(c-b); //Envia (série) o valor
//necessário para executar a instrução
//analogRead” (conversão A/D)
}
Para esta aplicação específica torna-se necessária
uma análise do tempo de conversão A/D, com vista a saber
qual o tempo que se consegue obter entre amostras. Para tal
foram lidos os valores de tempo de conversão obtidos experimentalmente, modificando os valores do factor de divisão.
Este factor de divisão (Prescaler) define o input clock do
conversor A/D, ou seja, se tivermos um factor de divisão de
32 e um clock de sistema de 16 MHz (comum a todos os
modelos de Arduino) teremos um input clock no conversor
A/D de:
Ao fazer variar o conteúdo do registo ADCSRA, mais precisamente dos bits designados por ADPS2, ADPS1 e ADPS0,
é possível fazer variar o valor do factor de divisão referido
anteriormente. As combinações possíveis encontram-se descritas na seguinte tabela:
Cada conversão A/D (de acordo com o especificado no datasheet do microcontrolador) demora cerca de 13 ciclos de
clock, à excepção da primeira conversão que demorará 25
ciclos de clock (efectua a inicialização do conversor A/D). Ao
considerar o valor obtido no cálculo efectuado acima, iríamos
obter uma taxa de amostragem de:
ADPS2
ADPS1
ADPS0
Factor de
divisão obtido
0
0
0
2
0
0
1
2
0
1
0
4
O que não corresponderá aos valores obtidos experimentalmente, correspondendo o valor acima a um valor teoricamente esperado. Devido a este facto vão ser efectuadas
medições experimentais, tentando assim obter estimativas
mais reais dos tempos necessários para efectuar a conversão A/D.
0
1
1
8
1
0
0
16
1
0
1
32
1
1
0
64
Para obter os valores de tempo de conversão, execução da
instrução “analogRead”, vai ser utilizada a função “micros()”.
Esta função retorna o valor de tempo, em µs, que passou
desde que se iniciou a execução do programa. Ao fazer a
diferença entre os valores de retorno da função, em sítios
distintos da execução do nosso programa, conseguimos
obter o tempo dispendido na sua execução. O código a utilizar será o seguinte:
1
1
1
128
Tabela 2 – Combinações possíveis (Registo ADCSRA)
Os valores obtidos, utilizando um Arduino Duemilinove com
ATMega168 (modelo testado), foram os seguintes:
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
void setup(void)
{
Serial.begin(9600); ); //Inicializa o envio
//por Série, e define a Baud Rate
sbi(ADCSRA, ADPS2); //Neste caso concreto o
//bit ADPS2 será definido com o valor 1
cbi(ADCSRA, ADPS1); //Neste caso concreto o
//bit ADPS1 será definido com o valor 0
cbi(ADCSRA, ADPS0); //Neste caso concreto o
//bit ADPS0 será definido com o valor 0
}
int a,b,c;
Tabela 3 – Tempo de conversão A/D obtido
Os valores obtidos na Tabela 3 correspondem a uma média
de 1000 valores obtidos para cada caso (Recolha e cálculo
da média efectuado recorrendo ao Software MatLab), ou
seja, aos valores de tempo obtidos ao fazer variar o respectivo factor de divisão.
15
A PROGRAMAR
ARDUINO E O CÁLCULO DA FFT
Outra conclusão a retirar das medições efectuadas, e que foi
possível verificar através da experiência, é o facto de apenas
se deverem considerar valores do factor de divisão
(Prescaler) acima de 2. Verificando-se que para factores de
divisão de valor 2 são obtidos alguns erros na conversão A/D
(Obtiveram-se alguns valores de leitura nulos).
rosa e algo complexa.
O código utilizado neste estudo baseia-se no algoritmo redescoberto por “Danielson and Lanczos” em 1942 (Press,
Teukolsky et al. 1992). O array de entrada (do tipo float) deverá ter uma dimensão correspondente a uma potência de
dois, ou seja, neste caso concreto analisado f[i] deverá estar
compreendido entre 0 e 2n - 1 ( n ϵ N). O que limita a utilização da memória SRAM disponível a vectores que possuam
essas dimensões. Num caso concreto em que se queira aplicar o algoritmo a um conjunto de números do tipo “a+jb”,
que não seja exactamente uma potência de dois, deverá ser
reservado espaço de memória para um array f[i] com um
valor máximo de i que corresponda ao valor da potência de
dois imediatamente acima do número pretendido menos um
(2n –1). Ao ser efectuada a passagem do array para a função
de cálculo da FFT deverá ser efectuado Zero padding (Chu
2008), ou seja, todos os restantes valores do array deverão
ser preenchidos com o valor zero.
Pela análise da Tabela 3, e pelo descrito acima, é óbvio que
o tempo de conversão mínimo que se pode obter será de
≅12µs. Este valor é apenas referente a uma entrada analógica, se quisermos considerar as 6 entradas analógicas disponíveis teremos de multiplicar esse valor por 6. Obtendo assim:
Podendo assim considerar que obtemos uma “conversão
muito mais rápida” ao efectuar a conversão A/D de cada
canal individualmente.
Ao utilizar a versão Arduino Duemilinove com ATmega168, e
como foi referido anteriormente, podemos armazenar na
memória SRAM disponível um array (do tipo float) com a
dimensão máxima de 128. Mas devido às razões enumeradas no início deste artigo o número de pontos máximo que
se consegue calcular actualmente será de 64.
Os valores da Tabela 3 podem ser descritos em número de
amostras por segundo (taxa de amostragem). Sendo os valores obtidos descritos na tabela seguinte:
Tornando-se agora possível, e recorrendo a um raciocínio
semelhante ao utilizado no cálculo experimental da conversão A/D, calcular qual o tempo necessário para executar o
algoritmo redescoberto por “Danielson and Lanczos”. Estimando assim o tempo total levado pelo Arduino a adquirir e a
aplicar a função de cálculo da FFT.
Os tempos de cálculo obtidos, utilizando o Arduino Duemilinove com ATMega168, foram os seguintes:
A taxa de amostragem que se consegue obter tem influência
directa, segundo o teorema de Nyquist, na frequência máxima do sinal de entrada que se consegue reconstruir sem
perda de informação. Ou seja, se conseguirmos uma taxa de
amostragem de 125KHz, a frequência máxima (sinal de entrada) que poderíamos reconstruir estaria situada nos
62.5KHz.
Arduino e o Cálculo da FFT
O algoritmo utilizado não foi “feito de raiz”, mas sim adaptado
de um já existente. Tentando assim neste artigo, apenas
encontrar e adaptar uma função existente na linguagem de
programação C que permitisse efectuar o cálculo da FFT. A
adaptação consistiu em alterar certas instruções utilizadas
pelo algoritmo original, que recorria a bibliotecas próprias da
linguagem C, para instruções equivalentes (que permitissem
obter o mesmo resultado) existentes actualmente nas bibliotecas disponíveis para o Arduino.
A principal dificuldade prendeu-se com a utilização, por parte
dos algoritmos existentes em C, de bibliotecas não existentes no ambiente de desenvolvimento do Arduino. Tornando a
adaptação de certos algoritmos encontrados uma tarefa mo-
Tabela 5 – Tempo total despendido (Cálculo FFT+ADC)
16
A PROGRAMAR
ARDUINO E O CÁLCULO DA FFT
Os valores obtidos na coluna 4 (Cálculo da FFT) da Tabela 5
foram obtidos efectuando a média de 1000 valores obtidos
experimentalmente (Recolha e cálculo da média efectuado
recorrendo ao Software MatLab). O algoritmo utilizado no
cálculo da FFT foi aplicado variando o número de pontos a
calcular, mas utilizando o mesmo array f[i] para todos os
testes efectuados. (Todos os pontos a calcular foram considerados como sendo 1+j).
A Tabela 6 apenas serve como indicação da gama de valores de tempo (em µs ) de execução, necessários para converter o número de pontos indicado na coluna 2 de analógico
para digital (por cada entrada analógica) e aplicar o respectivo algoritmo de cálculo da FFT. É de salientar que o valor
obtido para o cálculo final (coluna 3 da Tabela 6) utilizando
um factor de divisão de 2 e adquirindo 64 pontos (de cada
entrada analógica) corresponde apenas a ≅ 0.13s.
A coluna 3 corresponde ao produto do tempo de conversão
obtido na Tabela 3 pelo número de pontos a serem adquiridos, obtendo assim uma estimativa para o tempo de conversão A/D necessário em cada caso.
Conclusões
No modelo testado (Arduino Duemilinove com ATmega168),
e numa situação ideal, existiria a possibilidade de efectuar o
armazenamento em SRAM de cerca de 128 pontos distintos.
O que não se pode comparar quase aos 1000 pontos que se
conseguiriam armazenar utilizando o Arduino Mega com
ATmega1280. O que nos leva a considerar, e se quisermos
continuar a apostar no cálculo da FFT recorrendo directamente ao Arduino (e não ao processamento recorrendo a
computador), a considerar a sua aquisição (custo de aquisi-
O algoritmo utilizado poderá ser melhorado e a sua execução poderá ser optimizada, porém esse não será factor mais
importante mas sim a velocidade com que se efectua a
amostragem do sinal de entrada.
A coluna 5 (Tempo total) corresponde à soma do tempo necessário para efectuar a conversão A/D dos pontos referidos
na coluna 2 (Número de pontos utilizados) com o tempo necessário para aplicar o algoritmo de cálculo da FFT a esses
pontos. Se considerarmos que temos seis entradas analógicas, os tempos totais necessários serão o produto dos valores obtidos na coluna 5 por um factor de 6. Resultando os
seguintes valores finais:
ção ≅45€ por unidade). Recorrendo ao uso do Arduino Mega
teríamos a possibilidade de efectuar a aquisição e o cálculo
da FFT para 512 pontos. Este valor surge pois 2 11 corresponde a um valor de 2048, ou seja, 1024 números do tipo “a +
jb” o que excede a capacidade máxima de armazenamento
disponível. A solução é considerar a potência de dois imediatamente abaixo 210 que corresponde a um valor de 1024, ou
seja, a 512 pontos do tipo “a+jb”.
Como se sabe, o que irá definir se o desempenho obtido é
suficiente será o destino para o qual o nosso sistema é desenvolvido. Em caso de necessidade de maiores capacidades de processamento, não se deve descartar a hipótese do
cálculo da FFT recorrendo ao uso de p.ex. um computador (e
uso do Arduino apenas como datalogger).
Caso se queira melhorar o desempenho do Arduino no cálculo da FFT, será sem dúvida um bom desafio pois “O saber
não ocupa espaço de memória”.
Bibliografia
Chu, E. (2008). Discrete AND Continuous Fourier Transform.
Boca Raton, USA, A CHAPMAN & HALL BOOK.
Press, W. H., S. A. Teukolsky, et al. (1992). Numerical Recipes in C. Melborne, Australia, Cambridge University Press.
Tabela 6 – Tempo total despendido para 6 entradas
AUTOR
Escrito por Nuno Pessanha Santos
Mestrado em Engenharia Naval ramo de Armas e Electrónica pela Escola Naval. É um apaixonado pela área da electrónica e
das telecomunicações.
17