SRF-05 Proximity Sensor Controlling On Board Audio Oscillator - Amplitude?

Hello,

Up until now I have been using a serial connection to use Processing to control the level of an oscillator based on distance readings coming from Arduino. It all works fine... But, I need to be able to do this without needing a seperate computer to run Processing.

I have been getting some project guidance on how to lose my PC and use arduino alone to make a sine wave and subsequently to control the amplitude based on this distance. See the thread here:Using serial to Processing for sound, want to go stand alone.. - Project Guidance - Arduino Forum

So Grumpy Mike has very helpfully suggested that this link offers a good solution for the on board oscillator:http://interface.khm.de/index.php/lab/experiments/arduino-dds-sinewave-generator/

I am now in the process of adding the code to run the SRF-05 sesnsor. See code here:

/*
 *
 * DDS Sine Generator mit ATMEGS 168
 * Timer2 generates the  31250 KHz Clock Interrupt
 *
 * KHM 2009 /  Martin Nawrath
 * Kunsthochschule fuer Medien Koeln
 * Academy of Media Arts Cologne

 */

#include "avr/pgmspace.h"

// table of 256 sine values / one sine period / stored in flash memory
PROGMEM  prog_uchar sine256[]  = {
  127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,184,187,190,192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,239,240,
  242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,233,231,229,227,225,223,
  221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,115,111,108,105,102,99,96,93,90,87,84,81,78,
  76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,16,18,20,21,23,25,27,29,31,
  33,35,37,39,42,44,46,49,51,54,56,59,62,64,67,70,73,76,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124

};
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
int duration;                          // *** TWOGANS CHANGE - ADDED THE SENSOR STUFF HERE - Stores duratiuon of pulse in
int distance;                          // Stores distance
int srfPin = 3;                        // Pin for SRF05

int ledPin = 13;                 // LED pin 7
int testPin = 7;
int t2Pin = 6;
byte bb;

double dfreq;
// const double refclk=31372.549;  // =16MHz / 510
const double refclk=31376.6;      // measured

// variables used inside interrupt service declared as voilatile
volatile byte icnt;              // var inside interrupt
volatile byte icnt1;             // var inside interrupt
volatile byte c4ms;              // counter incremented all 4ms
volatile unsigned long phaccu;   // pahse accumulator
volatile unsigned long tword_m;  // dds tuning word m

void setup()
{
  pinMode(ledPin, OUTPUT);      // sets the digital pin as output
  Serial.begin(115200);        // connect to the serial port
  Serial.println("DDS Test");

  pinMode(6, OUTPUT);      // sets the digital pin as output
  pinMode(7, OUTPUT);      // sets the digital pin as output
  pinMode(11, OUTPUT);     // pin11= PWM  output / frequency output

  Setup_timer2();

  // disable interrupts to avoid timing distortion
  cbi (TIMSK0,TOIE0);              // disable Timer0 !!! delay() is now not available
  sbi (TIMSK2,TOIE2);              // enable Timer2 Interrupt

  dfreq=1000.0;                    // initial output frequency = 1000.o Hz
  tword_m=pow(2,32)*dfreq/refclk;  // calulate DDS new tuning word 

}
void loop()
{
  
  while(1) {
     if (c4ms > 250) {                 // timer / wait fou a full second
      c4ms=0;
      dfreq=400;             // *** TWOGAN'S CHANGE - set FIXED frequency of sine wave
      cbi (TIMSK2,TOIE2);              // disble Timer2 Interrupt
      tword_m=pow(2,32)*dfreq/refclk;  // calulate DDS new tuning word
      sbi (TIMSK2,TOIE2);              // enable Timer2 Interrupt 

      Serial.print(dfreq);
      Serial.print("  ");
      Serial.println(tword_m);
    }

   sbi(PORTD,6); // Test / set PORTD,7 high to observe timing with a scope
   cbi(PORTD,6); // Test /reset PORTD,7 high to observe timing with a scope
  }
 }
//******************************************************************
// timer2 setup
// set prscaler to 1, PWM mode to phase correct PWM,  16000000/510 = 31372.55 Hz clock

void Setup_timer2() {

// Timer2 Clock Prescaler to : 1
  sbi (TCCR2B, CS20);
  cbi (TCCR2B, CS21);
  cbi (TCCR2B, CS22);

  // Timer2 PWM Mode set to Phase Correct PWM
  cbi (TCCR2A, COM2A0);  // clear Compare Match
  sbi (TCCR2A, COM2A1);

  sbi (TCCR2A, WGM20);  // Mode 1  / Phase Correct PWM
  cbi (TCCR2A, WGM21);
  cbi (TCCR2B, WGM22);
}

//******************************************************************
// Timer2 Interrupt Service at 31372,550 KHz = 32uSec
// this is the timebase REFCLOCK for the DDS generator
// FOUT = (M (REFCLK)) / (2 exp 32)
// runtime : 8 microseconds ( inclusive push and pop)
ISR(TIMER2_OVF_vect) {

  sbi(PORTD,7);          // Test / set PORTD,7 high to observe timing with a oscope

  phaccu=phaccu+tword_m; // soft DDS, phase accu with 32 bits
  icnt=phaccu >> 24;     // use upper 8 bits for phase accu as frequency information
                         // read value fron ROM sine table and send to PWM DAC
  OCR2A=pgm_read_byte_near(sine256 + icnt);    

  if(icnt1++ == 125) {  // increment variable c4ms all 4 milliseconds
    c4ms++;
    icnt1=0;
   }   

 cbi(PORTD,7);            // reset PORTD,7
}
void runSensor (){                     // ***tWOGANS CHANGE - ADDED THE SENSOR FUNCTION HERE
  pinMode(srfPin, OUTPUT);
  digitalWrite(srfPin, LOW);           // Make sure pin is low before sending a short high to trigger ranging
  delayMicroseconds(2);
  digitalWrite(srfPin, HIGH);          // Send a short 10 microsecond high burst on pin to start ranging
  delayMicroseconds(10);
  digitalWrite(srfPin, LOW);           // Send pin low again before waiting for pulse back in
  pinMode(srfPin, INPUT);
  duration = pulseIn(srfPin, HIGH);    // Reads echo pulse in from SRF05 in micro seconds
  distance = duration/58;              // Dividing this by 58 gives us a distance in cm
  Serial.println(distance);
  delay(50);                           // Wait before looping to do it again
}

My question is this: I want to use my (distance) value to set the amplitude of the sine wave output however, I cannot see anywhere in the code in which this setting is made?

If anyone could offer some guidence on how I can do this I would be most grateful.

Many Thanks
Twogan

I want to use my (distance) value to set the amplitude of the sine wave output however, I cannot see anywhere in the code in which this setting is made?

The amplitude is set in the the look up table that defines the sample size.

So you can control the amplitude by dividing each sample just before you output it. As divide is a time consuming function it will slow the maximum sample rate down. A simple shift to the right ( one or two times ) will give some rudimental amplitude control.
For finer control copy the look up table into another variable and apply the divide as you copy it. Then you only have to do it once and it will not affect the sample rate.
However, this will mean that you will have to "trigger" a change in amplitude and there will be a short gap in the waveform as the look up table is adjusted.

Thank you so much for the continued attention on this. I think I do understand what you are suggesting.

As the sine wave will actually be used to shake a sheet of steel, high sound quality and perhaps therefore high sample rates will not be needed. So the best option for me is perhaps this:

you can control the amplitude by dividing each sample just before you output it...

I am assuming this is the 'look up table' you mention:

PROGMEM  prog_uchar sine256[]  = {
  127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,184,187,190,192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,239,240,
  242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,233,231,229,227,225,223,
  221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,115,111,108,105,102,99,96,93,90,87,84,81,78,
  76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,16,18,20,21,23,25,27,29,31,
  33,35,37,39,42,44,46,49,51,54,56,59,62,64,67,70,73,76,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124

OK, So I interpret these numbers as a sine wave - with 127 as the zero crossing point and O the bottom of the wave, and 254 as the top. So it follows that I can 'map' this range to between a narrower band (say 100 to 154 ) to give a lower amplitude. Do I have the logic correct?

If I do have that right, you are suggesting dynamically dividing the number just before output. So my distance value would become the number that we divide each sample by?

OK, you say this has an implication in processing speed that may rule out high sample rates, could a sample rate of say around 11khz be possible?

Or the other method of copying the table to a new Variable and divinding may work but it will be re-triggered every time the srf takes a reading (I figure this happening about every 60 milliseconds), again as the wave is being output through the steel sheet a little 'gap' shouldn't be a problem should it?

So

Do I need to go away and learn C++ in order to know how to implement either of these methods, or can you give me a further hint on the actual code I need to look at?
(I don't mean that flippantly, this is hardcore stuff for someone of my level of coding experience, I am massively interested and inspired, but I only need to know this one thing at this stage, so all the time it would take just to realise this one sound sculpture may mean I have to go back to Processing on the PC)

Appologies for extended post and as ever any help much appreciated!

OK, So I interpret these numbers as a sine wave - with 127 as the zero crossing point and O the bottom of the wave, and 254 as the top. So it follows that I can 'map' this range to between a narrower band (say 100 to 154 ) to give a lower amplitude. Do I have the logic correct?

Yes spot on.

So my distance value would become the number that we divide each sample by?

Well I would not think so. Because if you divide something by a large value you are likely to get 0 most of the time. You need to divide the sample number by a value between 0 and 1. That means it is a floating point division and those are not quick. However off hand I have no idea how quick it will be but I doubt you will be able to get 11K.

Or the other method of copying the table to a new Variable and divinding may work but it will be re-triggered every time the srf takes a reading

Well nearly, only every time you take a reading and that reading differs by a certain value, in that way the excitation is more continuous.
Note at the moment the look up table is in Program memory, you should copy it into a normal array and change where it is accessed from in the interrupt routine.

Do I need to go away and learn C++ in order to know how to implement either of these methods,

Well not C++ but maybe a bit of C but just basic stuff, you know it will be fun. :wink:

Get it working at a fixed amplitude before you delve into changing too much. The secret is to only cange a little at a time before you test again.

Many Thanks Grumpy Mike.

So I am on the right track with basic understanding of most of the concepts. I have an inkling that my ignorance of what even an 'interrupt routine' might be, could indicate I have a very steep learning curve to climb...

Any chance you can give me a starting point by showing me which line/s in the code offers a place to insert the division of the samples?

I imagine I can get the number I need to divide by, by 'mapping' my distance in cm to the range of 0 to 1 - e.g. map (distance,0,300,0,1) is that the way to go? (can you out put a range between 0 and 1 using map?)

And - I have noticed the Timer is disabled so this will cause a problem with the delay I use for the sensor operation, any tips here?

And lastly - Can you point me to a good (basic) beginners guide to learning the code I will need to implement this stuff?

Many Many Thanks indeed.

Twogan

This line:-

OCR2A=pgm_read_byte_near(sine256 + icnt);

Sets the register OCR2A to a value extracted from the lookup table. So to divide that by 0.5 you would do:-

OCR2A=(int) ((float)pgm_read_byte_near(sine256 + icnt) / 0.5);

You are using mixed mode arithmetic so ( float ) casts the number to be a floating point number, you divide by 0.5 and then cast it back into an integer before putting it in the timing register. Cast means a temporary convert.

And - I have noticed the Timer is disabled so this will cause a problem with the delay I use for the sensor operation, any tips here?

The delay uses Timer 0, this is not affected by this code.

I imagine I can get the number I need to divide by, by 'mapping' my distance in cm to the range of 0 to 1 - e.g. map (distance,0,300,0,1) is that the way to go?

Yes

(can you out put a range between 0 and 1 using map?)

No. Use mapfloat
Thread on mapfloat

I have an inkling that my ignorance of what even an 'interrupt routine' might be,

An interrupt is what it says. Suppose you are reading a book and your wife says make me a cup of tea. You stop reading and go and make the tea (if you know what is good for you ) after that you pick up the book and continue reading from the place you left off.
It is exactly the same in computing, something happens to trigger an interrupt, like a time timing out or a byte of serial data arriving. The program stops what it is doing and launches another function called an ISR or interrupt service routine to handle the interrupt, then it returns to the code it was performing when the interrupt happened.

Can you point me to a good (basic) beginners guide to learning the code I will need to implement this stuff?

There are so many it is hard to know where to start, but the Arduino Cookbook is a good source of consistent bits of information.
Arduino cookbook

An interrupt is what it says. Suppose you are reading a book and your wife says make me a cup of tea. You stop reading and go and make the tea (if you know what is good for you ) after that you pick up the book and continue reading from the place you left off.

Mike, let me congratulate you once again (did I before?. Sure you deserve it) :wink:

Thanks so much Grumpy Mike. I really appreciate all the time you have put in with me here.

And thanks for the interrupt explanation, makes total sense.

OK, so now I go and learn....

:slight_smile: