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
[vhdl]
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;
[/vhdl]




ps2_keyboard.vhd
[vhdl]
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 ——————
[/vhdl]

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

[c]
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();

[/c]

Next
[c]
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();
}
}
[/c]

PS2 Interrupt Handler

[c]
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;
}
}
}

[/c]

Leave a Reply

Your email address will not be published. Required fields are marked *