Send data from Windows to V-USB HID keyboard

1 post / 0 new
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


I was building a project using V-USB library ( on ATtiny85 mcu. It is separated into device firmware and OS interface (console application that only sends signal into USB interface).

It should detect window hotkey -> send usb signal (single byte or up to 64 bytes) to device -> device read eeprom block specified by the signal -> return corresponding keypress over standard HID keyboard interface.

This was actually great guide how to build a working V-USB keyboard:


I plan to use it for sending predefined macros and passwords (I know - security risk and all) and I wanted to have ability to trigger it from my main keyboard and also ability to store new keypress sequence into the device's memory.


So far I was able to do it in Ubuntu with use of libusb-1.0 but not under Windows (keyboard working but vendor requests could not be send to the device). Libusb-1.0 looks to be limitated when interfacing HID keyboard and mouse because Windows doesn't allow anything to mess with HID drivers. Someone would say it is for security reasons I would say they just want to annoy me.

I was able to switch the device from HID do WinUSB drivers but that destroyed keyboard functionality (vendor request were working).


I use standard requests for the keyboard (USBRQ_TYPE_CLASS) + custom vendor codes for my functions (USBRQ_TYPE_VENDOR):

usbMsgLen_t usbFunctionSetup(uint8_t data[8])
	// see HID1_11.pdf sect 7.2 and
	usbRequest_t* rq = (void*)data;

	//HID keyboard class specific commands - set/get is relative to host
	if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) { // ignore request if it's not a class specific request - in this case keyboard
		// see HID1_11.pdf sect 7.2
		switch(rq->bRequest) {
				usbMsgPtr = (usbMsgPtr_t)&idleRate; // send data starting from this byte
				return 1; // send 1 byte

				idleRate = rq->wValue.bytes[1]; // read in idle rate
				return 0; // send nothing

				usbMsgPtr = (usbMsgPtr_t)&protocolVersion; // send data starting from this byte
				return 1; // send 1 byte

				protocolVersion = rq->wValue.bytes[1];
				return 0; // send nothing

				usbMsgPtr = (usbMsgPtr_t)&keyboardReport; // send the report data
				return sizeof(keyboardReport);

				if(rq->wLength.word == 1) { // check data is available
					// 1 byte, we don't check report type (it can only be output or feature)
					// we never implemented "feature" reports so it can't be feature
					// so assume "output" reports
					// this means set LED status
					// since it's the only one in the descriptor
					flag1 |= FLG1_LED_CHANGE;   //Store the information in global flag
					return USB_NO_MSG; // send nothing but call usbFunctionWrite
				}else { // no data or do not understand data, ignore
					return 0; // send nothing

			default: // do not understand data, ignore
				return 0; // send nothing

    //Specific "vendor" commands - see "hid_rq.h"
	} else if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_VENDOR) {

		switch(rq->bRequest) {
				//In lower nibble store memory location and then set trigger flag for password sending in main loop
				flag1 = (flag1 & (~FLG1_SEND_PASS_ADDRESS)) | FLG1_SEND_PASS_TRIGGER | (rq->wValue.bytes[0] & FLG1_SEND_PASS_ADDRESS);
				return 0;

			case VENDOR_RQ_SET_BULK:                //Store data block to eeprom
				bulkBytesRemaining = (rq->wLength.bytes[0]);    //Limited to small data - no more than 256 expected (in fact no more than 64 expected)
				return USB_NO_MSG;


			default: // do not understand data, ignore
				return 0; // send nothing
	return 0;

Receiving data

usbMsgLen_t usbFunctionWrite(uint8_t* data, uchar len)
	//Store Lock-LEDs values (num-lock, caps-lock and scroll-lock)
	//Also detect if num-lock changed - this is used for sending password 0 in BIOS mode when "host-vendor" application is not yet running

	if((flag1 & FLG1_BULK_TRANSFER_ACTIVE) && len) {    //Bulk transfer

		if(bulkBytesRemaining == 0) {
			bulkBufferPointer = 0;
			flag1 &= ~(FLG1_BULK_TRANSFER_ACTIVE | FLG1_BULK_DATA_RECEIVED);      //Reset flags - no data received, nothing should run in main loop
			return 0x01;                                //Everything received - or rather nothing received since nothing was send

		if(len > bulkBytesRemaining) {                  //Message shorter than len-block
			len = bulkBytesRemaining;

		uint8_t cntr = 0;

		for(cntr = 0; cntr < len; ++cntr) {
			buffer[cntr + bulkBufferPointer] = data[cntr];

		bulkBufferPointer += len;
		bulkBytesRemaining -= len;

		if(bulkBytesRemaining == 0) {   //TODO - duplicity/complex
			bulkBufferPointer = 0;
			flag1 &= ~FLG1_BULK_TRANSFER_ACTIVE;        //Bulk transfer comleted
			flag1 |= FLG1_BULK_DATA_RECEIVED;           //Buffer filled with data - ready for processing
			return 0x01;                                //Everything received
		} else {
			return 0x00;                                //Some data remaining

	} else if((flag1 & FLG1_LED_CHANGE) && len) {       //Detect change on LEDs
		flag1 &= ~FLG1_LED_CHANGE;                      //Reset flag for lock-LED change

		if((ledState & LEDSTATE_NUM_LOCK) == (data[0]& LEDSTATE_NUM_LOCK)) {
			ledState = data[0];                         // Store received LED status
			return 0x01;                                // Success: 1 byte read
		} else {
			ledState = data[0];                         // Store received LED status
			numLockTimeout = numLockConfig.triggerCountTimeout;
			return 0x01;                                // Success: 1 byte read

	return 0xFF;                                        //Error occured - unknown data


Also HID report descriptor is for default bootable keyboard (I would prefer if the keyboard works in bios but it is not necessary) from HID1_11 documentation:

PROGMEM const char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
	0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
	0x09, 0x06,                    // USAGE (Keyboard)
	0xA1, 0x01,                    // COLLECTION (Application)
	0x75, 0x01,                    //   REPORT_SIZE (1)
	0x95, 0x08,                    //   REPORT_COUNT (8)
	0x05, 0x07,                    //   USAGE_PAGE (Keyboard)(Key Codes)
	0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)(224)
	0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)(231)
	0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
	0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs) ; Modifier byte
	0x95, 0x01,                    //   REPORT_COUNT (1)
	0x75, 0x08,                    //   REPORT_SIZE (8)
	0x81, 0x03,                    //   INPUT (Cnst,Var,Abs) ; Reserved byte
	0x95, 0x05,                    //   REPORT_COUNT (5)
	0x75, 0x01,                    //   REPORT_SIZE (1)
	0x05, 0x08,                    //   USAGE_PAGE (LEDs)
	0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)
	0x29, 0x05,                    //   USAGE_MAXIMUM (Kana)
	0x91, 0x02,                    //   OUTPUT (Data,Var,Abs) ; LED report
	0x95, 0x01,                    //   REPORT_COUNT (1)
	0x75, 0x03,                    //   REPORT_SIZE (3)
	0x91, 0x03,                    //   OUTPUT (Cnst,Var,Abs) ; LED report padding
	0x95, 0x06,                    //   REPORT_COUNT (6)
	0x75, 0x08,                    //   REPORT_SIZE (8)
	0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
	0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)                   //? - in some documentations this is 255 for unknown reason (this is corresponding to 104-layout boot keyboard)
	0x05, 0x07,                    //   USAGE_PAGE (Keyboard)(Key Codes)
	0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))(0)
	0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)(101)
	0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
	0xC0                           // END_COLLECTION

And corresponding keyboard report according to HID1_11:

typedef struct {
	uint8_t modifier;
	uint8_t reserved;
	uint8_t keycode[6];
} keyboard_report_t;



I was thinking that my original concept could be fixed by:

- providing custom device drivers

- creating multiple devices on the USB device


Sadly I have no idea how to do either one of them. Can you help me with some (easy to understand) example?


Not prefered methods:

- use 2 USB slots with 2 mcu that are connected by some bit-bang interface

- use buttons on the device

- use aditional cable connections (for example RS232 signaling) - only if using other connection could let me get rid of USB and keep the same function "send signal->handle eeprom->send keyboard macro" then it may be useful.