A video is available to support this article;
Introduction and background
RADAR (as used extensively, effectively and greatly developed further) back in World War 2 is the detection of objects using radio waves giving both the direction of the object and it’s range (distance) from the RADAR system. The acronym RADAR (which can be spelt as lower case ‘radar’) stands for RAdio Detection And Ranging. SODAR uses the exact same techniques as RADAR and gets the same results but uses sound as the wave being send rather than radio. So logical SODAR stands for Sonic Detection And Ranging.
You’ve probably all seen the movies of war ships with rotating oval dishes atop of the bridge (or some other high structure). These are the RADAR systems (well mostly, some may just be ‘listening’, but that’s another story). As they rotate they send out an Electromagnetic (radio) pulse and listen for the echo of it returning to the same dish. The time it took to return determines the distance of the object that reflected the wave and its direction is given by the direction that the radio dish is pointing in at that moment. Now you may point out that the dishes move quiet quickly (and they do) but bare in mind that radio waves move at the speed of light (as they are a form of light) and the round trip time from sending out to the returned echo is phenomenally short.
So combining the direction of the dish with any returned pulse gives you distance and direction to the object. Of course the dish continues rotating sending out and listening for returned pulses to build of a picture of the surrounding area. The principles of RADAR were well before WW2, almost from the earliest radio experiments were made but it took the invention of the cathode ray tube to provide a display to make an effective complete system.
Why not always use SODAR instead of RADAR?
As a general rule the higher the frequency of a wave the less it spreads out over distance. Sound (being a very low frequency) would be hopeless over distances of a few tens of meters (at absolute best). It’s also extremely slow compared to Radio and longer distances (as would be present in naval or other areas) would exaggerate the time for a pulse to return.
So why are you creating a SODAR system?
Because sonic modules are cheap, plentiful and for my needs they only need to operate over a short distance (around a meter) so the resolution will be fine and speed to returned pulse will not be an issue. Radar modules are available but will be used for another project another day.
How will you be using this system?
This article will create the basic system which you can then use as you wish but the obvious system use (and the one I will be creating) would be for collision avoidance for a small robot. But that will be another article.
Kit List
Arduino Nano (or Uno)
HC SR04 ultrasonic sensor
2003(a) stepper motor driver board
Stepper Motor (28BYJ-48)
128×64 pixel OLED Display (SSD1306 driver)
IR (infra-red) obstacle avoidance sensor
Power source for motor, i.e. Battery pack, power supply or 9v battery
Some wires
Breadboard
The completed project
Before we move on to the design, build and software for this project I thought it might be handy for you to look at the completed breadboard build to get a feel for where we are going. Look at the picture below:*
You can see the sonic module has been mounted on-top of a stepper motor. A stepper motor is a type of motor that we can control with extreme precision, so it’s direction would be known at any one time. This means that when a pulse is returned I will know in what direction the object lies. Just underneath the white card (which attached to the sonar sensor) is the obstacle avoidance sensor. It’s not easy to see but it’s a small cheap board that has a infra-red emitter and a sensor. The idea is if some of that IR is reflected back it will pull it’s output low and if nothing in front (no IR reflected) then it will be pulled eye. Above when the motor is finding it’s start position the card will go over the sensor and send the output low. We detect this and stop the motor, knowing that we are now at the start point ready to start out scan from a known position.
There’s also a small display which maps out the objects as they are found in both distance and direction. Obviously we need a computer to control the motor and do the distance calculation etc. etc. and in this instance I’ve chosen an Arduino Nano. In addition there is a small stepper motor controller board which the Arduino uses to control the stepper motor. More on driving stepper motors can be found in this article. Note that the stepper motor needs a separate power supply (in this case an additional battery pack) due to the high current used by them.
The circuit diagram.
Building and testing
The first thing we will do is add the display and get that working. You need an IC2 128×64 oled (SSD1306 driver). These are very commonly available. Other displays could be used but adapting them for use is beyond the scope or support of this article.
Installing the OLED display libraries
The first is the driver for 1306 OLED display driver chip. Go to the Arduino IDE library manager, “Sketch->Include Library->Manage Libraries”. Type “Adafruit SSD1306” into the search field. The first result should be the one you require titled “Adafruit SSD1306 by Adafruit”. Install this. The second library is the “Adafruit GFX library”, type in “adafruit graphics” and install the graphics library.
The screen I’m using is shown below and if you can source one like this with the same connections you should have very few problems getting yours up and running using this tutorial.
Connection Table
The following shows the connections required to the various Arduino models, in addition after the table is the connection diagram specific to the Arduino Nano.
OLED Connection | Nano Connection | Pro-Micro | Uno | Leonardo |
---|---|---|---|---|
VDD | 5V | 5V | 5V | 5V |
GND | GND | GND | GND | GND |
SCK | A5 | 3 | A5 (*) | #3 |
SDA | A4 | 2 | A4 (*) | 2 |
Load and upload the Adafruit example routines, these can be found at “File->Examples->Adafruit SSD1306->ssd1306_128x64_i2c”. You should see a demo being drawn on your screen.
Not working?
If it works then great, you’ve done it, if not then the most likely culprit(apart from bad wiring – please check!) is the I²C address of your display. These tend to be either 0x3C or 0x3D. So first try changing the line
#define OLED_Address 0x3C
to
#define OLED_Address 0x3D
and recompile/upload. If this doesn’t work it may be that your screen has an even different address to the most common ones. In this case you need to load a I²C address scanner onto your Arduino to get the screen to return its address and use that one in the code above. See this Link for a suitable scanner.
Adding the stepper motor
Add the stepper motor driver chip and stepper motor as shown in the circuit diagram. Note again that the stepper motor needs a separate power supply (in this case an additional battery pack) due to the high current used by them. Failure to do this can cause resetting of your MCU.
Stepper Software Library
To make things easier there are several libraries available for your MCU if you use the Arduino IDE as your development environment (and indeed even if you don’t but I won’t be covering them in this article). By default the Arduino IDE already includes a library and all you need do to use it is add the following line to your code
When the code below is uploaded the stepper motor should move to the left and then to the right.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
/* Stepper Motor Control - one revolution This program drives a unipolar or bipolar stepper motor. The motor is attached to digital pins 8 - 11 of the Arduino. The motor should revolve one revolution in one direction, then one revolution in the other direction. Created 11 Mar. 2007 Modified 30 Nov. 2009 by Tom Igoe */ #include <Stepper.h> const int stepsPerRevolution = 200; // change this to fit the number of steps per revolution // for your motor // initialize the stepper library on pins 8 through 11: Stepper myStepper(stepsPerRevolution, 8, 9, 10, 11); void setup() { // set the speed at 60 rpm: myStepper.setSpeed(60); // initialize the serial port: Serial.begin(9600); } void loop() { // step one revolution in one direction: Serial.println("clockwise"); myStepper.step(stepsPerRevolution); delay(500); // step one revolution in the other direction: Serial.println("counterclockwise"); myStepper.step(-stepsPerRevolution); delay(500); } |
Adding the ultrasonic module
Add the module to the motor using a little hot glue (or perhaps another method of attaching it of your choice) as shown below;
Left Stop Sensor
In order for the stepper motor to know where the far left is we add in a stop sensor. This is a simple infra sender and receiver module, when the ultrasonic module cuts the beam the Arduino knows the motor has gone as far left as we wish it to go. See the photo below.
Wire it up as shown in the circuit diagram and test by uploading the test code below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <Adafruit_GFX.h> // for writing text/graphics to screen #include <Adafruit_SSD1306.h> // driver library for oleds with 1306 driver chip #define OLED_Address 0x3C // I2C address of oled, if this doesn't work try 0x3D Adafruit_SSD1306 oled(128,64); // create our screen object setting resolution to 128x64 #define OUT_PIN 6 // Arduino pin tied to trigger pin on the ultrasonic sensor. void setup() { oled.begin(SSD1306_SWITCHCAPVCC, OLED_Address); // init the display oled.setTextColor(WHITE); oled.setTextSize(2); pinMode(OUT_PIN,INPUT); } void loop() { oled.clearDisplay(); oled.setCursor(0,0); oled.print(digitalRead(OUT_PIN)); oled.display(); } |
When you run this code you should see a “1” on the screen with nothing in front of the sensor but if you out something in front it should change to a “0” (preferably something light coloured, a finger usually works). This shows that when the object is near it reflects the infra-red light coming from the sender on the module back to the sensor. There is a sensitivity adjustment on the modules if you are having problems.
If this all works ok you need to place it somewhere where the moving sonar sensor will reflect the beam back. I placed a small piece of white card onto the sensor and bent the IR Sender LED and receiver up to make it more convenient. The picture and video at the top of the page make this more obvious to see. Upload the code below and you should see the motor move to the left until it breaks the stop sensor and then move to the right 180 degrees and then keep on repeating this pattern. If it doesn’t find the stop sensor after half a turn or so then it will stop and do nothing. This is to prevent a tangle of wires if something goes wrong.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
#include <Stepper.h> #include <Adafruit_GFX.h> // for writing text/graphics to screen #include <Adafruit_SSD1306.h> // driver library for oleds with 1306 driver chip #define OLED_Address 0x3C // I2C address of oled, if this doesn't work try 0x3D Adafruit_SSD1306 oled(128,64); // create our screen object setting resolution to 128x64 const int stepsPerRevolution = 32; // change this to fit the number of steps per revolution const int GearedStepsPerRev = 32*64; // change this to fit the number of steps per revolution const int HalfRev=GearedStepsPerRev/2; #define OUT_PIN 6 // initialize the stepper library on pins 8 through 11: Stepper myStepper(stepsPerRevolution, 7, 9, 8, 10); void setup() { oled.begin(SSD1306_SWITCHCAPVCC, OLED_Address); // init the display oled.setTextColor(WHITE); oled.setTextSize(2); // set the speed at 60 rpm: myStepper.setSpeed(1200); // Set IR sensor pin to input pinMode(OUT_PIN,INPUT); // find the home pos // keep rotating anti-clockwise until we detect the out pin of the IR sensor going low // we keep trak of total rotation and if exceeds half a turn then stop with an error, this will // stop wires becoming tangled if something has gone wrong with sensor ertc. int RotationCount=0; while((digitalRead(OUT_PIN)==1) &(RotationCount<HalfRev)) // detected or done half turn { myStepper.step(4); RotationCount+=4; oled.clearDisplay(); oled.setCursor(0,0); oled.print(digitalRead(OUT_PIN)); oled.display(); } if(RotationCount>=HalfRev) { oled.clearDisplay(); oled.setCursor(0,0); oled.print("Start not found!"); oled.display(); while(true); // go into infinite loop } } void loop() { // rotate 180 degrees anti-clcokwise: myStepper.step(-HalfRev); delay(500); // 180 degrees clockwise myStepper.step(HalfRev); delay(500); } |
Bringing it all together
The code below will make your build behave like a RADAR (SODAR), it will sweep the ultrasonic sensor left and right taking readings as it goes and displaying what it finds on the screen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
#include <Stepper.h> // Stepper motor library #include <NewPing.h> // ultrasound sensor library #include <Adafruit_GFX.h> // for writing text/graphics to screen #include <Adafruit_SSD1306.h> // driver library for oleds with 1306 driver chip #define OLED_Address 0x3C // I2C address of oled, if this doesn't work try 0x3D Adafruit_SSD1306 oled(128,64); // create our screen object setting resolution to 128x64 const int stepsPerRevolution = 32; // change this to fit the number of steps per revolution const int GearedStepsPerRev = 32*64; // change this to fit the number of steps per revolution const int HalfRev=GearedStepsPerRev/2; // So half a rev is 16*64=1024 steps for 180deg const int ScansPer180Degrees = 33; // 33 scans per 180 degree scan int8_t ObjectDistance[ScansPer180Degrees]; // distance of any scanned object, -1 for none int8_t AngleIncrement=1; // amount to increment to next scan, -1 sends in opp. dir. #define START_POS_PIN 6 // Pin used for the start position sensor to send input // low if detects sodar module, this could be a simple // lever contact switch but I prefer a no touch IR // solution, see the write up and video. #define TRIGGER_PIN 12 // Trigger pin on the ultrasonic sensor. #define ECHO_PIN 11 // Echo pin on the ultrasonic sensor. Stepper myStepper(stepsPerRevolution, 7, 9, 8, 10); // Initialise stepper object with pins // used to control stepper motor NewPing sodar(TRIGGER_PIN, ECHO_PIN,50); // NewPing setup of pins,50cm max range void setup() { oled.begin(SSD1306_SWITCHCAPVCC, OLED_Address); // init the display oled.setTextColor(WHITE); // For a BW display it's the only choice ! oled.setTextSize(2); // Small text size myStepper.setSpeed(1200); // set the speed at 60 rpm: pinMode(START_POS_PIN,INPUT); // Set IR sensor pin to input FindStart(); // FInd the start point for the Sodar sensor oled.clearDisplay(); // init all Y positions to -1 (means no object at that angle) for(uint8_t i=0;i<ScansPer180Degrees;i++) ObjectDistance[i]=-1; } void loop() { // Note that this loop will never be called if the start position of the sodar sensor is not found // This is to stop wires becoming quickly tangled if the start pos is not found // In the loop we simple perform a scan, update the display and move until we've done a comple 180 // degree sweep and then repeat backwards etc. etc. static int8_t Distance=0; // distance to object, 0 = nothing detected static uint8_t x,y; // x,y pos of where and object is static int8_t CurrentAngle=0; // The current angle of the stepper motor, starts at 0 // This value is not in degrees but in a range from 0 // to 31, thus splitting 180 degrees into 32 parts // rather than 180. We do not need 180 scans, it's // too much, our sensor cannot resolve that level // of detail and it would slow the scan down for no gain // See the AngleIncrment constant above // FInd the start point for the Sodar sensor oled.clearDisplay(); DrawRadarLine(63,CurrentAngle); Distance=sodar.ping_cm(); if(Distance!=0) ObjectDistance[CurrentAngle]=Distance*(64.0/50); // convert distance into pixels else ObjectDistance[CurrentAngle]=-1; // Nothing found, mark this with a -1 CurrentAngle+=AngleIncrement; if((CurrentAngle>31)|(CurrentAngle<1)) // of gone past 180 deg either way reverse direction AngleIncrement=-AngleIncrement; // reverse direction // Draw any known objects for(uint8_t Angle=0;Angle<ScansPer180Degrees;Angle++) { if(ObjectDistance[Angle]!=-1) // Only if an object exists { GetXYForAngleAndDistance(&x,&y,ObjectDistance[Angle],Angle); uint8_t ResolutionSize=abs(0-ObjectDistance[Angle])*0.08; if(ResolutionSize<2) ResolutionSize=2; oled.fillRect(x,63-y,ResolutionSize,ResolutionSize,WHITE); } } oled.display(); // Move snesor/stepper if(AngleIncrement>0) { // anti clockwise myStepper.step(-32); } else { myStepper.step(32); } } void DrawRadarLine(uint8_t Distance,uint8_t IntAngle) { //Draw line to distance at integer angle (not degree angle) indicated uint8_t x,y; // x y of end of line // plot line from centre of screen to these points GetXYForAngleAndDistance(&x,&y,Distance,IntAngle); oled.drawLine(63,63,x,63-y,WHITE); } void GetXYForAngleAndDistance(uint8_t *x,uint8_t *y,uint8_t Distance,uint8_t IntAngle) { // fills in X and Y with X position for Distance from IntAngle float RadAngle; if(IntAngle<16) // under 90deg ? { // convert IntAngle to a radian angle RadAngle=((2*PI)/64)*IntAngle; // convert to a radian angle (don't worry about the maths) *x=63-cos(RadAngle)*Distance; *y=sin(RadAngle)*Distance; } else { // over 90deg IntAngle=IntAngle-16; RadAngle=((2*PI)/64)*IntAngle; // convert to a radian angle (don't worry about the maths) *x=63+sin(RadAngle)*Distance; *y=cos(RadAngle)*Distance; } } void FindStart() { // Sends motor to start point, if not found then locks up code in // an endless loop in order to prevent damage to hardware through // tangling of the wires // This routine should be called in the setup routine just once. // keep rotating anti-clockwise until we detect the out pin of the IR sensor going low // we keep track of total rotation and if exceeds 3/4 of a turn then stop with an error, this will // stop wires becoming tangled if something has gone wrong with sensor ertc. int RotationCount=0; int ThreeQuarterRev=GearedStepsPerRev*0.75; int StepAmount=128; // at first we move fast to detect the start, then // we'll refine the position by reducing the size of step oled.clearDisplay(); oled.setCursor(0,0); oled.print("Looking for start"); oled.display(); while((digitalRead(START_POS_PIN)==1) &(RotationCount<ThreeQuarterRev)) // detected or done half turn { myStepper.step(StepAmount); RotationCount+=StepAmount; } if(RotationCount<HalfRev) { // found start, refine by tracking back past start and then in smallest increments back to start oled.clearDisplay(); oled.setCursor(0,0); oled.print("Start found"); oled.setCursor(0,10); oled.print("Tracking back"); oled.display(); StepAmount=1; while(digitalRead(START_POS_PIN)==0) myStepper.step(-StepAmount); oled.clearDisplay(); oled.setCursor(0,20); oled.print("Fine tracking to start"); oled.display(); // Now step back in small steps till we detect the start again in smallest steps possible, this // will give us greatest precision on always setting off from the same start point while(digitalRead(START_POS_PIN)==1) myStepper.step(1); oled.clearDisplay(); oled.print("Start Found"); delay(1000); // give time for user to read display, remove if not bothered oled.clearDisplay(); } else { oled.clearDisplay(); oled.setCursor(0,0); oled.print("Start not found!"); oled.display(); while(true); // go into infinite loop } } |
The way it works is it first (in the set-up routine) sends the sonar module to the left most position. Once this is found it continues with the main loop. Within the main loop it moves approx 6 degrees to the right, sends an ultrasonic pulse and waits for any reflection. If it gets one it works out the distance to the object and scales it onto the screen in the right position. If it receives no echo after the sonic pulse has travelled 50cm’s then it is deemed that nothing is in that direction. Experiment by placing objects around the SODAR for it to detect and display. You can see in the main loop that the routine returns a distance to the object or 0 if nothing is found. If you want to use this circuit in a collision avoidance system you would need to write code to avoid that object and look for gaps in the returned data to guide your robot through.
What Next?
There are two main things I want to do, one is to build a robot that can use this circuit to detect its surroundings and drive around obstacles or through gaps. However the “resolution” of the device is poor, the further away an object is the larger it appears. This is due to the normal way waves spread out. The solution to this is to either focus the beam more or to use a higher frequency (such as microwaves etc.). This is something I plan to look into.
The other is to add a larger more colourful screen instead of the small two colour one used. Watch this blog for what comes next!