Sonar Geiger Counter – Construction

As the second part of the prototype for the Sonar eye Version 2, here are the details of construction (first part is here).

First off, this version does a Geiger counter tick, not a vibration or sine wave. If you want those, check out the first prototype, which has the code and wiring diagrams for that version. This one is MUCH simpler to build, and has a very low part count. It also uses a lot less power, since the sonar device is >10mA, and the buzzer is very high impedance, also in the sub-10mA category (the board itself is in the 30-100mA category, but still uses very little power).

In use, it will tick more often as you get closer to objects, and slow down when you are far away. As well, since it’s an Arduino, you can reprogram it, and tweak the ticking (or in fact, move the Geiger wire from pin 10 to 11, and paste the sine wave code from the first prototype and create a waveform, if you wish).

Basically, the device requires the following:

  • An LED flashlight. The one I found provided 4.5 volts via 3 AAA batteries. This meant that the device had to run at 3.3v not 5.0v like most Arduinos. Since I was using a Pro Mini, this simply required moving a connection on the board (breaking one solder trace, and adding a dab of solder to the other side to form a connection). However, the power to the Pro Mini can be up to 12v, so you can use just about any case or battery setup you desire. For instance, Oriano mentioned a friend that wanted a device like this for his cane; in that case, the device may look more like a matchbox than a flashlight, with the batteries (or battery) arranged other ways.
  • A diode. This is optional, and was only required by my circuit, since the battery pack could be inserted wrong quite easily. If for example you are using a 9v battery, this should never be a problem (but if you want to be really sure, add it in series with the +v line).
  • A switch. the flashlight provided not just the power supply and case, but the switch. If you use another case, you’ll need one – for example, eBay has pushbutton switches (look for toggle on/off types).
  • A buzzer. As I mentioned in the previous article, you can get passive or active buzzers (just search for any piezo buzzer on eBay). Both types will work, but the passive one will have the most natural ‘tick’ sound. In this design, it is the change of the signal to the Geiger pin (#10) than makes the noise, as the buzzer diaphragm snaps out and in. However, if you use an active buzzer, that signal now is more like a on/off switch to the buzzer. At high frequencies, it won’t matter much, but as you move to lower frequencies, you’ll get an odd effect: A passive buzzer gets quieter (since it only ‘sounds’ at the start and end) while an active one will get louder (since it has more ‘on’ time to buzz loud). For this reason, you may wish to use an active buzzer, and use the length of tick timer to act as a form of volume control – short ticks are quieter, long ticks are louder.
  • The . Like most everything I buy to tinker with, it’s an eBay purchase. I’ve been able to get them for around $10 (and sometimes as low as $7) and they are perfect for designing an in-device circuit:

    Basically, you solder your wires, connect the devices, and program. In this case, pin 10 connects to the buzzer (the other buzzer wire goes to ground), analog pin 0 goes to the sensor (A0), and finally the power input goes to RAW and Ground. One note about power: If you have a stable supply at 3.3v or 5.0v, you can connect it to VCC and use that directly. But if your power supply doesn’t match, send it to RAW, and the on-board regulator will clean it up, and make the appropriate voltage available on VCC for your devices. Which voltage you use depends then on what the regulator provides – some allow you to switch between 3.3/5v, and some are fixed, while others, like mine, required solder and surgery.

  • A sonar sensor. I used the Maxbotix LV-EZ1 from Adafruit (best price). These devices are $25, but are better than the others I’ve tried (Parallax “PING))”) at $30, and eBay’s HC-SR04 at <$3) for a few reasons. Most importantly here, they work at 3.3v, whereas the eBay’s ones are good at 5v only. They have only one sensor that doubles as both sender and receiver, making for a smaller device (note I have tried the SR04, and might consider it in the future if cost becomes an issue, and I have 5v to play with). One important thing to remember in wiring: The +v line from the sensor connects to the VCC on the Pro Mini, NOT the Raw connection or the power line from the batteries. Only VCC provides the expected voltage the program needs to calculate distances. Ground of course goes to the common ground, and the Analog connector goes to The Pro’s Analog 0 pin. As for mounting, it has two holes for mounting – but in my case, I gutted the flashlight, and drilled a hole in the clear plastic bezel that let it fit snugly in place.

As you can see, the part count is low, and the wiring simple. I expect I put this device together for under $50-$60 dollars, not including labor. Give it a try – in fact, charge a bit for your time to build it, and you could have a business that not only helps others, but provides some extra cash for your needs – truly a win-win situation.

In line with that, all design/code here is open and free to use – although if you are successful, please consider posting a comment so readers know it can be a great way to help others. And I’ll be continuing with the modifications, so if anyone has requests or suggestions please add them.

Lastly, here is the SonarEye code for the Arduino ‘Geiger’ style (or you can download the .zip file here)

/*

 SonarEye Audio/Vibration Ranging Code - Geiger Counter style signal
 Copyright © 2012 - David Pankhurst

*/
#include "avr/pgmspace.h"
//----------------------------------------------------------------
#define PIN_SONAR_ANALOG (0)
#define PIN_GEIGER (10)
#define PIN_LED (13)
#define USECVALUE (1000000)
unsigned long curveInfo[]={ USECVALUE/200, USECVALUE/20, USECVALUE/8, USECVALUE/5, USECVALUE/4, USECVALUE/3, USECVALUE/2, USECVALUE/1, USECVALUE*2 }; // last value 2 sec long
PROGMEM  prog_uchar randBytes[]={203,149,194,245,96,70,195,219,194,121,9,120,132,117,223,20,193,219,236,142,215,252,241,196,203,38,164,233,89,233,111,17,87,48,85,26,214,238,23,93,118,84,233,35,235,74,203,180,64,145,159,209,124,243,137,7,61,14,179,184,204,62,252,127,75,7,228,23,105,35,152,186,101,152,120,76,66,129,32,27,113,66,53,123,114,135,32,77,90,254,83,171,1,29,125,89,111,143,232,116,212,64,3,126,29,139,45,74,177,189,239,118,174,235,165,100,87,122,216,136,133,229,221,15,62,100,47,28,176,199,101,147,225,238,94,164,221,98,136,149,109,146,18,125,223,211,22,192,81,190,99,71,86,155,192,229,64,237,57,42,248,105,137,230,172,99,135,25,236,10,80,71,110,29,235,223,22,143,66,200,29,95,10,176,167,44,220,89,214,70,153,252,154,202,23,151,124,60,36,189,202,129,11,177,238,63,89,95,65,68,190,245,31,130,10,153,99,155,155,253,102,148,79,56,214,243,183,50,189,9,33,242,119,199,83,205,252,72,144,131,17,18,89,190,63,254,89,120,19,128,186,32,230,125,188,171,196,174,8,123,188,89,147,51,149,74,94,155,53,107,111,186,132,221,75,83,35,154,109,239,68,72,22,63,105,65,74,157,230,25,29,10,200,47,92,93,155,254,186,208,213,146,93,228,192,202,121,88,1,95,121,49,137,245,9,81,148,204,18,254,243,177,141,235,225,17,66,249,208,125,124,229,15,204,181,204,211,43,147,205,175,227,199,71,168,213,93,235,135,64,83,105,112,10,54,152,114,206,81,204,21,158,95,210,224,52,30,143,26,226,123,174,241,122,249,128,63,178,252,89,182,103,86,65,176,186,60,22,216,37,123,157,113,31,23,211,253,233,199,148,236,161,180,45,119,71,4,109,1,112,136,131,236,39,220,131,191,145,101,86,90,85,103,108,156,112,3,28,98,225,141,167,20,88,238,125,161,51,253,155,188,129,229,74,47,203,30,192,191,84,32,210,203,130,145,69,158,194,208,195,170,147,192,249,139,116,240,62,248,139,197,180,72,235,141,195,163,82,24,84,23,161,70,192,192,221,250,123,93,137,248,88,151,184,226,242,0,226,161,180,141,119,193,42,255,93,44,212,41,74,24,6,149,60,146,89,6,69,138,246,88,92,27,130,81,132,219,209,156,97,68,103,188,48,162,149,200,250,99,92,72,63,123,35,177,192,187,220,105,204,40,237,138,75,109,7,15,96,194,223,128,227,101,238,231,213,136,124,30,107,210,26,165,207,10,179,124,31,207,144,30,2,165,97,108,188,65,164,99,76,144,183,1,10,215,4,58,64,220,44,202,18,255,224,244,25,18,168,81,222,113,194,21,118,221,5,61,17,50,36,143,246,11,38,8,208,241,203,216,77,115,169,196,98,80,134,241,246,251,91,67,19,46,200,154,12,239,218,205,79,220,7,234,251,175,125,106,139,250,214,182,84,247,81,214,234,249,40,129,0,191,244,134,134,192,80,169,147,78,240,4,71,145,143,199,211,103,223,172,196,73,210,57,189,126,39,43,158,47,191,146,145,69,55,112,22,170,98,17,81,62,218,131,125,130,63,142,86,156,8,204,217,148,9,185,117,143,210,65,57,211,90,215,126,62,21,126,241,209,241,110,145,38,58,186,122,36,135,110,138,131,181,194,88,122,137,131,192,120,19,77,67,191,202,217,23,82,240,181,93,30,182,6,86,160,27,192,128,79,92,1,52,114,14,19,155,25,23,204,235,24,82,67,125,167,202,110,44,11,5,122,53,15,147,11,165,11,233,204,192,188,188,181,176,184,102,81,109,160,127,42,51,72,145,20,196,236,14,94,117,42,41,149,36,81,162,80,114,109,30,39,18,239,213,84,201,69,221,203,45,109,65,157,237,20,170,159,191,197,67,59,123,123,17,185,193,235,252,173,174,139,21,145,176,248,60,157,70,166,209,98,209,131,214,221,108,110,91,205,163,91,54,51,93,179,140,174,206,122,224,118,248,248,220,34,102,135,214,176,138,50,239,124,103,62,34,74,101,224,89,239,249,97,162,128,194,81,58,17,23,73,101,19,180,49,219,138,41,181,255,4,252,22,59,82,120,202,196,205,116,5,1,91,20,214,14,179,174,250,128,238,32,154,241,147,161,136,39,222,4,232,171,87,183,26,21,82,88,87,65,13,57,139,45,56,13,195,109,6,168,42,197,49,145,242,229,57,185,163,22,59,43,23,85,183,214,135,254,85,12,239,138,83,176,118,32,235,153,173,165,51,167,207};
unsigned int g_rndPos=((sizeof(randBytes)/sizeof(randBytes[0]))<<3);
//----------------------------------------------------------------
unsigned long pos2curve(unsigned char pos)
{
  // convert pos to value
  pos=pos&0xFF;
  int i=pos>>5;
  long x=pos&0x1F, a=curveInfo[i], b=curveInfo[i+1];
  x=a+(((b-a)*x)>>5);
  return x;
}
//----------------------------------------------------------------
inline unsigned long RND_GetBit()
{
  if (++g_rndPos>=((sizeof(randBytes)/sizeof(randBytes[0]))<<3))
    g_rndPos=0;
  int bytePos=g_rndPos>>3, bitPos=g_rndPos&0x07;
  int value=pgm_read_byte_near(randBytes + bytePos);
  return (value>>bitPos)&0x01;
}
//----------------------------------------------------------------
unsigned long RND_GetBits(unsigned int total)
{
  unsigned long value=0;
  while (total-->0)
    value=(value<<1)|RND_GetBit(); // reverse bit order
  return value;
}
//----------------------------------------------------------------
void DoGeigerTick(unsigned long d=99999)
{
  // do more or less frequent ticks based on input value - freq ticks for lower values, less for higher
  // for now, assume input is 0-255
  unsigned long tick=micros();
  static unsigned long tickStart=0;
  static unsigned long tickDT=0; // time to next tick
  static unsigned long inD=0; // input value
  static unsigned long inD2=1000000/4; // input value
  static bool signalOn=false;
  if (d<512) // input changed?
  {
    inD=d;
    if (d>255)
      inD=255;
    inD2=1000000/((inD>>3)+9);
    inD=pos2curve(inD);
  }
  // now handle tick or not...
  if ( tick-tickStart<tickDT) 
    return; // not time
  tickStart=tick;
  if ( !signalOn ) // turn on tick
  {
    digitalWrite(PIN_GEIGER, HIGH);
    digitalWrite(PIN_LED, HIGH);
    signalOn=true;
    tickDT=inD2;
  }
  else // turn it off
  {
    digitalWrite(PIN_GEIGER, LOW); // cleanup
    digitalWrite(PIN_LED, LOW);
    signalOn=false;    
    // 'off' delay - related to distance: 0 is high (4k?) 511 is low (1htz?)
    tickDT=inD+ ((inD*RND_GetBits(3))>>6) ;
  }
}
//----------------------------------------------------------------
unsigned int readEcho(unsigned long *value)
{
  // get signal in - 0-1023 as per analogRead(0) (scale if necessary)
  // we limit how often we check - return success and value in 'value' or 0 and no change to value...
  unsigned long tick=micros();
  static unsigned long tickStart=0;
  if ( tick-tickStart<60000) // call max 16x a sec
    return 0;
  tickStart=tick;
  unsigned long dataIn=analogRead(PIN_SONAR_ANALOG);
  *value=dataIn;
  return 1;
}
//----------------------------------------------------------------
void setup()
{
  pinMode(PIN_GEIGER, OUTPUT);   
  pinMode(PIN_LED, OUTPUT);   
}
//----------------------------------------------------------------
void loop()
{
  unsigned long k=0;
  while(1) 
  {
    DoGeigerTick();
    if(readEcho(&k))
      DoGeigerTick(k);
  }
}
//----------------------------------------------------------------

Comments are closed.