Good night to everyone, I'm here to show my on-going project, its a little VU meter and spectrum analyzer that I made using an op-amp, an Arduino(I know that not everyone likes them, and I don't really like the Arduino in its essence to many bloat code, but it was a cheap way of getting a dev-board so to say, because I didn't have paypal account to buy a real ISP programmer, but I already have the parts coming to make an USBasp), and a 16x2 char lcd.
By now its still pretty incomplete, it still doesn't have buttons so I can change between the two modes, and a little menu to at least adjust lcd contrast and brightness via pwm, in fact that adjustment code and hardware are not even built/tested.
To control the lcd I use Pete Fleury lcd lib, and the FFT part is handled by the great Elm-chan FFT engine, the lcd is updated at about 14Hz via timer interruption and the rest is always running in the background so to say.
I'm using this also as a motive to learn how to use Eagle and how to lay down a pcb, that I will order in SeeedStudio, and in fact I have a little doubt, can anyone teach me how to define the board size so Seeed will cut the board to size and not to the 5x5cm square, the design it self is not yet concluded, I will add the buttons for the menu and at least i2c headers so I can have them for future extensions like a little led controller using some tlc5940 and a little uC to go between the i2c and the tlc.
This is my code, the comments are in Portuguese though:
/*
*****************************************************************************
** 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
}
And this are some photos:


And the input buffer/signal conditioner, if I'm not wrong its a full bridge precision rectifier, some people have said its half-bridge, but I think its full bridge:

And two videos showing it working, sadly my cam for some reason doesn't catch the real movement of the bars, they are slowed down between 3 and 6 times than reality.
http://www.youtube.com/watch?v=91fefTmt2hI
http://www.youtube.com/watch?v=Zpz8XP4go3U
And now where it is really bad, the Eagle schematic and pcb, the pcb as top and bottom ground plane:



What do you think about it?
Any suggestions or critics are welcome.
EDIT: Looks like the images are cutted and there is no scroll bar, you can see then doing right click and View Image.