Tuesday, July 12, 2016

USB Serial Keyboard/Mouse

Previously, we made a PS2 keyboard emulator.  Here we are going to create a USB keyboard that makes easy to control another PC from a laptop.  (Alternatively, we can turn a tablet into a keyboard/mouse, that's a project for another time.) We'll use the USB serial port from the laptop to transmitt keystrokes to a microcontroller that provides the USB keyboard device interface.  The microcontroller module we choose is the TI's EK-TM4C123GXL LaunchPad.  It features the TI's Tiva series microcontroller with an 80MHz Cortex M4F core.  The Cortex M4F adds more computational capabilities to Cortex M3 with DSP and single precision floating number instructions.  The board actually has two Tiva M4C123 microcontrollers: one serves as the In-Circuit Debug Interface (ICDI) and a USB serial interface and the other is programmable and has a USB OTG/Host/Device port with a USB micro-A/B connector as well as the other pins broken out to the Launchpad BoosterPack XL expansion connectors: two 2x10 headers that include 35 signals and power/ground.  In addition, there are two pushbutton switches and 3-color LEDs.  The board cost is $12.99, a very capable board at a very attractive price.


TI Code Composer Studio (Ver 6.1.1) with TI ARM compiler 5.2.6 is used as the development tools and TivaWare C Series (2.1.2)  as the device library.  In the CCS, we create a new project by selecting the device TM4C123GH6PM, Stellaris In-Circuit Debug Interface and TI v5.2.6 compiler version.  The startup code and the linker commands are automatically generated.  The startup code sets up the interrupt vectors, including the reset handler which jumps to c_int00 to start the application.  The linker script sets up the memory allocation.  This device has 256K Flash and 32K SRAM.  At this point, we can make sure things are set up correctly by compiling the skeleton code.  We can also take a look at the default compiler options.

The first thing to add is the UART.  We simply borrow code from the examples in the TivaWare.  One of the interesting features of Tiva is that the TivaWare Peripheral Driver Library (DriverLib) code resides in the internal ROM.   These functions are prefixed with ROM_.   Also there are the MAP_ version of the functions that make it easy to switch between the ROM version and the Flash version. We have to define a compiler variable TARGET_IS_TM4C123_RB1 in order to resolve these functions (by mapping to ROM addresses).  Here RB1 refers to Silicon Revision B1; it is not clear how to tell the silicon revision.  If the flash version of the driver functions are used, driverlib.lib has to be linked in.  It is relatively easy to modify compilation options from the project properties.  As a debugging aid, we print out __DATE__ and __TIME__ macros so that we know if the microcontroller is flashed with the latest code.

Next is to initialize the USB in the device mode and set up as the USB HID device class.  Again we borrow from the examples.  Thanks to the usblib, it is fairly easy to set up the HID keyboard device.  Be sure to enable the USB peripheral and configure the pins and add USB0DeviceIntHandler to the interrupt vectors.  A callback function is defined to receive the events from the HID keyboard driver; the events includes connected/disconnected, suspend/resume, transmit complete.  Finally the key press and release are sent by calling the key state change function.  For starter, the ascii character received from the UART has to be translated into USB HID keyboard usage code and we have to transmit both key press and key release.  Only a subset of the usage code is defined in the TivaWare; the complete code can easily be found on the web.  Microsoft has a USB HID to PS/2 scan code translation table.  Once the USB is plugged in, to a computer, it is recognized as USB HID Keyboard and the key strokes are received as we type on a serial terminal.  I have to set the force device mode, otherwise it is not detected.  By default, the VBUS and ID pins are not monitored;   And as a side effect, the disconnected event is not reported in the mode.

Now we need to develop an application to transmit key press and key release in a raw form.  The USB keyboard reports up to 6 keys along with keyboard modifiers (CTRL/SHIFT/ALT/GUI).  We'll have to see if we need to send modifiers together with the keys or separately.  Also multiple bytes has to be sent, so we need a way to synchronize.  The serial protocol has to be a little more complex: three bytes may be needed, one for modifiers, one for key code and one to indicate key press or release.  Some unused bits may be used for sync.  Timing should also be used: the three bytes are transmitted together.  Tests indicate that the modifiers' states have to be transmitted with the keys, so the modifiers have to be maintained.  Also note that you may have to turn the legacy USB keyboard/mouse mode in the BIOS; that was the case for the GRUB running on one of my older computers.

A further modification is to turn it into a keyboard and mouse composite device.  We simply call the composite initialization functions, then we deal with two separate devices.   The mouse device reports the pointer delta movements and the button states.  Using Python tkinter, we bind ButtonPress, ButtonRelease and Motion events.  From the event coordinates, we calculate the delta movements.  Note that the deltas are signed 8-bit numbers.  In the Tk event, the left/middle/right buttons are numbered 1, 2 and 3 respectively.  But it appears that the middle and the right buttons are swapped at the receiving end.  It might be a configuration issue.  I tested it with both Linux and Windows; it worked fine.

In the future, more input devices can be integrated.

Another version is created using Cypress PSoC5LP kit CY8CKIT-059, a small $10 board, which is more convenient to carry.   The implementation effort is similar.  But I had to dig a little deeper into setting up the USB descriptors for a composite HID device and the endpoint programming.  Most of the USB device code is auto generated.