sábado, 2 de junho de 2012

Entendendo e Manipulando o Código


No penúltimo post, apresentamos os códigos usados no Arduíno e no Processing para a captação de frequências, sem contúdo uma explicação mais aprofundada dos códigos, isso por se tratar do nosso primeiro teste bem sucedido com ele.

Depois disso pudemos estudar melhor o código e manipulá-lo de acordo com nosso propósito. Um detalhe importante, e já falado no post anterior, é que para que o código funcione é preciso que se adicione a biblioteca ffft na pasta libraries do Arduíno, a qual existe um link no post anterior.

A primeira coisa que descobrimos é que o código do Arduíno não funciona com qualquer versão de software, mas sim com a versão Arduíno 0022, quanto ao Processing funciona bem a comunicação com a versão mais atual: Processing 1.5.1.
Depois descobrimos que o software tem duas opções para a divisão de frêquencias: a primeira divide o espectro de aproximadamente 19200Hz por 64 faixas de 300Hz e a segunda divide o espectro de aproximadamente 9600Hz por 64 faixas de 150Hz. Essas duas opções limitam nosso campo de ação, com elas não podemos captar frequências especificas de notas musicais, mas sim intervalos nos quais estas frequências estão inseridas, pela segunda opção apresentar um intervalo menor e portanto uma definição maior optamos por ela.

É importante observar que nosso projeto sofreu portanto uma modificação de objetivo, nosso experimento não reconhecerá mais frequências de notas musicais específicas, e sim intervalos de frequências, para cada intervalo o Arduíno acionará uma luz. Teremos nesse experimento sete intervalos de frequências e portanto usaremos apenas 7 das 64 barras ou intervalos de frequências geradas pela biblioteca, essas barras começarão a captar a frequência aproximadamente de 400Hz até 1000Hz.

Para que o Arduíno identifique a frequência predominante foi preciso criar uma função que acha o intervalo de frequência de maior intensidade dentre os 7 escolhidos. Código abaixo:

int Maior(uint16_t lista[]){
int maior = 7;
for(int i=7;i<14;i++){
  if(lista[i]>lista[maior])maior=i;
  }
  if(lista[maior]<15)maior=0;
  return maior;
}

Como ainda não compramos todos os Leds, utilizamos um LED RGB que utiliza a porta 3 para vermelho, 4 para negativo, 5 para verde e 6 para Azul. E para controlá-lo, criamos uma função que recebe os três paramêtros de intersidade 0-255 para cade um:


void RGB(int r, int g, int b){
  digitalWrite(4, HIGH);
  analogWrite(3,r);
  analogWrite(5,g);
  analogWrite(6,b);
}

E outra que a utilizará, esta recebe apenas um valor de 1-7 o qual acionara uma cor pre-definida para o RGB.

void Cor(int n){
switch(n){
  case 1: RGB(255,0,0);break;
  case 2: RGB(0,255,0);break;
  case 3: RGB(0,0,255);break;
  case 4: RGB(255,255,0);break;
  case 5: RGB(255,0,255);break;
  case 6: RGB(150,255,10);break;
  case 7: RGB(255,255,255);break;
  default: RGB(0,0,0);
  }
}

Abaixo está o código comentado do Arduíno.




/*
This Example acquire analog signal from A0 of Arduino, and Serial out to Processing application to visualize.
Tested with preamplified audio data. Take a look at http://www.youtube.com/watch?v=drYWullBWcI

Analog signal is captured at 9.6 KHz, 64 spectrum bands each 150Hz which can be change from adcInit()
Load the this file to Arduio, run Processing application.

Original Fixed point FFT library is from ELM Chan, http://elm-chan.org/works/akilcd/report_e.html
Ported to the library and demo codes are from AMurchick http://arduino.cc/forum/index.php/topic,37751.0.html
Processing code is from boolscott http://boolscott.wordpress.com/2010/02/04/arduino-processing-analogue-bar-graph-2/
*/


#include <stdint.h>
#include <ffft.h>



#define  IR_AUDIO  0 // ADC canal para capturar


volatile  byte  position = 0;
volatile  long  zero = 0;

int16_t capture[FFT_N];            /* buffer capturador de onde */
complex_t bfly_buff[FFT_N];        /* FFT buffer */
uint16_t spektrum[FFT_N/2];        /* saida do espectro do buffer  */
int minimo = 10;


void RGB(int r, int g, int b){//Contralador RBG
  digitalWrite(4, HIGH);
  analogWrite(3,r);
  analogWrite(5,g);
  analogWrite(6,b);
}

void Cor(int n){//Função que define 7 cores para n de 1-7
switch(n){
  case 1: RGB(255,0,0);break;
  case 2: RGB(0,255,0);break;
  case 3: RGB(0,0,255);break;
  case 4: RGB(255,255,0);break;
  case 5: RGB(255,0,255);break;
  case 6: RGB(150,255,10);break;
  case 7: RGB(255,255,255);break;
  default: RGB(0,0,0);
  }
}

int Maior(uint16_t lista[]){//Função que define intervalo de frequência de maior intensidade
int maior = 7;
for(int i=7;i<14;i++){
  if(lista[i]>lista[maior])maior=i;
  }
  if(lista[maior]<15)maior=0;
  return maior;
}

void setup()
{
  Serial.begin(57600);
  adcInit();
  adcCalb();
  establishContact();  // manda um byte para estabelecer contato até o processing responder
  for(int i=4;i<11;i++)pinMode(i,OUTPUT);
}

void loop()
{
  if (position == FFT_N)
  {
    fft_input(capture, bfly_buff);
    fft_execute(bfly_buff);
    fft_output(bfly_buff, spektrum);

    for (byte i = 7; i < 14; i++){
      Serial.print(spektrum[i],BYTE);
    }//manda dados para o processing
   position = 0;
    int maior = Maior(spektrum);
    Cor(maior-6);//pede a função Cor() acender a cor maior-6
  }
}

void establishContact() {
 while (Serial.available() <= 0) {
      Serial.print('A', BYTE);   // manda um A maiusculo
      delay(300);
  }
}

// free running ADC fills capture buffer
ISR(ADC_vect)
{
  if (position >= FFT_N)
    return;
 
  capture[position] = ADC + zero;
  if (capture[position] == -1 || capture[position] == 1)
    capture[position] = 0;

  position++;
}
void adcInit(){
  /*  REFS0 : VCC use as a ref, IR_AUDIO : channel selection, ADEN : ADC Enable, ADSC : ADC Start, ADATE : ADC Auto Trigger Enable, ADIE : ADC Interrupt Enable,  ADPS : ADC Prescaler  */
  // free running ADC mode, f = ( 16MHz / prescaler ) / 13 cycles per conversion
  ADMUX = _BV(REFS0) | IR_AUDIO; // | _BV(ADLAR);
 //ADCSRA = _BV(ADSC) | _BV(ADEN) | _BV(ADATE) | _BV(ADIE) | _BV(ADPS2) | _BV(ADPS1); //pre-escala 64 : 19231 Hz - 300Hz por 64 divisões
  ADCSRA = _BV(ADSC) | _BV(ADEN) | _BV(ADATE) | _BV(ADIE) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // pre-escala 128 : 9615 Hz - 150 Hz porr 64 divisões, melhor para maioria das músicas(mais definição)
  sei();
}
void adcCalb(){
  Serial.println("Start to calc zero");
  long midl = 0;
  // get 2 meashurment at 2 sec
  // on ADC input must be NO SIGNAL!!!
  for (byte i = 0; i < 2; i++)
  {
    position = 0;
    delay(100);
    midl += capture[0];
    delay(900);
  }
  zero = -midl/2;
  Serial.println("Done.");
}


Quanto ao código do processing modificamos o código de barras para 7, ao invés das 64 do post anterior, contúdo é preciso que você modifique o campo da porta, pois ela lê a porta que o arduíno está utilizando, para saber a porta que seu arduíno está utilizando vá em tools-> Serial Port. No nosso caso utilizamos a porta "COM5"

// Feel Free to edit these variables ///////////////////////////
String  xLabel = "Frequency";
String  yLabel = "Values";
String  Heading = "Arduino FFT";
String  URL = "01/02/2010";
float Vcc = 255.0;    // the measured voltage of your usb
int NumOfVertDivisions=5;      // dark gray
int NumOfVertSubDivisions=10;  // light gray


int NumOfBars=7;    // Numero de barras/ Deve ser o mesmo que o arduíno envia.
                    // since you should change what the arduino sends
                   

// if these are changed, backgroung image has problems
// a plain background solves the problem
int ScreenWidth = 800, ScreenHeight=600;
/////////////////////////////////////////////////////////

//  Serial port stuff ///////////////////////
import processing.serial.*;
Serial myPort;       
boolean firstContact = false;
int[] serialInArray = new int[NumOfBars];
int serialCount = 0;
///////////////////////////////////////////////

int LeftMargin=100;
int RightMArgin=80;
int TextGap=50;
int GraphYposition=80;
float BarPercent = 0.4;

int value;

PFont font;
PImage bg;

int temp;
float yRatio = 0.58;
int BarGap, BarWidth, DivisounsWidth;
int[] bars = new int[NumOfBars];

void setup(){

  bg = loadImage("BG.jpg");

  /// NB SETTINGS ////////////////////////////////////////////////////////
  myPort = new Serial(this,"COM5",57600);
  ////////////////////////////////////////////////////////////////////////

  DivisounsWidth = (ScreenWidth-LeftMargin-RightMArgin)/(NumOfBars);
  BarWidth = int(BarPercent*float(DivisounsWidth));
  BarGap = DivisounsWidth - BarWidth;

  size(ScreenWidth,ScreenHeight);
  font = createFont("Arial",12);

  textAlign(CENTER);
  textFont(font);
}

void draw(){

  //background(bg);     // My one used a background image, I've
  background(250);      // commented it out and put a plain colour

  //  Headings();           // Displays bar width, Bar gap or any variable.
  Axis();
  Labels();
  PrintBars();
 // Line();
//  Dots();
}




// Send Recieve data //
void serialEvent(Serial myPort) {

  // read a byte from the serial port:
  int inByte = myPort.read();

  if (firstContact == false) {
    if (inByte == 'A') {
      myPort.clear();          // clear the serial port buffer
      firstContact = true;     // you've had first contact from the microcontroller
      myPort.write('A');       // ask for more
    }
  }
  else {
    // Add the latest byte from the serial port to array:
    serialInArray[serialCount] = inByte;
    serialCount++;

    // If we have 6 bytes:
    if (serialCount > NumOfBars -1 ) {

for (int x=0;x<NumOfBars;x++){
   
  bars[x] = int (yRatio*(ScreenHeight)*(serialInArray[x]/256.0));

}


      // Send a capital A to request new sensor readings:
      myPort.write('A');
      // Reset serialCount:
      serialCount = 0;
    }
  }
}

/////// Display any variables for testing here//////////////
void Headings(){
  fill(0 );
  text("BarWidth",50,TextGap );  
  text("BarGap",250,TextGap ); 
  text("DivisounsWidth",450,TextGap );
  text(BarWidth,100,TextGap );   
  text(BarGap,300,TextGap );   
  text(DivisounsWidth,520,TextGap );
}


void PrintBars(){

  int c=0;
  for (int i=0;i<NumOfBars;i++){
  
    fill((0xe4+c),(255-bars[i]+c),(0x1a+c));
    stroke(90);
    rect(i*DivisounsWidth+LeftMargin,   ScreenHeight-GraphYposition,   BarWidth,   -bars[i]);
    fill(0x2e,0x2a,0x2a);
//    text(float(bars[i])/(yRatio*(ScreenHeight))*Vcc,   i*DivisounsWidth+LeftMargin+BarWidth/2,   ScreenHeight-bars[i]-5-GraphYposition );
//    text("A",   i*DivisounsWidth+LeftMargin+BarWidth/2 -5,   ScreenHeight-GraphYposition+20 );
//    text(i,   i*DivisounsWidth+LeftMargin+BarWidth/2 +5,   ScreenHeight-GraphYposition+20 );
  }
}

void Axis(){

  strokeWeight(1);
  stroke(220);
  for(float x=0;x<=NumOfVertSubDivisions;x++){

    int bars=(ScreenHeight-GraphYposition)-int(yRatio*(ScreenHeight)*(x/NumOfVertSubDivisions));
    line(LeftMargin-15,bars,ScreenWidth-RightMArgin-DivisounsWidth+50,bars);
  }
  strokeWeight(1);
  stroke(180);
  for(float x=0;x<=NumOfVertDivisions;x++){

    int bars=(ScreenHeight-GraphYposition)-int(yRatio*(ScreenHeight)*(x/NumOfVertDivisions));
    line(LeftMargin-15,bars,ScreenWidth-RightMArgin-DivisounsWidth+50,bars);
  }
  strokeWeight(2);
  stroke(90);
  line(LeftMargin-15, ScreenHeight-GraphYposition+2, ScreenWidth-RightMArgin-DivisounsWidth+50, ScreenHeight-GraphYposition+2);
  line(LeftMargin-15,ScreenHeight-GraphYposition+2,LeftMargin-15,GraphYposition+80);
  strokeWeight(1);
}

void Labels(){
  textFont(font,18);
  fill(50);
  rotate(radians(-90));
  text(yLabel,-ScreenHeight/2,LeftMargin-45);
  textFont(font,10);
  for(float x=0;x<=NumOfVertDivisions;x++){

    int bars=(ScreenHeight-GraphYposition)-int(yRatio*(ScreenHeight)*(x/NumOfVertDivisions));
    text(round(x),-bars,LeftMargin-20);
  }

  textFont(font,18);
  rotate(radians(90)); 
  text(xLabel,LeftMargin+(ScreenWidth-LeftMargin-RightMArgin-50)/2,ScreenHeight-GraphYposition+40);
  textFont(font,24);
  fill(50);
  text(Heading,LeftMargin+(ScreenWidth-LeftMargin-RightMArgin-50)/2,70);
  textFont(font);

  fill(150);
  text(URL,ScreenWidth-RightMArgin-40,ScreenHeight-15);
  textFont(font);

}

Abaixo estão os dois vídeos do teste: