ArduinoUNOでPS/2キーボードを使ったメモ

## ArduinoUNOでPS／2キーボードを使う
昔のPC用のキーボードは同期シリアル通信なのでAVRなら簡単に接続できると思ってやってみた。
### 接続
普通に同期通信で 8O1 (スタートビット１、データ８、奇数パリティ、ストップビット１
Data,Clockともにオープンコレクタで4.7KΩでプルアップされている
キーボードとArduinoUNOの接続
Data   -> PIN0 (RX)
Clock  -> PIN4 (XCK)
電源を忘れずに


![SANY0339-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205783/a1a766f3-93c9-d076-30b5-b7f53c6976bc.jpeg)

## キーボードからの受信

最初まじめにレジスタを設定したけど、Serial.beginで初期設定後に同期通信に変更するだけで受信割り込みも含めて使用できる。

```
  Serial.begin(9600, SERIAL_8O1); //初期設定はおまかせして
  UCSR0C |= (1 << UMSEL00);   //同期通信モードへ変更
  
/*
 ワイヤードOR接続のおまじない、Input時はハイインピーダンス
　　Output時はLOWになる 
*/
  pinMode(0, INPUT);
  pinMode(4, INPUT);

  digitalWrite(0, LOW);
  digitalWrite(4, LOW);  

```

読み出しは普通にSerial.Read()が使える、データはキートップに印字された文字では無くスキャンコードと呼ばれるデータ、用途によっては変換が必要

```
uint8_t  get_usart() {
  uint8_t c;
  while (Serial.available() == 0)
    ;
  c = (0x00ff & Serial.read());
  return c;

}

```
## キーボードへの送信
パワーオンリセット以外でリセットする場合や、キーボードのLEDをコントロールする場合はUSARTを無効有効切り替えたりちょっと面倒な手続きが必要。

1. クロック、データが共にHIGHになるまで待つ
2. USARTを無効にする
3. マイコンからClockをLOWにする
3. マイコンからDataをLOWにする(start bit)
4. マイコンからClockをHIGHにする
5. キーボードがClockをLOWにするので順にDataへ書き込む
6. ５を8回繰り返したらパリティ
7. ストップビット
8. キーボードから受信完了としてもう一回クロックが来る
9. USARTを有効にしてキーボードからの返信を待つ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205783/307f7db3-f301-08db-837b-fe204fcd396f.png)


#### キーボードに対するコマンド(一部)
| 1 | 2 | 3 |
|:-:|:-:|:-:|
|Reset|0xFF   | リセット  |
|LED  |0xED   |次データでLEDの転倒消灯bit2=CapsLock,bit1=NumLock,bit0=ScrollLock |

#### 主な返信コード
| 1 | 2 | 3 |
|:-:|:-:|:-:|
| Ack | 0xFA  |正常受信   |
| Resend  |0xFE   |データ再送要求   |

```
#define CLK_LO  pinMode(4,OUTPUT);
#define CLK_HI  pinMode(4,INPUT);
#define DATA_LO pinMode(0,OUTPUT);
#define DATA_HI pinMode(0,INPUT);
#define WAIT_CLK_LO while(digitalRead(4) == HIGH);
#define WAIT_CLK_HI while(digitalRead(4) == LOW);

#define DELAY 50


uint8_t send_data(uint8_t data) {
  int shift = 1;
  uint8_t parity = 0;
  uint8_t rcv;


  WAIT_CLK_HI;   //busy?
  delayMicroseconds(DELAY);

  TIMSK1 &= ~(1 << OCIE1A); //timer off
  UCSR0B &= ~(1 << RXEN0); //usart off
  UCSR0C &= ~(1 << UMSEL00);

  CLK_LO;                 //RTS
  delayMicroseconds(DELAY);

  DATA_LO; //start bit
  delayMicroseconds(DELAY);
  CLK_HI;

  WAIT_CLK_LO;          //キーボード発生のクロック待ち
  delayMicroseconds(DELAY / 2);

  while (shift != 0x100) {

    if (data & shift) {
      DATA_HI;  //bit = 1
      parity++;
    } else {
      DATA_LO  //bit = 0
    }
    WAIT_CLK_LO;
    WAIT_CLK_HI;
    delayMicroseconds(DELAY / 2);
    shift <<= 1;
  }
  //parity
  if (parity & 0x01) {
    DATA_LO;  //bit = 1
  } else {
    DATA_HI;  //bit = 0
  }
  WAIT_CLK_LO;
  WAIT_CLK_HI;
  delayMicroseconds(DELAY/2);

  DATA_HI; //stop bit
  WAIT_CLK_LO;
  WAIT_CLK_HI;
  WAIT_CLK_LO;
  WAIT_CLK_HI;

  UCSR0B &= ~(1 << RXCIE0); // intrrupt off
  UCSR0B |= (1 << RXEN0); //usart on
  UCSR0C |= (1 << UMSEL00);
  while (!(UCSR0A & (1 << RXC0))); //キーボードから応答待ち
  rcv = UDR0;
  UCSR0B |= (1 << RXCIE0); // intrrupt on

  TIMSK1 |= (1 << OCIE1A);
  return rcv;
}
```

## まとめ
受信だけならSerial.beginに一行加えるだけで簡単にできる、読み出しデータがスキャンコードなので少々癖があるがキー数の多い簡単入力デバイスとして、ちょっとした工作や実験には便利。

参考にしたサイト
http://ioiodesu.web.fc2.com/PS2/PS2.HTML