                            Hello World,

 I've been planning for a while to create this PS/2 to USB keyboard and
mouse converter for a microcontroller. I had a Silicon Labs Universal Bee 
EFM8UB2 48pin variant on my shelf which has USB support so I decided to go 
with this one.
 This is a very featureful 8051-compatible microcontroller at low price,
in general that's my reason to use members of the 'Bee' family from Silabs.

 The folder contains a single source file written in C, I don't like the
traditional way of using many separate files for smaller projects. Sorry
if the code is too dense, you can try a code-beautifier on it if you like.
(As crowded as it is, the code is still highly structured.)
I started with assembly (sdas8051 in SDCC package) originally but C turned
out to be more adequate for the USB-protocol's complexity.
 The source can be compiled with the SDCC toolchain right away, but might be
compatible with Keil or other 8051 capable compilers too.
(Maybe with a little tweak for endianness, SDCC stores words/integers as
little-endian, but I avoided the usage of 16bit variables to circumvent
problems that might arise from this.)
 You can see the compilation commands in the Makefile as well as the
command to burn ('make burn') which uses my other project called 'chiprog'.
Of course the original efm8-programmer tool from SiLabs can be used as well.
There is a factory-burned USB bootloader in these chips which handles the
protocol so you don't need any separate programmer-kit for this chip at all.
The small assembly code at the beginning of my main() takes care of
escaping to the bootloader with a switch on a reset-pin to reload the flash
contents if needed. (Newer revisions/batches of the chip might not need
this as the mechanism supporting this escape should work in them already.)
 Being written mostly in C, other microcontroller brands with similar 
features might just as well work with the core of the source-code and
Input/Output-related modifications. (To go simpler, maybe it's worth porting
the code to the small and inexpensive PIC16F1455 USB-enabled microcontroller. 
Or the V-USB or USBtiny projects which can emulate USB on USB-less AVR MCUs.)

 I designed the converter to be as compatible as much with boot-protocol
standards, and it seemed to work fine with BIOS-es of different laptops
and operating systems. It handles the standard requests more-or-less to
a degree what is required to have a basic keyboard support with LED
indicators. Extra keys (like ACPI power-keys and multimedia keys) should
work too but I had no chance to try those on my simple keyboards. I'm using
this converter right now to type this very document and so far it seems
to work just fine, along with the mouse used simultaneously...

 The circuit is so simple that I didn't include any PCB-designs, actually
I have none, just wired the chip with thin wires on a prototype-PCB.
You can see in the datasheets/manuals for the respective chip-models of
the family how the chip should be connected to Vdd, Vio, Gnd and USB.
The only thing that is custom in my design is the connection of PS/2 pins:
the chip's reference manual states that only port P0 is available for
pin-voltage-edge sensing interrupts (INT0,INT1), but for flexibility of pins
to use I chose T0 input of Timer0 and T1 input of Timer1 instead, which 
can be assigned by the MCU-crossbar from any pin on P0..P3 ports. 
The schematic for my tried design looks like this 
(view with monospace font for correct alignments):

               ,--------------------->------------------------*-----*---------------*----O
               |                                              |     |               |  PS/2 +5V
               |   ,---------------------------------,        |     |               |  Keyboard
               *---|pin12:Vbus (senses USB)          |        |     |               |  
               |   |                                 |        /     /               |
      ,---*----*---|pin11:Vregin (bus-povered)       |    10  \     \ 10            |
      |   |1uF?    |                                 |   kOhm /     / kOhm          |
      | (===) ,----|pin10:Vdd (3.3V int. regulator)  |        \     \               |
 USB  |   |   |    |                                 |        |     |               |
      |   |  ===   |       EFM8UB20F32G-QFP48T       |        |     |               |
 5V O-'   |   |1uF |       -------------------       |PS2-    |     |     PS/2-     |
          |   |    |                       P3.2 pin28|--------*---------O Keyboard  |
 D- O--------------|pin9:D- (USB data-)              |Keyboard Data |     DATA      |
          |   |    |                                 |              |               |
 D+ O--------------|pin8:D+ (USB data+)              |PS2-Keyboard  |     PS/2-     |
          |   |    |                       P3.3 pin27|--------------*---O Keyboard  |         
GND O-*---*---*----|pin7:GND                         |ClockSense    |     CLOCK     | PS/2 +5V
      |            |                                 |              |               |  Mouse
      |    ----    |(for faulty bootloader)          |PS2-Keyboard  |   ,-------*---*----O
      *----o  o----|pin13:RSTb (C2CK)      P3.4 pin26|--------------'   |       |    
      | prog-button|                                 |ClockDrive        /       / 
      |            |                                 |                  \ 10    \ 10  
      |            |                                 |PS2-Mouse Data    / kOhm  / kOhm
      |            |                       P3.5 pin25|-------,          \       \ 
      |            |                                 |       |          |       |    PS/2-
      |            |                    P3.7   P3.6  |       '----------*----------O Mouse
      |            |                    pin23  pin24 |                          |    DATA 
      |            `---------------------------------'                          |
      |                              PS2-|      |PS2-Mouse                      |    PS/2-
      |                             Mouse|      |ClockSense                     |    Mouse
      |                        ClockDrive'------*-------------------------------*--O CLOCK
      |                                                                                      
      |                                                                         ,--O PS/2-Key GND
      '-------------------------------------------------------------------------*--O PS/2-Mouse GND

 Some protection resistors around 100 Ohm might be useful on PS/2 data and
clock lines to protect the devices during hotplugging (yes, this solution
seems to work with hotplugged PS/2 keyboards/mice). That being said, 
another protection series resistor around 10 Ohm might be handy for the 
PS/2 +5V power-rail too. I didn't try if these would affect the data-transfer
by weakening the PS/2 signals so be cautious about applying them. 
 The keyboard and mouse are supplied from the same 5V in my design as the 
USB circuitry but I didn't use the braced capacitor on the USB 5V pin though
it might help to smoothen out the supply voltage for older bulkier keyboards
and mouses with more power consumption. Anyway, in the code I asked for only
198mA from the USB port in the USB config-descriptor.
 A LED on P3.6 (pin24) with a series resistor towards ground is optional,
I only used it for debugging purposes. Currently the code only lits it up
when there's a PS/2 datalink or decoding error but it can be coded to
indicate traffic if needed. (Apart from that, not much PS/2 error-handling
happens in the current implementation at byte-level, the properly wired
and cabled PS/2 data with the parity checking seems reliable enough.)

 The PS/2 part of code: Timers 0 and 1 are set to their maximal value initially
to overflow on the PS/2-clock falling edge immediately and cause interrupt.
And every time an interrupt occurs for a PS/2-clock falling edge, the
timer-value is re-set to this value again-and-again to prepare for the next
clock-edge, so essentially it's not used as an actual timer, just a bridge
from the T0/T1-assigned pin to the IRQ.
 Receiving and sending the actual PS/2 data (LSB first) both happens in the
interrupt routines, that is, at the falling edges of PS/2 clock-lines. There's
a quirk though for sending: this microcontroller can't drive the clock-sense
pins low, which are used as T0/T1 (Timer external input) by the crossbar, so a
separate pin is wired together with them to act as a low-drives when needed.
(Sending the LED states to the keyboard requies a request-to-send signal
where PS/2 clock-line is to be pulled down for at least 100 microseconds.
Also, the mouse doesn't send any data at startup unless the host sends a 
special sequence to it, see more details in the source code.)

 The USB-HID part of the code is done in the main loop with some
synchronization to the PS/2 events when needed. It took me a while to
figure out from the USB and HID specifications what is essential for
the USB-enumeration and HID-data reporting to act as boot-time compatible
keyboard/mouse. Not every request is fully supported, for example Set_Idle
request doesn't set the idle time, just stores it. (Seemed unnecessary for the
task but later it might prove otrerwise.)
 The USB protocol is essentially a state-machine which when the device is
plugged in, first services all of the host's identification and enumeration
setup-requests through control packets on USB Endpoint0. When this is done
and the device appears beside other USB devices in the list (lsusb) the host
(PC) starts checking for key-event reports from Endpoint1 and Endpoint 2
at a regular intervals given in the Endpoint descriptors. These transfers
are called 'interrupt'-type transfers but not in the usual sense. 
When the MCU doesn't have any event to report it leaves the FIFO1/FIFO2 empty
and therefore it replies with 'NAK' signal to the regular report-request
of the host, and if there's data it sends it through Endpoint 1/2 FIFO.
During this working period the device (MCU) still should be able to check
for control-packets (setup-requests) from the host. In this case this mainly
includes setting keyboard Caps-Lock/Num-Lock LEDs: host sends a Set_Report
request along with a databyte that contains the bits for the states of the LEDs.
(These request-reply pairs can be watched in realtime by the 'usbmon' tool
for linux which I used for byte-level USB-debugging as seen in Makefile.)

The source code is extensively commented if you need more technical
information about the protocols...

In case you have a question don't hesitate to ask at: maxyst@atw.hu

                                                   M4XY5T - Year 2021

