Ps2KeyboardHost  1.0.1
Allows you to read from one or more PS2-style keyboards on an Arduino.
ps2_Keyboard.h
Go to the documentation of this file.
1 /*
2 Copyright (C) 2017 Steve Benz <s8878992@hotmail.com>
3 
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8 
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13 
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 USA
18 */
19 #pragma once
20 
21 #if defined(ARDUINO) && ARDUINO >= 100
22 #include "Arduino.h" // for attachInterrupt, FALLING, HIGH & LOW
23 #else
24 #include "WProgram.h"
25 #endif
26 #include <stdint.h>
27 #include <util/atomic.h>
28 #include "ps2_NullDiagnostics.h"
29 #include "ps2_KeyboardLeds.h"
30 #include "ps2_KeyboardOutput.h"
31 #include "ps2_TypematicRate.h"
33 #include "ps2_ScanCodeSet.h"
34 #include "ps2_Parity.h"
36 
37 namespace ps2 {
38 
39  /*
40  * Excellent references:
41  * http://www.computer-engineering.org/ps2keyboard/
42  */
43 
120  template<int DataPin, int ClockPin, int BufferSize = 16, typename Diagnostics = NullDiagnostics>
121  class Keyboard {
122  static const uint8_t immediateResponseTimeInMilliseconds = 10;
123 
124  Diagnostics *diagnostics;
125 
126  // These are not marked as volatile because they are only modified in the interrupt
127  // handler and at startup (before the interrupt handler is enabled).
128  uint8_t ioByte = 0;
129  uint8_t bitCounter = 0;
130  uint32_t failureTimeMicroseconds = 0;
131  uint32_t lastReadInterruptMicroseconds = 0;
132  Parity parity = Parity::even;
133  bool receivedHasFramingError = false;
134 
135  // This guy is marked volatile because it's exchanged between the interrupt handler and normal code.
136  KeyboardOutputBuffer<BufferSize, Diagnostics> inputBuffer;
137 
138  // Commands sent from the host to the ps2 keyboard. Private to this class because
139  // the point of the class is to encapsulate the protocol.
140  enum class ps2CommandCode : uint8_t {
141  reset = 0xff,
142  resend = 0xfe,
143  disableBreakAndTypematicForSpecificKeys = 0xfd,
144  disableTypematicForSpecificKeys = 0xfc,
145  disableBreaksForSpecificKeys = 0xfb,
146  enableBreakAndTypeMaticForAllKeys = 0xfa,
147  disableBreakAndTypematicForAllKeys = 0xf9,
148  disableTypematicForAllKeys = 0xf8,
149  disableBreaksForAllKeys = 0xf7,
150  useDefaultSettings = 0xf6,
151  disable = 0xf5,
152  enable = 0xf4,
153  setTypematicRate = 0xf3,
154  readId = 0xf2,
155  setScanCodeSet = 0xf0,
156  echo = 0xee,
157  setLeds = 0xed,
158  };
159 
160  void readInterruptHandler() {
161  // The timing of the PS2 keyboard is such that you really need to read the data
162  // line just as fast as possible. If you do it with digitalRead, it'll work right
163  // almost all the time (like one character in 100 will suffer a failure - with the
164  // keyboards I have to-hand.)
165  //
166  // Although this looks fantastically more complicated than plain digitalRead, the
167  // instruction count is lower. It'd be even lower if the methods listed here were
168  // implemented a little better (e.g. as template methods rather than macros).
169  uint8_t dataPinValue = (*portInputRegister(digitalPinToPort(DataPin)) & digitalPinToBitMask(DataPin)) ? 1 : 0; // ==digitalRead(DataPin);
170 
171  lastReadInterruptMicroseconds = micros();
172  // TODO: Perhaps there should be code here that detects if bitCounter > 0 && the time since
173  // the last read interrupt is > a millisecond and, if so, log that and reset
174  // bitCounter.
175  //
176  // It doesn't really need doing principally because the error-recovery code is already
177  // going to catch this (even if it doesn't do it very well). If you have other interrupts
178  // in your system and they happen with concurrently with the keyboard, I think it's unlikely
179  // you'll ever be able to craft a foolproof system. That since the "Ack" request only
180  // gets the previous byte. What we'd really need to recover would be something that asks
181  // the keyboard for all keys currently pressed, and there's no such thing.
182  //
183  // But perhaps I'm wrong and it's not as bad as all that.
184 
185  switch (bitCounter)
186  {
187  case 0:
188  if (dataPinValue == 0) {
189  receivedHasFramingError = false;
190  }
191  else {
192  this->diagnostics->packetDidNotStartWithZero();
193 
194  // If we get a failure here, it should mean that previous byte is
195  // not actually framed correctly and the stop bit and parity bit somehow
196  // matched our expectations (or maybe they didn't and we got here anyway?)
197  receivedHasFramingError = true;
198  failureTimeMicroseconds = lastReadInterruptMicroseconds;
199  }
200  ++bitCounter;
201  parity = Parity::even;
202  break;
203  case 1:
204  case 2:
205  case 3:
206  case 4:
207  case 5:
208  case 6:
209  case 7:
210  case 8:
211  if (dataPinValue)
212  {
213  ioByte |= (1 << (bitCounter - 1));
214  parity ^= 1;
215  }
216  ++bitCounter;
217  break;
218  case 9:
219  if (parity != (Parity)dataPinValue) {
220  this->diagnostics->parityError();
221  receivedHasFramingError = true;
222  failureTimeMicroseconds = lastReadInterruptMicroseconds;
223  }
224  ++bitCounter;
225  break;
226  case 10:
227  if (dataPinValue == 0) {
228  this->diagnostics->packetDidNotEndWithOne();
229  receivedHasFramingError = true;
230  failureTimeMicroseconds = lastReadInterruptMicroseconds;
231  }
232 
233  if (!receivedHasFramingError) {
234  this->diagnostics->receivedByte(ioByte);
235  this->inputBuffer.push((KeyboardOutput)ioByte);
236  }
237  bitCounter = 0;
238  ioByte = 0;
239  }
240  }
241 
242  void writeInterruptHandler() {
243  int valueToSend;
244 
245  switch (bitCounter)
246  {
247  case 0:
248  ++bitCounter;
249  break;
250  case 1:
251  case 2:
252  case 3:
253  case 4:
254  case 5:
255  case 6:
256  case 7:
257  case 8:
258  valueToSend = (this->ioByte & (1 << (bitCounter - 1))) ? HIGH : LOW;
259  digitalWrite(DataPin, valueToSend);
260  parity ^= valueToSend;
261  ++bitCounter;
262  break;
263  case 9:
264  digitalWrite(DataPin, (uint8_t)parity);
265  ++bitCounter;
266  break;
267  case 10:
268  pinMode(DataPin, INPUT_PULLUP);
269  ++bitCounter;
270  break;
271  case 11:
272  if (digitalRead(DataPin) != LOW) {
273  // It'd be better to actually resend, but I don't think it's a thing,
274  // and it seems impossible to test anyway.
275  this->diagnostics->sendFrameError();
276  }
277 
278  enableReadInterrupts();
279  break;
280  }
281  }
282 
283  void sendByte(uint8_t byte1) {
284  // ISSUE: If we really want to be tight about it, we should check to see if the keyboard
285  // is already in the middle of transmitting something before we launch into this.
286 
287  detachInterrupt(digitalPinToInterrupt(ClockPin));
288 
289  // Inhibit communication by pulling Clock low for at least 100 microseconds.
290  pinMode(ClockPin, OUTPUT);
291  digitalWrite(ClockPin, LOW);
292  delayMicroseconds(120);
293 
294  // Make sure when interrupts resume, we start in the right state - note that we
295  // start sending data immediately on the first byte, because we will initiate
296  // the start bit in this clock pulse.
297  this->receivedHasFramingError = false;
298  this->inputBuffer.clear();
299  this->bitCounter = 0;
300  this->parity = Parity::even;
301  this->ioByte = byte1;
302  attachInterrupt(digitalPinToInterrupt(ClockPin), Keyboard::staticWriteInterruptHandler, FALLING);
303 
304  // Apply "Request-to-send" by pulling Data low, then release Clock.
305  pinMode(DataPin, OUTPUT);
306  digitalWrite(DataPin, LOW);
307  pinMode(ClockPin, INPUT_PULLUP);
308  }
309 
310  void enableReadInterrupts() {
311  this->receivedHasFramingError = false;
312  this->bitCounter = 0;
313  this->ioByte = 0;
314  this->parity = Parity::even;
315  this->inputBuffer.clear(); // gratuitious? Probably...
316 
317  attachInterrupt(digitalPinToInterrupt(ClockPin), Keyboard::staticReadInterruptHandler, FALLING);
318  }
319 
320  static Keyboard *instance;
321 
322  static void staticReadInterruptHandler() {
323  instance->readInterruptHandler();
324  }
325 
326  static void staticWriteInterruptHandler() {
327  instance->writeInterruptHandler();
328  }
329 
336  KeyboardOutput expectResponse(uint16_t timeoutInMilliseconds = immediateResponseTimeInMilliseconds)
337  {
338  unsigned long startMilliseconds = millis();
339  unsigned long stopMilliseconds = startMilliseconds + timeoutInMilliseconds;
340  unsigned long nowMilliseconds;
341  KeyboardOutput actualResponse;
342  do
343  {
344  actualResponse = this->inputBuffer.peek();
345  if (actualResponse == KeyboardOutput::none && this->receivedHasFramingError) {
346  actualResponse = KeyboardOutput::garbled;
347  // Note that clearing this is intended to prevent future polling from trying to get the
348  // bad character re-sent, but it might not work, depending on the nature of the error.
349  // Future interrupts could set it again.
350  this->receivedHasFramingError = false;
351  }
352  nowMilliseconds = millis();
353  } while (actualResponse == KeyboardOutput::none && (nowMilliseconds < stopMilliseconds || (stopMilliseconds < startMilliseconds && startMilliseconds <= nowMilliseconds)));
354 
355  if (actualResponse == KeyboardOutput::none) {
356  this->diagnostics->noResponse(KeyboardOutput::none);
357  }
358  return actualResponse;
359  }
360 
365  bool expectResponse(KeyboardOutput expectedResponse, uint16_t timeoutInMilliseconds = immediateResponseTimeInMilliseconds) {
366  KeyboardOutput actualResponse = expectResponse(timeoutInMilliseconds);
367 
368  if (actualResponse == KeyboardOutput::none) {
369  // diagnostics already reported
370  return false;
371  }
372  else if ( actualResponse != expectedResponse ) {
373  this->diagnostics->incorrectResponse(actualResponse, expectedResponse);
374  return false;
375  }
376  else {
377  this->inputBuffer.pop();
378  return true;
379  }
380  }
381 
382  bool expectAck() {
383  return this->expectResponse(KeyboardOutput::ack);
384  }
385 
386  bool sendCommand(ps2CommandCode command) {
387  return this->sendData((byte)command);
388  }
389 
390  bool sendCommand(ps2CommandCode command, byte data) {
391  return this->sendCommand(command)
392  && this->sendData(data);
393  }
394 
395  bool sendCommand(ps2CommandCode command, const byte *data, int numBytes) {
396  if (!this->sendCommand(command)) {
397  return false;
398  }
399  for (int i = 0; i < numBytes; ++i) {
400  if (!this->sendData(data[i])) {
401  return false;
402  }
403  }
404  return true;
405  }
406 
407  bool sendData(byte data) {
408  this->diagnostics->sentByte(data);
409  this->sendByte(data);
410  bool isOk = this->expectAck();
411  if (!isOk)
412  {
413  this->enableReadInterrupts();
414  }
415  return isOk;
416  }
417 
418  void sendNack() {
419  this->diagnostics->sentByte((byte)ps2CommandCode::resend);
420  this->sendByte((byte)ps2CommandCode::resend);
421  }
422 
423  public:
424  Keyboard(Diagnostics &diagnostics = *Diagnostics::defaultInstance())
425  : inputBuffer(diagnostics)
426  {
427  this->diagnostics = &diagnostics;
428  instance = this;
429  }
430 
436  void begin() {
437  pinMode(ClockPin, INPUT_PULLUP);
438  pinMode(DataPin, INPUT_PULLUP);
439 
440  // If the pin that support PWM output, we need to turn it off
441  // before getting a digital reading. DigitalRead does that
442  digitalRead(DataPin);
443 
444  this->enableReadInterrupts();
445  }
446 
472  bool awaitStartup(uint16_t timeoutInMillis = 750) {
473  return this->expectResponse(KeyboardOutput::batSuccessful, timeoutInMillis);
474  }
475 
494  KeyboardOutput code = this->inputBuffer.pop();
495 
496  if (code == KeyboardOutput::none && this->receivedHasFramingError)
497  {
498  // NACK's affect what the device perceives as the last character sent, so if we
499  // interrupt immediately, it might decide to send the last scan code (which we
500  // already have) again. The clock is between 17KHz and 10KHz, so the fastest
501  // send time for the 12 bits is going to be around 12*1000000/17000 700us and the
502  // slowest would be 1200us. Again, that's the full cycle. Most of the errors
503  // are going to be found at the parity & stop bit, so we'll wait 200us, as a guess,
504  // before asking for a retry.
505  //
506  // In hindsight, I think likely the most effective way to do error detection would
507  // be to wait for a certain amount of time after the last read interrupt when a
508  // failure has previously been detected. For that to work, then when the keyboard
509  // sends a multi-byte sequence, there'd have to be a pause between one byte and
510  // the next; I haven't validated that such a pause actually exists, however.
511  //
512  // The other concern is how to ensure that we can get Arduino-time during that
513  // window; it'll be hard to guarantee that in a general purpose library.
514  if (failureTimeMicroseconds + 200 > micros()) {
515  return KeyboardOutput::none;
516  }
517  if (this->bitCounter > 3) {
518  this->sendNack();
519  }
520  else {
521  this->diagnostics->clockLineGlitch(this->bitCounter);
522  this->receivedHasFramingError = false;
523  this->bitCounter = 0;
524  this->ioByte = 0;
525  this->parity = Parity::even;
526  }
528  }
529  else if (code == KeyboardOutput::batSuccessful) {
530  // The keyboard will send either batSuccessful or batFailure on startup.
531  // The way this class is structured, we can't really be sure that begin()
532  // will be called immediately after power-up, nor can we be sure that the
533  // Arduino wasn't just reset (while the keyboard had power the whole time).
534  // So we can't write code in begin() that just waits for the message.
535  // We could have a flag that is true until the first action is taken
536  // with the keyboard, but that seems needlessly wasteful.
537  code = this->inputBuffer.pop();
538  }
539  else if (code == KeyboardOutput::batFailure) {
540  diagnostics->startupFailure();
541  code = this->inputBuffer.pop();
542  }
543 
544  return code;
545  }
546 
554  bool sendLedStatus(KeyboardLeds ledStatus)
555  {
556  return sendCommand(ps2CommandCode::setLeds, (byte)ledStatus);
557  }
558 
566  bool reset(uint16_t timeoutInMillis = 1000) {
567  this->inputBuffer.clear();
568  this->sendCommand(ps2CommandCode::reset);
569  return this->expectResponse(KeyboardOutput::batSuccessful, timeoutInMillis);
570  }
571 
575  uint16_t readId() {
576  this->sendCommand(ps2CommandCode::readId);
577  KeyboardOutput response = this->expectResponse();
578  if (response == KeyboardOutput::none) {
579  return 0xffff;
580  }
581  this->inputBuffer.pop();
582  uint16_t id = ((uint8_t)response) << 8;
583  response = this->expectResponse();
584  if (response == KeyboardOutput::none) {
585  return 0xffff;
586  }
587  this->inputBuffer.pop();
588  id |= (uint8_t)response;
589  return id;
590  }
591 
595  if (!this->sendCommand(ps2CommandCode::setScanCodeSet, 0)) {
596  return ScanCodeSet::error;
597  }
598  ScanCodeSet result = (ScanCodeSet)this->expectResponse();
599  if (result == ScanCodeSet::pcxt || result == ScanCodeSet::pcat || result == ScanCodeSet::ps2) {
600  this->inputBuffer.pop();
601  return result;
602  }
603  else {
604  return ScanCodeSet::error;
605  }
606  }
607 
611  bool setScanCodeSet(ScanCodeSet newScanCodeSet) {
612  return this->sendCommand(ps2CommandCode::setScanCodeSet, (byte)newScanCodeSet);
613  }
614 
620  bool echo() {
621  this->sendByte((byte)ps2CommandCode::echo);
622  return this->expectResponse(KeyboardOutput::echo);
623  }
624 
630  byte combined = (byte)rate | (((byte)startDelay) << 4);
631  return this->sendCommand(ps2CommandCode::setTypematicRate, combined);
632  }
633 
638  {
639  return this->sendCommand(ps2CommandCode::useDefaultSettings);
640  }
641 
645  bool enable() { return this->sendCommand(ps2CommandCode::enable); }
646 
650  bool disable() { return this->sendCommand(ps2CommandCode::disable); }
651 
656  return this->sendCommand(ps2CommandCode::enableBreakAndTypeMaticForAllKeys);
657  }
658 
663  return this->sendCommand(ps2CommandCode::disableBreaksForAllKeys);
664  }
665 
678  bool disableBreakCodes(const byte *specificKeys, int numKeys) {
679  return this->sendCommand(ps2CommandCode::disableBreaksForSpecificKeys, specificKeys, numKeys);
680  }
681 
686  return this->sendCommand(ps2CommandCode::disableTypematicForAllKeys);
687  }
688 
693  return this->sendCommand(ps2CommandCode::disableBreakAndTypematicForAllKeys);
694  }
695 
707  bool disableTypematic(const byte *specificKeys, int numKeys) {
708  return this->sendCommand(ps2CommandCode::disableTypematicForSpecificKeys, specificKeys, numKeys);
709  }
710 
724  bool disableBreakAndTypematic(const byte *specificKeys, int numKeys) {
725  return this->sendCommand(ps2CommandCode::disableBreakAndTypematicForSpecificKeys, specificKeys, numKeys);
726  }
727  };
728 
729  template<int DataPin, int ClockPin, int BufferSize, typename Diagnostics>
731 }
KeyboardOutput readScanCode()
Returns the last code sent by the keyboard.
Definition: ps2_Keyboard.h:493
bool resetToDefaults()
Restores scan code set, typematic rate, and typematic delay.
Definition: ps2_Keyboard.h:637
bool disableBreakAndTypematic(const byte *specificKeys, int numKeys)
Instructs the keyboard to disable typematic (additional key down events when the user presses and hol...
Definition: ps2_Keyboard.h:724
Keyboard(Diagnostics &diagnostics= *Diagnostics::defaultInstance())
Definition: ps2_Keyboard.h:424
bool awaitStartup(uint16_t timeoutInMillis=750)
After the keyboard gets power, the PS2 keyboard sends a code that indicates successful or unsuccessfu...
Definition: ps2_Keyboard.h:472
bool reset(uint16_t timeoutInMillis=1000)
Resets the keyboard and returns true if the keyboard appears well, false otherwise.
Definition: ps2_Keyboard.h:566
bool disableTypematic(const byte *specificKeys, int numKeys)
Instructs the keyboard to disable typematic (additional key down events when the user presses and hol...
Definition: ps2_Keyboard.h:707
ScanCodeSet getScanCodeSet()
Gets the current scancode set.
Definition: ps2_Keyboard.h:594
Definition: ps2_AnsiTranslator.h:24
bool disableBreakCodes()
Makes the keyboard no longer send "break" or "unmake" events when keys are released.
Definition: ps2_Keyboard.h:662
bool enableBreakAndTypematic()
Re-enables typematic and break (unmake, key release) for all keys.
Definition: ps2_Keyboard.h:655
bool setTypematicRateAndDelay(TypematicRate rate, TypematicStartDelay startDelay)
Sets the typematic rate (how fast presses come) and the delay (the time between key down and the star...
Definition: ps2_Keyboard.h:629
bool disableBreakCodes(const byte *specificKeys, int numKeys)
Instructs the keyboard to stop sending "break" codes for some keys (that is, it disables the notifica...
Definition: ps2_Keyboard.h:678
ScanCodeSet
Definition: ps2_ScanCodeSet.h:22
bool enable()
Allows the keyboard to start sending data again.
Definition: ps2_Keyboard.h:645
Parity
Definition: ps2_Parity.h:22
bool disableBreakAndTypematic()
Re-enables typematic and break for all keys.
Definition: ps2_Keyboard.h:692
KeyboardOutput
Byte-codes sent back from the Ps2 keyboard to the host.
Definition: ps2_KeyboardOutput.h:31
bool disableTypematic()
Makes the keyboard no longer send multiple key down events while a key is held down.
Definition: ps2_Keyboard.h:685
bool disable()
Makes it so the keyboard will no longer responsd to keystrokes.
Definition: ps2_Keyboard.h:650
KeyboardLeds
The LED&#39;s available on a standard PS2 keyboard.
Definition: ps2_KeyboardLeds.h:23
bool sendLedStatus(KeyboardLeds ledStatus)
Sets the keyboard&#39;s onboard LED&#39;s.
Definition: ps2_Keyboard.h:554
TypematicRate
Definition: ps2_TypematicRate.h:22
TypematicStartDelay
Definition: ps2_TypematicStartDelay.h:22
void begin()
Definition: ps2_Keyboard.h:436
bool setScanCodeSet(ScanCodeSet newScanCodeSet)
Sets the current scancode set.
Definition: ps2_Keyboard.h:611
uint16_t readId()
Returns the device ID returned by the keyboard - according to the documentation, this will always be ...
Definition: ps2_Keyboard.h:575
Instances of this class can be used to interface with a PS2 keyboard. This class does not decode the ...
Definition: ps2_Keyboard.h:121
bool echo()
Sends the "Echo" command to the keyboard, which should send an "Echo" in return. This can be used to ...
Definition: ps2_Keyboard.h:620