{"id":1978,"date":"2022-08-29T13:04:24","date_gmt":"2022-08-29T12:04:24","guid":{"rendered":"https:\/\/pmortensen.eu\/world2\/?p=1978"},"modified":"2023-05-27T17:50:27","modified_gmt":"2023-05-27T16:50:27","slug":"controlling-leds-on-ps-2-keyboards","status":"publish","type":"post","link":"https:\/\/pmortensen.eu\/world2\/2022\/08\/29\/controlling-leds-on-ps-2-keyboards\/","title":{"rendered":"Controlling the three LEDs on PS\/2 keyboards"},"content":{"rendered":"<p>When repurposing old (or new) <a href=\"https:\/\/en.wikipedia.org\/wiki\/PS\/2_port\">PS\/2<\/a> keyboards as macro keyboards (because they are much easier to interface with than USB keyboards), there are three free <a href=\"https:\/\/en.wikipedia.org\/wiki\/Light-emitting_diode\">LEDs<\/a> that can also be repurposed, e.g., for various status purposes (say, indicating a macro key action is in progress or indicating a <strong><em>mode<\/em><\/strong> the macro keyboard is in, like an extra slow macro key mode or an extra fast macro key mode. Or\/and special flashing in some error conditions).<\/p>\n<p>Here it is documented how these LEDs can be controlled.<\/p>\n<h2 id=\"Hardware_requirements\">Hardware requirements<\/h2>\n<p>There aren&#8217;t any hardware requirements! At least not if using a standard (5&nbsp;V) microcontroller to interface to the PS\/2 keyboard. The required <a href=\"https:\/\/en.wikipedia.org\/wiki\/Open_collector\">open collector<\/a> output <strong><em>can be simulated<\/em><\/strong> by changing the I\/O port to <strong><em>output<\/em><\/strong> (with a value of 0) for <strong><em>the (driving) &#8216;0&#8217;<\/em><\/strong> and changing the I\/O port to <strong><em>input<\/em><\/strong> for <strong><em>the &#8216;1&#8217;<\/em><\/strong> (the pull-up resistors in the keyboard takes care of the proper voltage level). In essence, the <strong><em>I\/O port direction becomes the signal<\/em><\/strong> as seen from the microcontroller side. This is similar to how it was implemented in hardware for the original IBM PC.<\/p>\n<p>There needs to be hardware that can pull the two signal lines (<a href=\"https:\/\/en.wikipedia.org\/wiki\/PS\/2_port\">CLK<\/a> and <a href=\"https:\/\/en.wikipedia.org\/wiki\/PS\/2_port\">DATA<\/a>) <em>low<\/em>, but <strong><em>not<\/em><\/strong> pull them high (they must not be driven high as that would cause a conflict when the keyboard sends information in the other direction\u2014the normal operation of the keyboard when the user presses keys). This is similar to how an <a href=\"https:\/\/en.wikipedia.org\/wiki\/Serial_Peripheral_Interface\">SPI<\/a> or <a href=\"https:\/\/en.wikipedia.org\/wiki\/I%C2%B2C\">I\u00b2C<\/a> interface works.<\/p>\n<p>A naive approach (or if the I\/O direction can not be changed), is to implement it with discrete <a href=\"https:\/\/en.wikipedia.org\/wiki\/Bipolar_junction_transistor\">BJTs<\/a> (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Open_collector\">open collector<\/a>), <a href=\"https:\/\/en.wikipedia.org\/wiki\/Field-effect_transistor\">FETs<\/a> (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Open_collector\">open drain<\/a>), using <a href=\"https:\/\/en.wikipedia.org\/wiki\/Transistor%E2%80%93transistor_logic\">TTL<\/a> chips or <a href=\"https:\/\/en.wikipedia.org\/wiki\/CMOS\">CMOS<\/a> chips. In all cases there must be a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Pull-up_resistor\">pull-up resistor<\/a> somewhere (that is already the case for a PS\/2 keyboard), and it should have a sufficiently low value to satisfy timing requirements. In the original IBM PC, a <a href=\"https:\/\/en.wikipedia.org\/wiki\/List_of_7400-series_integrated_circuits#Larger_footprints\">TTL 74LS125<\/a> with <a href=\"https:\/\/en.wikipedia.org\/wiki\/Three-state_logic\">three-state<\/a> outputs was used for this purpose. The input was fixed to ground, and the controlling signal was to the (inverting) enable pin. The pull-up resistor was 4.7 k\u03a9.<\/p>\n<p><!-- Not required to show for now.\n\nHere is the BJT solution. It works fine. \n\nXXX Figure 1. A BJT <a href=\"https:\/\/en.wikipedia.org\/wiki\/Common_emitter\">common-emitter amplifier<\/a> with <a href=\"https:\/\/en.wikipedia.org\/wiki\/Open_collector\">open collector<\/a> output. The collector resistance is presumed to be elsewhere.\n\nNote that there aren't pull-up resistors in the circuit as those are already guaranteed to be included in the keyboard.\n\nThe fall time has been measured to 60 ns and the rise time to about 450 ns. This seems to work reliably. The general requirement for maximum tolerable fall and rise times for MOS circuits (which is presumed to be how the keyboard is implemented) for fall and rise times is 1000 ns.\n--><\/p>\n<p><!-- Outcommented for now. Inconclusive test results. Remeasuring it, we got about 2.5 \u00b5sec for the rising edge for CLK for the BJT assert.\n \nThe keyboard itself used for this testing produces signals with a fall time of about 35 ns and a rise time to about 2.3 \u00b5sec (with our BJT circuit connected). The result for the rise time is surprising... With passive pull-up, we would have expected about the same result\n\nThe result is not much different without our BJT circuit connected.\n--><\/p>\n<p>Note that the BJT solution is <em>inverting<\/em> (e.g., a high on the I\/O port used to drive the circuit for CLK will result in a (driving) low on CLK on the PS\/2 interface side), so this must be accounted for, e.g., in the software. A floating input to the BJT circuit is OK, but it is best to set it to a driving low when the microcontroller program initialises the hardware (I\/O ports). Note that many diagrams, incl. in the first source, do not show the base resistor. It is <em>required<\/em> to limit the current; otherwise, the microcontroller may be damaged. Its value can be on the order of 10 k\u03a9.<\/p>\n<p>Note that if the CLK signal is connected to an interrupt capable pin, the software has to account for that; asserting the CLK will generate an interrupt that must be ignored (to not be interpreted as something the keyboard sends).<\/p>\n<h2>Protocol, bit level<\/h2>\n<p>Before sending the first bit to the keyboard, a certain protocol is <strong><em>required<\/em><\/strong> to <em>initiate<\/em> sending information to the keyboard:<\/p>\n<ol>\n<li>Hold CLK low for at least 100 \u00b5sec (more than one bit time), for instance 150 \u00b5sec (0.15 millisecond).<\/li>\n<li>Assert the DATA signal low<\/li>\n<li>Release the CLK signal (setting it to high)<\/li>\n<li>Hold the DATA signal low <strong><em>until the keyboard starts producing clock signals<\/em><\/strong> on CLK. That is, a falling edge on CLK. This can take up to a whopping 6 ms (6000&nbsp;\u00b5sec) (but it can be much shorter), with some keyboards not going over 750&nbsp;\u00b5sec. 750&nbsp;\u00b5sec corresponds to about 11 bit times (a full frame). Note that the waiting for the falling edge on CLK is automatically included in sending the first bit (see below).<\/li>\n<\/ol>\n<p>The keyboard samples on the <em>rising edge<\/em> of the CLK signal (in normal operation, when the keyboard sends key codes as a result of key presses, the significant edge is the falling edge), so the DATA signal can be set up shortly after the falling edge of the CLK signal.<\/p>\n<p>So the sequence to send a single bit (say, a data bit) is:<\/p>\n<ol>\n<li>Wait for a falling edge of CLK<\/li>\n<li>Set up the DATA signal<\/li>\n<li>Nothing more to do, other than wait! Sufficient time must pass so that the data is sampled by the keyboard on the rising edge of CLK (on the order of 50&nbsp;\u00b5sec later)<\/li>\n<\/ol>\n<p>Note: For 1., if implemented in a bit-banging busy wait way (the most common) with a microcontroller, it is crucial to have a timeout, otherwise the program may hang, e.g., if the keyboard is not connected&#8230;<\/p>\n<h2>Protocol, byte level<\/h2>\n<p>Similar (but not identical) to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Asynchronous_serial_communication\">asynchronous serial communication<\/a>, when sending to the keyboard, a data frame consists of:<\/p>\n<ol>\n<li>8 data bits with the least significant bit (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Bit_numbering#Bit_significance_and_indexing\">LSB<\/a>) send first<\/li>\n<li>Parity bit, always odd parity (an <strong><em>even<\/em><\/strong> number of &#8216;1&#8217; data bits results in a parity bit of 1 (a more round-about way of saying it is that the number of &#8216;1&#8217; bits, including the parity bit itself, is odd&#8230;))<\/li>\n<li>Stop bit. Always &#8216;1&#8217;. This also frees up the DATA signal (as the next data bit will be <em>from<\/em> the keyboard). This also marks the end of the time that the host will assert any of the signals.<\/li>\n<li>Acknowledge bit. Always &#8216;0&#8217;. This is send <em>from<\/em> the keyboard (reverse flow of information).\n<p>Note 1: When sending to the keyboard, a start bit is not send (in contrast to when the keyboard is sending). There are still 11 pulses on CLK (corresponding to 11 bits), but that is due to the extra bit at the end, the acknowledge bit.<br \/>\nOr an alternative interpretation is that the asserted low DATA line is some sort of start bit &#8211; but it is a very weird start bit (very long, variable length, up to about 90 bit times long (about 8 frames), and does not work unless CLK has the right polarities at the right times), and does not fit with how the rest of the bits are clocked (from the keyboard) &#8211; it could be interpreted as the host providing the first (rising) CLK edge, but that is a bit of a stretch.<\/p>\n<p>Note 2: In most cases, after the keyboard has received a command byte, it will send back an acknowledge byte. Thus if we want to send more than one command byte (that is the case for setting the LEDs), we can&#8217;t send the second command byte immediately after the first. One way is to simply to ignore what the keyboard sends back and wait a sufficient amount of time before blindly sending the second command byte, say 2 ms (2000 \u00b5sec). It is not known how robust this method is. For instance, if the keyboard takes longer than expected to send the acknowledge byte for the first command byte, it could result in a transmit error and thus disabling the keyboard&#8230;<\/p>\n<p>Note 3: When the keyboard sends, it also uses odd parity.<\/p>\n<h3>Parity bit<\/h3>\n<p>The parity bit must be set correct. Otherwise, the keyboard will ignore the command byte.<\/p>\n<h2>Protocol, message level<\/h2>\n<p>Only two commands are needed for this purpose, reset, 0xFF (not strictly necessary as power on does the same) and 0xED followed by a byte where the last three bits (bit 0, bit 1, and bit 2) represent the three LEDs:<\/p>\n<ul>\n<li>Bit 0: Scroll Lock<\/li>\n<li>Bit 1: Num Lock<\/li>\n<li>Bit 2: Caps Lock<\/li>\n<\/ul>\n<p>Note that an acknowledge byte is send by the keyboard for each of the two bytes. <\/p>\n<p>It may or may not be necessary to disable the keyboard before sending the commands to set the LEDs. Also, a transmit error may result in the keyboard becoming disabled. In that case, <a href=\"https:\/\/www.win.tue.nl\/~aeb\/linux\/kbd\/scancodes-13.html#mcf4\">command 0xF4<\/a> can enable it.<\/p>\n<h2>Could the built-in asynchronous serial port in a microcontroller be used to encode and decode the signal to the PS\/2 keyboard?<\/h2>\n<p>That is, hardware support for the task, including generating and checking for the parity bit.<\/p>\n<p>The signals are similar to asynchronous serial communication, but it<br \/>\nis different enough that it would be cumbersome to try to use an asynchronous serial port. Though it requires more CPU cycles on the microcontroller and increases the complexity somewhat, the microcontroller for a macro keyboard is very lightly loaded, so this is usually not a problem. <\/p>\n<p>One problem is that the baud rate is not fixed, only specified to be in the range 10,000 to 16,600 (thus, the bit time is in the range 60 \u00b5sec to 100 \u00b5sec). Indeed, the baud rate does vary between keyboards. One keyboard appeared to use the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Serial_port#Settings\">standard baud rate 14,400<\/a> whereas some other ones were measured to use 13,300 baud and 12,000 baud, respectively. The baud rate would have to be determined first. One way is to do it during RESET. Sending the RESET command only requires the initiating sequence as described above (near <em>&#8220;Before sending the first bit to the keyboard&#8221;<\/em>) as the command is all &#8216;1&#8217;s (0xFF) and the parity bit also happens to be &#8216;1&#8217; (the DATA line does not need to be driven low for any of the bits and can just be left as is). Another way is to measure it beforehand for the particular keyboard. This is feasible if keyboards are not changed often. This has the advantage that it is not necessary to connect the CLK signal.<\/p>\n<p>Another problem is the missing start bit when <em>sending<\/em> to the keyboard. Starting the UART at the right time would require being able to see into the future (the UART needs to be started before the keyboard starts to outputting the first CLK cycle (for the first data bit)).<\/p>\n<p>Conclusion: The emphasis here is setting the LEDs, but using the async serial hardware in a microcontroller for <em>receiving<\/em> from the keyboard (when the user types) using a baud rate that has been determined somehow is <em>definitely<\/em> feasible. When combined with interrupts for received bytes, it would put minimum load on the microcontroller and the software complexity (and size) would be reduced.<\/p>\n<h2>Could the built-in SPI or I\u00b2C support in a microcontroller be used to encode and decode the signal to the PS\/2 keyboard?<\/h2>\n<p>Seen from afar, they seem closer matched than asynchronous serial communication, at least for sending to the keyboard (no start bit and the variable waiting time before the first data bit is not a problem as it is controlled by synchronous nature of it (the CLK signal)).<\/p>\n<p>However, the parity bit would have to be computed in software. And it results in more 8 bits on the wire. How does the more than 8 bits (9, 10, or 11 bits, depending on the mode and how it is implemented) map to the hardware? For instance, the SPI hardware in the AVR microcontrollers only works with 8 bits at a time.<\/p>\n<p>This is an open question.<\/p>\n<h2>Results<\/h2>\n<p>Controlling the LEDs in the bit-banging way (without hardware support or use of interrupt capable I\/O pins) was successfully tested with three different PS\/2 keyboards, including <a href=\"https:\/\/www.reddit.com\/r\/keyboards\/comments\/ohpmow\/btc_5349_restored_by_me\/\">a very old one from 1989<\/a> using the DIN-5 interface.<\/p>\n<p>The microcontroller platform was <a href=\"https:\/\/store.arduino.cc\/arduino-leonardo-with-headers\">Arduino Leonardo<\/a>, and it was also demonstrated that the Arduino environment is perfectly suitable for full-blown use of C++, incl. polymorphic calls (sub classes were used to represent (and hide the details) of different ways of using the required open collector outputs).<\/p>\n<p>Both the BJT open collector external circuit and the microcontroller emulation of open collector worked successfully.<\/p>\n<h2>References<\/h2>\n<ul>\n<li><em><a href=\"https:\/\/www.avrfreaks.net\/sites\/default\/files\/PS2%20Keyboard.pdf\">The PS\/2 Mouse\/Keyboard Protocol<\/a><\/em> by Adam Chapweske. 2003-05-09. (Though this may now go into an infinite loop, never loading. Possibly related to cookies or login. A workaround is not currently known). In PDF format. It contains the most detailed information about sending commands to PS\/2 keyboards (though it could have been written in a clearer and more succinct way). It also contains the command byte to send to change the LEDs and the definition of which bits correspond to which LEDs.<br \/>\nNote: This reference exists in many places, many of which are <strong><em>broken<\/em><\/strong> (e.g., all links with <strong><em>www.computer-engineering.org<\/em><\/strong> (listed as the original source) are broken (the DNS domain does not exist any more). <a href=\"https:\/\/web.archive.org\/web\/20161116003545\/http:\/\/computer-engineering.org\/ps2protocol\/\">A version<\/a> (2003-05-09) on Internet Archive.). Another place is <em><a href=\"http:\/\/www-ug.eecg.toronto.edu\/msl\/nios_devices\/datasheets\/PS2%20Keyboard%20Protocol.htm\">The PS\/2 Keyboard Interface<\/a><\/em> (different title, but essentially the same content. Slightly older revision: 2003-04-01. In HTML format.). And there is <em><a href=\"https:\/\/www.tayloredge.com\/reference\/Interface\/atkeyboard.pdf\">The AT-PS\/2 Keyboard Interface<\/a><\/em> (yet another title, but essentially the same content). From 2001. In PDF format.<\/li>\n<li><em><a href=\"https:\/\/en.wikipedia.org\/wiki\/PS\/2_port\">PS\/2<\/a><\/em>. Wikipedia. It does cover sending to the keyboard at the bit level, but it is too vague. E.g., the width of the initial CLK negative pulse is not specified.<\/li>\n<li><em><a href=\"https:\/\/www.win.tue.nl\/~aeb\/linux\/kbd\/scancodes-12.html\">Keyboard commands<\/a><\/em>. Very detailed description of the commands that PS\/2 keyboards accept and the possible responses from the keyboard, incl. different versions and non-standard commands. It only covers the message level, not the signal level.<\/li>\n<li><em><a href=\"https:\/\/wiki.osdev.org\/PS\/2_Keyboard\">PS\/2 Keyboard<\/a><\/em>. A rare find (the search engines will not return this one): Very detailed information about the command level of PS\/2 keyboards, incl. the responses the keyboard is expected to send. It includes a list of 17 commands with a detailed description of the parameters. It only covers the message level, not the signal level. But it seems to be in error for the reset command (0xFF); empirically, a keyboard <strong><em>both<\/em><\/strong> sends an acknowledge byte (0xFA) almost immediately (e.g., after 500 \u00b5sec) and the result (0xAA &#8211; &#8216;self-test passed&#8217;) several hundred milliseconds after that (e.g., after 495 milliseconds).<\/li>\n<li><em><a href=\"https:\/\/www.win.tue.nl\/~aeb\/linux\/kbd\/scancodes-1.html#ss1.4\">1. Keyboard scancodes<\/a><\/em>. Another rare find. It lists command codes for setting extra LEDs for some obscure keyboards (like <a href=\"https:\/\/www.win.tue.nl\/~aeb\/linux\/kbd\/scancodes-6.html#logitechinternet\">Logitech Internet keyboard<\/a>). It also actually lists the scan codes, incl. the later added left and right Windows and context menu keys (paraphrased): <em>&#8220;The Microsoft keyboard adds 0xE0&nbsp;0x5B (LeftWindow), 0xE0&nbsp;0x5C (RightWindow), and 0xE0&nbsp;0x5D (Menu)&#8221;.<\/em> For the last number in the sequences, the decimal numbers are 91, 92, and 93, respectively. 0xE0 (decimal 224) is the standard for the scan code sequences with a length of 2. <br \/>Though <strong><em>empirically<\/em><\/strong> (observed with two different keyboards, one of them a USB one in PS\/2 mode), they seem to instead be 0xE0&nbsp;0x1F (LeftWindow), 0xE0&nbsp;0x27 (RightWindow), and 0xE0&nbsp;0x2B (context menu) (decimal 31, 39, and 47, respectively)<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>When repurposing old (or new) PS\/2 keyboards as macro keyboards (because they are much easier to interface with than USB keyboards), there are three free LEDs that can also be repurposed, e.g., for various status purposes (say, indicating a macro &hellip;<\/p>\n<p class=\"read-more\"> <a class=\"more-link\" href=\"https:\/\/pmortensen.eu\/world2\/2022\/08\/29\/controlling-leds-on-ps-2-keyboards\/\"> <span class=\"screen-reader-text\">Controlling the three LEDs on PS\/2 keyboards<\/span> Read More &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[31,28,26,32],"tags":[],"_links":{"self":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/1978"}],"collection":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/comments?post=1978"}],"version-history":[{"count":200,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/1978\/revisions"}],"predecessor-version":[{"id":3813,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/1978\/revisions\/3813"}],"wp:attachment":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/media?parent=1978"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/categories?post=1978"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/tags?post=1978"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}