Das PS/2-Interface alter Tastaturen (oder auch Mäuse) kann genutzt werden um ohne viel Aufwand eine einfache Eingabemöglichkeit für ein FPGA zu realisieren. In diesem Artikel möchte ich zeigen, wie eine PS/2 Tastatur an ein FPGA angeschlossen werden und die empfangenen Daten auf einer LED-Leiste ausgegeben werden können.
Aufbau der PS/2-Schnittstelle:
Bei der PS/2-Schnittstelle handelt es sich um eine einfache und (früher) sehr weit verbreitete serielle Schnittstelle für die Eingabegeräte eines Computers (z. B. Mäuse und Tastaturen). Als Steckverbinder wird Geräteseitig ein sechspoliger Mini-DIN-Stecker verwendet, welcher wie folgt belegt ist:
Hinweis:
Bei sehr alten Mainboards mit einem AT-Format wird ein fünfpoliger Steckverbinder genutzt. Beide Schnittstellen sind in der Regel kompatibel zueinander.
Die Schnittstelle ist als Open-Collector ausgeführt, weswegen zwei zusätzliche Pull-up Widerstände für den Betrieb notwendig sind. Die Betriebsspannung der Geräte ist auf 5 V festgelegt.
Über eine Kombination von unterschiedlichen Pegeln kann der Empfänger dem Sender seinen aktuellen Status mitteilen und ggf. Übertragungen pausieren.
Ein PS/2 Datenpaket besteht aus 11 Bits, welche LSB First gesendet werden. Jede Nachricht ist dabei folgendermaßen aufgebaut:
Bei den übertragenen Daten handelt es sich Scancodes der gedrückten Tasten. Die Vergabe der Scancodes ist historisch gegeben und stammt von den früheren IBM-Tastaturen.
Der Wert des Paritätsbits hängt von der Anzahl übertragender Einsen im Datenpaket ab. Es wird gesetzt, wenn die Anzahl enthaltener Einsen gerade ist und wird dem entsprechend gelöscht, wenn die Anzahl enthaltener Einsen ungerade ist.
Wird eine Taste länger als 500 ms (standardmäßig) gedrückt, wiederholt die Tastatur das Senden des Codes in sehr schneller Folge, bis die Taste losgelassen wird. Beim Loslassen der Taste sendet die Tastatur ein 0xF0 gefolgt von dem Zahlencode der entsprechenden Taste. Auf diese Weise weiß der Host, dass die Taste losgelassen wurde.
Implementierung für das FPGA:
Das PS/2-Interface besteht aus zwei Komponenten:
- Den Schieberegistern für die PS/2-Tastatur
- Ein Zustandsautomat für die Erzeugung der Status-Signale
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity PS2_Interface is Generic ( TIMEOUT : INTEGER := 250000 ); Port ( Clock : in STD_LOGIC; nReset : in STD_LOGIC; -- PS/2 interface signals PS2_Clk : in STD_LOGIC; PS2_Data : in STD_LOGIC; Valid : in STD_LOGIC; RxComplete : out STD_LOGIC; Busy : out STD_LOGIC; RxData : out STD_LOGIC_VECTOR(7 downto 0) ); end PS2_Interface; architecture PS2_Interface_Arch of PS2_Interface is type PS2_State_t is (STATE_WAIT_START, STATE_RECEIVE, STATE_DATA_READY); signal PS2_DataSR : STD_LOGIC_VECTOR(1 downto 0) := (others => '1'); signal PS2_ClkSR : STD_LOGIC_VECTOR(1 downto 0) := (others => '1'); signal PS2_Buffer : STD_LOGIC_VECTOR(10 downto 0) := (others => '1'); signal CurrentState : PS2_State_t := STATE_WAIT_START; begin PS2_Shift_Proc : process variable TimeoutCounter : INTEGER := 0; begin wait until rising_edge(Clock); PS2_DataSR <= PS2_DataSR(0) & PS2_Data; PS2_ClkSR <= PS2_ClkSR(0) & PS2_Clk; if(PS2_ClkSR = "10") then PS2_Buffer <= PS2_DataSR(1) & PS2_Buffer(10 downto 1); end if; case CurrentState is when STATE_WAIT_START => if((PS2_DataSR(1) = '0') and (PS2_ClkSR(1) = '1')) then TimeoutCounter := TIMEOUT; CurrentState <= STATE_RECEIVE; else CurrentState <= STATE_WAIT_START; end if; when STATE_RECEIVE => if(PS2_Buffer(0) = '0') then RxData <= PS2_Buffer(8 downto 1); CurrentState <= STATE_DATA_READY; elsif(Timeout = 0) then PS2_Buffer <= (others => '1'); CurrentState <= STATE_WAIT_START; else TimeoutCounter := Timeout - 1; CurrentState <= STATE_RECEIVE; end if; when STATE_DATA_READY => if(Valid = '1') then PS2_Buffer <= (others => '1'); CurrentState <= STATE_WAIT_START; else CurrentState <= STATE_DATA_READY; end if; end case; if(nReset = '0') then PS2_Buffer <= (others => '0'); CurrentState <= STATE_WAIT_START; end if; end process; Busy <= '1' when (CurrentState = STATE_RECEIVE) else '0'; RxComplete <= '1' when (CurrentState = STATE_DATA_READY) else '0'; end PS2_Interface_Arch;
Über das Schieberegister wird das Takt- und das Datensignal in das FPGA einsynchronisiert. Bei einer fallenden Flanke wird der aktuelle Zustand der Datenleitung in dem Vektor PS2_Buffer
gespeichert:
wait until rising_edge(Clock); PS2_DataSR <= PS2_DataSR(0) & PS2_Data; PS2_ClkSR <= PS2_ClkSR(0) & PS2_Clk; if(PS2_ClkSR = "10") then PS2_Buffer <= PS2_DataSR(1) & PS2_Buffer(10 downto 1); end if;
Parallel wartet der Zustandsautomat so lange im Zustand STATE_WAIT_START
bis ein Startbit empfangen wird. Sobald ein Startbit empfangen wurde wechselt der Zustandsautomat in den Zustand STATE_RECEIVE
und verweilt dort so lange bis ein Timeout aufgetreten oder das Startbit einmal komplett durchgeschoben wurde (sprich alle Datenbits gesendet worden sind).
Ist die Übertragung erfolgreich abgeschlossen worden, so werden die Datenbits aus dem Vektor PS2_Buffer
an den Ausgang des PS/2-Interfaces gelegt und in den Zustand STATE_DATA_READY
gewechselt. In diesem Zustand wird das RxComplete
-Flag gesetzt und der Zustandsautomat wartet auf einen Handshake, welcher durch das Setzen des Valid
-Eingangs eingeleitet wird.
Das fertige Interface kann nun in ein Top-Level Design eingefügt und auf die Hardware übertragen werden:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity Top is Port ( Clock : in STD_LOGIC; nReset : in STD_LOGIC; PS2_Clk : in STD_LOGIC; PS2_Data : in STD_LOGIC; Valid : in STD_LOGIC; Status : out STD_LOGIC_VECTOR(1 downto 0); Data : out STD_LOGIC_VECTOR(7 downto 0) ); end Top; architecture Top_Arch of Top is component PS2_Interface is Port ( Clock : in STD_LOGIC; nReset : in STD_LOGIC; PS2_Clk : in STD_LOGIC; PS2_Data : in STD_LOGIC; Valid : in STD_LOGIC; RxComplete : out STD_LOGIC; Busy : out STD_LOGIC; RxData : out STD_LOGIC_VECTOR(7 downto 0) ); end component; begin Interface : PS2_Interface port map (PS2_Clk => PS2_Clk, PS2_Data => PS2_Data, Clock => Clock, nReset => '1', Valid => Valid, Busy => Status(0), RxComplete => Status(1), RxData => Data ); end Top_Arch;
Alternativ kann das Desin auch mit der nachfolgenden Testbench verifiziert werden:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; architecture Top_TB_Arch of Top_TB is procedure PS2_Transmit( signal Data : in STD_LOGIC_VECTOR(7 downto 0); signal PS2_Clock : out STD_LOGIC; signal PS2_Data : out STD_LOGIC) is variable Parity : STD_LOGIC := '1'; begin wait for 1 ns; PS2_Clock <= '1'; PS2_Data <= '1'; wait for 5us; PS2_Data <= '0'; wait for 20us; PS2_Clock <= '0'; wait for 40us; PS2_Clock <= '1'; wait for 20us; for i in 0 to (Data'length - 1) loop PS2_Data <= Data(i); if(Data(i) = '1') then Parity := not Parity; end if; wait for 20us; PS2_Clock <= '0'; wait for 40us; PS2_Clock <= '1'; wait for 20us; end loop; PS2_Data <= Parity; wait for 20us; PS2_Clock <= '0'; wait for 40us; PS2_Clock <= '1'; wait for 20us; PS2_Data <= '1'; wait for 20us; PS2_Clock <= '0'; wait for 40us; PS2_Clock <= '1'; wait for 20us; end procedure; constant CLOCKPERIODE : TIME := 8 ns; signal SimulationClock : STD_LOGIC := '0'; signal SimulationResetN : STD_LOGIC := '1'; signal PS2_Data : STD_LOGIC := '1'; signal PS2_Clk : STD_LOGIC := '1'; signal Valid : STD_LOGIC := '0'; signal Status : STD_LOGIC_VECTOR(1 downto 0); signal Data : STD_LOGIC_VECTOR(7 downto 0); signal KeyboardData : STD_LOGIC_VECTOR(7 downto 0) := (others => '0'); component Top is Port ( Clock : in STD_LOGIC; nReset : in STD_LOGIC; PS2_Clk : in STD_LOGIC; PS2_Data : in STD_LOGIC; Valid : in STD_LOGIC; Status : out STD_LOGIC_VECTOR(1 downto 0); Data : out STD_LOGIC_VECTOR(7 downto 0) ); end component; begin process begin wait for (CLOCKPERIODE / 2); SimulationClock <= '1'; wait for (CLOCKPERIODE / 2); SimulationClock <= '0'; end process; DUT : Top port map ( PS2_Data => PS2_Data, PS2_Clk => PS2_Clk, Valid => Valid, Status => Status, Data => Data, Clock => SimulationClock, nReset => SimulationResetN ); Stimulus : process begin KeyboardData <= x"AA"; PS2_Transmit(KeyboardData, PS2_Clk, PS2_Data); wait; end process; end Top_TB_Arch;
Das komplette Projekt kann bei GitHub heruntergeladen werden.
Schreibe einen Kommentar