Blog Amazing things that
bring positive results

An Ultrasonic Range Sensor, Linux, Ruby, and an Arduino

1. Intro

Recently, I needed to count open-close machine cycles for a customer. We couldn’t trust the machine readouts, so we needed an external device to count the cycles accurately. This device also needed to be built quickly and easily moved from machine to machine. Those requirements pushed my thoughts toward using an ultrasonic sensor and an Arduino. Although Ruby is not the first language I normally think of for the client-side of something like this, the customer uses Ruby extensively in their enterprise. I knew they’d be able to support a Ruby app without any trouble. Below is a trimmed down version of what my associate Tom and I came up with.

2. Hardware

The device is very simple, consisting of three main components:

  1. Radio Shack Ultrasonic Range Finder Unit
  2. Arduino Uno Rev 3
  3. RadioShack Project Enclosure

Two holes were drilled into one side of the box for the Arduino’s USB port and power connector. However, we ended up powering the Arduino though just the USB connection. Two other holes were drilled in one end of the box for the ultrasonic’s emitter and receiver modules.

Holes Cut in Project Box

Holes Cut in Project Box

Following the how-to in the resources section below, the following pins were connected between the ultrasonic sensor and the Arduino with some spare computer connectors. Using something like SchmartBoard jumpers would make the connections even easier.

Ultrasonic Pin Arduino Pin
GND GND
VCC 5V
SIG Digital 7

The Arduino and ultrasonic sensor were then mounted into the project box using double sided tape and hot melt glue.

Mounted Arduino and Sensor

Mounted Arduino and Sensor

Next the box was mounted on a magnetic base and arm, and the unit was ready for software.

Finished Unit with Base on Side

Finished Unit with Base on Side


Finished Unit Standing Up On Base

Finished Unit Standing Up On Base

3. Software

The only thing that gave me trouble in Ruby was the read_timeout setting for the serial port. gets and readline wouldn’t work properly without setting this value much higher than I would have expected. With the timeout set too low, gets would return before it had read a full line from the Arduino. This would throw the cycle counting code off when a number like 68 was read as a 6 followed by an 8 in a separate read.

All of the code below is available on GitHub.

require 'serialport'

# Make sure we got the right number of arguments
if ARGV.size < 2
  puts "Usage: ruby client.rb [serial_port] [inches_to_target]"
  puts "Example: ruby client.rb /dev/ttyACM0 40"

  exit
end

# Latch variables so we only trigger once on a close or open
is_open = false
is_closed = false

# Keeps track of the number of open-close cycles
cycle_count = 0

# Parameters used to set up the serial port
port_str  = ARGV[0] # The serial port is grabbed from the command line arguments
baud_rate = 9600
data_bits = 8
stop_bits = 1
parity = SerialPort::NONE
 
 # Set up the serial port with the settings from above
sp = SerialPort.new(port_str, baud_rate, data_bits, stop_bits, parity)

# We have to set the read timeout to a very high value or we may get partial reads
sp.read_timeout=(1000)

# Grab the distance to the target from the command line arguments
inches_to_target = ARGV[1].to_i

 # Wait to make sure the serial port is initialized
sleep(2)

# Loop forever reading from the serial port
while true do
	# Grab the next string from the serial port
	value = sp.gets.chomp
	
	# Check to see if we have a closed condition within a +/- 3 inch range (adjust as needed)
	if not value.nil? and value.to_i > inches_to_target - 3 and value.to_i < inches_to_target + 3
		# Make sure the target wasn't already closed
		if not is_closed
			#puts "Closed"

			# If the target was previously open we want to increment the cycle count
			if is_open
				# Keep track of the cycle count
				cycle_count += 1

				# Let the user know what the current cycle count is
				puts cycle_count
			end			

			# Flip the latch bits so that we only enter here once
			is_closed = true
			is_open = false
		end
	# We're outside the range the defines the closed condition
	else
		# Make sure the target wasn't already open
		if not is_open
			#puts "Open"

			# Flip the latch bits so that we only enter here once
			is_open = true
			is_closed = false
		end
	end
end
  

The require 'serialport' line and lines 33 through 41 allow us to establish a connection to the Arduino. You'll need to make sure the serialport gem is installed as shown below in the Usage section. If you're not interested in the logic that determines open versus closed, focus on the sp.gets.chomp statement and ignore everything below that except the nil check.

I modified existing code for the Arduino, and the original source file is listed in the resources section under "Support Files". I changed the main loop to send only the range value (in inches) that I was interested in. See the original source file for an example of how to use centimeters.

// Main program loop
void loop()
{
  long rangeInInches; //The distance to the target in inches
  
  // Get the current signal time
  ultrasonic.DistanceMeasure();
  
  // Convert the time to inches
  rangeInInches = ultrasonic.microsecondsToInches();
  
  // Send the number of inches to the target to the client
  Serial.println(rangeInInches);
}

4. Usage

The device is set up by aiming the ultrasonic's emitter and receiver at the object that opens and closes. This could be something like a sliding door, a machine's parting line, or a robot arm that always returns to the same place during a cycle. An important thing to remember is that this sensor plays by different rules than an optical sensor. The ultrasonic sensor will register against clear things like Plexiglas. The best way to get good readings is by shooting against a hard surface that's perpendicular to the line of sight of the ultrasonic sensor. A measurement should be taken of the distance from the ultrasonic sensor to the target object. This will be used when starting the Ruby application.

This application has only been tested with Ruby 2.0.0, but should work fine with 1.9.3. If you have any questions on how to install Ruby on Linux, have a look at the RVM and Ruby parts of Ryan Bigg's blog post here. I would highly discourage you from installing Ruby from most Linux distribution repositories, especially Ubuntu's.

In order to get the Ruby app to run, the serialport gem has to be installed first.


$ gem install serialport

If the program is run without any arguments it will display a usage message.


$ ruby client.rb
Usage: ruby client.rb [serial_port] [inches_to_target]
Example: ruby client.rb /dev/ttyACM0 40

The serial port normally shows up as /dev/ttyACMx on my Ubuntu based laptop, where x is a number between 0 and 3 usually. In some cases your Arduino might show up as something like /dev/ttyUSBx. The inches_to_target argument is the distance to where the ultrasonic sensor should see the target (closed condition). If the application sees anything outside of a +/- range around this distance it will count it as an open condition. When it sees something within this range again (closed), it counts that as a cycle. At the end of each cycle the application outputs a line showing the cycle count. You could easily add code that would display the present time, the last time a cycle was made, and the difference between the two, which can give you the cycle time of a machine.

5. Conclusion

In practice this system has been fairly intuitive and easy to use, although the ultrasonic sensor's reliable range is far less than the vendor's spec of 157 inches. Again, an important thing to remember when trying to get reliable readings is to shoot against a hard surface, and keep the "beam" of the ultrasonic sensor as perpendicular (90 degrees) to the face of the target object as possible.

Still have questions? Have suggestions that will make the hardware or software better? Please let us know in the comments section.

6. Resources

  1. Ultrasonic Range Finder - Radio Shack
  2. Ultrasonic Range Finder User's Guide
  3. Ultrasonic Range Finder How-To - Radio Shack Blog
  4. Ultrasonic Range Finder Support Files
  5. Arduino Uno Rev 3
  6. ruby-serialport Ruby Serial Library
  7. Example of Using Ruby getc to Read From Arduino
  8. Using Ruby with the Arduino - Arduino Forums

Comments (2)

  1. [...] Those requirements pushed my thoughts toward using an ultrasonic sensor and an Arduino. Although Ruby is not the first language I normally think of for the client-side of something like this, the customer uses Ruby …  [...]

  2. Steven

    2013/12/06 at 11:11 PM

    Thank you! Your code is Exactly what ive dreamT oF writing over the past three years.

The comments are now closed.