Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.
Photo

2D Motion Capture, early results


  • Please log in to reply
10 replies to this topic

#1 Corey Kosak

Corey Kosak

    Advanced Member

  • Members
  • PipPipPip
  • 276 posts
  • LocationHoboken, NJ

Posted 07 November 2010 - 02:34 AM

The goal of this hacky little project is to use proximity sensors to do 2D (and, maybe someday, 3D) motion capture. I'm not really sure what's out there in this regard, so I just decided to cobble together something and see how far I got. It was pretty fun and the results have been half-decent!

First, click here for the experimental setup.

In the photo we have two PING))) Ultrasonic Sensors from Parallax, mounted at right angles to one another. Because I forgot to buy the right kind of connector, I had solder wires to the X axis (the one attached to the wine bottle), which took a lot of concentration. On the other hand, the Y axis is just sticking out of my breadboard, which was convenient. The breadboard itself is held in a vertical position by a heavy pair of pliers oriented just so.

The wiring was pretty simple. These things are hooked up to +5 and GND in the normal way. The SIG pin of the X-axis (wine bottle) detector is wired to digital pin 5, and that of the Y-axis detector is wired to digital pin 6.

Because these detectors have a somewhat narrow angle field that they can see (+/- 20 degrees), the effective 2D field in which I could detect stuff in this setup is kind of small. But anyway, here is the code:

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using Pin=Microsoft.SPOT.Hardware.Cpu.Pin;

namespace hacks.netduino.avatar {
  public static class Program {
    public static void Main() {
      try {
        Doit();
      } catch(Exception e) {
        Debug.Print("ugh: "+e);
      }
    }

    private static void Doit() {
      const int numReadings=2000;
      const Pin xPin=Pins.GPIO_PIN_D5;
      const Pin yPin=Pins.GPIO_PIN_D6;

      for(var i=0; i<numReadings; ++i) {
        var y=Probe(yPin);
        var x=Probe(xPin);
        Debug.Print(i+" "+y+" "+x);
      }
    }

    private static long Probe(Pin pin) {
      //set some things up early
      long machineEndTime=0;
      NativeEventHandler handler=(d1, d2, t) => machineEndTime=Utility.GetMachineTime().Ticks;
      var pwm=new PWM(pin);

      //note the time, fire what we hope to be a 5 microsecond pulse
      var machineStartTime=Utility.GetMachineTime().Ticks;
      pwm.SetPulse(5, 65535);
      var dummy=new string('*', 3); //burn some time
      pwm.Dispose();

      //wait a long time for the echo
      using(var iport=new InterruptPort(pin, false,
        Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow)) {
        iport.OnInterrupt+=handler;
        Thread.Sleep(150);
        return machineEndTime-machineStartTime;
      }
    }
  }
}

The specifications say that the way to talk to this thing is to send it a 5-microsecond pulse, then wait for a while, then measure the length of the pulse that comes back. My first problem was how to accurately send a 5-microsecond pulse. I suspected that there wasn't really a way, so I decided to (ab)use the PWM feature. My little trick was to set a period of 5 microseconds, a duration of 65535 microseconds, and then to dispose the thing real quick so it would never have a chance to fire a second pulse. You can see this in the code here:

      pwm.SetPulse(5, 65535);
      var dummy=new string('*', 3); //burn some time
      pwm.Dispose();

Obviously for this to have any chance of working, a lot of things need to be true
  • The pin isn't doing anything before the call to pwm.Pulse()
  • The pin goes high right after pwm.Pulse(), and stays high for 5 microseconds
  • The framework is slow enough that the call to Dispose happens sometime between 5 and 65535 microseconds.

Who knows if any of that is true? Sometimes ignorance is bliss.

Now my next task was to measure the width of the pulse coming back, which is how the device tells me how far away the measured object is. My first plan was that I would configure the pin to give me an interrupt on both the high and the low edges of the pulse coming back. However for some reason I could never get it to work. (In that version of the program, every once in a while I *would* get two edges, but most often I would only get (what I assume to be) the trailing edge). Since I couldn't really get that to work, instead I wrote the program that you see above, where I store the starting "machine time" (not really sure what "machine time" is) at the beginning, and the ending machine time after the interrupt fires.

I ran it, and tried to store a bunch of samples in memory. I quickly ran out of memory unfortunately (How big is the heap available to my program anyway? Is it possible that it's as low as 5-10 kilobytes? That's what I think I was seeing, but I didn't have time to research it). Anyway, I decided to print my output to the debug window instead.

Then it was showtime. I decided to trace a square in the air with my fist. I wanted to see what the program came up with (one should expect some distortion because of the geometry of the situation: when my hand is moving, say, straight up and down, even though its X coordinate isn't changing relative to me, it is changing its angle and therefore its distance with respect to the X detector. It seems we eventually need to do a little coordinate transformation here. But first let's see if it works at all)

Here is a video of me making the square:

http://www.youtube.com/watch?v=bVYQGHbyJSk

Now, in the video I claimed I was actually going to use the collected data in this web post, but it didn't turn out very well. Instead for the purposes of this post I will use data from an earlier run. Here is the data: Attached File  run2.txt   1.5KB   11 downloads

If we plot the X and Y axes vs time, we get this graph.

There is some intriguing activity at the bottom there. It looks sort of like two sine waves. If we adjust our axes to zoom into that area, we get:
this fabulous graph.

Now for a plot of Y vs X, which should actually represent what we tried to trace out in space. That looks like this.

Hmm, what's that little blob in that corner? Why, it looks (sort of) like our square! Wow!!!!!!

I hope this hacky little project was of interest to people. Thanks for reading my project report!

#2 Corey Kosak

Corey Kosak

    Advanced Member

  • Members
  • PipPipPip
  • 276 posts
  • LocationHoboken, NJ

Posted 07 November 2010 - 04:17 AM

My goodness. I think I got the parameters to SetPulse in the wrong order. Fascinating. :angry:

#3 Chris Walker

Chris Walker

    Secret Labs Staff

  • Moderators
  • 7767 posts
  • LocationNew York, NY

Posted 07 November 2010 - 04:25 AM

Corey, this is pretty cool! BTW, if you need lots of ram on your Netduino Plus, you can reflash it with the regular Netduino firmware to remove the networking stack's memory requirements... We designed the Netduino Plus to work well with the regular Netduino's firmware as well (just minus the networking). Chris

#4 bill.french

bill.french

    Advanced Member

  • Members
  • PipPipPip
  • 260 posts
  • LocationPrinceton, NJ

Posted 07 November 2010 - 04:52 AM

While I was admiring your project, and your strategic use of .Dispose(), I couldn't help but think the displayed frame from your youtube video looked like you were trying to punch Beaker, but missed.

Posted Image

#5 Chris Walker

Chris Walker

    Secret Labs Staff

  • Moderators
  • 7767 posts
  • LocationNew York, NY

Posted 07 November 2010 - 04:56 AM

While I was admiring your project, and your strategic use of .Dispose(), I couldn't help but think the displayed frame from your youtube video looked like you were trying to punch Beaker, but missed.

*ROFL* :lol:

#6 Corey Kosak

Corey Kosak

    Advanced Member

  • Members
  • PipPipPip
  • 276 posts
  • LocationHoboken, NJ

Posted 07 November 2010 - 05:01 AM

OMG. Hilarious!!!

#7 bill.french

bill.french

    Advanced Member

  • Members
  • PipPipPip
  • 260 posts
  • LocationPrinceton, NJ

Posted 07 November 2010 - 05:37 AM

Glad you enjoyed my very advanced Paint.Net skills!

Thinking about it (the project) some more, the datasheet says 5us is "typical"... (and since you say you got the pwm call wrong anyway) which makes me wonder if you could have gotten away with something like:

out1.Write(true);
out1.Write(false);

which, according to here, gets you a 60us pulse.

... I also wonder if you had gpio pins to spare, you could you one pin in output mode to fire the pulse, then another pin in input mode (both tied to the sig pin on the ping thing) to read the returning pulse, then you wouldn't have to worry about .Dispose()'s timing.

#8 Corey Kosak

Corey Kosak

    Advanced Member

  • Members
  • PipPipPip
  • 276 posts
  • LocationHoboken, NJ

Posted 07 November 2010 - 05:44 AM

Ooh, that's a great idea. I bet that could smooth out some of the glitchy input and maybe also give me some more precise readings. Awesome! I'll have to try that tomorrow...

#9 Corey Kosak

Corey Kosak

    Advanced Member

  • Members
  • PipPipPip
  • 276 posts
  • LocationHoboken, NJ

Posted 07 November 2010 - 11:49 PM

I took bill.french's suggestions above and created a second version of the system. It seems to work a lot better!

Here is the data from a recent run, where I try to draw a square again:Attached File  run5.txt   2.48KB   3 downloads

Here is the graph of Y vs X with outliers cropped. It should sort of look like a square. (By the way in my previous plots, I was plotting X vs Y rather than Y vs X. You just have to kind of transpose them in your mind.)

The new circuit is a little more complicated. Initially, I just wired together (for each axis) a Netduino output port, an input port, and the signal port of the device. This didn't work, and I concluded that the problem must be that the Netduino's output port is going to hold the whole shebang low and so we'll never see a signal coming back from the device. So instead I wired it like this:

output port -> 2K ohm resistor -> (input port + SIG pin of device)

Of course I Have No Idea What I Am Doing™, but once again the thing kind of did work. If I can find that fancy-pants circuit drawing tool you folks use, I'll try to enter the circuit into that. Until then, here is the new code:

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using Pin=Microsoft.SPOT.Hardware.Cpu.Pin;
using ResistorMode=Microsoft.SPOT.Hardware.Port.ResistorMode;

namespace hacks.netduino.avatar {
  public static class Program {
    public static void Main() {
      try {
        Doit();
      } catch(Exception e) {
        Debug.Print("ugh: "+e);
      }
    }

    private static void Doit() {
      const int numReadings=2000;
      const Pin yOut=Pins.GPIO_PIN_D0;
      const Pin yIn=Pins.GPIO_PIN_D1;
      const Pin xOut=Pins.GPIO_PIN_D2;
      const Pin xIn=Pins.GPIO_PIN_D3;

      using(var ySensor=new HackyDistanceSensor(yOut, yIn)) {
        using(var xSensor=new HackyDistanceSensor(xOut, xIn)) {
          for(var i=0; i<numReadings; ++i) {
            var y=ySensor.Probe();
            //I'm a little bit worried about the sensors hearing each other's echoes, so I put in a delay here
            Thread.Sleep(75);

            var x=xSensor.Probe();
            //..and here
            Thread.Sleep(75);

            Debug.Print(i+" "+y+" "+x);
          }
        }
      }
    }
  }

  public class HackyDistanceSensor : IDisposable {
    private readonly OutputPort outputPort;
    private readonly InterruptPort interruptPort;
    private long risingEdgeTicks;
    private long fallingEdgeTicks;
    private int fallingEdgesRemaining;
    private readonly AutoResetEvent autoResetEvent=new AutoResetEvent(false);

    public HackyDistanceSensor(Pin outPin, Pin inPin) {
      this.outputPort=new OutputPort(outPin, false);
      this.interruptPort=new InterruptPort(inPin, true, ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeBoth);

      interruptPort.OnInterrupt+=(pin, edge, t) => {
        var ticks=t.Ticks;
        if(edge!=0) {
          risingEdgeTicks=ticks;
        } else {
          fallingEdgeTicks=ticks;
          if(--fallingEdgesRemaining==0) {
            autoResetEvent.Set();
          }
        }
      };
    }

    public void Dispose() {
      outputPort.Dispose();
      interruptPort.Dispose();
    }

    public long Probe() {
      //don't wake me up until the second falling edge
      this.fallingEdgesRemaining=2;

      //send as short a pulse as we can
      outputPort.Write(true);
      outputPort.Write(false);

      //wait for the second falling edge
      var result=autoResetEvent.WaitOne(150, false);

      //if no second falling edge within 150ms, return 0
      return result ? fallingEdgeTicks-risingEdgeTicks : 0;
    }
  }
}


#10 Chris Walker

Chris Walker

    Secret Labs Staff

  • Moderators
  • 7767 posts
  • LocationNew York, NY

Posted 08 November 2010 - 12:09 AM

Of course I Have No Idea What I Am Doing™, but once again the thing kind of did work. If I can find that fancy-pants circuit drawing tool you folks use, I'll try to enter the circuit into that.


Fancy-pants circuit drawing tool:
http://fritzing.org/

#11 Corey Kosak

Corey Kosak

    Advanced Member

  • Members
  • PipPipPip
  • 276 posts
  • LocationHoboken, NJ

Posted 08 November 2010 - 01:13 AM

Thanks! That was kind of fun: Attached File  Avatar.txt   102.69KB   17 downloads (This file is really Avatar.fz but apparently I am not permitted to upload a file with a .fz extension. So I renamed it to .txt)




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

home    hardware    projects    downloads    community    where to buy    contact Copyright © 2016 Wilderness Labs Inc.  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.