97. Bearing Witness

“Bearing Witness” 6: Ultrasonics

IMG_20160425_234005Next, to get the ultrasonic range-finder working. The idea is to make the artwork interactive: the candles will glow until someone walks up to it, at which point it will begin a new sequence of bright flash / darkness / slow candle lighting.

I bought this ultrasonic sensor on eBay for £1.63. It should allow the measurement of distance-to-objects (i.e., people) from 2cm to 400cm (4 metres). It has four connectors, two for power (Vcc / Ground), one to trigger a short burst of ultrasonic sound, and one to return a signal when the echo is received. The time difference between pulse and echo represents the distance to the nearest large object.

I wrote some code (see end of post) to send a 10 ms (millisecond) pulse to the “Trigger” pin (i.e. turn it on (5 Volts), wait 10ms, turn it off) and then keep checking the “Echo” pin (i.e. do continual “digitalRead”s). The “Echo” pin stays positive until an echo is received, so the “on” time equates to distance (that is, twice the distance – there and back). This is what I found (after an initial 165 reads; “1” is high (5 Volts from the sensor), “0” is low (zero volts)):

Object at 9cm:  [165 zeros]11000000000000...
Object at 10cm: [165 zeros]11111111100000...

IMG_20160407_175734The ceiling is 181 cm away, so, if one extra cm equates to seven reads then it should be around (181 – 9) * 7 = 1204 reads, plus the original 165 plus the two = 1369 reads before an echo from the ceiling would be expected. (For interest: since sound travels at about 340 metres per second, each digitalRead must be taking about ((1.81 – 0.09) *2) metres / 340 m/s / 1026 readings = about 6 microseconds for a digitalRead.)

However, doing continuous reads ties up the processor, which is problematic since it needs to drive the flickering necessary to simulate up to five candles. The problem is that if the processor goes off to do something else for an extended period then the candle effect may be noticeably affected.

Doing it properly
A better way to program the ultrasonic sensor is by using an interrupt. Interrupts allow the processor to get on with other things while it’s waiting for an echo. The Arduino pin (connected to the Echo output on the US device) is accessed in a different way. Instead of repeatedly checking to see if its voltage has changed, we setup an interrupt for that pin. Interrupts are defined in Setup() using the attachInterrupt command.

 pinMode(EchoPin, INPUT); 
 attachInterrupt(digitalPinToInterrupt(EchoPin), echo, FALLING);

We need to send a trigger in the same way – make a pin High for 10 mS so the US device sends out a sound pulse – but then the Arduino can get on with whatever you like. The trigger also sets the Echo pin High; when the US device receives an echo, it changes the Echo pin from High to Low (5 Volts to 0 Volts) which causes the Arduino to stop what it’s doing and run the function you defined in the attachInterrupt command, after which it carries on where it left off. This “special” function is called the “Interrupt Service Routine” (ISR). It’s really small so as not to slow down the other code significantly. In this example, the ISR is called “echo”.

// We get here when the US device interrupts the processor
void echo()
{
 TimeLastEcho = micros(); // Make a note of the time
 EchoReceived = true;     // Set a flag to say this has happened
}

So, I have two functions; one triggers the US pulse and stores the trigger time, the other (echo) makes a note of the time the echo was received. The difference in times (in microseconds) is the time it took for the sound to travel from the US device to the nearest large object and back again (plus some fixed overhead). Since the speed of sound is a constant, this time equates to distance.

In the attachInterrupt command you define what sort of signal the Arduino looks for. I’m using “FALLING”, which means that it looks for a change in voltage from High (5 Volts) to Low (0 Volts). So it ignores Low to High changes (as occur when the trigger pulse is sent).

On the Arduino Uno (used for testing) and the Nano (used for the build) interrupts can only be handled on pins 2 or 3. Since PWM for “dimming” the LEDs is only available on pins 3, 5, 6, 9, 10, and 11, it’s best we use pin 2 for the ultrasonic detector’s interrupts. (The trigger, however, can be sent from any pin, even the so-called “analogue” ones.) To complicate the issue, the Arduino has its own internal way of numbering its pins – which differs between Arduino types. The digitalPinToInterrupt() function is used to convert the number by which a pin is known (e.g. 2) to whatever internal number the Arduino calls it.

So, when the program wants to test the distance between the artwork and the nearest object or person, it just needs to send a trigger and check the variable EchoReceived from time-to-time to see if we have a found a distance. The trigger and echo times will be accurate regardless of when the times are checked, and a distance can be calculated.

A big problem
So, I rewrote the software to use interrupts. The problem was, it kept giving me a negative distance. Yep, that sounds easy enough to fix. I checked the code. I printed it out and went through it slowly. No matter how hard I looked, it looked ok.

Time in microseconds returns an Unsigned Long variable, which is microseconds since the arduino was started / reset. That means that every 70 minutes or so the maximum value is reached (4,294,967,295) and the count starts again at zero. I had assumed that this meant that subtracting one time from the other would fail if the wraparound occurred between the two times. For example, 4 – 4294967293 should equal 7. In fact, using Unsigned Long variables, this calculation does, indeed, give the correct answer.

I was under the misapprehension that I needed additional code to deal with the time wraparound, so I had a time calculation function. It only subtracted one time from the other as I hadn’t written the (unnecessary) extra code. However, the function seemed to be returning the wrong value. This was the code:

Serial.println(TimeLastEcho - TimeLastTrigger);
Serial.println(timeCalc(TimeLastEcho - TimeLastTrigger));

long timeDiff(unsigned long toTime, unsigned long fromTime)
{
 unsigned long TimeDiff = toTime - fromTime;
 long x = (long) TimeDiff;
 return (x);
}

The problem was, the two print statements printed different time differences. Why? I spent a long time puzzling over this, trying all sorts of things. In the end, I decided to test the Arduino itself. And, Bingo! I connected an LED (and 580 Ohm resistor) to Pin 2 (and Ground). It should only flash briefly 4 times a second – each time a trigger pulse is sent to the ultrasonic device. Instead, the LED just lit up, even with a test program that doesn’t use Pin 2 at all. For some reason, either the arduino makes Pin 2 permanently high or it’s cycling rapidly between Low and High.

So, the problem is, the US device is being triggered by the Arduino frequently, not just when the code tells it to. Which means:

  • The US device keeps returning an echo
  • The arduino keeps calling the interrupt function (ISR)
  • The variable TimeLastEcho keeps being updated

The reason I get different values each time I printed the time difference is because one of the variables (TimeLastEcho) has been changed. The code is correct, the hardware was faulty. When I used a different pin for the trigger, it worked just fine.

Next question. What broke the Arduino? Erm, it’s possible I may have shorted Pin 2 to ground while High (the Arduino, not me!) during early tests of the US device. Oops!

So, problem solved. I modified the test hardware to add some LEDs and wrote a test program using interrupts to light a different number of LEDs according to this distance. So I could move my hand backwards and forwards to change how many were lit. (Note that I’m using an Arduino Nano for this test.)


I found the distances returned to be quite accurate, but it tends to produce occasional spurious results. One factor is that after an echo reaches the device the sound doesn’t immediately stop; it bounces around and can return a second echo. Usually this doesn’t matter, but if send another trigger pulse too soon after receiving an echo then you may get a reflection from the previous trigger picked up as the echo. My conclusion is that it’s best to wait at least half a second between measurements.  Another problem is that the ultrasonic pulse spreads out, so it doesn’t always reflect from what you may expect. The answer, I think, is to compare two consecutive measurements and wait until two similar readings are received.

Next, fitting everything into the frame.

 


int echoes[50];

void setup()
{
  pinMode(7, OUTPUT);
  digitalWriteFast(7, LOW);

  pinMode(10, INPUT);
  Serial.begin(9600);
}

void loop()
{
  digitalWrite(7, HIGH);
  delay(10);
  digitalWrite(7, LOW);

  int echo = 0;
  int i = 0;
  for (i = 1; i <= 165; i++) // Initial wait
  {
    echo = digitalRead(10);
  }
  for (i = 1; i <= 40; i++) // Look for echo
  { 
    echo = digitalRead(10);
    echoes[i] = echo;
  }
  for (i = 1; i <= 40; i++) // Display result
  {
    Serial.println(echos[i]);
  }
  Serial.println("-----------------");

  delay(5000);
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s