Go Down

Topic: 40 Hz Auditory Click Train w/ Arduino Due (Read 551 times) previous topic - next topic

ckelley

Hi folks,

I'm working on generating a 40 Hz click train, where 10ms of noise will be output at 40Hz for 3 seconds repeatedly.  I was originally using an Arduino Uno and a square pulse in place of noise, but I just got a Due for the DAC to do "noisy clicks". 

I've put my code below.  I'm not exactly sure what my issue is, but when I hook up DAC0 to an oscilloscope, all I'm getting is noise - not the noisy clicks at 40Hz, but low-power ambient noise.  Although the square pulse from the Uno was sufficient to drive some computer speakers, I am aware that this signal may require amplification to drive the speakers, but it should at the very least be visible w/ a scope.  Some possible issues may be the way I'm calling AnalogWrite() or map(), but I'm really at a loss on this one.  No issues when I compile or upload the script, and staring at the IDE and Arduino Reference Pages hasn't done me much good, so I've turn to you nice people.

Code: [Select]

#include "math.h"

const int artifactPin = 13;
int Z = 4;
int S = 1;
int A = 1;
const int L = 1000;
double sig[L];

void setup()
{
  analogWriteResolution(12);
  randomSeed(S);
  //generate stochastic signal 10ms long
  for (int i = 0; i < L; i++)
  {
    sig[i] = random(0,1);
  }

  //find sum of squared values in signal
  double sigsum = 0;
  for (int i = 0; i < L; i++)
  {
    sigsum = sigsum + pow(sig[i],2);
  }

  //modulate signal with amplitude using sum of squared values
  double amp = A / sqrt(2) * L / sigsum;
  for (int i = 0; i < L; i++)
  {
    sig[i] = sig[i] * amp;
  }

  //rescale signal to 0-4096
  for (int i = 0; i < L; i++)
  {
    sig[i] = map(sig[i],0,amp,0,4096);
  }
}

void loop()
{
  delay(15000);
  //120 clicks over three seconds
  for (int i = 0; i < 120; i++)
  {
    //1000 samples per click
    for (int j = 0; j < L; j++)
    {
      if (i == 0)
      {
        digitalWrite(artifactPin,HIGH);
        analogWrite(DAC0, sig[j]);
        delayMicroseconds(10);
      }
      else
      {
        analogWrite(DAC0, sig[j]);
        delayMicroseconds(10);
      }
    }
    digitalWrite(artifactPin,LOW);
    analogWrite(DAC0,0);
    //15 second interstimulus period
    delay(15);
  }
}



Any help or advice would be hugely appreciated.  Thanks!

MrAl

Hello there,


It looks like you may be trying to use the map() function with factions, which probably does not work as you intended it to work.  If that is the case and you really do need fractions, you can convert this function to use doubles or floats as needed:

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

Try making all the var's doubles instead of longs.

You can probably rig it so that your inputs are all longs to begin with hwoever, if that's really the problem.

I say this because your function uses sigsum in a denominator with what looks like a whole number which means it could result in a fraction, but you might verify if this is true or not.

Keep in mind that if it is a fraction then it will probably always convert to zero because the conversion from float to long will convert a fraction like 0.1 to 0 all the time so you'll loose a lot of information there.
If you convert the function to floats it will then be able to handle fractions, or alternately if you pre-multiply everything with a number like 4096 (depends on the range of your input data however) then that will convert it into a number that the DAC can use directly, so probably no need to use the map() function anyway unless you need to ensure that there is a lower limit that you dont want anything less than to go to the DAC.

If your fraction range is 0 to 1 then you can multiply by 4095, but if the range is 0 to 2, then you can multiply by 4095/2, etc.  So for example 0.1 will result in a number 409 or 410, which can then go to the DAC.


MrAl

Hi,

I took another look and i noticed right away that the upper bound in the map function is 4096 and that is probably too high so it should be max 4096.

But one of the main problems seems to be that the generator function random() only generates samples 0 or 1, and nothing in between.  That means all the sig entries are either 0 or 1, so it makes no sense to square them later for example.
If you want fractional values, then you can probably use something like random(0,4095)/4096.

I am not entirely sure what you want to generate though, because the timing itself will be somewhat random but that would be a simpler algorithm so i assume you want completely random codes for each sample.
If you state exactly what you want to generate we can go from there if you like.

ckelley

Thanks a bunch for your reply.

You're definitely right about map() and random(). I thought random would work like rand() in C, but just using random(0,4095) might be OK for my purposes.

So my end goal is to drive some computer speakers with a 40 Hz click train, played for three seconds repeatedly, with a 15 second delay between click trains.  Each click is going to be composed of noise.  Basically, I know someone who used LabView and a daq board to drive the speakers, but I figured I could do it with a Due and save a bunch of money.  (This is all for some electrophysiology studies in the rat auditory cortex in case you're interested).

I've attached an image of the signal I am roughly trying to replicate.

Anyway, I've been using this guy's LabView code as a guide, which is where all that modulation with "amp" came from.  But I think if I just generate random values between 0 and 4095, that might do the trick.

I am, however, super curious about your concerns regarding the timing of the signal.  Also, any other advice you might have about the project would be super helpful.  Either way, I'll be sure to have a go at playing around with random() and map().  Thanks again.


MrAl

#4
Dec 22, 2015, 11:37 am Last Edit: Dec 22, 2015, 11:44 am by MrAl
Hi again,


First, i guess you picked up on the fact that i mistyped the 'max' which should have been 4095 not 4096 as i believe 4096 is too high for the dac and probably will simulate the same thing as a zero, which is no output.

So yes, i think that rand(0,4095) will create samples that vary in value from 0 to 4095, but you can double check that.

Doing rand() several times in a row and storing the values in an array will create a sound that they usually call "white noise".  I am still not entirely sure that is what you want because you describe the noise as a 'click' and a 'click' seems like something you might hear a switch make when you switch the light on in a room or something.  A click would be a modulated noise signal, that starts out with an attack that is high and then damps out probably exponentially, with a function like e^(-t/T) where T is the time constant.  So the entire function for one click would look like:
A*e^(-t/T)*sig(t)

and in discrete form something like:
A*e^(-5*k/N)*sig[k]

where k is the sample number in each burst group and sig[k] are the random samples before modulation and A is an optional scaling factor, and N is the number of samples in the group.
Then again, a 'click' can mean a lot of different sounds so maybe you want to clarify again a little bit.  From the picture you posted though it looks like just burst of white noise, which would not be modulated with the extra damping function.  Alternately you may want to try the two methods and pick the one you consider to be best for the application, which BTW does sound interesting.

The timing i was talking about was random because the samples were just 1's and 0's, which means you would have maybe several 0's then a 1, then maybe two 0's then another two 1's, etc., so there was an apparent timing to the signal.  This is another way to generate the noise, but i think you are doing it better with the DAC.  Of course if you later want to pursue this idea to use a cheaper Arduino board, that is probably an option too.  This other method works because a string of 1's and 0's that are spaced by random intervals also sounds like noise :-)

One other thing i should mention after reading your last post.  It sounds like you intend to drive the speaker directly from the Due DAC output pin.  I am not sure i can agree with that because any speaker, no matter how small, contains significant inductance which would cause momentary bursts of energy back into the pin, which could be absorbed by the ESD diodes and that could be too much for them which could easily blow the pin and make it useless.
At the very least a resistor in series, to limit the max current back into the pin.  Unfortunately that also then limits the volume, which leads us to some sort of amplifier like an op amp.  An op amp buffer would isolate the speaker from the pin so that the speaker could not damage the pin.  Alternately, two small transistors or even one transistor set up as a simple buffer amplifier (maybe a voltage follower) and that would be cheap and easy (1k base resistor for example).  Usually a speaker is also capacitor coupled so that the DC content is removed from the signal so it does not draw excessive current from the amplifier.
These kinds of circuits could be found on the web under "voltage follower transistor amplifier" or something like that.

I'll post more also if i think of anything else that might help.



ckelley

Thanks again for such a thoughtful response.  Yesterday I was able to successfully replicate the signal shown in the attachment using the noise bursts without any modulation and run through a Grass pre-amp  to the speakers.

You've definitely touched on what I think is a huge issue with this setup: the definition of a "click." Actually, the guy who wrote the LabView program I'm replicating and I spent a while arguing about this recently.  The literature for auditory steady state recordings uses "click" pretty shamelessly but without much of a strict definition.  I was getting decent responses from the rats just by driving some speakers with a square wave generated with an Arduino Uno, but my colleague has gotten significantly stronger responses with this white noise paradigm.  I am, however, pretty interested in trying out this modulation with a damping function.  I might not get around to running the experiments for a while, with the holidays coming up, but I'll be sure to keep you posted on how it goes.

Thank you again, and happy holidays if that's you're bag.

Go Up