Custom HID controller

For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
hodrob
 
Posts: 4
Joined: Fri Dec 08, 2017 7:02 am

Custom HID controller

Post by hodrob »

Hi,

I have a Feather M0 Bluefruit LE, which I want to use as a customize HID controller. I know, there's an already implemented HID GATT service in the firmware, but I need a much more different thing. So - as in the healththermometer example -, I tried to create the HID service and characteristics myself, with the 'addService' and 'addCharacteristic' functions. Unfortunately, when I'm trying to read out the values of a characteristic from my phone, I can receive nothing. If I change the service and characteristic UUID to some random number, it starts working.

Before I would invest more energy into this, is even possible to "override" the existing HID service? Or is there a "collision" in the module, when I'm trying to do this?

User avatar
hodrob
 
Posts: 4
Joined: Fri Dec 08, 2017 7:02 am

Re: Custom HID controller

Post by hodrob »

Let me answer my question: it is possible to "override" the existing GATT service, the Windows can now recognize my HID device. Although the 32-byte characteristic size is a very strict limit in case of the HID descriptor, and also, I cannot set the report_reference descriptor, so it won't work.

User avatar
equack
 
Posts: 16
Joined: Tue Jan 02, 2018 3:27 pm

Re: Custom HID controller

Post by equack »

I'm very interested in this problem as I need to emulate a proper HID joystick controller with proportional controls. Nobody from Adafruit seems to comment on these issues here in the forum or answers my direct support emails which is quite frustrating. Can you explain how far you got with the "roll your own" HID and exactly what the problem is? Can you share any code? I'm just about to either redesign my solution or bite the bullet and write my own firmware. It sure would be nice to have source code.

User avatar
hiemal
 
Posts: 1
Joined: Thu Feb 15, 2018 9:38 pm

Re: Custom HID controller

Post by hiemal »

I bought a bluefruit LE friend for this purpose too, thinking the gamepad service would be able to give analog stick data... now I'm stuck and frustrated, with a board that has no use to me.

User avatar
equack
 
Posts: 16
Joined: Tue Jan 02, 2018 3:27 pm

Re: Custom HID controller

Post by equack »

I have switched to the Feather nRF52. I still don't have the joystick mode working yet but I'm getting close. The PC sees it as a joystick. I have modified Adafruit's code extensively.

User avatar
hodrob
 
Posts: 4
Joined: Fri Dec 08, 2017 7:02 am

Re: Custom HID controller

Post by hodrob »

Hi,
Sorry for the late response, I didn't follow the thread after a while... I made the HID working more-or-less, but the characteristic limit is 32-byte, which is just not enough for a HID descriptor. I was able to do only a limited feature HID device (maybe few buttons?). I wanted to do a more complex device, so I had to change to a Feather nRF52. (Which is still not perfect, as I'm fighting with power consumption issues...)

Maybe I have my quick sketch somewhere about that working trial, but it's just not enough for any real HID device. I can try to dig that up, if absolute necessary, but I don't recommend the Feather M0 Bluefruit as a HID device.

User avatar
equack
 
Posts: 16
Joined: Tue Jan 02, 2018 3:27 pm

Re: Custom HID controller

Post by equack »

Hodrob- can you share your nRF52 code?

User avatar
hodrob
 
Posts: 4
Joined: Fri Dec 08, 2017 7:02 am

Re: Custom HID controller

Post by hodrob »

Doing HID is pretty straightforward on nrf52. Here's my code. I didn't have time to add the output report and other things I want yet, just the buttons.

hid_game.cpp:

Code: Select all

#include "hid_game.h"

enum {
	REPORT_ID_GAMEPAD = 1,
};

const uint8_t hid_report_descriptor[] =
{
  //------------- Keyboard Report  -------------//
  HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP     ),
  HID_USAGE      ( HID_USAGE_DESKTOP_GAMEPAD  ),
  HID_COLLECTION ( HID_COLLECTION_APPLICATION ),
    HID_REPORT_ID ( REPORT_ID_GAMEPAD       ),
    HID_USAGE_PAGE( HID_USAGE_PAGE_BUTTON   ),
      HID_USAGE_MIN    ( 1                                      ),
      HID_USAGE_MAX    ( 32                                     ),
      HID_LOGICAL_MIN  ( 0                                      ),
      HID_LOGICAL_MAX  ( 1                                      ),

      HID_REPORT_COUNT ( 32                                     ),
      HID_REPORT_SIZE  ( 1                                      ),
      HID_INPUT        ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),
  HID_COLLECTION_END,
};

HidGame::HidGame(void) :
	BLEHidGeneric(1, 0, 0)
{
	prev_button_state = 0;
}

err_t HidGame::begin(void)
{
	uint16_t input_len[] = { sizeof(hid_game_button_t) };
	
	setReportLen(input_len, NULL, NULL);
	enableBootProtocol(false, false);
	setReportMap(hid_report_descriptor, sizeof(hid_report_descriptor));
	
	VERIFY_STATUS(BLEHidGeneric::begin());
	
	/* Change connection interval to 11.25-15 ms when starting HID
	 * min = 9*1.25=11.25 ms, max = 12*1.25= 15 ms */
	Bluefruit.setConnInterval(9, 12);
	//Bluefruit.setConnInterval(6, 8);
	
	return ERROR_NONE;
}

bool HidGame::reportButtons(hid_game_button_t buttons)
{
	if (prev_button_state == buttons)
		return true;
#if 0
	Serial.println(buttons, HEX);
#endif
	prev_button_state = buttons;
	
	return inputReport(REPORT_ID_GAMEPAD, &buttons, sizeof(buttons));
}
hid_game.h:

Code: Select all

#ifndef _HID_GAME_H
#define _HID_GAME_H

#include <bluefruit.h>

typedef uint32_t	hid_game_button_t;

class HidGame : public BLEHidGeneric
{
private:
	hid_game_button_t prev_button_state;
	
public:
	HidGame(void);
	
	virtual err_t begin();
	
	/* Set 32 buttons' state (bit-field) */
	bool reportButtons(hid_game_button_t buttons);
};

#endif /* _HID_GAME_H */
The simplified main code:

Code: Select all

HidGame hid_game;

void setup(void)
{
	if (hid_game.begin())
		error("ERROR");

	.......

	/* Include game service */
	Bluefruit.Advertising.addService(hid_game);

	......
}

void loop(void)
{
	if (button_polling_timer_expired()) {
		hid_game_button_t buttons;

		button_polling_timer_restart();

		/* Poll and report buttons */
		buttons = button_task();
		hid_game.reportButtons(buttons);
	}
}

User avatar
equack
 
Posts: 16
Joined: Tue Jan 02, 2018 3:27 pm

Re: Custom HID controller

Post by equack »

Bless you Hodrob. Your solution worked for me. I was able to add XYZ axes to your code by using the following report descriptor:

Code: Select all

const uint8_t hid_report_descriptor[] =
    {
      //------------- XYZ axis 8 button Joystick Report  -------------//
      HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP     ),
      HID_USAGE      ( HID_USAGE_DESKTOP_GAMEPAD  ),
      HID_COLLECTION ( HID_COLLECTION_APPLICATION ),
        HID_REPORT_ID ( REPORT_ID_GAMEPAD       ),
        HID_USAGE_PAGE( HID_USAGE_PAGE_BUTTON   ),
          HID_USAGE_MIN    ( 1                                      ),
          HID_USAGE_MAX    ( 8                                     ),
          HID_LOGICAL_MIN  ( 0                                      ),
          HID_LOGICAL_MAX  ( 1                                      ),

          HID_REPORT_COUNT ( 8                                     ),
          HID_REPORT_SIZE  ( 1                                      ),
          HID_INPUT        ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),

        HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP),
          HID_USAGE   ( HID_USAGE_DESKTOP_X                   ),
          HID_USAGE   ( HID_USAGE_DESKTOP_Y                    ),
          HID_USAGE   ( HID_USAGE_DESKTOP_Z                    ),
 
          HID_LOGICAL_MIN ( 0x00                                   ), 
          HID_LOGICAL_MAX ( 0xFF                                   ),  
          HID_REPORT_COUNT( 3                                      ), // X, Y, Z position 
          HID_REPORT_SIZE ( 8                                      ),     //  8 bit value 
          HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ), // input values 
          
      HID_COLLECTION_END,
    };
I tested it using a tool I found called Pointy's Joystick Test. http://www.planetpointy.co.uk/joystick- ... plication/. I have not experimented with 16 bit axis values or reports larger than 32 bits as I do not require them for my application.

I'm not clear on whether the LOGICAL_MIN and LOGICAL_MAX should be signed or unsigned (the axis values themselves behave as if they are signed), but it seems to work this way. I'm also not clear on whether I should use HID_USAGE_DESKTOP_GAMEPAD or HID_USAGE_DESKTOP_JOYSTICK.

There is an extremely useful discussion about supporting multiple axes and large numbers of buttons here https://forum.pjrc.com/threads/23681-Many-axis-joystick although it is not BLE specific. Apparently there are people building elaborate custom flight simulator cockpits that require large numbers of inputs.

Locked
Please be positive and constructive with your questions and comments.

Return to “Wireless: WiFi and Bluetooth”