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.
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:
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.
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.
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;