Ultrasonic Range Finding

By: Paul Bartosek

Overview

During the course of EE 552 several groups have found the need to measure distances quickly using some form of non-contact measurement solution. This document is meant as an introduction to ultrasonic rangefinding or sonar, and as such presents a possible implementation which is quick and cheap to implement using resources available to students.


Background

The way that sonar works is by sending a directional burst of sound from a transmitter, this burst hits an object and is reflected back towards the receiver, which should be in close proximity to the transmitter. The distance between the transmitter/receiver assembly and the object is directly proportional to the time between sending and receiving the pulse and can be calculated as follows:

Distance=343 m/s * Time

This is of course only valid when the speed of sound through air is 343 m/s at about 25 degrees C, since this speed varies with temperature.


Possible Hardware Implementation

There are many transmitters/receivers available for use in this application. However there is only one type of transducer available from Moris Locker, fortunately it is well suited towards this task. It is tuned with a center frequency of 40 kHz with a bandwidth of 5 kHz, since this is in the ultrasonic range it will not be an annoying high pitched whine. Using one of the Altera UP1 boards available for student use there are external circuits which need to be built in order to have a fully functioning sonar system. These are, of course, the transmitter and receiver.

Building a transmitter using this method is a trivial task. Basically the FPGA outputs a 40 kHz signal when it wants the transmitter to transmit, this signal is amplified and then powers the transducer. Due to the fact that the transmitter puts a huge amount of noise onto the power line it is recommended that it be isolated from the receiver circuit which is highly sensitive to power fluctuations. This is accomplished using an opto-isolator which serves to both isolate the transducer from the rest of the circuit and amplify the signal to power it. Therefore it requires its own power supply, a 9v battery will supply ample power for quite some time. This voltage can be increased in order to provide a higher amplitude pulse, which translates into a longer range. The final transmitter circuit is provided below.


Another possible, although untested, configuration would be to use a 40 kHz monostable multivibrator which could be activated and deactivated by the FPGA.

Detection of the returning pulse is handled by the receiver circuitry which consists of a bandpass filter and an amplifier. The bandpass filter is used to tighten the frequency response of the receiver (translation - better noise rejection). Using the circuit below the bandpass characteristics include a center frequency of 40.8 kHz with 3 dB points at 41.2 kHz and 39.4 kHz. Once the signal is received by the transducer and filtered it is passed on to the amplifier which raises it past the logic threshold level of the FPGA. Finally a clamping circuit is added in this implementation in order to protect the FPGA from over voltages. This circuit is meant to be powered from a 9 volt source, the clamping circuit can be omitted if the supply voltage is lowered to 5 volts. Below is the receiver circuit schematic.


Credit is due to the 2D-Room Mapper project and all of its group members for the underlying transmitter and receiver circuits.


VHDL Code

Provided below is VHDL code which will measure the distance between the unit and an object and display the distance in centimeters on the two 7-segment displays on the UP1 board. In order to use the above transmitter circuit we need to be able to provide a 40 kHz signal to it. Since the clock on the UP1 board is fixed at 25.175 MHz we will need to use a clock divider, in this case it will provide a clock frequency of 80.689 kHz. Within the VHDL code is a state machine with two states, in the sending_pulse state it does just that, sends a 40 kHz signal to the transmitter circuit until either an object is detected or the maximum alloted time is elapsed. There must be an upper maximum to the range of the sonar or if there is no object there it will be stuck in this state forever. It may also be in the display state which as the name would suggest displays the measured distance on the 7-segment displays, as well it serves to provide ample time for any transmitted pulse to return to the receiver so a false reading does not occur.

The incoming pulse level is latched at at a rate of 25.175 MHz so that we do not accidentally miss the first reflected pulse, however the actual counter which determines the distance only runs at a rate of 80.689 kHz. This still, in theory, provides a measurement resolution of 343 m/s / 80689 kHz / 2 or 2.13 mm. A higher degree of accuracy could be obtained if the distance counter ran at 25.175 MHz but would require a significantly larger counter which means more logic cells.

Once the distance is determined, led_disp is updated. This causes the 7-segment display to also be updated with (roughly) the distance in centimetres between the object and the transceiver assembly. Note: if "00" is displayed there is no object within range, if "99" is displayed an object has been detected but is over 1 metre away so the distance can not accurately be displayed in centimetres on a two digit display.

If you would prefer to download the file click here.

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

entity sonar is
  generic (
    -- slow clock is 25.175 Mhz / 156 / 2 = 80.689 kHz
    clock_divisor : positive := 156;

    -- max clock cycles to wait for pulse bounce back
    max_range		: positive := 1000;

    -- time between measurement cycles in clock cycles
    display_time	: positive := 5000
  );

  port (
    -- reset is the right user button and is active low
    -- button is the left user button and is active high
    -- it toggles between measure and hold functions
    -- sonar_in is active high
    clk, reset, button, sonar_in : in std_logic;
    slow_clk, running : buffer std_logic;
    led1, led2 : out std_logic_vector(6 downto 0);
    sonar_out : buffer std_logic
  );
end sonar;

architecture behavioral of sonar is
type state_type is (sending_pulse, display);
signal state : state_type;
signal led_disp, led_disp2, timer : std_logic_vector(16 downto 0);
signal slow_clk_count : std_logic_vector(7 downto 0);
signal button_down, obj, recd_obj : std_logic;
signal button_count : std_logic_vector(5 downto 0);
constant zero	: std_logic_vector(6 downto 0) := "1000000";
constant one	: std_logic_vector(6 downto 0) := "1111001";
constant two	: std_logic_vector(6 downto 0) := "0100100";
constant three	: std_logic_vector(6 downto 0) := "0110000";
constant four	: std_logic_vector(6 downto 0) := "0011001";
constant five	: std_logic_vector(6 downto 0) := "0010010";
constant six	: std_logic_vector(6 downto 0) := "0000010";
constant seven	: std_logic_vector(6 downto 0) := "1111000";
constant eight	: std_logic_vector(6 downto 0) := "0000000";
constant nine	: std_logic_vector(6 downto 0) := "0010000";

begin 
  -- process to divide clock by any integer amount x2
  process (clk)
  begin
    if clk'EVENT and clk = '1' then
      if slow_clk_count = clock_divisor then
        slow_clk_count <= (others => '0');
        slow_clk <= not slow_clk;
      else
        slow_clk_count <= slow_clk_count + 1;
      end if;
    end if;
  end process;

  -- latch return pulse at 25 MHz so we don't happen
  -- to miss the first return pulse
  process (clk, reset)
  begin
    if reset = '0' then
      obj <= '0';
    elsif clk'EVENT and clk = '1' then
      if recd_obj = '1' then
        obj <= '0';
      elsif sonar_in = '1' then
        obj <= '1';
      end if;
    end if;
  end process;

  -- this process runs the whole thing
  process (slow_clk, sonar_in)
  begin
    if reset = '0' then
      state <= display;
      button_down <= '0';
      timer <= (others => '1');
      running <= '0';
      led_disp <= (others => '0');
      sonar_out <= '1';
    elsif slow_clk'EVENT and slow_clk = '1' then

      -- when a button is pressed toggle between running
      -- and not running
      -- once a button is pressed it waits for 64 clock
      -- cycles before registering the next button state
      -- (button debouncer)
      if button = '0' then
        if button_down = '0' then
          running <= not running;
          button_down <= '1';
          button_count <= (others => '0');
        else
          button_count <= button_count + 1;
        end if;
      elsif button_down = '1' then
        if button_count = 63 then
          button_down <= '0';
          button_count <= (others => '0');
        else
          button_count <= button_count + 1;
        end if;
      end if;

      -- state machine keeps firing of ultrasonic pulses
      -- and times how long it takes the first one to get
      -- back, then waits for a period of time as set by
      -- display_time, this keeps the led output from
      -- changing too quickly to be readable
      case state is
        when sending_pulse => timer <= timer + 1;
          if running = '0' then
            state <= display;
            sonar_out <= '1';
          elsif obj = '1' then
            state <= display;
            led_disp <= timer;
            timer <= (others => '0');
            sonar_out <= '1';
            recd_obj <= '1';
          elsif timer >= max_range then
            led_disp <= (others => '0');
            timer <= (others => '0');
            state <= display;
            sonar_out <= '1';
          else
            sonar_out <= not sonar_out;
          end if;
        when display => if timer > display_time then
            timer <= (others => '0');
            state <= sending_pulse;
            recd_obj <= '0';
          elsif running = '1' then
            timer <= timer + 1;
          end if;			
        when others => timer <= (others => '0');
          state <= display;
      end case;
    end if;
  end process;

  -- this process takes led_disp and interprets it into
  -- human usable data output to the led display
  -- each clock cycle is about 2.13 mm
  -- 343 m/s / 80 kHz / 2
  process (led_disp)
    begin
    -- take care of most significant digit
    -- and output on left 7 segment display
    if led_disp >=0 and led_disp < 45 then
      led1 <= zero;
      led_disp2 <= led_disp;
    elsif led_disp >= 45 and led_disp < 90 then
      led1 <= one;
      led_disp2 <= led_disp - 45;
    elsif led_disp >= 90 and led_disp < 135 then
      led1 <= two;
      led_disp2 <= led_disp - 90;
    elsif led_disp >= 135 and led_disp < 180 then
      led1 <= three;
      led_disp2 <= led_disp - 135;
    elsif led_disp >= 180 and led_disp < 225 then
      led1 <= four;
      led_disp2 <= led_disp - 180;
    elsif led_disp >= 225 and led_disp < 270 then
      led1 <= five;
      led_disp2 <= led_disp - 225;
    elsif led_disp >= 270 and led_disp < 315 then
      led1 <= six;
      led_disp2 <= led_disp - 270;
    elsif led_disp >= 315 and led_disp < 360 then
      led1 <= seven;
      led_disp2 <= led_disp - 315;
    elsif led_disp >= 360 and led_disp < 405 then
      led1 <= eight;
      led_disp2 <= led_disp - 360;
    elsif led_disp >= 405 then
      led1 <= nine;
      led_disp2 <= led_disp - 405;
    end if;

    -- take care of least significant digit
    -- and output on right 7 segment display
    if led_disp2 >= 0 and led_disp2 < 3 then
      led2 <= zero;
    elsif led_disp2 >= 3 and led_disp2 < 8 then
      led2 <= one;
    elsif led_disp2 >= 8 and led_disp2 < 12 then
      led2 <= two;
    elsif led_disp2 >= 12 and led_disp2 < 17 then
      led2 <= three;
    elsif led_disp2 >= 17 and led_disp2 < 22 then
      led2 <= four;
    elsif led_disp2 >= 22 and led_disp2 < 26 then
      led2 <= five;
    elsif led_disp2 >= 26 and led_disp2 < 31 then
      led2 <= six;
    elsif led_disp2 >= 31 and led_disp2 < 36 then
      led2 <= seven;
    elsif led_disp2 >= 36 and led_disp2 < 40 then
      led2 <= eight;
    elsif led_disp2 >= 40 then
      led2 <= nine;
    end if;
  end process;
end behavioral;

Questions? Comments? Concerns?
Email bartosek@ee.ualberta.ca