Tuesday, 12 February 2013

Library: USBMouse implementation

Today I'm going to show you how to implement USB mouse emulation using InputStick library. In order to understand what's going on you need to have at least some basic knowledge about USB and HID (Human Interface Device) class. At this moment I'm not going to explain every detail, although I think about writing detailed tutorial at some point in the future. My goal for today is to show, that using InputStick library, which takes care of many USB related details, it is possible to implement simple USB device in approximately 100 lines  of code. If you want to learn more about USB and HID there are many good materials available on the Internet:

At this moment InputStick library is still under development. Current version has some limitations:
  • only HID class is supported.
  • only 1 device configuration supported.
  • only 1 interface supported.
  • total size of all descriptors must be lass than 512 bytes.

In final release I hope to solve at least some of this problems. Adding support for more interfaces and additional USB classes has highest priority. 512 bytes limit may be hard to overcome, due to memory limitations of microcontroller used in InputStick device, however it should be more than enough for most cases.
 
Brief description of Java classes used in this example:
 
  • USBDevice - every USB device must inherit from this class, which stores device related data and takes care of communication with InputStick device.
  •  
  • USBDeviceData - stores data describing the USB device.
  •  
  • USBDescriptor - abstract class used as a base class for specific USB descriptors (Device, Configuration, Interface, etc.). Descriptors are a data structures containing information about USB device, what allows USB host to learn about devices capabilities.
  •  
  • EndpointConfig - stores USB endpoint configuration: type, direction, size etc.
  •  
  • EndpointBuffer - FIFO data buffer for a USB endpoint.
 
Before going to implementation I will briefly describe USB mouse protocol. USB mouse uses one IN endpoint (data goes from device to USB host). Mouse sends data to host using USB reports. In most simple case, which is presented here (mouse with two buttons), report consists of 3 data bytes: state of buttons, change in X coordinate, change in Y coordinate. Some examples:
 
{0x00, 0x0A, 0x00} - moves pointer 10 pixels right.
{0x00, 0x00, 0x05} - moves pointer 5 pixels down.
{0x01, 0x00, 0x00} - left button pressed.
{0x02, 0x00, 0x00} - right button pressed.
{0x03, 0x00, 0x00} - both buttons pressed.
{0x00, 0x00, 0x00} - all buttons released.
 
Now let's take a look at the implementation:
 
public class USBMouse extends USBDevice {
private boolean isLMBPressed;
private boolean isRMBPressed;
private EndpointBuffer mouseEndp;
public USBMouse(String mac) {
super(mac);
//setup descriptors
//Vendor ID, Product ID, Endpoint 0 size
DeviceDescriptor devDesc = new DeviceDescriptor(0x0483, 0xF772, 64);
//Number of interfaces, Attribures: bus powered, required current: 100mA
ConfigurationDescriptor confDesc = new ConfigurationDescriptor(1, 0xE0, 100);
//interface number, Alternate settings, Number of endpoints: 1 (EP1IN)
InterfaceDescriptor intDesc = new InterfaceDescriptor(0, 0, 1);
HIDDescriptor hidDesc = new HIDDescriptor();
ReportDescriptor reportDesc = new ReportDescriptor();
//Address: EP1IN, Attributes: Interrupt endpoint type, size: 3 bytes, Polling interval: 32ms
EndpointDescriptor endp1inDesc = new EndpointDescriptor(0x81, 0x03, 3, 32);
//Language code
StringDescriptor langIDString = new StringDescriptor(StringDescriptor.LANGID_US_ENG);
//Vendor name
StringDescriptor vendorString = new StringDescriptor("InputStick");
//Product name
StringDescriptor productString = new StringDescriptor("MyMouse");
//Serial number
StringDescriptor serialString = new StringDescriptor("SN123");
//Report descriptor:
reportDesc.usagePage(ReportDescriptor.PAGE_GENERIC_DESKTOP);
reportDesc.usage(ReportDescriptor.DESKTOP_MOUSE);
reportDesc.collection(ReportDescriptor.COLLECTION_APPLICATION);
reportDesc.usage(ReportDescriptor.DESKTOP_POINTER);
reportDesc.collection(ReportDescriptor.COLLECTION_PHYSICAL);
reportDesc.usagePage(ReportDescriptor.PAGE_BUTTON);
reportDesc.usageMinimum(1);
reportDesc.usageMaximum(3);
reportDesc.logicalMinimum(0);
reportDesc.logicalMaximum(1);
reportDesc.reportCount(2);
reportDesc.reportSize(1);
reportDesc.input(ReportDescriptor.DATA | ReportDescriptor.VARIABLE | ReportDescriptor.ABSOLUTE);
reportDesc.reportCount(1);
reportDesc.reportSize(6);
reportDesc.input(ReportDescriptor.CONSTANT);
reportDesc.usagePage(ReportDescriptor.PAGE_GENERIC_DESKTOP);
reportDesc.usage(ReportDescriptor.DESKTOP_X);
reportDesc.usage(ReportDescriptor.DESKTOP_Y);
reportDesc.logicalMinimum(-127);
reportDesc.logicalMaximum(127);
reportDesc.reportSize(8);
reportDesc.reportCount(2);
reportDesc.input(ReportDescriptor.DATA | ReportDescriptor.VARIABLE | ReportDescriptor.RELATIVE);
reportDesc.collectionEnd();
reportDesc.collectionEnd();
//Class: HID, Subclass, Protocol: Boot
intDesc.setClass(0x03, 0x01, 0x02);
hidDesc.addReport(Descriptor.DESCRIPTOR_REPORT, reportDesc.getLength());
confDesc.addLowLevelDescriptor(intDesc);
confDesc.addLowLevelDescriptor(hidDesc);
confDesc.addLowLevelDescriptor(endp1inDesc);
//setup endpoints
EndpointConfig ep0cfg = new EndpointConfig(USB.ENDPOINT_CONTROL);
EndpointConfig ep1cfg = new EndpointConfig(USB.ENDPOINT_INTERRUPT);
//Endpoint RX state: ready to receive data, Buffer address, Size: 64, Buffer Address: no buffer assigned
ep0cfg.setRXconfig(USB.EP_RX_VALID, 0x18, 0x40, 0);
//Endpoint TX state: stalled, Buffer address, Size: 64, Buffer Address: no buffer assigned
ep0cfg.setTXconfig(USB.EP_TX_STALL, 0x58, 0x40, 0);
//Endpoint RX state: disabled, Buffer address, Size: 0, Buffer Address: no buffer assigned
ep1cfg.setRXconfig(USB.EP_RX_DISABLED, 0x00, 0x00, 0);
//Endpoint TX state: NAK, Buffer address, Size: 3, Buffer Address: no buffer assigned
ep1cfg.setTXconfig(USB.EP_TX_NAK, 0xB0, 0x03, 0);
//setup device data
USBDeviceData devData = super.getDeviceData();
//Max endpoint index: 2, Number of configurations: 1
devData.setDeviceInfo(2, 1);
//device is interested in correct transfer and reset events
devData.setImrMsk(USB.CNTR_CTRM | USB.CNTR_RESETM);
//adding data
devData.addEndpoint(ep0cfg);
devData.addEndpoint(ep1cfg);
devData.addDescriptor(devDesc);
devData.addDescriptor(confDesc);
devData.addDescriptor(reportDesc);
devData.addDescriptor(langIDString);
devData.addDescriptor(vendorString);
devData.addDescriptor(productString);
devData.addDescriptor(serialString);
//get buffer for EP1IN
mouseEndp = super.getEndpointBuffer(1);
}
//press or release left/right button
public void setKeyState(int keyCode, boolean state) {
if (keyCode == 1) {
isLMBPressed = state;
}
if (keyCode == 2) {
isRMBPressed = state;
}
}
//move mouse pointer by x,y
public void move(int x, int y) {
byte[] report = new byte[3];
//setup report data
report[0] = 0x00;
report[1] = (byte)x;
report[2] = (byte)y;
if (isLMBPressed) {
report[0] += 1;
}
if (isRMBPressed) {
report[0] += 2;
}
//enqueue USB report, library will manage delivering it to InputStick device as soon as possible
mouseEndp.enqueue(BTCommand.getBytes(BTCommand.CMD_ENDP_IN, BTCommand.ENDP_1, report));
}
}
view raw gistfile1.java hosted with ❤ by GitHub

 
If you're familiar with USB and HID, comments should more less explain everything what's happening. If not, well, take a look at sites linked at the beginning of this post, USB is not as difficult as it may seem :)
 

No comments:

Post a Comment