PmodMAXSONAR

From Hamsterworks Wiki!

Jump to: navigation, search

This FPGA Project was completed in March 2015.

Contents

About the project

I've got a Digilent PmodMAXSONAR module, which is pretty much like most Ultrasonic modules (like the one in Sonar project) except it has a small micro-controller that provides a serial interface in addition to the usual pulse-width encoded output. This makes it ideal to connect to systems that can't measure the width of a pulse - for example if it is a Raspberry Pi running Linux. The serial protocol is pretty simple - once the sensor enabled signal is asserted, the following characters are transmitted at 9600 baud - 'R', three digits and then the new line character.

So I've stupidly decided to process the serial data in the FPGA to generate a binary number, then display that on the LEDs. I now know that the ceiling is 70 inches above the arm restof the sofa.

Pmod sonar.jpg

The design is in three modules:

  1. A module to receive characters over the RS232 link
  2. A state machine to process those characters to give a distance
  3. The top level module to connect the signals to the outside world.

Source files

rs232_input.vhd

This is a really ugly way to implement RS232 - but what the heck, it works!

It carefully synchronizes and then removes and glitches in the signal under 1 us, but then it uses a stupid method to extract the bits.

------------------------------------------------------------
-- rs232_input.vhd - Get 5 characers over RS232
--
-- Author: Mike Field <hamster@snap.net.nz>
--
-- Not the most efficient of implementations, but it works!
--
------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity rs232_input is
    Generic ( clk_freq  : natural;
              baud_rate : natural);
    Port ( clk : in STD_LOGIC;
           serial_in     : in STD_LOGIC;
           rx_char       : out STD_LOGIC_VECTOR (7 downto 0);
           rx_char_valid : out STD_LOGIC);
    end rs232_input ;
    
architecture Behavioral of rs232_input is
    signal   serial_in_synced        : std_logic := '1';
    signal   serial_in_almost_synced : std_logic := '1';
    
    signal   deglitch_counter        : unsigned(6 downto 0) := to_unsigned(99,7);
    constant deglitch_max_count      : unsigned(6 downto 0) := to_unsigned(99,7);
    signal   deglitched              : std_logic := '1';
    signal   deglitched_last         : std_logic := '1';
    
    signal   rx_counter              : unsigned(17 downto 0) := (others => '0');
    signal   rx_state                : std_logic := '0';
    signal   new_char                : std_logic_vector(7 downto 0) := (others => '0');
begin

    -------------------------------------------------
    -- Process to first synchronise the input signal, 
    -- then to deglitch it to 100 clock cycles (1us).
    -------------------------------------------------
deglitch_proc: process(clk) 
    begin
        if rising_edge(clk) then
            deglitched_last <= deglitched;
            if deglitched = serial_in_synced then
                deglitch_counter <= deglitch_max_count;
            else
                if deglitch_counter = 0 then
                    deglitched       <= serial_in_synced;
                    deglitch_counter <= deglitch_max_count;
                else
                    deglitch_counter <= deglitch_counter-1;
                end if;
            end if;
            serial_in_synced        <= serial_in_almost_synced;
            serial_in_almost_synced <= serial_in;  
        end if;
    end process;

rx_proc: process(clk)
    begin
        if rising_edge(clk) then
            rx_char_valid <= '0'; 
            if rx_state = '0' then
                if deglitched_last = '1' and deglitched = '0' then
                    rx_state   <= '1';
                    rx_counter <= (others => '0');
                end if;
            else
                if rx_counter = to_unsigned(3*clk_freq/baud_rate/2,18) then
                    new_char <= deglitched & new_char(7 downto 1); 
                elsif rx_counter = to_unsigned(5*clk_freq/baud_rate/2,18) then
                    new_char <= deglitched & new_char(7 downto 1); 
                elsif rx_counter = to_unsigned(7*clk_freq/baud_rate/2,18) then
                    new_char <= deglitched & new_char(7 downto 1); 
                elsif rx_counter = to_unsigned(9*clk_freq/baud_rate/2,18) then
                    new_char <= deglitched & new_char(7 downto 1); 
                elsif rx_counter = to_unsigned(11*clk_freq/baud_rate/2,18) then
                    new_char <= deglitched & new_char(7 downto 1); 
                elsif rx_counter = to_unsigned(13*clk_freq/baud_rate/2,18) then
                    new_char <= deglitched & new_char(7 downto 1); 
                elsif rx_counter = to_unsigned(15*clk_freq/baud_rate/2,18) then
                    new_char <= deglitched & new_char(7 downto 1); 
                elsif rx_counter = to_unsigned(17*clk_freq/baud_rate/2,18) then
                    new_char <= deglitched & new_char(7 downto 1); 
                elsif rx_counter = to_unsigned(19*clk_freq/baud_rate/2,18) then
                    rx_char <= new_char;
                    rx_char_valid <= '1'; 
                    rx_state   <= '0';
                end if;
                rx_counter <= rx_counter +1;
            end if; 
            
        end if;
    end process;
end Behavioral;

pmod_sonar.vhd

This is a simple two-state FSM to process the received characters. If any invalid characters are received then 'error' is asserted until a valid reading is received.

I would recommend that you also add a timeout, which sets the error signal if no characters have been received for a few milliseconds to let you know if you are using stale data.

And it isn't perfect, as it will accept any number of digits between the 'R' and new line.

------------------------------------------------------------
-- pmod_sonar.vhd - Parse the input from the PmodMAXSONAR
--
-- Author: Mike Field <hamster@snap.net.nz>
--
-- Pretty simple implementation to process the ASCII strings
-- from the PMODmaxsonar.
--
------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity pmod_sonar is
    Generic ( clk_freq  : natural;
              baud_rate : natural);
    Port ( clk : in STD_LOGIC;
           sonar_serial_in : in STD_LOGIC;
           sonar_enable    : out STD_LOGIC;
           distance        : out STD_LOGIC_VECTOR (11  downto 0);
           error           : out STD_LOGIC := '1');
end pmod_sonar;

architecture Behavioral of pmod_sonar is
    component rs232_input is
    Generic ( clk_freq  : natural;
              baud_rate : natural);
    Port (    clk           : in STD_LOGIC;
              serial_in     : in STD_LOGIC;
              rx_char       : out STD_LOGIC_VECTOR (7 downto 0);
              rx_char_valid : out STD_LOGIC);
end component;
      
    signal d               : unsigned(11 downto 0) := (others => '0');
    signal processing_data : STD_LOGIC := '0';

    constant char_R        : STD_LOGIC_VECTOR (7 downto 0) := "01010010";
    constant char_0        : STD_LOGIC_VECTOR (7 downto 0) := "00110000";
    constant char_9        : STD_LOGIC_VECTOR (7 downto 0) := "00111001";
    
    constant char_Newline  : STD_LOGIC_VECTOR (7 downto 0) := "00001101";
    
    signal   new_char      : STD_LOGIC_VECTOR (7 downto 0) := (others => '0');
    signal   new_char_valid : STD_LOGIC;
begin

i_rs232_input: rs232_input GENERIC MAP (
        clk_freq  => clk_freq,
        baud_rate => baud_rate)
    PORT  MAP (
        clk           => clk,
        serial_in     => sonar_serial_in,
        rx_char       => new_char,
        rx_char_valid => new_char_valid);

    sonar_enable <= '1';

clk_proc: process(clk)
    begin
        if rising_edge(clk) then            
            if new_char_valid = '1' then
                if processing_data = '0' then
                    if new_char = char_r then
                        d <= (others => '0');
                        processing_data  <= '1';
                    end if;
                else
                    if new_char = char_newline then
                        distance         <= std_logic_vector(d);
                        processing_data  <= '0';
                        error            <= '0';
                    elsif unsigned(new_char) >= unsigned(char_0) and unsigned(new_char) <= unsigned(char_9) then
                        d <= (d(d'high-1 downto 0) &"0") 
                           + (d(d'high-3 downto 0) &"000")
                           + unsigned(new_char(3 downto 0));
                    else
                        -- Error - non digit seen.
                        error <= '1';
                        processing_data  <= '0';
                    end if;
                end if;               
            end if;
        end if;
    end process;
end Behavioral;

pmod_sonar_test.vhd

------------------------------------------------------------
-- pmod_sonar_test.vhd - A simple test of the PMODmaxsonar
--
-- Author: Mike Field <hamster@snap.net.nz>
--
-- Just wire a PmodMAXSONAR to the interface module, and the 
-- outputs to the LEDs
--
------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity pmod_sonar_test is
    Port ( clk : in STD_LOGIC;
           pmod_sonar_serial : in STD_LOGIC;
           pmod_sonar_enable : out STD_LOGIC;
           led               : out STD_LOGIC_VECTOR (15 downto 0));
end pmod_sonar_test;

architecture Behavioral of pmod_sonar_test is
    component pmod_sonar is
    Generic ( clk_freq  : natural;
              baud_rate : natural);
    Port ( clk             : in STD_LOGIC;
           sonar_serial_in : in STD_LOGIC;
           sonar_enable    : out STD_LOGIC;
           distance        : out STD_LOGIC_VECTOR (11 downto 0);
           error           : out STD_LOGIC);
    end component;

begin
        
i_pmod_sonar: pmod_sonar Generic Map (clk_freq  => 100000000, baud_rate => 9600) Port map ( 
           clk             => clk,
           sonar_serial_in => pmod_sonar_serial,
           sonar_enable    => pmod_sonar_enable,
           error           => led(15),
           distance        => led(11 downto 0));
end Behavioral;

basys3.xdc

The constraints for the clock signal, the LEDs and the PmodMAXSONAR plugged into the the top row of PMOD JA1

set_property PACKAGE_PIN W5 [get_ports clk]							
	set_property IOSTANDARD LVCMOS33 [get_ports clk]
	create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports clk]

## LEDs
set_property PACKAGE_PIN U16 [get_ports {led[0]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[0]}]
set_property PACKAGE_PIN E19 [get_ports {led[1]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[1]}]
set_property PACKAGE_PIN U19 [get_ports {led[2]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[2]}]
set_property PACKAGE_PIN V19 [get_ports {led[3]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[3]}]
set_property PACKAGE_PIN W18 [get_ports {led[4]}]					
    set_property IOSTANDARD LVCMOS33 [get_ports {led[4]}]
set_property PACKAGE_PIN U15 [get_ports {led[5]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[5]}]
set_property PACKAGE_PIN U14 [get_ports {led[6]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[6]}]
set_property PACKAGE_PIN V14 [get_ports {led[7]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[7]}]
set_property PACKAGE_PIN V13 [get_ports {led[8]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[8]}]
set_property PACKAGE_PIN V3 [get_ports {led[9]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[9]}]
set_property PACKAGE_PIN W3 [get_ports {led[10]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[10]}]
set_property PACKAGE_PIN U3 [get_ports {led[11]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[11]}]
set_property PACKAGE_PIN P3 [get_ports {led[12]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[12]}]
set_property PACKAGE_PIN N3 [get_ports {led[13]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[13]}]
set_property PACKAGE_PIN P1 [get_ports {led[14]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[14]}]
set_property PACKAGE_PIN L1 [get_ports {led[15]}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {led[15]}]


##Pmod Header JA
##Sch name = JA2
set_property PACKAGE_PIN L2 [get_ports {pmod_sonar_enable}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {pmod_sonar_enable}]
##Sch name = JA3
set_property PACKAGE_PIN J2 [get_ports {pmod_sonar_serial}]					
	set_property IOSTANDARD LVCMOS33 [get_ports {pmod_sonar_serial}]

Personal tools