LusoRobótica - Robótica em Português

Electrónica => Electrónica Geral => Tópico iniciado por: senso em 04 de Novembro de 2010, 00:40

Título: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 04 de Novembro de 2010, 00:40
  Boa noite a todos, não sabia bem onde colocar este tópico, mas aqui vai, no principio do ano lectivo como tinha um pouco mais de tempo livre e como acho graça a coisas que reajam a musica, decidi fazer um pequeno VU meter usando um comum Lcd 16x2 e um pequeno circuito para converter o sinal de áudio que varia no máximo entre -1,4v e 1,4v se não estou em erro, para uma variação entre 0-5v para ler numa entrada analógica do atmega(neste caso é um arduino, mas sem usar o IDE do arduino), por enquanto está algo simples e provavelmente até um pouco difícil de se perceber o código pois está tudo separado em funções, mas isso é para permitir no futuro acrescentar mais umas quantas funções, uma delas será um spectrum analyzer aplicando um FFT ao sinal de audio, mas tudo a seu tempo.
  Então, para começar temos o circuito que trata o sinal, como devem ter visto á uns tempos andava á procura de amplificadores operacionais(op-amps em inglês abreviado) para fazer um pequeno filtro/conversor, acabei por ficar com uma colecção de samples de variados modelos que irão ser úteis para outras brincadeiras e actualmente estou a usar um TLV2372 da Texas Instruments, configurado como um rectificador de precisão com um ganho de 10x, isto quer dizer que as partes do sinal que são negativas passam a positivas e multiplica o sinal 10 vezes para que tire o máximo partido da gama do ADC do atmega que são de 0-5v, esta foi a parte difícil do projecto, seguidamente usei uma biblioteca para interagir com o lcd, estando ela disponível aqui:
http://www.jump.to/fleury (http://www.jump.to/fleury)

  Por fim foi uma questão de ler os dois canais do conversor analógico-digital, fazer umas contas para converter os valores de 0-1023 que o adc gera para um valor entre 0 e 75, este valor pode parecer estranho, mas estou a usar o LCD na vertical, então como cada carácter tem um tamanho de 8x5 pixeis, ficamos com 5 linhas por carácter, como o lcd tem 16 colunas, mas a coluna inicial tem uma letra a dizer L e R logo sobram 15 colunas e 5*15=75. A leitura dos dados, calculos e escrita no lcd são todos realizados numa interrupção gerada pelo timer1, que gera interrupções a cerca de 15Hz (15 vezes por segundo) e o resultado até é relativamente fluido, mas na minha opinião ainda não está perfeito/como quero, e um dos objectivos deste pequeno projecto é aprender um pouco mais sobre condicionamento de sinais analógicos, por isso quando tiver tempo(lá para Dezembro, com sorte) queria experimentar mais alguns tipos diferentes de filtros e a sua influencia assim como implementar mais algumas funções como já referi acima.
  Para além de extras em software tambem tenciono adicionar controlo do contraste e da backlight do lcd via software usado PWM, assim como adicionar uns botões para poder alterar essas definições e para mudar de modo, adicionar uns caracteres diferentes para não serem umas simples barras, coisitas assim.
  O código penso que está relativamente bem comentado, mas se quiserem usar para os vossos projectos (em principio passar isto para arduino é muito simples, é meter tudo quanto é inits no setup, as declarações das funções e os includes ficam acima do setup no sitio normal deles, as funções abaixo do loop e no loop não é preciso meter nada) e tiverem duvidas digam.

Código: [Seleccione]
/*

****************************************************************************
** Pinos do lcd - 16x2
** 1 2 3 4 5 6 7 8 9 10 11   12   13   14 15   16
** Gnd Vcc Ctr RS RW En D0 D1 D2 D3 D4   D5   D6 D7 An   Cat
**PB 4 5 0   1    2    3
**PD 7
**
** Ctr - Contrast
** An - Anode(+)
** Cat - Cathode(-)
****************************************************************************
*/

#include <avr/io.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#define F_CPU 16000000UL
#include <util/delay.h>
#include <math.h>
#include "lcd.h"

/*
** Defines usados no programa
*/

#define FULL 0xFF //Caracter "cheio", consultar datasheet para perceber
#define BLANK 0xFE //Caracter em branco

/*
***********************************************************************
** Constantes globais usadas no programa
***********************************************************************
*/

static const PROGMEM unsigned char MyChars[] = { //Dados na flash que não são precisos na Ram para nada
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // 1 linha
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 2 linhas
0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, // 3 linhas
0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, // 4 linhas
0x00, 0x00, 0x1F, 0x10, 0x10, 0x10, 0x00, 0x00, //simbolo L
0x00, 0x00, 0x1F, 0x05, 0x0D, 0x12, 0x00, 0x00, //Simbolo R
};

uint8_t i=0; //Variavel de iterações
volatile uint8_t j=0; //variavel de iterações (só para a ISR)
uint16_t newReading1 = 0; //Variavel para guardar o valor lido pelo ADC
uint16_t newReading2 = 0; //Variavel para guardar o valor lido pelo ADC
uint16_t lastReading1 = 0;
uint16_t lastReading2 = 0;
uint32_t mapped1 = 0; //Variavel para guardar o valor de adc_var_1*map
uint8_t sector1 = 0; //Coluna do lcd
uint8_t sectorRest1 = 0;
uint32_t mapped2 = 0; //Variavel para guardar o valor de adc_var_2*map
uint8_t sector2 = 0; //Coluna do lcd
uint8_t sectorRest2 = 0;
volatile uint8_t lcd_linha1[16];
volatile uint8_t lcd_linha2[16];

/*
***********************************************************************
** Declarações dos protótipos das funções
***********************************************************************
*/

int adc_read(char channel); //Função usada para ler um canal arbitrário do ADC
void adc_init(void); //Função para inicializar o ADC
void vu_mode(void);
void vu_mode_init(void); //Inicialização do modo VU meter
void timer1_init(void); //Inicialização do Timer1

/*
***********************************************************************
** Inicio do main
***********************************************************************
*/

int main(void){

adc_init();
lcd_init(LCD_DISP_ON); //Inicializa o LCD, sem cursor visivel
lcd_clrscr(); //Limpa o lcd e coloca o cursor em (0,0)
vu_mode_init();
timer1_init();
sei(); //Inicia as interrupções

while(1){
//Loop infinito
//Em branco pois é tudo feito na interrupção
}

return 1;
}


/*
***********************************************************************
** ISR
** Corre todo o vu_mode e faz o refresh do display.
** Todo o trabalho é feito por interrupção, deixando o CPU livre
** entre interrupções.
** Actualiza as duas linhas na totalidade.
***********************************************************************
*/



ISR(TIMER1_COMPA_vect){

vu_mode();
lcd_gotoxy(0,0);
for(j=0; j<16; j++){
lcd_putc(lcd_linha1[j]); }

lcd_gotoxy(0,1);
for(j=0; j<16; j++){
lcd_putc(lcd_linha2[j]); }
}

/*
***********************************************************************
** Funções usadas
***********************************************************************
*/

/*
***********************************************************************
** Inicializa o ADC no modo 10bits a 125Khz
***********************************************************************
*/

void adc_init(void){

ADCSRA |= ((1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)); //16Mhz/128 = 125Khz
ADMUX |= (1<<REFS0); //Referencia de 5v, com condensador no pino Aref
ADCSRA |= (1<<ADEN); //Adc ligada
ADCSRA |= (1<<ADSC); //Fazer uma primeira conversão para iniciar o circuito e porque é a mais lenta
}

/*
***********************************************************************
** Passa-se o canal a ler e devolve um valor de 10bits do ADC
***********************************************************************
*/

int adc_read(char channel){

ADMUX &= 0xF0; //Limpa o canal anterior
ADMUX |= channel; //Define o novo canal a ler do ADC
ADCSRA |= (1<<ADSC); //Inicia uma nova conversão
while(ADCSRA & (1<<ADSC)); //Espera que a conversão seja feita
return ADCW; //Retorna o valor do ADC, em modo 10 bits
}


/*
***********************************************************************
** Le o canal 0 e 1 do adc, faz uma detecção de pico e depois mapeia
** o valor 0..1023 do adc para 0..75 barras no display 16x2
***********************************************************************
*/

void vu_mode(void){

newReading1 = adc_read(0);
newReading2 = adc_read(1);

if(newReading1 > lastReading1){
lastReading1 = newReading1; }
else{
lastReading1 = (lastReading1*3 + newReading1)/4; } //Decaimento "suave"


mapped1 = ((lastReading1 * 75)/1024); //Pega nos 0..1023 e devolve 0..75
sector1 = mapped1/5; //Segmentos FULL na linha 0
sectorRest1 = mapped1 % 5; //Segmento final da linha 0

if(newReading2 > lastReading2){
lastReading2 = newReading2; }
else{
lastReading2 = (lastReading2*3 + newReading2)/4; } //Decaimento "suave"

mapped2 = ((lastReading2 * 75)/1024); //Pega nos 0..1023 e devolve 0..75
sector2 = mapped2/5; //Segmentos FULL na linha 1
sectorRest2 = mapped2 % 5; //Segmento final da linha 1

//Linha 0
for(i=0; i<(sector1); i++){
lcd_linha1[i+1] = FULL; }
if(sectorRest1>=1){
lcd_linha1[i+1] = ((sectorRest1-1)); }
for(i=(sector1 + 1);i<15; i++){
lcd_linha1[i+1] = BLANK; }

//Linha 1
for(i=0; i<(sector2); i++){
lcd_linha2[i+1] = FULL; }
if(sectorRest2>=1){
lcd_linha2[i+1] = ((sectorRest2-1)); }
for(i=(sector2 + 1);i<15; i++){
lcd_linha2[i+1] = BLANK; }

}

/*
***********************************************************************
** Carrega da flash os caracteres especiais para fazer as barras e as
** letras L e R na CGRAM do display
***********************************************************************
*/

void vu_mode_init(void){

lcd_command(_BV(LCD_CGRAM)); //Coloca CG RAM no endereço 0
for(i=0; i<48; i++){
lcd_data(pgm_read_byte_near(&MyChars[i])); } //Lê os dados da flash e carrega na Ram do LCD

lcd_gotoxy(0,0); //Linha 0 coluna 0
lcd_putc(4); //Escreve L na esquerda
lcd_gotoxy(0,1); //Linha 1 coluna 0
lcd_putc(5); //Escreve R na direita
lcd_linha1[0]=4;
lcd_linha2[0]=5;

}

/*
***********************************************************************
** Inicializa o timer1(16 bits) no modo CTC com prescaller de 1024
***********************************************************************
*/

void timer1_init(void){

TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode
OCR1A = 1000; //Para gerar interrupções a ??Hz para o refresh do display, valor obtido experimentalmente
TIMSK1 |= (1 << OCIE1A); // Enable CTC interrupt
TCCR1B |= ((1<<CS12)|(1<<CS10));//Inicia timer 1 com clock div de 1024
}


Umas fotos do circuito, daqui a uns dias já passo o circuito para o Eagle para se perceber melhor:
(https://lusorobotica.com/proxy.php?request=http%3A%2F%2Fimg87.imageshack.us%2Fimg87%2F4756%2Fdsc02825e.jpg&hash=637ccae7e4b723ced48ff7464385413e899a5aa1)
Visão geral

(https://lusorobotica.com/proxy.php?request=http%3A%2F%2Fimg151.imageshack.us%2Fimg151%2F6405%2Fdsc02826aj.jpg&hash=cbe417a9402cdfce70f7824612805bc73964785a)
Em pormenor

E aqui fica o esquema do filtro que estou actualmente a utilizar, os diodos devem ser schottky's para diminiur as perdas, mas estou a usar uns simples 1N4148, era o que havia na universidade, e funcionam bem.
(https://lusorobotica.com/proxy.php?request=http%3A%2F%2Fimg193.imageshack.us%2Fimg193%2F1453%2Ffiltrolm.jpg&hash=7a581252bb7accc540719e72e2453321720a2282)

E um filme do VU meter em funcionamento, a qualidade não é do melhor(apesar de o ficheiro original ser perfeitamente visível ainda não descobri qual a magia a fazer para o youtube não me comer a qualidade e o frame rate dos filmes), mas o filme não consegue retratar de todo o movimento das barras que é mais rápido e fluido que no filme, não muito, mas é.
MOV02827.MPG (http://www.youtube.com/watch?v=91fefTmt2hI#)

EDIT: Esquema adicionado.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: ivitro em 04 de Novembro de 2010, 09:45
Tá muito fixe senso, este trabalho que tiveste pode ser muito util noutros projectos ;) lembro me de ver algo parecido naquele fulano que fez o CB do carro a mostrar muitas coisas..


hmm so um pergunta. que esquema usas-te no condicionamento de sinal?  somador amplificador certo??
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: Sérgio_Sena em 04 de Novembro de 2010, 10:33
very good ! :)
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 04 de Novembro de 2010, 13:47
Já adicionei o esqema ;)
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: ivitro em 04 de Novembro de 2010, 13:51
Thx..


Ali os diodos é so para teres tensão positiva? não percebi muito bem :S
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 04 de Novembro de 2010, 13:54
O sinal de audio normal, varia mais ou menos(consoante o volume) entre -1,4v e +1,4v, como a tensão negativa não é lida pelo adc do arduino, e até porque tensão negativa destroi o adc, os diodos estão a funcionar como uma ponte retificadora, e depois o sinal é amplificado 10 vezes ou seja, se entra um sinal de 0,5v sai um sinal de 5v, que é para aproveitar os 10bits do adc e não ter um sinal muito pequeno por assim dizer que nem se iam notar diferenças depois de convertido no adc.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: ivitro em 04 de Novembro de 2010, 22:53
hmm ok então era o que pensava mais ou menos,  não sabia era que a tenção negativa dava cabo do adc.


se tivesses feito um amplificador somador não era melhor? tipo a tensão negativa agora ia tar positiva e na mesma entre 0 e 5, a menos que não de -1,4V a 0V seja indiferente.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 12 de Janeiro de 2011, 01:33
Finalmente!!!
Já tenho a parte do spectrum analyser a bombar!
A correr um FFT de 64 pontos e a actualizar o lcd a cada 10-15hz está super suave, a camera é que não capta bem o movimento.
Preciso de aumentar o ganho do meu filtro que o sinal está meio atenuado demais para o FFT.
Vou limpar o código e melhorar os comentários e mais logo ou amanhã coloco aqui junto com um filme.
E como a ideia é aprender talvez faça um pequeno circuito para juntar os dois sinais esquerdo e direito num sinal mono, pois o fft é só de um dos canais.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: metRo_ em 12 de Janeiro de 2011, 09:22
Finalmente!!!
Já tenho a parte do spectrum analyser a bombar!
A correr um FFT de 64 pontos e a actualizar o lcd a cada 10-15hz está super suave, a camera é que não capta bem o movimento.
Preciso de aumentar o ganho do meu filtro que o sinal está meio atenuado demais para o FFT.
Vou limpar o código e melhorar os comentários e mais logo ou amanhã coloco aqui junto com um filme.
E como a ideia é aprender talvez faça um pequeno circuito para juntar os dois sinais esquerdo e direito num sinal mono, pois o fft é só de um dos canais.

Teres feito o FFT é muito bom pois é util na análise de montes de sinais.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 12 de Janeiro de 2011, 21:56
Efectivamente eu não fiz o FFT, todo o crédito do FFT vai para o elm-chan e o seu fantástico FFT feito em assembly, corre super rápido com 64 pontos, e mesmo com mais pontos não se queixa, mas como só tenho um pequeno lcd de 16 caracteres já estou a deitar fora metade dos pontos do FFT.
Quanto ao uso real de um FFT num avr, não é assim tanto quanto isso, porque para ter 10 bits de precisão estou a correr o adc com um clock de 125Khz, isto quer dizer que tenho uma frequência de conversão de aproximadamente 9,6Khz penso que como tenho um op-amp á entrada do adc que podia mandar um overclock no adc para os 250Khz, ai já teria uma amostragem a pouco mais de 19Khz, praticamente toda a banda audio, mas para mostrar tal coisa precisava de um lcd gráfico, e infelizmente o meu não dá sinais de vida.
Ainda tenho de implementar uma espécie de milis com um timer livre e ver quanto tempo demora a captura e execução do fft, mas deve ser 1ms ou menos.

Então aqui fica o código, no fim vou anexar um ficheiro com todo o projecto para AvrStudio pois estou a incluir vários ficheiros que já tem algumas modificações feitas para funcionar tudo certinho.

O código não é tão grande quanto parece, tem muitos comentários e penso que está simples de se ler e bem indentado.
Quanto a futuros extras, adicionar uns quantos botões, criar um mini menu para mudar entre FFT e VU meter, e poder controlar o contraste e a backlight do lcd via pwm, depois, bem dêem ideias de que mais posso adicionar a este mini projecto.


Código: [Seleccione]
/*
*****************************************************************************
** LCD VU meter and FFT spectrum analyser *
** Using Peter Fleury lcd lib and el-chan fft engine for avr *
** Made for Atmega328p/Arduino Duemilanove *
** Tiago Angelo 12/01/2011 *
** V0.6 *
** *
*****************************************************************************

****************************************************************************
**
** Pinos do lcd - 16x2
** 1 2 3 4 5 6 7 8 9 10 11   12   13   14 15   16
** Gnd Vcc Ctr RS RW En D0 D1 D2 D3 D4   D5   D6 D7 An   Cat
**PB 4 5 0   1    2    3
**PD 7
**
** Ctr - Contrast
** An - Anode(+)
** Cat - Cathode(-)
****************************************************************************
*/

#include <avr/io.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#define F_CPU 16000000UL
#include <util/delay.h>
#include <math.h>
#include <inttypes.h>
#include <avr/pgmspace.h>

#include "lcd.h"
#include "ffft.h"

/*
** Defines usados no programa
*/
#define NUM_SAMPLES 64 //Samples usadas para calcular o FFT
#define FFT_SIZE (64/2) //Numero de valores devolvidos pelo FFT

#define FULL 0xFF //Caracter "cheio", consultar datasheet para perceber
#define BLANK 0xFE //Caracter em branco

/*
***********************************************************************
** Constantes globais usadas no programa
***********************************************************************
*/

static const PROGMEM unsigned char vuChars[] = { //Dados na flash que não são precisos na Ram para nada
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // 1 linha
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 2 linhas
0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, // 3 linhas
0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, // 4 linhas
0x00, 0x00, 0x1F, 0x10, 0x10, 0x10, 0x00, 0x00, //simbolo L
0x00, 0x00, 0x1F, 0x05, 0x0D, 0x12, 0x00, 0x00, //Simbolo R
};
static const PROGMEM unsigned char fftChars[] = { //Dados na flash que não são precisos na Ram para nada
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, //1 coluna
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, //2 coluna
0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, //3 coluna
0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, //4 coluna
0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, //5 coluna
0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, //6 coluna
0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, //7 coluna
0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, //7 coluna
};

uint8_t i,k; //Variaveis de iterações
uint8_t sector2 = 0; //Numero de colunas para o vu meter, linha 2
uint8_t sectorRest2 = 0; //Numero de colunas para o vu meter, linha 2
uint8_t sector1 = 0; //Numero de colunas para o vu meter, linha 1
uint8_t sectorRest1 = 0; //Numero de colunas para o vu meter, linha 1
uint8_t count = 0;
volatile uint8_t j=0; //variavel de iterações (só para a ISR)
volatile uint8_t lcd_linha1[16]; //Dados da linha 1 do lcd
volatile uint8_t lcd_linha2[16]; //Dados da linha 2 do lcd
uint16_t newReading1 = 0; //Variavel para guardar o valor lido pelo ADC
uint16_t newReading2 = 0; //Variavel para guardar o valor lido pelo ADC
uint16_t lastReading1 = 0;
uint16_t lastReading2 = 0;
uint16_t adcVal= 0; //Usado para guardar o valor lido pelo adc no modo fft
uint32_t mapped1 = 0; //Variavel para guardar o valor de adc_var_1*map
uint32_t mapped2 = 0; //Variavel para guardar o valor de adc_var_2*map

//Estas 3 são especificas para o FFT
int16_t capture[FFT_N]; //Buffer de captura
complex_t bfly_buff[FFT_N]; //Buffer do FFT
uint16_t spectrum[(FFT_N/2)]; //Buffer de saida do FFT


/*
***********************************************************************
** Declarações dos protótipos das funções
***********************************************************************
*/

int adc_read(char channel); //Função usada para ler um canal arbitrário do ADC
void adc_init(void); //Função para inicializar o ADC
void vu_mode(void);
void vu_mode_init(void); //Inicialização do modo VU meter
void fft_mode_init(void);
void fft_mode(void);
void timer1_init(void); //Inicialização do Timer1
void lcd_test(void);

/*
***********************************************************************
** Inicio do main
***********************************************************************
*/

int main(void){

adc_init();
lcd_init(LCD_DISP_ON); //Inicializa o LCD, sem cursor visivel
lcd_clrscr(); //Limpa o lcd e coloca o cursor em (0,0)
fft_mode_init(); //Inicialização do modo fft
//vu_mode_init(); //Inicialização do modo vu meter
timer1_init(); //Inicialização/configuração do timer para gerar as interrupções
sei(); //Inicia as interrupções

while(1){ //Loop infinito

//vu_mode(); //Modo vu meter
fft_mode(); //Modo fft
//lcd_test(); //Modo de teste do lcd
}

return 1;
}

/*
***********************************************************************
** ISR
** Corre todo o vu_mode e faz o refresh do display.
** Todo o trabalho é feito por interrupção, deixando o CPU livre
** entre interrupções.
** Actualiza as duas linhas na totalidade.
***********************************************************************
*/

ISR(TIMER1_COMPA_vect){

lcd_gotoxy(0,0);
for(j=0; j<16; j++){
lcd_putc(lcd_linha1[j]); }

lcd_gotoxy(0,1);
for(j=0; j<16; j++){
lcd_putc(lcd_linha2[j]); }
}

/*
***********************************************************************
** Funções usadas
***********************************************************************
*/

/*
***********************************************************************
** Inicializa o ADC no modo 10bits a 125Khz
***********************************************************************
*/

void adc_init(void){

ADCSRA |= ((1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)); //16Mhz/128 = 125Khz
ADMUX |= (1<<REFS0); //Referencia de 5v, com condensador no pino Aref
ADCSRA |= (1<<ADEN); //Adc ligada
ADCSRA |= (1<<ADSC); //Fazer uma primeira conversão para iniciar o circuito e porque é a mais lenta
}

/*
***********************************************************************
** Passa-se o canal a ler e devolve um valor de 10bits do ADC
***********************************************************************
*/

int adc_read(char channel){

ADMUX &= 0xF0; //Limpa o canal anterior
ADMUX |= channel; //Define o novo canal a ler do ADC
ADCSRA |= (1<<ADSC); //Inicia uma nova conversão
while(ADCSRA & (1<<ADSC)); //Espera que a conversão seja feita
return ADCW; //Retorna o valor do ADC, em modo 10 bits
}

/*
***********************************************************************
** Le o canal 0 e 1 do adc, faz uma detecção de pico e depois mapeia
** o valor 0..1023 do adc para 0..75 barras no display 16x2
***********************************************************************
*/

void vu_mode(void){

newReading1 = adc_read(0);
newReading2 = adc_read(1);

if(newReading1 > lastReading1){
lastReading1 = newReading1; }
else{
lastReading1 = (lastReading1*3 + newReading1)/4; } //Decaimento "suave"

mapped1 = ((lastReading1 * 75)/1024); //Pega nos 0..1023 e devolve 0..75
sector1 = mapped1/5; //Segmentos FULL na linha 0
sectorRest1 = mapped1 % 5; //Segmento final da linha 0

if(newReading2 > lastReading2){
lastReading2 = newReading2; }
else{
lastReading2 = (lastReading2*3 + newReading2)/4; } //Decaimento "suave"

mapped2 = ((lastReading2 * 75)/1024); //Pega nos 0..1023 e devolve 0..75
sector2 = mapped2/5; //Segmentos FULL na linha 1
sectorRest2 = mapped2 % 5; //Segmento final da linha 1


//Linha 0
for(i=0; i<(sector1); i++){
lcd_linha1[i+1] = FULL; }
if(sectorRest1>=1){
lcd_linha1[i+1] = ((sectorRest1-1)); }
for(i=(sector1 + 1);i<15; i++){
lcd_linha1[i+1] = BLANK; }

//Linha 1
for(i=0; i<(sector2); i++){
lcd_linha2[i+1] = FULL; }
if(sectorRest2>=1){
lcd_linha2[i+1] = ((sectorRest2-1)); }
for(i=(sector2 + 1);i<15; i++){
lcd_linha2[i+1] = BLANK; }

}

/*
***********************************************************************
** Le o canal 0 do adc, ao subtrair 512 á sample de 1023 bits cria um
** sinal positivo ou negativo centrado em 0, é preciso para o fft
** usando o FFT feito pelo elm-chan calcula um FFT de 64 pontos
** e preenche as duas linhas do lcd com barras
***********************************************************************
*/

void fft_mode(void){
count = 0;
adc_read(0);
cli();
while(count != NUM_SAMPLES){
ADCSRA |= (1<<ADSC);
while((ADCSRA & (1<<ADSC))){};
adcVal = ADCW;
capture[count] = ((int16_t)(adcVal)-512);
count++;
}
sei();

fft_input(capture,bfly_buff);
fft_execute(bfly_buff);
fft_output(bfly_buff,spectrum);

k=0;
for(i=1; i<17; i++){
sector1 = spectrum[i]/16;

if(sector1>7){
lcd_linha2[k]=FULL;
lcd_linha1[k]=(sector1-8);
}
else{
lcd_linha2[k]=sector1;
lcd_linha1[k]=BLANK;
}

k++;

}
}

/*
***********************************************************************
** Função de teste usada para afinar o gerador de barras verticais
***********************************************************************
*/

void lcd_test(void){

for(i=0; i<16; i++){

sector1=i;

if(sector1>7){
lcd_linha2[i]=FULL;
lcd_linha1[i]=(sector1-8);
}
else{
lcd_linha2[i]=sector1;
lcd_linha1[i]=BLANK;
}

}


}

/*
***********************************************************************
** Carrega da flash os caracteres especiais para fazer as barras e as
** letras L e R na CGRAM do display
***********************************************************************
*/

void vu_mode_init(void){

lcd_command(_BV(LCD_CGRAM)); //Coloca CG RAM no endereço 0
for(i=0; i<48; i++){
lcd_data(pgm_read_byte_near(&vuChars[i])); } //Lê os dados da flash e carrega na Ram do LCD

lcd_gotoxy(0,0); //Linha 0 coluna 0
lcd_putc(4); //Escreve L na esquerda
lcd_gotoxy(0,1); //Linha 1 coluna 0
lcd_putc(5); //Escreve R na direita
lcd_linha1[0]=4;
lcd_linha2[0]=5;
}

/*
***********************************************************************
** Carrega da flash os caracteres especiais para fazer as barras e as
** na CGRAM do display
***********************************************************************
*/

void fft_mode_init(void){

lcd_command(_BV(LCD_CGRAM)); //Coloca CG RAM no endereço 0
for(i=0; i<64; i++){
lcd_data(pgm_read_byte_near(&fftChars[i])); } //Lê os dados da flash e carrega na Ram do LCD

lcd_clrscr();
}

/*
***********************************************************************
** Inicializa o timer1(16 bits) no modo CTC com prescaller de 1024
***********************************************************************
*/

void timer1_init(void){

TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode
OCR1A = 1100; //Para gerar interrupções a 14Hz para o refresh do display, valor obtido experimentalmente
TIMSK1 |= (1 << OCIE1A); // Enable CTC interrupt
TCCR1B |= ((1<<CS12)|(1<<CS10));//Inicia timer 1 com clock div de 1024
}
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 14 de Janeiro de 2011, 02:01
E aqui está o video, mas mais uma vez ficou lento, entre 3-6x mais lento que na realidade :(
Arduino spectrum analyser (http://www.youtube.com/watch?v=Zpz8XP4go3U#)
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: ricardo-reis em 14 de Janeiro de 2011, 03:53
gosto muito disto.. parabéns pelo projecto..
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: metRo_ em 14 de Janeiro de 2011, 09:37
Está muito bom, tenho que analisar essa biblioteca ffft, sabes se só dá para o atmega328p ou se ele fez para vários?!
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 14 de Janeiro de 2011, 16:53
Sim, dá, é o que estou a usar.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: metRo_ em 14 de Janeiro de 2011, 17:04
Sim, dá, é o que estou a usar.

A minha pergunta é se eles fez para outros micros?
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 14 de Janeiro de 2011, 17:08
É código assembly que não depende de registos, em principio corre nos antigos megas e nos atmegas, desde que tenhas ram funciona em qualquer um, com 64 pontos ocupa se não me engano 300-350 bytes de ram.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: metRo_ em 14 de Janeiro de 2011, 17:45
É código assembly que não depende de registos, em principio corre nos antigos megas e nos atmegas, desde que tenhas ram funciona em qualquer um, com 64 pontos ocupa se não me engano 300-350 bytes de ram.

Eu sei, mas há sempre um ou outra instrução que pode ser diferente.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 14 de Janeiro de 2011, 23:24
Esta é a página original:
http://elm-chan.org/works/akilcd/report_e.html (http://elm-chan.org/works/akilcd/report_e.html)

Pelo menos em todos os Atmega corre.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 20 de Janeiro de 2011, 08:00
Hoje deu-me para inventar no Eagle e tentar desenhar uma coisa a sério em vez de seguir o tutorial da Sparkfun e esquecer o assunto.
Portanto, aqui vai desgraça, é supostamente o circuito de condicionamento de sinal, headers para meter o lcd por cima, entrada audio, um atmega328, mas provavelmente um atmega8 faz a mesma coisa, e um regulador de tensão.
Que dizem disto:
(https://lusorobotica.com/proxy.php?request=http%3A%2F%2Fimg513.imageshack.us%2Fimg513%2F6522%2Fsemttulonx.png&hash=98f1e2d8da1fa81f707971d6af3cdb4ebd57c244)

(https://lusorobotica.com/proxy.php?request=http%3A%2F%2Fimg801.imageshack.us%2Fimg801%2F1613%2Fsemttulo2jb.png&hash=2e7a7d73c811231be31e16da0ae2cefd33b06219)

Acho que tem um monte de saltos de um plano para o outro, provavelmente irei refazer tudo de novo e mudar algumas coisas de sitio, e talvez arriscar e passar tudo para smd assim sempre ficava com uma placa para aprender a soldar smd's.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: CBX em 20 de Janeiro de 2011, 12:33
evita os cantos em L, de resto não parece mal...

e visto teres os planos de GND não precisas de ligar com pistas os GND, o "ratsnest" fazia isso sozinho  :P
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 20 de Janeiro de 2011, 12:45
Tenho de ir corrigir isso, e correr o DRC para ver se dá para mandar fazer umas placas lol.
Se não fosse a resistência e o led ficava tudo por baixo do lcd, vá, o LM7805 deve ter de se dobrar para fora e meter um dissipador para não assar, ainda hoje vou refazer a placa só para aprender a routear eheh.
Obrigado pelo comentário CBX.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: CBX em 20 de Janeiro de 2011, 13:06
o regulador podes sempre usar o 78XXL da biblioteca v-reg, sempre ficas com a "footprint" na placa e facilita a dissipação sem necessidade de usar um dissipador
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 20 de Janeiro de 2011, 13:14
Eu sei, não tenho é espaço para ele debaixo do lcd, pelo menos agora, vou almoçar e logo á tarde volto a dar no Eagle!
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: amando96 em 20 de Janeiro de 2011, 23:17
Não conseguiste passar os dados do som através de USB? vi um post teu no fórum do arduino.
Pois queria fazer um VU meter com uma matriz de LEDs para colocar numa ciaxa de PC, mas preferia fazer através de USB, tenho 5 ou 6 fichas de USB diferentes, e só uma de som... que muitas vezes é para os earphones  :P
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 20 de Janeiro de 2011, 23:42
Mas eu uso um cabo em Y para ligar a este "projecto", até porque sem nada ligado a par com a entrada dos op-amps como usam tão pouca corrente pelo menos no meu portatil a saida de som nem sequer liga lol, sente que tem ficha ligada, mas como não tem carga a pedir corrente não liga.
Por usb, ainda andei ás voltas com o SDK do Winamp, mas aquilo está meio morto e super complexo, tens de andar a declarar arrays assim á sorte e por magia caem lá os dados, mas tem montes de variaveis pelo meio, e o resultado foi que nem me quis chatear, assim sempre aprendi um bocadinho sobre filtros analógicos, se bem que é para continuar a inventar.
Com este código facilmente se altera por exemplo para uma matriz de leds RGB por exemplo que ficava muito giro, eu vou tentar mandar uns pcb's disto só para perceber como funciona gerar gerbers e essas coisas todas e depois ficar com uma base para copos porque tem um monte de pistas trocadas lol.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: amando96 em 21 de Janeiro de 2011, 00:10
Ah, então só funciona com phones ligados? por vezes uso só a coluna do PC, talvez fique bem mais simples usar só um microfone num pino analógico, assim sempre posso usar com tudo, basta por na próximidade de qualquer fonte de som  :P
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 21 de Janeiro de 2011, 00:11
Não, ainda tenho de ir rever a coisa na breadboard que é para adicionar mais umas funções, mas com uma resistência pull-down vai ao sitio.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: msr em 23 de Janeiro de 2011, 00:13
senso, e onde estás a pensar fazer a PCB?

Tem atenção às duas camadas que estás a usar. Se utilizares só uma fica-te mais barato e como é uma placa relativamente simples se calhar até tu a fazias. O problema de ser single-layer é mesmo o routing :D
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: metRo_ em 23 de Janeiro de 2011, 13:12
senso, e onde estás a pensar fazer a PCB?

Tem atenção às duas camadas que estás a usar. Se utilizares só uma fica-te mais barato e como é uma placa relativamente simples se calhar até tu a fazias. O problema de ser single-layer é mesmo o routing :D

O mais barato depende de ode vai fazer, se vai mandar fazer numa empresa o preço é o mesmo.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: msr em 23 de Janeiro de 2011, 17:39
senso, e onde estás a pensar fazer a PCB?

Tem atenção às duas camadas que estás a usar. Se utilizares só uma fica-te mais barato e como é uma placa relativamente simples se calhar até tu a fazias. O problema de ser single-layer é mesmo o routing :D

O mais barato depende de ode vai fazer, se vai mandar fazer numa empresa o preço é o mesmo.

Single-layer ao mesmo preço de double-layer (com furos metalizados incluídos)? Onde?
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 23 de Janeiro de 2011, 22:56
SeeedStudio, 20$ para 10 pcb's de 5x5cm, double layer, silk-screen. solder mask, tudinho.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: metRo_ em 24 de Janeiro de 2011, 12:03
Single-layer ao mesmo preço de double-layer (com furos metalizados incluídos)? Onde?

Em quase todas as empresas em que vi preços de pcbs quando o preço não é o mesmo diferem poucos cêntimos.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: msr em 25 de Janeiro de 2011, 00:56
SeeedStudio, 20$ para 10 pcb's de 5x5cm, double layer, silk-screen. solder mask, tudinho.

Isso é um preço estupidamente bom!
Depois partilha os resultados :)

Citação de: metRo_
Em quase todas as empresas em que vi preços de pcbs quando o preço não é o mesmo diferem poucos cêntimos.

E que empresas foram essas se é que se pode saber? :) Nas que conheço a diferença de single para double layer é significativa. Incluindo solder mask, não só têm duas camadas para colocar solda como têm de metalizar os furos, o que sai naturalmente mais caro.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 01 de Fevereiro de 2011, 00:05
Acho que já estou a apanhar o jeito do Eagle, que acham do pcb, já tem umas quantas alterações, vêm assim algum erro estupido que eu não esteja a reparar/não saiba ainda, pois nunca fiz nenhuma pcb:

(https://lusorobotica.com/proxy.php?request=http%3A%2F%2Fimg29.imageshack.us%2Fimg29%2F489%2Fpcba.png&hash=cde161e1633808d62c60f04c5006a38772b4c186)

E com o groundplane:
(https://lusorobotica.com/proxy.php?request=http%3A%2F%2Fimg269.imageshack.us%2Fimg269%2F1650%2Fpcb2.png&hash=2fa18170830ea02776e6788434053ecb43c248b8)
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: msr em 01 de Fevereiro de 2011, 04:39
senso, respeita o desenho mecânico dos componentes. Como tens, dificilmente conseguirás soldar os componentes. Repara por exemplo no regulador de tensão, está mesmo em cima de um condensador. Tens muito espaço na PCB que podes aproveitar para distribuir melhor os componentes.

- já correste o DRC? (muito importante!)
- o componente que indica "3.5mm" é para o DC power jack? Existem uns mais jeitosos (gosto pessoal) e mais comuns (http://www.o-digital.com/uploads/2179/2187-3/DC_Power_Jack_262.jpg). Dá uma vista de olhos na library da Sparkfun: http://www.opencircuits.com/SFE_Footprint_Library_Eagle (http://www.opencircuits.com/SFE_Footprint_Library_Eagle)
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 01 de Fevereiro de 2011, 04:46
Boas noites msr, e obrigado pelo teu comentário.

O jack 3,5mm é o jack de entrada de audio, a alimentação são aqueles dois headers á esquerda do LM7805.

Penso que não terei problemas com o regulador e o condensador, pois o condensador que está no pcb é de 25v e planeio usar condensadores de 16v, que são mais pequenos, mas mesmo assim acho que vou mover o regulador de tensão para a parte de baixo do pcb para depois o dobrar e ficar paralelo com o pcb, e com alguma limpeza das pistas deixo o groundplane inferior como dissipador.
Mas ainda tenho de imprimir o pcb numa folha de papel e ver se todos os componentes batem certo com os pads e assim.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: CBX em 01 de Fevereiro de 2011, 15:53
voltando ao assunto do jack de 3.5mm, essa footprint parece um bocado esquisita, estes são bem mais comuns: http://www.sparkfun.com/products/8032 (http://www.sparkfun.com/products/8032) e não ocupam tanto espaço.

um attiny84 não seria suficiente para isso? tem exactamente 10 I\O, ADC e também ocuparia muito menos espaço.

de resto, à primeira vista não se vê nenhum erro, verifica com o DRC do sitio onde vais fazer os PCB's

se quiseres um serviço de PCB's bem mais barato dá uma vista de olhos aqui: http://iteadstudio.com/store/index.php?main_page=product_info&cPath=19_20&products_id=173 (http://iteadstudio.com/store/index.php?main_page=product_info&cPath=19_20&products_id=173)

segundo li no fórum do arduino usam a mesma "fabhouse" que o seeedstudio, demoram é um pouco mais de tempo a entregar
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 01 de Fevereiro de 2011, 16:53
Não é esquesito, tambem é relativamente comum, o jack que tenho no pcb, é uma questão de ver qual comsigo comprar mais barato lol.
Um Attiny84 tem 512Bytes de Ram, estou actualmente a usar um pouco mais que isso e é dificil cortar mais no uso de ram, o fft precisa de muta ram para correr e pretendo expandir o projecto, e então os IO extra dão jeito.

Sim, ainda é mais barato lol, 10pcbs a menos de 10€.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 02 de Fevereiro de 2011, 19:50
Estado actual:
morto :(

Alterei os pinos onde ligo o lcd para deixar a porta spi livre e puff, nem um olá mundo o lcd me mostra :(

Actualização, voltou á vida, mas descobri um bug fantástico no código que gera as barras, e está algo dificil de dar cabo dele.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: msr em 04 de Fevereiro de 2011, 16:02
senso, mas essa alteração foi ja na PCB que mandaste fazer?

CBX, esses preços são mesmo China Power. Absolutamente brutais!
Mas tens alguma experiencia em concreto com eles? Em quanto é que ficam os portes de 10 placas e quanto tempo é que demoram a chegar?

Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: CBX em 04 de Fevereiro de 2011, 16:21
apenas comprei uns componentes, demorou menos de 2 semanas a chegar

nunca mandei fazer pcb's ai, o que li foi no antigo fórum do arduino, a fab house é a mesma do(a) seeedstudio e os portes são $3usd

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1284352545 (http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1284352545)
(post #9)

11€ com portes por 10 pcb's, em que 5 garantidamente funcionam não é nada mau  ;D
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 04 de Fevereiro de 2011, 22:05
Ainda não mandei fazer nada, estou á espera de ter os componentes todos na mão para não desenhar uma pcb com resistencias 1206 e depois comprar 0805 e ficar a pé, isto é para se fazer com calma, não tem pressa.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 14 de Fevereiro de 2011, 19:07
Mais um bocado a brincar com isto e o filtro começou a mostrar as suas fraquezas :(
Alguem tem sugestões, o Njay uma vez falou de um que acho que era simplesmente umas resistências, e como isto é para aprender, poderias repetir aqui ou meter um link para o outro post onde falas-te nisso Njay?
Se alguém tiver sugestões, força!
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: metRo_ em 14 de Fevereiro de 2011, 19:21
Mais um bocado a brincar com isto e o filtro começou a mostrar as suas fraquezas :(
Alguem tem sugestões, o Njay uma vez falou de um que acho que era simplesmente umas resistências, e como isto é para aprender, poderias repetir aqui ou meter um link para o outro post onde falas-te nisso Njay?
Se alguém tiver sugestões, força!

O que se passou ao certo que não percebi? Que filtro estás a usar?
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 14 de Fevereiro de 2011, 19:47
O que está ai nos esquemas acima, bem acho que op-amp fritou após umas horas de testes de código lol, saida sempre a 5v, e a resposta não era a melhor, ao usar um adc externo a fazer sampling a 100Ksps mostra-se um pouco lento a responder e cheira-me que os diodos não serão de todo a melhor coisa, mas não tenho aqui condensadores para fazer um simples divisor resistivo directo.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: andre_f_carvalho em 13 de Março de 2011, 19:33
boas,


e que tal um destes, onde ele já filtra as frequências e mas usar como multiplexer para muda entre as varias frequências que ele dispõe

http://www.sparkfun.com/products/10024 (http://www.sparkfun.com/products/10024)
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 28 de Março de 2011, 21:51
Já não postava aqui novidades á algum tempo, mas o projecto este efectivamente parado, mas tenho muitas novidades.

Primeiro, respondo ao André, não uso esse chip porque é limitado, pois só me dá 7 frequências e porque deste modo tenho muito mais flexibilidade e como é feito em software o custo é nulo.

Actualmente tenho um novo filtro de entrada, muito mais simples que o anterior, é simplesmente um condensador no sinal de entrada para bloquear o sinal dc e depois duas resistências, uma liga o sinal de entrada aos 5v e outra á massa, assim quanto não tenho sinal tenho 2.5v na entrada dos adc's.

Estou actualmente a usar um adc externo da microchip de 12 bits, interface SPI e capaz de ler 100k samples por segundo, bastante mais rápido que as 16K samples que o adc interno do avr.

Por agora o modo fft precisa de umas limadelas para ficar em condições, o modo VU está muito melhor, mas apesar disso segue a musica durante 7 ou 8 segundos e depois por 1s parece que satura e fica praticamente a zeros, já com o antigo filtro de entrada acontecia o mesmo, ainda não percebi bem porque, e não tenho máquina aqui para filmar, mas vou tentar com o telemovel.

Estou mesmo assim pensar em adicionar um op-amp para fazer de buffer porque quanto mais aumento o som nas colunas menor é o sinal que o adc lê.
O código actualmente está assim, com alguns comentários em ingles, outros em português, precisa de uma certa limpeza e tem já funções para controlar o contraste e o brilho por pwm, mas ainda não tenho isso ligado, falta tambem um sistema de menus para altenar entre modo fft e modo vu e para controlar brilho e contraste.
Código: [Seleccione]
/*
*****************************************************************************
** LCD VU meter and FFT spectrum analyser *
** Using Peter Fleury lcd lib and el-chan fft engine for avr *
** Made for Atmega328p/Arduino Duemilanove *
** Tiago Angelo 12/01/2011 *
** Stable working version *
**
**  -Added SPI lcd
**  -Atomic execution of the fft
** *
*****************************************************************************

****************************************************************************
**
** Pinos do lcd - 16x2
** 1 2 3 4 5 6 7 8 9 10 11   12   13   14 15   16
** Gnd Vcc Ctr RS RW En D0 D1 D2 D3 D4   D5   D6 D7 An   Cat
**PC 5 4 3   2    1    0
**PD 7
**
** Ctr - Contrast
** An - Anode(+)
** Cat - Cathode(-)
****************************************************************************
**
** ADC pins
** Clock(13) - PB5
** Dout(12) - PB4
** Din(11)  - PB3
** CS/SHDN(10) - PB2
*/

#include <avr/io.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#define F_CPU 16000000UL
#include <util/delay.h>
#include <math.h>
#include <inttypes.h>
#include <avr/pgmspace.h>

#include "lcd.h"
#include "ffft.h"

/*
** Defines usados no programa
*/

#define BAUDRATE 9600
#define BAUD_PRESCALLER (((F_CPU / (BAUDRATE * 16UL))) - 1)

#define NUM_SAMPLES 64 //Samples usadas para calcular o FFT
#define FFT_SIZE (64/2) //Numero de valores devolvidos pelo FFT

#define FULL 0xFF //Caracter "cheio", consultar datasheet para perceber
#define BLANK 0xFE //Caracter em branco

#define MENUBT PB4 //Botão usado para chamar o menu
#define VU 1
#define FFT 2

#define MOSI PB3
#define MISO PB4
#define SCK PB5
#define SS PB2
#define ADDR 0x06 //Address/configuration byte for the spi ADC
#define DUMMY 0x00 //Dummy byte, because in each SPI comm we must send data
#define DATA_MASK 0x0F //Mask used to extract the first 4 bits that arrive in the second byte received


/*
***********************************************************************
** Constantes globais usadas no programa
***********************************************************************
*/

static const PROGMEM unsigned char vuChars[] = { //Dados na flash que não são precisos na Ram para nada
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // 1 linha
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 2 linhas
0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, // 3 linhas
0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, // 4 linhas
0x00, 0x00, 0x1F, 0x10, 0x10, 0x10, 0x00, 0x00, //simbolo L
0x00, 0x00, 0x1F, 0x05, 0x0D, 0x12, 0x00, 0x00, //Simbolo R
};
static const PROGMEM unsigned char fftChars[] = { //Dados na flash que não são precisos na Ram para nada
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, //1 coluna
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, //2 colunas
0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, //3 colunas
0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, //4 colunas
0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, //5 colunas
0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, //6 colunas
0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, //7 colunas
};


/*
**
*/

uint8_t i,k; //Variaveis de iterações
uint8_t sector2 = 0; //Numero de colunas para o vu meter, linha 2
uint8_t sectorRest2 = 0; //Numero de colunas para o vu meter, linha 2
uint8_t sector1 = 0; //Numero de colunas para o vu meter, linha 1
uint8_t sectorRest1 = 0; //Numero de colunas para o vu meter, linha 1
uint8_t pressed=0; //Usado para contar o tempo que o botão está premido
uint8_t count = 0;
uint8_t last_time=0;
uint8_t mode=0;
volatile uint8_t j=0; //variavel de iterações (só para a ISR)
volatile uint8_t time=0; //Uma espécie de milis para fazer deboucing não bloqueante
volatile uint8_t lcd_linha1[16]; //Dados da linha 1 do lcd
volatile uint8_t lcd_linha2[16]; //Dados da linha 2 do lcd
volatile uint8_t flag = 0;
uint16_t newReading1 = 0; //Variavel para guardar o valor lido pelo ADC
uint16_t newReading2 = 0; //Variavel para guardar o valor lido pelo ADC
uint16_t lastReading1 = 0;
uint16_t lastReading2 = 0;
uint16_t counter = 0;
uint16_t adcVal= 0; //Usado para guardar o valor lido pelo adc no modo fft
uint32_t mapped1 = 0; //Variavel para guardar o valor de adc_var_1*map
uint32_t mapped2 = 0; //Variavel para guardar o valor de adc_var_2*map


//Estas 3 são especificas para o FFT
int16_t capture[FFT_N]; //Buffer de captura
complex_t bfly_buff[FFT_N]; //Buffer do FFT
uint16_t spectrum[(FFT_N/2)]; //Buffer de saida do FFT


/*
***********************************************************************
** Declarações dos protótipos das funções
***********************************************************************
*/

void vu_mode(void);
void vu_mode_init(void); //Inicialização do modo VU meter
void fft_mode_init(void);
void fft_mode(void);
void timer1_init(void); //Inicialização do Timer1
void timer0_init(void); //Inicialização do Timer0
void update_pwm(uint8_t brightness, uint8_t contrast);
void SPI_init(void);
uint8_t SPI_Write(uint8_t data);
uint16_t Read_ADC(uint8_t channel);
void USART_init();
unsigned char USART_receive();
void USART_putchar(unsigned char);
void USART_putstring(char* StringPtr);

/*
***********************************************************************
** Inicio do main
***********************************************************************
*/

int main(void){

//DDRB &= ~(1<<MENUBT); //Coloca botão do menu como entrada

USART_init();
SPI_init();
lcd_init(LCD_DISP_ON); //Inicializa o LCD, sem cursor visivel
lcd_clrscr(); //Limpa o lcd e coloca o cursor em (0,0)
lcd_home();
fft_mode_init(); //Inicialização do modo fft
//vu_mode_init(); //Inicialização do modo vu meter
timer1_init(); //Inicialização/configuração do timer para gerar as interrupções
sei(); //Inicia as interrupções

while(1){ //Loop infinito

if(flag == 1){
//vu_mode();
flag = 0; //Modo vu meter
fft_mode(); //Modo fft
}

}

return 1;
}

/*
***********************************************************************
** ISR
** Corre todo o vu_mode/fft e faz o refresh do display.
** Todo o trabalho é feito por interrupção, deixando o CPU livre
** entre interrupções.
** Actualiza as duas linhas na totalidade.
***********************************************************************
*/

ISR(TIMER1_COMPA_vect){

lcd_gotoxy(0,0);
for(j=0; j<16; j++){
lcd_putc(lcd_linha1[j]); }

lcd_gotoxy(0,1);
for(j=0; j<16; j++){
lcd_putc(lcd_linha2[j]); }

flag = 1;
}


/*
***********************************************************************
** Funções usadas
***********************************************************************
*/



/*
***********************************************************************
** Le o canal 0 e 1 do adc, faz uma detecção de pico e depois mapeia
** o valor 0..1023 do adc para 0..75 barras no display 16x2
***********************************************************************
*/

void vu_mode(void){

/*
counter++;
itoa(counter, buffer, 10);
USART_putstring(buffer);
USART_putstring(" , ");
itoa(newReading1, buffer, 10);
USART_putstring(buffer);
USART_putchar('\n');
*/


newReading1 = abs(Read_ADC(0)-2048);
newReading2 = abs(Read_ADC(1)-2048);

if(newReading1 >= lastReading1){
lastReading1 = newReading1; }
else{
lastReading1 = (lastReading1*7 + newReading1)/8; } //Decaimento "suave"

mapped1 = ((uint32_t)((uint32_t)lastReading1 * 75)/2048); //Pega nos 0..1023 e devolve 0..75
sector1 = mapped1/5; //Segmentos FULL na linha 0
sectorRest1 = mapped1 % 5; //Segmento final da linha 0

if(newReading2 >= lastReading2){
lastReading2 = newReading2; }
else{
lastReading2 = ((uint32_t)lastReading2*7 + newReading2)/8; } //Decaimento "suave"

mapped2 = ((uint32_t)((uint32_t)lastReading2 * 75)/2048); //Pega nos 0..1023 e devolve 0..75
sector2 = mapped2/5; //Segmentos FULL na linha 1
sectorRest2 = mapped2 % 5; //Segmento final da linha 1

//Linha 0
for(i=0; i<(sector1); i++){
lcd_linha1[i+1] = FULL; }
if(sectorRest1>=1){
lcd_linha1[i+1] = ((sectorRest1-1)); }
for(i=(sector1 + 1);i<15; i++){
lcd_linha1[i+1] = BLANK; }

//Linha 1
for(i=0; i<(sector2); i++){
lcd_linha2[i+1] = FULL; }
if(sectorRest2>=1){
lcd_linha2[i+1] = ((sectorRest2-1)); }
for(i=(sector2 + 1);i<15; i++){
lcd_linha2[i+1] = BLANK; }

}

/*
***********************************************************************
** Le o canal 0 do adc, ao subtrair 512 á sample de 1023 bits cria um
** sinal positivo ou negativo centrado em 0, é preciso para o fft
** usando o FFT feito pelo elm-chan calcula um FFT de 64 pontos
** e preenche as duas linhas do lcd com barras
***********************************************************************
*/

void fft_mode(void){
count = 0;
cli();
while(count != NUM_SAMPLES){
adcVal = abs(Read_ADC(0)-2048);
capture[count] = ((int16_t)(adcVal)-1024);
count++;
}

fft_input(capture,bfly_buff);
fft_execute(bfly_buff);
fft_output(bfly_buff,spectrum);

k=0;
for(i=1; i<17; i++){
sector1 = spectrum[i]/8;

if((sector1>=0) & (sector1<7)){
lcd_linha2[k]=sector1;
lcd_linha1[k]=BLANK;
}

if((sector1>=7) & (sector1<15)){
lcd_linha2[k]=FULL;
lcd_linha1[k]=(sector1-8);
}

if(sector1>16){
lcd_linha2[k]=FULL;
lcd_linha1[k]=FULL;
}

k++;

}
sei();
}

/*
***********************************************************************
** Carrega da flash os caracteres especiais para fazer as barras e as
** letras L e R na CGRAM do display
***********************************************************************
*/

void vu_mode_init(void){

lcd_command(_BV(LCD_CGRAM)); //Coloca CG RAM no endereço 0
for(i=0; i<48; i++){
lcd_data(pgm_read_byte_near(&vuChars[i])); } //Lê os dados da flash e carrega na Ram do LCD

lcd_gotoxy(0,0); //Linha 0 coluna 0
lcd_putc(4); //Escreve L na esquerda
lcd_gotoxy(0,1); //Linha 1 coluna 0
lcd_putc(5); //Escreve R na direita
lcd_linha1[0]=4;
lcd_linha2[0]=5;
}

/*
***********************************************************************
** Carrega da flash os caracteres especiais para fazer as barras e as
** na CGRAM do display
***********************************************************************
*/

void fft_mode_init(void){

lcd_command(_BV(LCD_CGRAM)); //Coloca CG RAM no endereço 0
for(i=0; i<56; i++){
lcd_data(pgm_read_byte_near(&fftChars[i])); } //Lê os dados da flash e carrega na Ram do LCD

lcd_clrscr();
}

/*
***********************************************************************
** Inicializa o timer1(16 bits) no modo CTC com prescaller de 1024
***********************************************************************
*/

void timer1_init(void){

TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode
OCR1A = 600; //Para gerar interrupções a 14Hz para o refresh do display, valor obtido experimentalmente
TIMSK1 |= (1 << OCIE1A); // Enable CTC interrupt
TCCR1B |= ((1<<CS12)|(1<<CS10));//Inicia timer 1 com clock div de 1024
}

/*
***********************************************************************
** Inicializa o timer0(8 bits) no modo PWM phase correct
** Usado para gerar o pwm para o controlo do contraste e do brilho
** do lcd
***********************************************************************
*/
void timer0_init(void){
TCCR0A |= ((1<<COM0B1)|(1<<WGM00)); //Phase correct pwm
TCCR0B |= ((1<<CS01)|(1<<CS00));

}


void update_pwm(uint8_t brightness, uint8_t contrast){

OCR0A = brightness;
OCR0B = contrast;

}


void SPI_init(void){

DDRB |= ((1<<MOSI)|(1<<SCK)|(1<<SS)); //Mosi, sck and ss are outputs
DDRB &= (~(1<<MISO)); //Miso is input
PORTB |= (1<<SS); //Start with SS at 1 (HIGH)

//Enable SPI, Master mode
//MSB first
//CPOL=0
//CPHA=0
//SPI mode 0
//sck = F_CPU/8 = 2Mhz
//SPSR = (1<<SPI2X); //SPI clock x2
SPCR = ((1<<SPE)|(1<<MSTR)|(1<SPR0));

}

uint8_t SPI_Write(uint8_t data){

SPDR = data; //Start data transfer
while(!(SPSR & (1<<SPIF))); //Wait for the end of the transmission

return SPDR; //Return the received data

}

uint16_t Read_ADC(uint8_t channel){
uint8_t upper_addr, lower_addr, upper_data, lower_data;

if(channel > 3){
upper_addr = ADDR | 0x01; }
else{
upper_addr = ADDR; }
lower_addr = channel<<6;

PORTB &= (~(1<<SS)); //Put SS at 0 (LOW), to start the data transfer

SPI_Write(upper_addr);
upper_data = SPI_Write(lower_addr);
lower_data = SPI_Write(DUMMY);
upper_data &= DATA_MASK;

PORTB |= (1<<SS); //Puts SS at 1 (HIGH), because we already stoped all the data transfer

return ((upper_data<<8)|lower_data);


}

void USART_init(void){

UBRR0H = (uint8_t)(BAUD_PRESCALLER>>8);
UBRR0L = (uint8_t)(BAUD_PRESCALLER);
UCSR0B = (1<<RXEN0)|(1<<TXEN0);
UCSR0C = (3<<UCSZ00);
}


unsigned char USART_receive(void){

while(!(UCSR0A & (1<<RXC0)));
return UDR0;

}


void USART_putchar( unsigned char data){

while(!(UCSR0A & (1<<UDRE0)));
UDR0 = data;

}


void USART_putstring(char* StringPtr)
{
   while (*StringPtr != 0x00) {
      USART_putchar(*StringPtr);
      StringPtr++;
   }
}

Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: andre_f_carvalho em 28 de Março de 2011, 22:25
bem, sendo algum dinheiro investido ou n com este chip ja se tem as frequencias definidas e pouco em algum codigo, e se calhar resolve esse teu problema depois dos 7 ou 8s mas pts, achar uma solução diferente é meio caminho andado para obter conhecimentos:D
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 28 de Março de 2011, 22:39
Mas esse chip só serve para a parte do spectrum analyzer, o VU meter não funciona com esses chips.
Tenho ideia que o problema ou é um overflow, que já detectei uma variavel que fazia overflow, provavelmente é o mesmo problema, ou então é uma race condition entre o tempo de update do lcd e as interrupções, mas tenho que analizar isso, provavelmente hoje.

E estou a ponderar mudar para um lcd gráfico, nada de exótico, os simples e mais que provados 128x64 com o controlador KS0018 que tenho aqui um belo pisa papeis de 50€ que nem a Sparkfun sabe porque é que não funciona, provavelmente estragado, mas quem tem a factura é o TigPt...
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: andre_f_carvalho em 28 de Março de 2011, 22:57
sim eu sei que é para spectrum analyzer, mas sempre podes deitar o lcd e fazer um SA, e relação ao tiago, acho mais provavelmente ele emigrou:P, se alguem sober a morada dele é que podia falar com ele, é que uma das coisas que pode acontecer se ele n se interessar por isto é o forum ficar desligado por falta de pagamento:S
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: senso em 28 de Abril de 2011, 22:11
Bem...
Tanta volta que dei com isto para descobrir que as pontas que eu comprei fazem mau contacto com o jack do portatil, bem sabia que ainda ia ter problemas por ter comprado material tão barato....
Tenho de ir atravessar a cidade para comprar mais meia duzia de breadboards que estão sempre todas ocupadas, que amanha devem chegar brinquedos novos e não tenho sitio onde os montar lol, aproveitar e comprar cabos de audio decentes para depois os cortar e meter uns headers lol.

Fica o abre olhos que material barato só dá chatice.
Título: Re: VU meter com Atmega328p e lcd16x2
Enviado por: vsta41 em 02 de Abril de 2013, 05:29
Por favor, onde eu posso obter o ffft.h biblioteca?