Custom PS2 Keyboard GPIO for Spartan 6 Microblaze

Description

This is an old post I never finished. I just thought I would at least share the code for the project. I made a keyboard interface with PS2 that plugged into the Spartan 6 Microblaze. You will need to make sure you have the right voltage and resistors in the right place. There is also a wire for the clock and data. PS2 is a pretty old standard and there are a lot of diagrams out there for it.



VHDL

user_logic.vhd

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

library proc_common_v3_00_a;
use proc_common_v3_00_a.proc_common_pkg.all;

-- DO NOT EDIT ABOVE THIS LINE --------------------

--USER libraries added here

------------------------------------------------------------------------------
-- Entity section
------------------------------------------------------------------------------
-- Definition of Generics:
--   C_NUM_REG                    -- Number of software accessible registers
--   C_SLV_DWIDTH                 -- Slave interface data bus width
--
-- Definition of Ports:
--   Bus2IP_Clk                   -- Bus to IP clock
--   Bus2IP_Resetn                -- Bus to IP reset
--   Bus2IP_Data                  -- Bus to IP data bus
--   Bus2IP_BE                    -- Bus to IP byte enables
--   Bus2IP_RdCE                  -- Bus to IP read chip enable
--   Bus2IP_WrCE                  -- Bus to IP write chip enable
--   IP2Bus_Data                  -- IP to Bus data bus
--   IP2Bus_RdAck                 -- IP to Bus read transfer acknowledgement
--   IP2Bus_WrAck                 -- IP to Bus write transfer acknowledgement
--   IP2Bus_Error                 -- IP to Bus error response
------------------------------------------------------------------------------

entity user_logic is
  generic
  (
    -- ADD USER GENERICS BELOW THIS LINE ---------------
    --USER generics added here
    -- ADD USER GENERICS ABOVE THIS LINE ---------------

    -- DO NOT EDIT BELOW THIS LINE ---------------------
    -- Bus protocol parameters, do not add to or delete
    C_NUM_REG                      : integer              := 4;
    C_SLV_DWIDTH                   : integer              := 32
    -- DO NOT EDIT ABOVE THIS LINE ---------------------
  );
  port
  (
    -- ADD USER PORTS BELOW THIS LINE ------------------
    
	 --USER ports added here
--	 clk, rst : in std_logic;
		ps2d, ps2c : in std_logic;
--		rx_en : in std_logic;
--		rx_done_tick : out std_logic;
		shift_en : out std_logic;
		dout : out std_logic_vector(7 downto 0);
		myinterrupt : out std_logic;
		
    -- ADD USER PORTS ABOVE THIS LINE ------------------

    -- DO NOT EDIT BELOW THIS LINE ---------------------
    -- Bus protocol ports, do not add to or delete
    Bus2IP_Clk                     : in  std_logic;
    Bus2IP_Resetn                  : in  std_logic;
    Bus2IP_Data                    : in  std_logic_vector(C_SLV_DWIDTH-1 downto 0);
    Bus2IP_BE                      : in  std_logic_vector(C_SLV_DWIDTH/8-1 downto 0);
    Bus2IP_RdCE                    : in  std_logic_vector(C_NUM_REG-1 downto 0);
    Bus2IP_WrCE                    : in  std_logic_vector(C_NUM_REG-1 downto 0);
    IP2Bus_Data                    : out std_logic_vector(C_SLV_DWIDTH-1 downto 0);
    IP2Bus_RdAck                   : out std_logic;
    IP2Bus_WrAck                   : out std_logic;
    IP2Bus_Error                   : out std_logic
    -- DO NOT EDIT ABOVE THIS LINE ---------------------
  );

  attribute MAX_FANOUT : string;
  attribute SIGIS : string;

  attribute SIGIS of Bus2IP_Clk    : signal is "CLK";
  attribute SIGIS of Bus2IP_Resetn : signal is "RST";

end entity user_logic;

------------------------------------------------------------------------------
-- Architecture section
------------------------------------------------------------------------------

architecture IMP of user_logic is

  --USER signal declarations added here, as needed for user logic
	type statetype is (idle, dps, load);
	signal state_reg, state_next : statetype;
	-- filter
	signal filter_reg, filter_next : std_logic_vector(7 downto 0);
	signal f_ps2c_reg, f_ps2c_next : std_logic;

	signal b_reg, b_next : std_logic_vector(10 downto 0);
	signal n_reg, n_next : unsigned(3 downto 0);
	signal shift_reg, shift_next : std_logic := '0';
	
	signal fall_edge : std_logic;
	signal end_buff : std_logic;
--	signal rx_en : std_logic;
	signal zeros : std_logic_vector(C_SLV_DWIDTH-1 downto 0);
	
  ------------------------------------------
  -- Signals for user logic slave model s/w accessible register example
  ------------------------------------------
  signal slv_reg0                       : std_logic_vector(C_SLV_DWIDTH-1 downto 0);
  signal slv_reg1                       : std_logic_vector(C_SLV_DWIDTH-1 downto 0);
  signal slv_reg2                       : std_logic_vector(C_SLV_DWIDTH-1 downto 0);
  signal slv_reg3                       : std_logic_vector(C_SLV_DWIDTH-1 downto 0);
  signal slv_reg_write_sel              : std_logic_vector(3 downto 0);
  signal slv_reg_read_sel               : std_logic_vector(3 downto 0);
  signal slv_ip2bus_data                : std_logic_vector(C_SLV_DWIDTH-1 downto 0);
  signal slv_read_ack                   : std_logic;
  signal slv_write_ack                  : std_logic;

begin

  --USER logic implementation added here

-- filter 
process (Bus2IP_Clk, Bus2IP_Resetn) 
begin
  if (Bus2IP_Resetn = '0') then
    filter_reg <= (others => '0');
    f_ps2c_reg <= '0';
  elsif (Bus2IP_Clk'event and Bus2IP_Clk='1') then
    filter_reg <= filter_next;
    f_ps2c_reg <= f_ps2c_next;
  end if;
end process;


filter_next <= ps2c & filter_reg(7 downto 1);
f_ps2c_next <= '1' when filter_reg = "11111111" else
               '0' when filter_reg = "00000000" else
               f_ps2c_reg;

fall_edge <= f_ps2c_reg and (not f_ps2c_next);

-- rx_en will need to be set to 1 always by this design
--rx_en <= '1';
zeros <= (others => '0');

-- registers
process (Bus2IP_Clk, Bus2IP_Resetn)
begin
	if (Bus2IP_Resetn = '0') then
		state_reg <= idle;
		n_reg <= (others => '0');
		b_reg <= (others => '0');
		slv_reg0 <= (others => '0');
	elsif (Bus2IP_Clk'event and Bus2IP_Clk='1') then
		state_reg <= state_next;
		n_reg <= n_next;
		b_reg <= b_next;
		shift_reg <= shift_reg;
		slv_reg0 <= zeros or  b_reg(8 downto 1);
	end if;
end process;

-- next-state logic
process(state_reg, n_reg, b_reg, fall_edge, ps2d)

begin
	myinterrupt <= '0';
	--rx_done_tick <= '0';
	state_next <= state_reg;
	n_next <= n_reg;
	b_next <= b_reg;
  
  case state_reg is 
    when idle =>
--      myinterrupt <= '1';
      if (fall_edge = '1') then 
        --shift in start bit
        b_next <= ps2d & b_reg(10 downto 1);
        n_next <= "1001"; -- set count to 8 again
        state_next <= dps;
      end if;
    when dps => 
 --     myinterrupt <= '0';
      if (fall_edge = '1' ) then
        b_next <= ps2d & b_reg(10 downto 1);
        if (n_reg = 0) then 
          state_next <= load;
        else 
          n_next <= n_reg - 1;
        end if;
      end if;
    when load =>
		-- here we handle if signal f0 and following signal are 
		-- asserted - we don't want to transmit them to dout.
      -- one more state to complete last shift
		state_next <= idle;
--		rx_done_tick <= '1';
		myinterrupt <= '1';
		if (b_reg(8 downto 0) = x"12" or b_reg(8 downto 0) = x"59") then
			shift_next <= not shift_reg;
			if (shift_reg = '1') then
--				rx_done_tick <= '0';
				myinterrupt <= '0';
			end if;
		elsif (b_reg(8 downto 0) = "11110000") then
			end_buff <= '1';
--			rx_done_tick <= '0';
			myinterrupt <= '0';
		elsif (end_buff = '1') then
			end_buff <= '0';	
--			rx_done_tick <= '0';
			myinterrupt <= '0';
		end if;
  end case;
end process;

shift_en <= shift_reg;
dout <= b_reg(8 downto 1);

  slv_reg_write_sel <= Bus2IP_WrCE(3 downto 0);
  slv_reg_read_sel  <= Bus2IP_RdCE(3 downto 0);
  slv_write_ack     <= Bus2IP_WrCE(0) or Bus2IP_WrCE(1) or Bus2IP_WrCE(2) or Bus2IP_WrCE(3);
  slv_read_ack      <= Bus2IP_RdCE(0) or Bus2IP_RdCE(1) or Bus2IP_RdCE(2) or Bus2IP_RdCE(3);


  -- implement slave model software accessible register(s) read mux
  SLAVE_REG_READ_PROC : process( slv_reg_read_sel, slv_reg0, slv_reg1, slv_reg2, slv_reg3 ) is
  begin

    case slv_reg_read_sel is
      when "1000" => slv_ip2bus_data <= slv_reg0;
      when "0100" => slv_ip2bus_data <= slv_reg1;
      when "0010" => slv_ip2bus_data <= slv_reg2;
      when "0001" => slv_ip2bus_data <= slv_reg3;
      when others => slv_ip2bus_data <= (others => '0');
    end case;

  end process SLAVE_REG_READ_PROC;
--
--  ------------------------------------------
--  -- Example code to drive IP to Bus signals
--  ------------------------------------------
  IP2Bus_Data  <= slv_ip2bus_data when slv_read_ack = '1' else
                  (others => '0');

  IP2Bus_WrAck <= slv_write_ack;
  IP2Bus_RdAck <= slv_read_ack;
  IP2Bus_Error <= '0';

end IMP;




ps2_keyboard.vhd

port
  (
    -- ADD USER PORTS BELOW THIS LINE ------------------
    --USER ports added here
	 ps2d, ps2c : in std_logic;
	 myinterrupt : out std_logic;
    -- ADD USER PORTS ABOVE THIS LINE ------------------

UCF and MPD files

The MPD file needs to be modified first. This will then allow us to add the io ports when mapping the device.

Microblaze Interrupt Handler

microblaze_register_handler(interrupt_handler_dispatcher, NULL);
                XIntc_EnableIntr(XPAR_INTC_0_BASEADDR, (XPAR_FIT_TIMER_0_INTERRUPT_MASK | XPAR_PUSH_BUTTONS_5BITS_IP2INTC_IRPT_MASK  | XPAR_PS2_KEYBOARD_0_MYINTERRUPT_MASK | XPAR_AXI_AC97_0_INTERRUPT_MASK ));
                XIntc_MasterEnable(XPAR_INTC_0_BASEADDR);
                microblaze_enable_interrupts();

Next

void interrupt_handler_dispatcher(void* ptr) {
        int intc_status = XIntc_GetIntrStatus(XPAR_INTC_0_BASEADDR);

  // Check the AC97. Just one method - no need for a separate handler
  if (intc_status & XPAR_AXI_AC97_0_INTERRUPT_MASK) {
          XIntc_AckIntr(XPAR_INTC_0_BASEADDR, XPAR_AXI_AC97_0_INTERRUPT_MASK);
          fillSound();

  }

        // Check the FIT interrupt first.
        if (intc_status & XPAR_FIT_TIMER_0_INTERRUPT_MASK){
                XIntc_AckIntr(XPAR_INTC_0_BASEADDR, XPAR_FIT_TIMER_0_INTERRUPT_MASK);
                timer_interrupt_handler();
        }
        // Check the push buttons.
        if (intc_status & XPAR_PUSH_BUTTONS_5BITS_IP2INTC_IRPT_MASK){
                XIntc_AckIntr(XPAR_INTC_0_BASEADDR, XPAR_PUSH_BUTTONS_5BITS_IP2INTC_IRPT_MASK);
                pb_interrupt_handler();
        }
  if (intc_status & XPAR_PS2_KEYBOARD_0_MYINTERRUPT_MASK) {
                XIntc_AckIntr(XPAR_INTC_0_BASEADDR, XPAR_PS2_KEYBOARD_0_MYINTERRUPT_MASK);
                ps2_interrupt_handler();
  }
}

PS2 Interrupt Handler

void ps2_interrupt_handler() {
  int buttonValue = XGpio_ReadReg(XPAR_PS2_KEYBOARD_0_BASEADDR, 0x0);
  xil_printf("rnValue0=%d",buttonValue);

  if (getGameInAction() == 0) {
                switch (buttonValue) {
                                // left button
                        case 28:
                                        setTankPositionGlobal(getTankPositionGlobal() - 5);
                                        drawTank(framePointer0);
                                        break;
                                case 27:
                                        if (!isHaveTankBullet()) {
                                                setHaveTankBullet(1);
                                                setHaveTankBulletSound(1);
                                                setTankBulletPositionX(getTankPositionGlobal() + 15);
                                                setTankBulletPositionY(TANK_Y_POSITION - 2*TANK_BULLET_HEIGHT);
                                                drawTankBullet(framePointer0);
                                        }
                                        break;
                                case 35:
                                        setTankPositionGlobal(getTankPositionGlobal() + 5);
                                        drawTank(framePointer0);
                                        break;
    }
  }
}

Comments are closed.