PS / 2インターフェイスを使用してRaspberryキーボードを作成する

こんにちは、ハラジテリ!



この投稿では、Raspberry Piを使用してPS / 2キーボードをエミュレートすることについて説明します。



最近、暑い夜に、私の古い友人から電話があり、彼のためにプログラムを書くように頼まれました。 データ入力(バーコード)を自動化する必要がありました。 彼が働いていたアウトレットはイタリアの店のチェーンに属していました。 商品に関するすべての作業がイタリアのプログラムで行われたことは明らかです。 しかし、わが国の大部分は1Cで動作するように訓練されているという事実のために、1Cで販売することを決定し、営業日までに販売結果をイタリアのシステムにアップロードしました。 これは不便が生じた場所です。



1日に販売される商品の量は、1.5千ポジションを超える可能性があります。 それらのそれぞれのバーコードをイタリアのシステムに入力することは問題になりました。 入力アルゴリズムは次のとおりでした。彼らはバーコードを入力し、「Enter」を押しました-そして再び。 午前中に会合を開き、オプションを詳細に検討することに同意しました。



考え直すことなく、Excelシートからデータを順番に取得してアクティブウィンドウに送信するプログラムを作成しました。 すべてが機能することを確認した後、私は寝ました。



翌日、なんらかの理由ですぐに考えなかった驚きが待っていました。 イタリアのプログラムがインストールされたマシンでは、ハッシュリストでサードパーティのプログラムの起動を禁止する厳しいグループポリシーがありました。 管理者(また、イタリア人)は、責任を負い、サードパーティのプログラムの起動を許可したくありませんでした。 保護システムを回避するために、私は地元の指導者から承認を受けませんでした。 そのとき、ハードウェア側からこの問題を解決するというアイデアが生まれました(結局、誰もサードパーティのキーボードを接続することを禁止していません)。 また、私の場所でほこりを集めていたRaspberry Piについても思い出しました。



PS / 2インターフェイスを説明するドキュメントの検索が開始されました。 幸いなことに、最初に利用可能なリソース()で、必要な情報がすべて見つかりました。



キーボードPS / 2ケーブルのピン配列



結局のところ、すべての作業に4本のワイヤが必要でした。

黒は地球です。

赤-5V;

黄色-時刻同期ピン(CLOCK)。

白-データピン(DATA)。



色は若干異なる場合があります。 これらの4本のワイヤーからでも、黄色と白の2本しか必要ありませんでした(キーボードとは対照的に、私のデバイスは地面と5vでは必要ありませんでした)。



PS / 2プロトコルの説明(キーボード->ホストコントローラー)



用語を定義しましょう:ピンに電圧がある場合、この状態を1、それ以外は0と見なします。



デフォルトでは、コンピューターがデータを受信できる場合、両方のピン(CLOCKおよびDATA)が状態1に設定されます。これはコンピューターのマザーボード上のホストコントローラーによってインストールされます。 電圧を両方のピンに供給することで制御を受け入れます(MPコントローラーは電圧を除去します)。 データ転送を開始するには、コンピューターに0番目のビットを送信する必要があります(状態0と混同しないでください)。 これを行うには、DATAピンで状態0を設定し、その直後にCLOCKピンの状態を(この順序で)0に変更します。 ホストコントローラーに対して、最初のビットを送信することを明確にしました。 CLOCKピンの状態を状態1に戻すと、ホストコントローラーは最初のビットを読み取ります。 したがって、他のすべてのビットを送信します。



最初のビットは常に0です。これは開始ビットです(データを送信していることをお知らせください)。

次に、押したいキーのスキャンコードの8ビットを送信します。

10番目のビットはパリティビットを示します(ユニット数が偶数の場合は1、それ以外の場合は0)。

最後の11ビットはストップビットで、常に1です。



したがって、1つのデータパケットは11ビットで構成されます。



たとえば、「0」キー(スキャンコード45h =バイナリ形式で1000101)を押したい場合、次のビット配列がホストコントローラーに送信されます:01010001001。



このデータを受け入れると、コンピューターはこのキーを押すコマンドを処理しますが、まだ押す必要があります。 これを行うには、最初にF0hコマンドを送信し、次に押す必要があるキーのキーコードを再スキャンする必要があります。 また、状態の変更の合間に一時停止する必要があります。 経験的には、Pythonで作業する場合は0.00020秒、Javaでコーディングする場合は1 nanosekを最適な値に設定します。



ホストコントローラーへの接続スキーム



画像



デバイスをキーボードと並列に接続した理由について少し説明します。 コンピューターの電源を入れると、BIOSはPS / 2コネクターの状態をチェックします。 コンピューターとキーボードは、仕事への準備を伝えます。 キーボードは内部診断を実行し、コンピューターに準備状況を報告する必要があります。 その後、BIOSでPS / 2インターフェイスを操作できます。 コンピューターからの読み取りコマンドを実装するには、私は面倒でした。



これで、コンピューターの電源を入れると、キーボードからその準備ができたことが通知されます。 その後、Raspberry Piの電源を入れます。インターフェイスを制御するとすぐに、キーボードを操作できなくなります。 仕事中、私は衝突を特定しませんでした。 すべてが正常に機能しましたが、時々、コンピューターは送信されたデータを誤って処理し、Raspberryはデータを受信するように構成されていなかったため(この場合、キーコードを再送信するコマンド)、エラーは無視されました。 この問題は、データ送信の頻度を減らすことで解決しました。



ソフトウェア部



最初に、サーバー側はpi4jライブラリを使用してJavaで作成されましたが、ロジックアナライザーが示すように、Javaマシンは遅延でうまく機能しませんでした(大きすぎることが判明し、コンピューターがデータを誤って頻繁に受信しました)。 Pythonの方がはるかに優れていることが証明され、コードは迅速に実行され、システムのロードは数倍少なくなりました。



Phytonコード自体は次のとおりです。



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のサーバー側:

 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のクライアント側:

 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); } } }
      
      







デバイスからコンピューターに送信されるデジタル信号の例。 下部チャネルCLOCK、上部データ。 Pythonから信号を送信します



画像



おわりに



もちろん、純粋な水のタスクのようなささいな無駄のためにラズベリーを使用しています。 Arduinoを使用するか、安価なアームプロセッサで回路を構築できます。 しかし、それは最初に思い浮かんだのはただの即興演奏であり、必要なすべてのスペアパーツが中国から届くまで待ちたくありませんでした。



一般的に、この経験が誰かに役立つことを願っています。 個人的には、プロセス全体から多くの喜びを得ました。



All Articles