📜 ⬆️ ⬇️

Making a Raspberry Keyboard with a PS / 2 Interface

Hello, dear habrazhiteli!

In this post, I’ll talk about PS / 2 keyboard emulation using the Raspberry Pi.

Recently, in one of the hot evenings, my old friend called me and asked me to write a program for him. It was necessary to automate data entry (bar codes). The outlet where he worked was owned by a chain of Italian stores. It is clear that all the work with the goods was carried out in the Italian program. But due to the fact that in our country the majority of them are trained to work on 1C, it was decided to sell it, and by the end of the working day, upload the sales results to the Italian system. This is where the inconvenience.

During the day, the quantity of goods sold could exceed one and a half thousand items. Entering the barcode of each of them into the Italian system has become problematic. The input algorithm was as follows: a barcode was entered, the "Enter" key was pressed - and a new one. We agreed to meet in the morning and consider in detail the options.
')
Without thinking for a long time, I wrote a program that consistently takes data from an Excel sheet and sends it to the active window. Making sure that everything works, I went to bed.

The next day I was in for a surprise, which I, for some reason, did not immediately think. By car with the Italian program were hard group policies that prohibit the launch of third-party programs on the hash list. Admins (also Italians, by the way) did not want to take responsibility and allow the launch of third-party programs. I did not get approval for circumventing the protection system from local management. It was then that the idea of ​​solving this problem from the hardware side came (after all, no one forbids connecting a third-party keyboard). I remembered about the Raspberry Pi, which was gathering dust at my home.

Began the search for documentation describing the PS / 2 interface. Fortunately, on the first available resource (), I found all the necessary information.

PS / 2 keyboard cable pinout


As it turned out, 4 were necessary for the entire work of the wire.
Black is the earth;
Red - 5V;
Yellow - pin time synchronization (CLOCK);
White - data pin (DATA).

Colors may vary slightly. Even from these 4 wires I needed only two - yellow and white (in the ground and 5v my device, unlike the keyboard, did not need).

PS / 2 protocol job description (keyboard -> host controller)


We will define the terminology: if there is voltage on the pin, we will consider this state as 1, otherwise 0.

By default, when the computer is able to receive data, the state is set to 1 on both pins (CLOCK and DATA). It is installed by the host controller on the computer's motherboard. We take control of ourselves, applying our voltage to both pins (the controller of the MC will release its voltage). To initialize the data transfer, we need to send the 0th bit to the computer (not to be confused with the state 0). To do this, in the DATA pin, set the state to 0 and immediately after this, the state of the CLOCK pin is also set to 0 (in that order). We let the host controller know that we want to transmit the first bit. Now, if you return the state of the CLOCK pin to state 1, the host controller counts the first bit. Thus, we will transmit all other bits.

The first bit is always 0, this is the start-bit (let us know that we are transmitting data).
Next, we transmit 8 bits of the scan code of the key we want to press.
The tenth bit is the parity bit (if the number of units is even, then 1, otherwise 0).
The last 11 bits is a stop bit, always 1.

Thus, one data packet is formed from 11 bits.

For example, if we want to press the "0" key (scan code 45h = 1000101 in binary form) the following array of bits is sent to the host controller: 01010001001.

By accepting this data, the computer will process the command for pressing this key, but it still needs to be pressed. To do this, you must first send the F0h command, and then - re-scan the key code that must be pressed. Also, it is necessary to hold a pause between state changes. Experimentally, I found the most appropriate: 0.00020 seconds if working on Python and 1 nanosek, if you code in Java.

Connection diagram to the host controller


image

A few words about why I connected the device in parallel with the keyboard. When you turn on the computer, the BIOS checks the status of the PS / 2 connectors. Computer and keyboard exchange readiness to work. The keyboard should conduct internal diagnostics and report to the computer about its readiness. Only after that the BIOS allows working with the PS / 2 interface. Implement reading commands from the computer, I was too lazy.

Now when you turn on the computer, the keyboard will report to it about its readiness. After that, I turn on the Raspberry Pi and as soon as it takes control over the interface, work from the keyboard will be impossible. I have not identified any conflicts during the work. Everything worked as it should, except that occasionally the computer incorrectly processed the data sent to it, and since my Raspberry is not configured to receive data (in this case, the command to resend the key code), the error was simply ignored. This problem was solved by reducing the frequency of data transmission.

Software part


At first I wrote the server part in Java using the pi4j library, but as the logic analyzer showed, the Java machine worked poorly with delays (they were too large and the computer very often did not correctly receive the data). Python showed itself much better, the code was executed quickly, and the system was loaded several times less.

Here is the Phyton code itself:

import socket, select import RPi.GPIO as GPIO import time #       def sendBit(pinState): if pinState==0: GPIO.output(pinData, 0) else: GPIO.output(pinData, 1) GPIO.output(pinClock, 0) time.sleep(sleepInterval) GPIO.output(pinClock,1) time.sleep(sleepInterval) #      def sendArray(args): GPIO.setmode(GPIO.BCM) # ,   (  1) GPIO.setup(pinData,GPIO.OUT, initial=GPIO.HIGH) GPIO.setup(pinClock,GPIO.OUT, initial=GPIO.HIGH) # 0  GPIO.output(pinData, 0) GPIO.output(pinClock, 0) time.sleep(sleepInterval) GPIO.output(pinClock,1) time.sleep(sleepInterval) #    for v in args: sendBit(v) # - GPIO.output(pinData, 1) GPIO.output(pinClock, 0) time.sleep(sleepInterval*2) GPIO.output(pinClock,1) time.sleep(sleepInterval*200) GPIO.cleanup() pinClock=4 pinData=15 sleepInterval=0.00020 CONNECTION_LIST = [] RECV_BUFFER = 4096 PORT = 8928 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # this has no effect, why ? server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(("0.0.0.0", PORT)) server_socket.listen(10) CONNECTION_LIST.append(server_socket) print "     " + str(PORT) while 1: read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[]) for sock in read_sockets: if sock == server_socket: sockfd, addr = server_socket.accept() CONNECTION_LIST.append(sockfd) else: try: data = sock.recv(RECV_BUFFER) i=0 scanCode=[] print " :"+data for bukva in data: if bukva=="1": scanCode.append(int(1)) else: scanCode.append(int(0)) i=i+1 sendArray(scanCode) sendArray([0,0,0,0,1,1,1,1,1]) sendArray(scanCode) sock.close() CONNECTION_LIST.remove(sock) except: sock.close() CONNECTION_LIST.remove(sock) continue server_socket.close() 


Java server part:
 import com.pi4j.io.gpio.GpioController; import com.pi4j.io.gpio.GpioFactory; import com.pi4j.io.gpio.GpioPinDigitalOutput; import com.pi4j.io.gpio.PinState; import com.pi4j.io.gpio.RaspiPin; import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent; import com.pi4j.io.gpio.event.GpioPinListenerDigital; import com.pi4j.io.gpio.GpioPinDigitalInput; import com.pi4j.io.gpio.PinPullResistance; import java.net.ServerSocket; import java.net.Socket; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; class controller { private static int[] sc1 = {0,0,0,0,1,1,1,1,1}; //F0h private static int port = 8928; public static GpioController gpio=GpioFactory.getInstance(); public static GpioPinDigitalOutput pinClock = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_07,"Com1", PinState.HIGH); public static GpioPinDigitalOutput pinData = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_16,"Com2", PinState.HIGH); public static void main(String[] args) throws IOException { //     ServerSocket server=null; try { server = new ServerSocket(port); } catch (IOException e) { System.err.println("Could not listen on port:"+port); System.exit(1); } Socket client = null; System.out.println("  !"); while (true) { try { client = server.accept(); BufferedReader in = new BufferedReader (new InputStreamReader (client.getInputStream())); String line; while ((line = in.readLine())!=null) { if (line.indexOf("exit")>-1) { return; } else { //     int[] buf=new int[9]; for (int i=0; i<9;i++) { buf[i]=Integer.parseInt(line.substring(i,i+1)); } System.out.println(" : "+line);//   signal(buf); //    F0h signal(sc1); //   signal(buf); PrintWriter out = new PrintWriter (client.getOutputStream(), true); out.print("finished\r\n"); out.flush(); } } } } catch (IOException e) { client.close(); System.out.println("Shutdown gpio controller"); } } } //      static private void setPin(GpioPinDigitalOutput pinObj,int signalByte) { if (signalByte==0) { pinObj.low(); } else { pinObj.high(); } } //     static private void signal(int[] bits) { int sleepInterval=1; // - setPin(pinData,0); setPin(pinClock,0); sleeper(sleepInterval); setPin(pinClock,1); sleeper(sleepInterval); //    for (int i=0; i<9; i++) { setPin(pinData,bits[i]); setPin(pinClock,0); sleeper(sleepInterval); setPin(pinClock,1); sleeper(sleepInterval); } // - setPin(pinData,1); setPin(pinClock,0); sleeper(sleepInterval*2); setPin(pinClock,1); sleeperM(1); } //     static private void sleeper(int i) { try { Thread.sleep(0,i); } catch(InterruptedException e) { System.out.println("Sleepin errore"); } } //     . static private void sleeperM(int i) { try { Thread.sleep(i); } catch(InterruptedException e) { System.out.println("Sleepin errore"); } } } 


Java client part:
 import java.net.ServerSocket; import java.net.Socket; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.Scanner; class tcpClient { private static int port = 8928; public static void main (String[] args) throws IOException { String host="localhost"; Socket server; PrintWriter out=null; Scanner sc= new Scanner (System.in); System.out.println("Input host adress:"); host=sc.nextLine(); System.out.println("Connecting to host "+host+"..."); try { server = new Socket(host,port); out = new PrintWriter (server.getOutputStream(), true); } catch (IOException e) { System.err.println(e); System.exit(1); } System.out.println("Connected!"); BufferedReader stdIn = new BufferedReader (new InputStreamReader(System.in)); String msg; while ((msg = stdIn.readLine()) != null) { out.println(msg); } } } 


An example of a digitized signal sent from my device to a computer. Lower channel CLOCK, upper DATA. I send signals from Python


image

Conclusion


Of course, use Raspberry for such a shallow task of pure water waste. You could use an Arduino or build a circuit on a cheap arm processor. But it was just an improvisation that first came to mind, and I didn’t really want to wait until all the necessary parts from China arrived.

In general, I hope someone will benefit from this experience. Personally, I got a lot of pleasure from the whole process.

Source: https://habr.com/ru/post/234939/


All Articles