/*
*****************************************************************************
** 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
}