Ardiuno vs. Spark (Mic and Accel)

133 views
Skip to first unread message

Peter Chestna

unread,
Dec 21, 2014, 1:07:25 AM12/21/14
to cube...@googlegroups.com
I wrote a program for the Arduino based on the demo code for the Spark Core to test out the microphone and accelerometer. You can find it here: http://pastebin.com/7TvW0uke.

I'm sure I'm doing something wrong but cannot find my mistake. Here are the differences:

1. The microphone sensitivity is poor. I have to tap on the box to get it to register above the threshold I measured with a silent background. I have placed my phone on top of the cube playing music and it does not register.

2. The accelerometer values that I am reading from the analog pins are drastically different than those in the demo app. The logic worked identically once I corrected for the observed values.

Dave Morgan

unread,
Dec 21, 2014, 3:05:28 AM12/21/14
to Peter Chestna, cube...@googlegroups.com
I notice that GAIN_CONTROL and Z are sharing the same pin.  That doesn't seem right to me.  Perhaps this is a clue?  You might want to double-check all your pin assignments to make sure they are correct.


--
You received this message because you are subscribed to the Google Groups "cubetalk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cubetalk+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/cubetalk/99cf8686-e178-4f58-8175-9c75a7a8b92b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

alex hornstein

unread,
Dec 21, 2014, 9:18:15 AM12/21/14
to cube...@googlegroups.com

1.  The gain_control pin can actually be used to set the mic sensitivity.  We built a circuit very similar to the automatic gain control circuit on a video camera, because we figured the cube would be in very quiet places and very loud places, and we wanted it to be able to work in either.  Basically, as you output an analog voltage to the gate of a mosfet, it varies the resistance between the source and drain of the mosfet, and that acts like the feedback resistor in an amplifier.  We put the microcontroller in the feedback loop to make the circuit as tweakable as possible.
To change the gain, you can analogWrite to the GAIN_CONTROL pin, digital pin 3 on the arduino -- the higher the analogWrite value, the higher the gain.  In the demo code, I kept it off, because it worked decently at the default gain.
If you want to adjust the gain control, I recommend either scoping the microphone input pin on the micro, if you have a scope, or programming the arduino to print out the max and min values that it reads from the mic over serial, and then adjust the gain control values in code.  

One last thing -- the mic amplifier outputs a signal centered around 1.65v (3.3v/2).  If you're running your arduino at 5V and you want better sensitivity, an easy fix is to wire the 3.3V output of the arduino to the arduino's AREF, and you'll decrease your signal range but improve your sensitivity.  This is how the spark is reading its microphone.

2.  The accelerometers are different for the arduino and the spark because the arduino has a 10-bit ADC (values from 0-1023) and the spark core has a 12-bit ADC (values from 0-4095).  If you scale the values from the spark demo code down by 4, they ought to work, but it sounds like you've got it under control.

happy hacking,
--me

Mark Kriegsman

unread,
Dec 22, 2014, 1:19:24 PM12/22/14
to cube...@googlegroups.com
Pete and I did some hacking, and noted a few things, most notably that individual readings from the Mic aren't a good proxy for 'sound level'.  In order to get 'sound level', you need to take samples over a brief period (eg, a few ms), and consider them together.  In a fancy world, you could do FFT and get frequency buckets out.  In our "should be working on our day jobs" world, we went with "just measure the highest peak volume in any of the samples over the sampling period".  This gave better results overall.

Still need to puzzle out what the best options are for AGC vs explicit programmed gain control.  I suspect it has a lot to do with the surrounding audio environment and what sort of sounds you want to react to (e.g., music vs dog barks vs chatting).

Oh, and by the way -- the L3D cube is pretty to play with!  :D

-Mark

Peter Chestna

unread,
Dec 23, 2014, 1:29:19 AM12/23/14
to cube...@googlegroups.com
Huge thanks to Mark for his help today. I will post the code once I clean it up a bit.

Peter Chestna

unread,
Dec 27, 2014, 9:27:25 AM12/27/14
to cube...@googlegroups.com
I'm still not happy with the mic results. It's far better than it was but still has some issues. Anyone else using an Arduino and want to work on it with me?

Werner M.

unread,
Jul 16, 2015, 9:22:35 PM7/16/15
to cube...@googlegroups.com
Hi Peter,

The first visualization I got working on the Arduino plugged to my cube is FFT_Joy.
I had to figure out a couple things by myself, and here are some of my remarks:

- First, the gain control pin, which Alex already pointed out, is #3. In initMicrophone(), it is set to LOW by default. I have left it this way until now, after reading Alex's post about setting it with analogWrite(). I shall test it and see how the Arduino reacts to it.

- As Alex also pointed out, the Arduino's ADC has less resolution than the Photon's. Hence, the FFTJoy() function needed to be changed in the first for() loop:
  * Where real[i]=analogRead(MICROPHONE)-2048; //change this value to 1024 (this is the cap for converting the values coming from the ADC; the lower, the more your voltage levels will increase. I set mine at 550, which is slightly above half the top cap)
  * I believe this would be like "setting the AGC max gain" in software. Alex?

- I also added to this function a call to analogReference(INTERNAL); this sets the resolution of the ADC to 1.1V as opposed to the default which is 5V. Lowering this cap makes the Arduino slightly more sensitive to changes in voltage ("anti-aliases" the ADC output levels, if you will). This is more of a refinement in code, the ADC should react fine in the default setting.

#include <Adafruit_NeoPixel.h>
#include <math.h>

//set up the pin that controls the LEDs, the type of LEDs (WS2812B) and the number of LEDs in the cube (8*8*8=512)
#define PIXEL_PIN 2
// How many NeoPixels are attached to the Arduino?
#define PIXEL_COUNT 512
// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
// Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest
// example for more information on possible values.
#define PIXEL_TYPE NEO_GRB + NEO_KHZ800
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

#define SIDE 8

#define MICROPHONE 14
#define GAIN_CONTROL 3
//this sets the input voltage range (input sentitivity)
//adjust this value for higher (< 1024) or lower (= 1024)
#define INPUT_LEVEL 550
//this sets our 'sample rate'.  I went through a bunch of trial and error to
//find a good sample rate to put a soprano's vocal range in the spectrum of the cube
#define SAMPLE_RATE 200  //98 hits just the right spot to put a 1KHz tone in the center spectrum of the cube
                         
//however, the original author was right in choosing a higher value, as trial and
                         
//error proved that most vocal and instrument frequencies are put in the center with 200

/*  datatype definitions
*/


typedef struct{
 
unsigned char red, green, blue;
} color;

typedef struct{
 
float x;
 
float y;
 
float z;
} point;

/******************************
 * function definitions
 * ***************************/

void initCube();
void initMicrophone();
void FFTJoy();
void setPixel(int x, int y, int z, color col);
color getPixel
(int x, int y, int z);
color colorMap
(float val, float min, float max);
color lerpColor
(color a, color b, int val, int min, int max);
short FFT(short int dir,int m,float *x,float *y);

/*********************************
 * FFTJoy variables *
 * *******************************/

//M adjusts the span (or spread) of the fft's frequency spectrum.
//Higher values widen the spectrum, lower values narrow. Through
//trial and error the optimal value for M was determined to be 4
//(making 2^4=16 samples), which proves to be the best setup for
//audio analysis in the cube's narrow span of led strips.
#define M 4
#define ARRAY_SIZE 16 //(int)pow(2,M)
float real[ARRAY_SIZE];
float imaginary[ARRAY_SIZE];
float maxVal=0;
float sample;

//maxBrightness is the brightness limit for each pixel.  All color data will be scaled down
//so that the largest value is maxBrightness
int maxBrightness=50;
color black
;

void setup() {
  strip
.begin(); // this initializes the NeoPixel library.
  initCube
();
  initMicrophone
();
}

void loop() {
 
FFTJoy();
 
//this sends the updated pixel color to the hardware.
  strip
.show();
}

/********************************************
 *   Initialization functions
 * *****************************************/

void initCube()
{
    black
.red=0;
    black
.green=0;
    black
.blue=0;
}

void initMicrophone()
{
  pinMode
(GAIN_CONTROL, OUTPUT);
  digitalWrite
(GAIN_CONTROL, LOW);
  analogReference
(INTERNAL); //sets the resolution (volts / unit)
                             
//e.g., DEFAULT=5V (0.0049V / unit), INTERNAL=1.1V (0.0011V / unit)
}

/********************************************
 *   FFT JOY functions
 * *****************************************/

 
void FFTJoy(){
   
for(int i=0;i<pow(2,M);i++)
   
{
        real
[i]=analogRead(MICROPHONE)-INPUT_LEVEL;
        delayMicroseconds
(SAMPLE_RATE);

     
//  Serial.print(real[i]);
        imaginary
[i]=0;
   
}
    FFT
(1, M, real, imaginary);
   
for(int i=0;i<pow(2,M);i++)
   
{
        imaginary
[i]=sqrt(pow(imaginary[i],2)+pow(real[i],2));
//        Serial.print(imaginary[i]);
       
if(imaginary[i]>maxVal)
            maxVal
=imaginary[i];
   
}
   
if(maxVal>100)
        maxVal
-=0.5;
//    Serial.println();
   
for(int i=0;i<pow(2,M)/2;i++)
   
{
        imaginary
[i]=SIDE*imaginary[i]/maxVal;
       
int y;
       
for(y=0;y<=imaginary[i];y++)
            setPixel
(i,y,SIDE-1,colorMap(y,0,SIDE));
       
for(;y<SIDE;y++)
            setPixel
(i,y,SIDE-1,black);
   
}
   
for(int z=0;z<SIDE-1;z++)
       
for(int x=0;x<SIDE;x++)
           
for(int y=0;y<SIDE;y++)
           
{
                color col
=getPixel(x,y,z+1);
                setPixel
(x,y,z,col);
//                char output[50];
//                sprintf(output, "%d %d %d:  %d %d %d", x,y,z+1, col.red, col.green, col.blue);
//                Serial.println(output);
           
}

    sample
++;
   
if(sample>=pow(2,M))
        sample
-=pow(2,M);
}

short FFT(short int dir,int m,float *x,float *y)
{
   
int n,i,i1,j,k,i2,l,l1,l2;
   
float c1,c2,tx,ty,t1,t2,u1,u2,z;

   
/* Calculate the number of points */
   n
= 1;
   
for (i=0;i<m;i++)
      n
*= 2;

   
/* Do the bit reversal */
   i2
= n >> 1;
   j
= 0;
   
for (i=0;i<n-1;i++) {
     
if (i < j) {
         tx
= x[i];
         ty
= y[i];
         x
[i] = x[j];
         y
[i] = y[j];
         x
[j] = tx;
         y
[j] = ty;
     
}
      k
= i2;
     
while (k <= j) {
         j
-= k;
         k
>>= 1;
     
}
      j
+= k;
   
}

   
/* Compute the FFT */
   c1
= -1.0;
   c2
= 0.0;
   l2
= 1;
   
for (l=0;l<m;l++) {
      l1
= l2;
      l2
<<= 1;
      u1
= 1.0;
      u2
= 0.0;
     
for (j=0;j<l1;j++) {
         
for (i=j;i<n;i+=l2) {
            i1
= i + l1;
            t1
= u1 * x[i1] - u2 * y[i1];
            t2
= u1 * y[i1] + u2 * x[i1];
            x
[i1] = x[i] - t1;
            y
[i1] = y[i] - t2;
            x
[i] += t1;
            y
[i] += t2;
         
}
         z
=  u1 * c1 - u2 * c2;
         u2
= u1 * c2 + u2 * c1;
         u1
= z;
     
}
      c2
= sqrt((1.0 - c1) / 2.0);
     
if (dir == 1)
         c2
= -c2;
      c1
= sqrt((1.0 + c1) / 2.0);
   
}

   
/* Scaling for forward transform */
   
if (dir == 1) {
     
for (i=0;i<n;i++) {
         x
[i] /= n;
         y
[i] /= n;
     
}
   
}

   
return(0);
}

/********************************************
 *   Pixel control functions
 * *****************************************/

//sets a pixel at position (x,y,z) to the col parameter's color
void setPixel(int x, int y, int z, color col)
{
   
int index = (z*SIDE*SIDE) + (x*SIDE) + y;
        strip
.setPixelColor(index,strip.Color(col.red,  col.green, col.blue));
   
}

//returns the color value currently displayed at the x,y,z location
color getPixel
(int x, int y, int z)
{
   
int index = (z*SIDE*SIDE) + (x*SIDE) + y;
    uint32_t col
=strip.getPixelColor(index);
    color pixelColor
;
    pixelColor
.red=(col>>16)&255;
    pixelColor
.green=(col>>8)&255;
    pixelColor
.blue=col&255;
   
return pixelColor;
}

//returns a color from a set of colors fading from blue to green to red and back again
//the color is returned based on where the parameter *val* falls between the parameters
//*min* and *max*.  If *val* is min, the function returns a blue color.  If *val* is halfway
//between *min* and *max*, the function returns a yellow color.  
color colorMap
(float val, float min, float max)
{
 
float range=1024;
  val
=range*(val-min)/(max-min);
  color colors
[6];
  colors
[0].red=0;
  colors
[0].green=0;
  colors
[0].blue=maxBrightness;
 
  colors
[1].red=0;
  colors
[1].green=maxBrightness;
  colors
[1].blue=maxBrightness;
 
  colors
[2].red=0;
  colors
[2].green=maxBrightness;
  colors
[2].blue=0;
 
  colors
[3].red=maxBrightness;
  colors
[3].green=maxBrightness;
  colors
[3].blue=0;
 
  colors
[4].red=maxBrightness;
  colors
[4].green=0;
  colors
[4].blue=0;

  colors
[5].red=maxBrightness;
  colors
[5].green=0;
  colors
[5].blue=maxBrightness;
 
 
if (val<=range/6)
   
return(lerpColor(colors[0], colors[1], val, 0, range/6));
 
else if (val<=2*range/6)
   
return(lerpColor(colors[1], colors[2], val, range/6, 2*range/6));
 
else if (val<=3*range/6)
   
return(lerpColor(colors[2], colors[3], val, 2*range/6, 3*range/6));
 
else if (val<=4*range/6)
   
return(lerpColor(colors[3], colors[4], val, 3*range/6, 4*range/6));
 
else if (val<=5*range/6)
   
return(lerpColor(colors[4], colors[5], val, 4*range/6, 5*range/6));
 
else
   
return(lerpColor(colors[5], colors[0], val, 5*range/6, range));
}

//returns a color that's an interpolation between colors a and b.  The color
//is controlled by the position of val relative to min and max -- if val is equal to min,
//the resulting color is identical to color a.  If it's equal to max, the resulting color
//is identical to color b.  If val is (max-min)/2, the resulting color is the average of
//color a and color b
color lerpColor
(color a, color b, int val, int min, int max)
{
    color lerped
;
    lerped
.red=a.red+(b.red-a.red)*(val-min)/(max-min);
    lerped
.green=a.green+(b.green-a.green)*(val-min)/(max-min);
    lerped
.blue=a.blue+(b.blue-a.blue)*(val-min)/(max-min);
   
return lerped;
}



Werner M.

unread,
Jul 16, 2015, 9:24:37 PM7/16/15
to cube...@googlegroups.com
In my last post I had lots of trouble with posting code in the <code> snippet. The editor wouldn't stop freezing my browser intermittently. Hell..

May I suggest we change this forum to a more stable platform than Google Groups?
Reply all
Reply to author
Forward
0 new messages