Flora and ffft library. Is it possible?
Moderators: adafruit_support_bill, adafruit

Flora and ffft library. Is it possible?

by essenciya on Tue Jul 30, 2013 12:47 am

Hi there,
I'm trying to build a wearable project based on Flora, very like http://learn.adafruit.com/piccolo/overview, in fact I want to use the same ffft lib, but instead of driving a matrix, I want to drive led those http://www.adafruit.com/products/1376 leds.
The problem I have is, the code for poccolo project (https://github.com/adafruit/piccolo) is using A0 pin for getting ADC output (I'm sorry if my terminology is incorrect). I wasn't able to find A0 input pin on Flora, but the microcontroller MEGA32U4 suppose to have one. Basically I stuck on where I should connect microphone on Flora to make ffft lib to work? And is it even possible with Flora? I assume breaking raw mic input into frequencies programmatically is a pretty heavy operation, maybe it's too much to ask from flora?
Thanks
P.S. Guys I really appreciate the fact you have so many tutorials and crazy projects we can bounce of
essenciya
 
Posts: 7
Joined: Tue Jul 30, 2013 12:29 am

Re: Flora and ffft library. Is it possible?

by pburgess on Tue Jul 30, 2013 1:51 am

Hey there,

It's a little tricky, but I think it's doable. There are three analog-capable pins on the Flora: D6, D9 and D10 (corresponding to ADC channels 7, 9 and 10), according to pighixxx's diagram of awesomeness.

So, first choose which of those pins best suits your wiring, then we need to make some tweaks to the Piccolo code (or whatever code you derive from it). Here's the ADC setup code:

Code: Select all | TOGGLE FULL SIZE
  ADMUX  = ADC_CHANNEL; // Channel sel, right-adj, use AREF pin
  ADCSRA = _BV(ADEN)  | // ADC enable
           _BV(ADSC)  | // ADC start
           _BV(ADATE) | // Auto trigger
           _BV(ADIE)  | // Interrupt enable
           _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz
  ADCSRB = 0;                // Free run mode, no high MUX bit
  DIDR0  = 1 << ADC_CHANNEL; // Turn off digital input for ADC pin
  TIMSK0 = 0;                // Timer0 off


Delete the ADMUX and DIDR0 lines, then append this extra block of code:
Code: Select all | TOGGLE FULL SIZE
#if (ADC_CHANNEL > 7)
  ADMUX  = B00100000 | (ADC_CHANNEL - 8);
  DIDR2  = 1 << (ADC_CHANNEL - 8);
#else
  ADMUX  = ADC_CHANNEL;
  DIDR0  = 1 << ADC_CHANNEL;
#endif

Remember, ADC_CHANNEL must be 7, 9 or 10 on the Flora, and the channel number does not always directly correspond to the pin number.

I've not actually tested this code, just scrounged around in the datasheet. I think it'll work, but let me know if you run into any trouble.
User avatar
pburgess
 
Posts: 2663
Joined: Sun Oct 26, 2008 2:29 am

Re: Flora and ffft library. Is it possible?

by essenciya on Tue Jul 30, 2013 7:16 am

Hey pburgess,
Thanks for quick response, I'm looking on the http://forum.arduino.cc/index.php?topic=179657.0 and it confuses me a bit. What you saying is mic should sit on pin D6,D9 or D10 (corresponding to A7, A9 and A10), but ADC on the diagram marked as ADC10, ADC12 and ADC13 respectively. So let's say for my wiring I decided to put mic on D10/A10/ADC13. What would be a value of the ADC_CHANNEL? 10 or 13?
Let me tell what I tried and how it "didn't work" :)
Given pure piccolo code, I removed all matrix references and following libs Adafruit_GFX.h, Adafruit_LEDBackpack.h My setup looks like:
Code: Select all | TOGGLE FULL SIZE
#define ADC_CHANNEL 10 //Microphone is connected to D10 pin of the Flora
void setup() {
  uint8_t i, j, nBins, binNum, *data;

  memset(peak, 0, sizeof(peak));
  memset(col , 0, sizeof(col));

  for(i=0; i<8; i++) {
    minLvlAvg[i] = 0;
    maxLvlAvg[i] = 512;
    data         = (uint8_t *)pgm_read_word(&colData[i]);
    nBins        = pgm_read_byte(&data[0]) + 2;
    binNum       = pgm_read_byte(&data[1]);
    for(colDiv[i]=0, j=2; j<nBins; j++)
      colDiv[i] += pgm_read_byte(&data[j]);
  }

  #if (ADC_CHANNEL > 7)
    ADMUX  = B00100000 | (ADC_CHANNEL - 8);
    DIDR2  = 1 << (ADC_CHANNEL - 8);
  #else
    ADMUX  = ADC_CHANNEL;
    DIDR0  = 1 << ADC_CHANNEL;
  #endif
 
  ADCSRA = _BV(ADEN)  | // ADC enable
           _BV(ADSC)  | // ADC start
           _BV(ADATE) | // Auto trigger
           _BV(ADIE)  | // Interrupt enable
           _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz
  ADCSRB = 0;                // Free run mode, no high MUX bit
  TIMSK0 = 0;                // Timer0 off

  sei(); // Enable interrupts

  Serial.begin(9600);//For Debugging
}

In a loop I'm printing to serial out directly after calculation for the current column is done:
Code: Select all | TOGGLE FULL SIZE
...
 // Clip output and convert to byte:
 if(level < 0L)      c = 0;
    else if(level > 10) c = 10; // Allow dot to go a couple pixels off top
    else                c = (uint8_t)level;

    Serial.print("column:");
    Serial.print(x);
    Serial.print(" level ");
    Serial.println(c);
...


What I see in serial out:
Code: Select all | TOGGLE FULL SIZE
column:0 level 4
column:1 level 4
column:2 level 0
column:3 level 0
column:4 level 0
column:5 level 0
column:6 level 0
column:7 level 0
column:0 level 4
column:1 level 4
column:2 level 0
column:3 level 0
column:4 level 0
column:5 level 0
column:6 level 0
column:7 level 0

And so on, without any changes in numbers despite noise or quietness.
I feel, like it should be some small thing, but i keep overlooking it. Can you spot what can be wrong? Nothing else was changed in original piccolo code or ffft lib. Nothing else is connected to Flora (just a mic).
Highly appreciate any lead on this,
Thanks,
Max
essenciya
 
Posts: 7
Joined: Tue Jul 30, 2013 12:29 am

Re: Flora and ffft library. Is it possible?

by pburgess on Tue Jul 30, 2013 2:45 pm

Blargh, you're right...it was late when I wrote that...A7, A9 and A10 are the analog pin numbers, but 10, 12 and 13 are the actual channel numbers. So yes, use the latter numbers. Pin D6 = ADC channel 10, D9 = channel 12, D10 = 13.
User avatar
pburgess
 
Posts: 2663
Joined: Sun Oct 26, 2008 2:29 am

Re: Flora and ffft library. Is it possible?

by essenciya on Wed Jul 31, 2013 4:05 am

Unfortunately setting channel to 13 didn't change much. Similar output as in my previous post, but numbers for first and second column changed to 4 and 3. What is weird why other always zeros? May it be that from 128 frequencies ffft lib outputs piccolo project chosen least interesting?
essenciya
 
Posts: 7
Joined: Tue Jul 30, 2013 12:29 am

Re: Flora and ffft library. Is it possible?

by pburgess on Thu Aug 01, 2013 1:26 pm

I think the smattering of 4s & 3s are just an artifact of how the ffft library works...very limited resolution fixed-point math stuff. What's baffling right now is why isn't it reading the mic? Could you post your complete sketch?
User avatar
pburgess
 
Posts: 2663
Joined: Sun Oct 26, 2008 2:29 am

Re: Flora and ffft library. Is it possible?

by essenciya on Fri Aug 02, 2013 8:27 am

I run out of ideas, I hope you can see what's wrong with the sketch:
Code: Select all | TOGGLE FULL SIZE
#include <avr/pgmspace.h>
#include <ffft.h>
#include <math.h>
#include <Wire.h>
#include <Adafruit_NeoPixel.h>

int16_t       capture[FFT_N];    // Audio capture buffer
complex_t     bfly_buff[FFT_N];  // FFT "butterfly" buffer
uint16_t      spectrum[FFT_N/2]; // Spectrum output buffer
volatile byte samplePos = 0;     // Buffer position counter

byte
  peak[8],      // Peak level of each column; used for falling dots
  dotCount = 0, // Frame counter for delaying dot-falling speed
  colCount = 0; // Frame counter for storing past column data
int
  col[8][10],   // Column levels for the prior 10 frames
  minLvlAvg[8], // For dynamic adjustment of low & high ends of graph,
  maxLvlAvg[8], // pseudo rolling averages for the prior few frames.
  colDiv[8];    // Used when filtering FFT output to 8 columns

PROGMEM uint8_t
  // This is low-level noise that's subtracted from each FFT output column:
  noise[64]={ 8,6,6,5,3,4,4,4,3,4,4,3,2,3,3,4,
              2,1,2,1,3,2,3,2,1,2,3,1,2,3,4,4,
              3,2,2,2,2,2,2,1,3,2,2,2,2,2,2,2,
              2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,4 },
  // These are scaling quotients for each FFT output column, sort of a
  // graphic EQ in reverse.  Most music is pretty heavy at the bass end.
  eq[64]={
    255, 175,218,225,220,198,147, 99, 68, 47, 33, 22, 14,  8,  4,  2,
      0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },

  col0data[] = {  2,  1,  // # of spectrum bins to merge, index of first
    111,   8 },           // Weights for each bin
  col1data[] = {  4,  1,  // 4 bins, starting at index 1
     19, 185,  38,   2 }, // Weights for 4 bins.  Got it now?
  col2data[] = {  5,  2,
     11, 156, 118,  16,   1 },
  col3data[] = {  8,  3,
      5,  55, 165, 164,  71,  18,   4,   1 },
  col4data[] = { 11,  5,
      3,  24,  89, 169, 178, 118,  54,  20,   6,   2,   1 },
  col5data[] = { 17,  7,
      2,   9,  29,  70, 125, 172, 185, 162, 118, 74,
     41,  21,  10,   5,   2,   1,   1 },
  col6data[] = { 25, 11,
      1,   4,  11,  25,  49,  83, 121, 156, 180, 185,
    174, 149, 118,  87,  60,  40,  25,  16,  10,   6,
      4,   2,   1,   1,   1 },
  col7data[] = { 37, 16,
      1,   2,   5,  10,  18,  30,  46,  67,  92, 118,
    143, 164, 179, 185, 184, 174, 158, 139, 118,  97,
     77,  60,  45,  34,  25,  18,  13,   9,   7,   5,
      3,   2,   2,   1,   1,   1,   1 },
  // And then this points to the start of the data for each of the columns:
  *colData[] = {
    col0data, col1data, col2data, col3data,
    col4data, col5data, col6data, col7data };


#define N_PIXELS  15  // Number of pixels in strand
#define LED_PIN_INNER    12  // NeoPixel LED strand is connected to this pin
#define LED_PIN_MIDLE    6  // NeoPixel LED strand is connected to this pin
#define LED_PIN_OUTER    9  // NeoPixel LED strand is connected to this pin
#define ADC_CHANNEL 13 //Microphone is connected to this pin

//Adafruit_NeoPixel  strip_inner = Adafruit_NeoPixel(N_PIXELS, LED_PIN_INNER, NEO_GRB + NEO_KHZ800);
//Adafruit_NeoPixel  strip_midle = Adafruit_NeoPixel(N_PIXELS, LED_PIN_MIDLE, NEO_GRB + NEO_KHZ800);
//Adafruit_NeoPixel  strip_outer = Adafruit_NeoPixel(N_PIXELS, LED_PIN_OUTER, NEO_GRB + NEO_KHZ800);

void setup() {
  uint8_t i, j, nBins, binNum, *data;

  memset(peak, 0, sizeof(peak));
  memset(col , 0, sizeof(col));

  for(i=0; i<8; i++) {
    minLvlAvg[i] = 0;
    maxLvlAvg[i] = 512;
    data         = (uint8_t *)pgm_read_word(&colData[i]);
    nBins        = pgm_read_byte(&data[0]) + 2;
    binNum       = pgm_read_byte(&data[1]);
    for(colDiv[i]=0, j=2; j<nBins; j++)
      colDiv[i] += pgm_read_byte(&data[j]);
  }

//  strip_inner.begin();
//  strip_midle.begin();
//  strip_outer.begin();

  // Init ADC free-run mode; f = ( 16MHz/prescaler ) / 13 cycles/conversion

  #if (ADC_CHANNEL > 7)
    ADMUX  = B00100000 | (ADC_CHANNEL - 8);
    DIDR2  = 1 << (ADC_CHANNEL - 8);
  #else
    ADMUX  = ADC_CHANNEL;
    DIDR0  = 1 << ADC_CHANNEL;
  #endif
 
  ADCSRA = _BV(ADEN)  | // ADC enable
           _BV(ADSC)  | // ADC start
           _BV(ADATE) | // Auto trigger
           _BV(ADIE)  | // Interrupt enable
           _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz
  ADCSRB = 0;                // Free run mode, no high MUX bit
  TIMSK0 = 0;                // Timer0 off

  sei(); // Enable interrupts

  Serial.begin(9600);//For Debugging
}

void loop() {
  uint8_t  i, x, L, *data, nBins, binNum, weighting, c;
  uint16_t minLvl, maxLvl;
  int      level, y, sum;

  while(ADCSRA & _BV(ADIE)); // Wait for audio sampling to finish

  fft_input(capture, bfly_buff);   // Samples -> complex #s
  samplePos = 0;                   // Reset sample counter
  ADCSRA |= _BV(ADIE);             // Resume sampling interrupt
  fft_execute(bfly_buff);          // Process complex data
  fft_output(bfly_buff, spectrum); // Complex -> spectrum

  // Remove noise and apply EQ levels
  for(x=0; x<FFT_N/2; x++) {
    L = pgm_read_byte(&noise[x]);
    spectrum[x] = (spectrum[x] <= L) ? 0 :
      (((spectrum[x] - L) * (256L - pgm_read_byte(&eq[x]))) >> 8);
  }

  // Downsample spectrum output to 8 columns:
  for(x=0; x<8; x++) {
    data   = (uint8_t *)pgm_read_word(&colData[x]);
    nBins  = pgm_read_byte(&data[0]) + 2;
    binNum = pgm_read_byte(&data[1]);
    for(sum=0, i=2; i<nBins; i++)
      sum += spectrum[binNum++] * pgm_read_byte(&data[i]); // Weighted
    col[x][colCount] = sum / colDiv[x];                    // Average
    minLvl = maxLvl = col[x][0];
    for(i=1; i<10; i++) { // Get range of prior 10 frames
      if(col[x][i] < minLvl)      minLvl = col[x][i];
      else if(col[x][i] > maxLvl) maxLvl = col[x][i];
    }

    if((maxLvl - minLvl) < 8) maxLvl = minLvl + 8;
    minLvlAvg[x] = (minLvlAvg[x] * 7 + minLvl) >> 3; // Dampen min/max levels
    maxLvlAvg[x] = (maxLvlAvg[x] * 7 + maxLvl) >> 3; // (fake rolling average)

    // Second fixed-point scale based on dynamic min/max levels:
    level = 10L * (col[x][colCount] - minLvlAvg[x]) /
      (long)(maxLvlAvg[x] - minLvlAvg[x]);

    // Clip output and convert to byte:
    if(level < 0L)      c = 0;
    else if(level > 10) c = 10; // Allow dot to go a couple pixels off top
    else                c = (uint8_t)level;

    Serial.print("column:");
    Serial.print(x);
    Serial.print(" level ");
    Serial.println(c);
   
    if(c > peak[x]) peak[x] = c; // Keep dot on top

    if(peak[x] <= 0) { // Empty column?
      //Clean the strip here
      continue;
    } else if(c < 8) { // Partial column?
      //Clean part of the strip here
    }

    y = N_PIXELS - peak[x];

    //Draw strips here!!!

  }

//   strip_inner.show();
//   strip_midle.show();
//   strip_outer.show(); 
   
  // Every third frame, make the peak pixels drop by 1:
  if(++dotCount >= 3) {
    dotCount = 0;
    for(x=0; x<8; x++) {
      if(peak[x] > 0) peak[x]--;
    }
  }

  if(++colCount >= 10) colCount = 0;
}

ISR(ADC_vect) { // Audio-sampling interrupt
  static const int16_t noiseThreshold = 4;
  int16_t              sample         = ADC; // 0-1023

  capture[samplePos] =
    ((sample > (512-noiseThreshold)) &&
     (sample < (512+noiseThreshold))) ? 0 :
    sample - 512; // Sign-convert for FFT; -512 to +511

  if(++samplePos >= FFT_N) ADCSRA &= ~_BV(ADIE); // Buffer full, interrupt off
}


Wiring is as follows: mic (http://www.adafruit.com/products/1063) connected to 3.3V, ground and D10, nothing else is connected at the moment. The reading is all zeros right now, and column 0 sometimes shows 10 (only 10 or 0, nothing in between, so I don't think mic reading affecting it, but rather ffft calcs)
Any advice is highly appreciated,
Thanks
Max Ch
essenciya
 
Posts: 7
Joined: Tue Jul 30, 2013 12:29 am

Re: Flora and ffft library. Is it possible?

by pburgess on Fri Aug 02, 2013 4:17 pm

Ah! I think I see what it is. There's no AREF pin on the Flora. One more small code change tells it to use VCC as a voltage reference:

Code: Select all | TOGGLE FULL SIZE
#if (ADC_CHANNEL > 7)
  ADMUX  = _BV(REFS0) | _BV(MUX5) | (ADC_CHANNEL - 8);
  DIDR2  = 1 << (ADC_CHANNEL - 8);
#else
  ADMUX  = _BV(REFS0) | ADC_CHANNEL;
  DIDR0  = 1 << ADC_CHANNEL;
#endif
}


Computers will make our lives simpler! :D
User avatar
pburgess
 
Posts: 2663
Joined: Sun Oct 26, 2008 2:29 am

Re: Flora and ffft library. Is it possible?

by essenciya on Mon Aug 05, 2013 5:25 am

Pburgess, huge personal thanks for your help. But it still didn't work. Seems like it just don't want to read from mic. On the weekend I put all wiring on, and run different code (based on http://learn.adafruit.com/led-ampli-tie/the-code) which also takes input from mic (analog read from pin though) and outputs to my leds. All worked as expected, so mic, power supply, led strips itself and soldering all should be good. Now when I run my code with ffft, it just stay dead on the same hardware. This leads me to conclusion it must be software problem. And it has to do with reading input bytes from ADC right? How can I debug this? I'm good in dynamic languages (basically I'm Java/Android developer) but having problems when it comes to interruptions and direct memory access type of stuff, if you can lead me to the right directions I can dig )
essenciya
 
Posts: 7
Joined: Tue Jul 30, 2013 12:29 am

Re: Flora and ffft library. Is it possible?

by pburgess on Mon Aug 05, 2013 3:15 pm

Okay, one more pass through the datasheet, this time with the benefit of a good night's rest:
Code: Select all | TOGGLE FULL SIZE
#if (ADC_CHANNEL > 7)
  ADMUX  = _BV(REFS0) | (ADC_CHANNEL - 8);
  ADCSRB = _BV(MUX5);        // Free run mode, high MUX bit
  DIDR2  = 1 << (ADC_CHANNEL - 8);
#else
  ADMUX  = _BV(REFS0) | ADC_CHANNEL;
  ADCSRB = 0;                // Free run mode, no high MUX bit
  DIDR0  = 1 << ADC_CHANNEL;
#endif

Wired up and tested with a Flora. This should work for you, though you may need to edit the 'eq' table to bring out certain frequencies for whatever you're listening to.
User avatar
pburgess
 
Posts: 2663
Joined: Sun Oct 26, 2008 2:29 am

Re: Flora and ffft library. Is it possible?

by essenciya on Mon Aug 05, 2013 7:40 pm

can you show your test sketch?
essenciya
 
Posts: 7
Joined: Tue Jul 30, 2013 12:29 am

Re: Flora and ffft library. Is it possible?

by pburgess on Mon Aug 05, 2013 8:57 pm

It's the exact code you posted, with my latest ADMUX/ADCSRB/DIDR2 changes, with the mic connected to pin 10 (ADC channel 13).
User avatar
pburgess
 
Posts: 2663
Joined: Sun Oct 26, 2008 2:29 am

Re: Flora and ffft library. Is it possible?

by essenciya on Mon Aug 12, 2013 5:36 am

Unfortunately I still couldn't get it to work. Buffer return always the same. But thanks for all help, maybe I'll return later to this and will find how to make it work, I'm done fighting it for now though.
essenciya
 
Posts: 7
Joined: Tue Jul 30, 2013 12:29 am

Re: Flora and ffft library. Is it possible?

by pburgess on Mon Aug 12, 2013 11:46 pm

You might need to edit the 'eq' array to all zeros to start. Also, before you do that, whistle a range of different tones at it...it might just be that it's not responding to whatever pitch you were testing with.
User avatar
pburgess
 
Posts: 2663
Joined: Sun Oct 26, 2008 2:29 am