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