Electronic – State switches in FSM

clockdebouncestate-machinesvhdl

I have a simple board with 6 buttons, consisting of 3 columns and 2 rows I would like to detect the pressed button. My code below works with this fsm:

  scan_fsm : process (reset, clk)
  begin  -- process key_scanner

     if reset = '1' then 
            Send <= '0';
            fsm_state <= start;
            scannel_val <= "0000";

    elsif clk'event and clk = '1' then

      if state_inc = '1' then

        scan_complete <= '0';

        case fsm_state is

          when start =>
               Send <= '0';
            scannel_val <= "0000";
            my_switch   <= "001";
            fsm_state <= state1;

          when state1 => 
            case bcd_val is
              when "01" =>
                    Send <= '1';
                    scannel_val <= "0001";
              when "10" =>
                    Send <= '1';
                    scannel_val <= "0010"; -- Value 2 
              when others =>
                    scannel_val <= "0000";
                    Send <= '0';
            end case;
            my_switch   <= "010";
            fsm_state <= state2;

          when state2 => 
            case bcd_val is
              when "01" => 
                  Send <= '1';
                  scannel_val <= "0011"; 
              when "10" => 
                  Send <= '1';
                  scannel_val <= x"0100";  -- Value 4
              when others => 
                    scannel_val <= "0000";
                    Send <= '0'; 
            end case;
            my_switch   <= "100";
            fsm_state <= state3;

          when state3 => 
            case bcd_val is
              when "01" => 
                    Send <= '1';
                    scannel_val <= "0101"; 
              when "10" => 
                    Send <= '1';
                    scannel_val <= "0110";
              when others => 
                    scannel_val <= "0000";
                    Send <= '0'; 
            end case;
                my_switch   <= "001";
            fsm_state <= state1;
            scan_complete <= '1';

           when others => scannel_val <= "0000";
        end case;

      end if;
    end if;
  end process scan_fsm;

I have 4 states, start and a state for each column. I would like to improve my board and prevent glitches. I would like to add fatures like if 2 buttons pressed at the same time, I should get the first value which reached few miliseconds or nanoseconds before the other one. Also I would like to prevent a second value being read or a third when already a button is being pressed.

So, no matter how many buttons pressed, only one value should be read. I thought about adding a 5th state, idle, when this occurs but then how do I get out of idle?

Instead of idle I can also switch to state "start" but then this would make the send signal "0" which would be false if already a button is being pressed. Also how many processes are in this FSM? What is a process in VHDL? Is it 3; reset, clk and state_inc? statce_inc becomes "1" every 200Hz with the help of a counter. I added some debouncing code I found from the internet but doesn't help 🙁 Anyone has a debouncing suggestion?

Another issue, I would like to get rid of the state start actually, the only reason I have it is because I should assign my_switch to "001" before state1 . In my first implementation I was assigning my_switch to "001" in state1 and "010" in state2 and "100" in state3. In this implementation since my keypad is

______
|1|2|3|
|4|5|6|

When I pressed 2, 1 was displayed when I press 3, 2 was displayed. I was thinking Cpp/Java way but I was wrong… then I added a dummy state start just to set my_switch before entering the real scanning… So if I can solve this I can also save a state! Suggestions are welcome! I solved this with entering the first scan without a set value. Do you have better ideas?

Best Answer

What you can do is create an idle state, and when you detect a button press you switch straight to the idle. Then you can have a counter in idle which waits for a while before you start scanning buttons again.

For example:

-- include numeric std
signal idle_counter : unsigned(31 downto 0);

      ...

      when state3 => 
        case bcd_val is
          when "01" => 
                Send <= '1';
                scannel_val <= "0101"; 
                fsm_state <= idle;   -- switch to idle
          when "10" => 
                Send <= '1';
                scannel_val <= "0110";
                fsm_state <= idle;   -- switch to idle
          when others => 
                scannel_val <= "0000";
                Send <= '0'; 
                fsm_state <= state1;   -- carry on scanning
        end case;
        idle_counter <= (others => '0');
        ...
      when idle =>
        if (idle_counter < some_timeout) then  -- some_timeout is how long you want to wait (in clock cycles)
           idle_counter <= idle_counter + "1"; -- increment counter
           fsm_state <= idle;                  -- stay here
        else
           idle_counter <= (others => '0');    -- reset counter
           fsm_state <= state1;                -- go back into checking
        end
        --scannel_val is valid here, so you can do something with it if you'd like.
        ...
      when others => scannel_val <= "0000";

There is one process in this code you've posted.

It starts with

scan_fsm : process (reset, clk) <= Sensitivity list
begin  -- process key_scanner

and ends with

end process scan_fsm;

The clock and reset are part of the sensitivity list. That means that this code is only going to be triggered when either clock changes or reset changes.

De-bouncing can be done in a separate process. Basically, you want to make sure that the button was pressed for a number of microseconds before it triggers. What we want to do is save the value of the input for a number of clock cycles. Then only register a 1 when the history has been constantly 1 for for the past 32 clock cycles.

Example:

-- these are our row inputs (to become bdc_val)
signal row1 : in std_logic; 
signal row2 : in std_logic;

...
-- this is our history vector
signal row1_z : std_logic_vector(31 downto 0);
signal row2_z : std_logic_vector(31 downto 0);

-- debounced signal
signal bcd_val : std_logic_vector(1 downto 0);

-- a whole vector of ones for convenience
signal ones    : std_logic_vector(31 downto 0);
...

ones <= (others => '1');

-- generate our histories
gen_z : process (reset, clk)
begin  
  if (reset = '1') then
    row1_z <= (others => '0');
    row2_z <= (others => '0');
    bcd_val <= (others => '0');
  elsif (rising_edge(clk)) then
    row1_z(31 downto 1) <= row1_z(30 downto 0); -- shift everything up 1
    row1_z(0) <= row1;                          -- save the most recent input
    row2_z(31 downto 1) <= row2_z(30 downto 0); -- shift everything up 1
    row2_z(0) <= row2;                          -- save the most recent input

    -- we only want bcd_val to be 1 when the entire row history for the past 32 cc is 1
    if (row1_z = ones) then
      bcd_val(0) <= '1';
    else 
      bcd_val(0) <= '0';
    end

    if (row2_z = ones) then
      bcd_val(1) <= '1';
    else 
      bcd_val(1) <= '0';
    end
  end
end