Старт ARM. Реализуем USB HID на SAM3U. Часть 1.

Всем привет!), сегодня речь пойдет об USB HID (основа Keil\ARM\Boards\…\…\HID). USB HID (human interface device) class — класс устройств USB для взаимодействия с человеком. Этот класс включает в себя такие устройства как клавиатура, мышь, игровой контроллер. Класс USB HID определен в нескольких документах, предоставляемых USB Implementers Forum, в частности, рабочей группой по работе с устройствами. Иногда всё-таки приходит время перехода с виртуальных com портов (сюда же CDC), на чистый usb протокол, и над протокол HID (пользовательский протокол). USB HID характеризуется типом передачи, существует 4 типа передачи:

1. Передача по управлению (USB_ENDPOINT_TYPE_CONTROL 0x00).
2. Передача по прерыванию (USB_ENDPOINT_TYPE_INTERRUPT 0x03).
3. Передачи массивами (USB_ENDPOINT_TYPE_BULK 0x02).
4. Изохронная передача данных (USB_ENDPOINT_TYPE_ISOCHRONOUS 0x01).

И конечными точками (USB_ENDPOINT_IN, USB_ENDPOINT_OUT). Конечная точка это канал для работы с пользовательским интерфейсом. Например у SAM3U4C у него аж 6 штук. Причем каждая конечная точка отличается размером буфера и количеством банков.
Самое главное это правильное описание дескриптора пакета передачи данных, для начала укажем сколько байт мы хотим передать, принять или тоже самое для особенных пакетов данных.
usbdesc.c

#define HID_INPUT_REPORT_BYTES       64             /* size of report in Bytes */
#define HID_OUTPUT_REPORT_BYTES      64             /* size of report in Bytes */
#define HID_FEATURE_REPORT_BYTES     64             /* size of report in Bytes */

Далее необходима описать сам USB дескриптор.

const U8 HID_ReportDescriptor[] = {
  HID_UsagePageVendor( 0x00                     ),
  HID_Usage          ( 0x01                     ),
  HID_Collection     ( HID_Application          ),
    HID_LogicalMin   ( 0                        ),  /* value range: 0 - 0xFF */
    HID_LogicalMaxS  ( 0xFF                     ),
    HID_ReportSize   ( 8                        ),  /* 8 bits */
    HID_ReportCount  ( HID_INPUT_REPORT_BYTES   ),
    HID_Usage        ( 0x01                     ),
    HID_Input        ( HID_Data | HID_Variable | HID_Absolute ),
    HID_ReportCount  ( HID_OUTPUT_REPORT_BYTES  ),
    HID_Usage        ( 0x01                     ),
    HID_Output       ( HID_Data | HID_Variable | HID_Absolute ),
    HID_ReportCount  ( HID_FEATURE_REPORT_BYTES ),
    HID_Usage        ( 0x01                     ),
    HID_Feature      ( HID_Data | HID_Variable | HID_Absolute ),
  HID_EndCollection,
};

Или можно еще так, делал для себя, когда hid-овские полтергейсты не довали мне с ним нормально обмениваться данными.

const U8 HID_ReportDescriptor[] = {
//.................................
0x06, 0xFF, 0xFF,														
// 04|2   , Usage Page (vendor defined?)
0x09, 0x01,																	
// 08|1   , Usage      (vendor defined
0xA1, 0x01,																	
// A0|1   , Collection (Application)
//..................................
// IN report
0x09, 0x02,																	
// 08|1   , Usage      (vendor defined)
0x09, 0x03,																	
// 08|1   , Usage      (vendor defined)
0x15, 0x00,																	
// 14|1   , Logical Minimum(0 for signed byte?)
0x26, 0xFF, 0x00,														
// 24|1   , Logical Maximum(255 for signed byte?)
0x75, 0x08,																	
// 74|1   , Report Size(8) = field size in bits = 1 byte
// 94|1   , ReportCount(size) = repeat count of previous item
0x95, HID_INPUT_REPORT_BYTES,
0x81, 0x02,																	
// 80|1   , IN report (Data,Variable, Absolute)
//....................................
// OUT report
0x09, 0x04,																	
// 08|1   , Usage      (vendor defined)
0x09, 0x05,																	
// 08|1   , Usage      (vendor defined)
0x15, 0x00,																	
// 14|1   , Logical Minimum(0 for signed byte?)
0x26, 0xFF, 0x00,														
// 24|1   , Logical Maximum(255 for signed byte?)
0x75, 0x08,																	
// 74|1   , Report Size(8) = field size in bits = 1 byte
// 94|1   , ReportCount(size) = repeat count of previous item
0x95, HID_OUTPUT_REPORT_BYTES,
0x91, 0x02,								
// 90|1   , OUT report (Data,Variable, Absolute)
// Feature report
0x09, 0x06,									
// 08|1   , Usage      (vendor defined)
0x09, 0x07,									
// 08|1   , Usage      (vendor defined)
0x15, 0x00,									
// 14|1   , LogicalMinimum(0 for signed byte)
0x26, 0xFF, 0x00,									
// 24|1   , Logical Maximum(255 for signed byte)
0x75, 0x08,									
// 74|1   , Report Size(8) =field size in bits = 1 byte
0x95, HID_FEATURE_REPORT_BYTES,					
// 94|x   , ReportCount in byte
0xB1, 0x02,									
// B0|1   , Feature report
0xC0								
// C0|0   , End Collection
};

Далее делаем USB конфигурацию.

/* Configuration 1 */
  USB_CONFIGUARTION_DESC_SIZE,       /* bLength */
  USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType */
  WBVAL(                             /* wTotalLength */
    USB_CONFIGUARTION_DESC_SIZE +
    USB_INTERFACE_DESC_SIZE     +
    HID_DESC_SIZE               +
    USB_ENDPOINT_DESC_SIZE*2
//добавляем один USB_ENDPOINT_DESC_SIZE для отправки данных на ПК
  ),
  0x01,                              /* bNumInterfaces */
  0x01,                              /* bConfigurationValue: 0x01 is used to select this configuration */
  0x00,                              /* iConfiguration: no string to describe this configuration */
//  USB_CONFIG_BUS_POWERED /*|*/       /* bmAttributes */
  USB_CONFIG_SELF_POWERED
/*USB_CONFIG_REMOTE_WAKEUP*/,
  USB_CONFIG_POWER_MA(100),          /* bMaxPower, device power consumption is 100 mA */

/* Interface 0, Alternate Setting 0, HID Class */
  USB_INTERFACE_DESC_SIZE,           /* bLength */
  USB_INTERFACE_DESCRIPTOR_TYPE,     /* bDescriptorType */
  0x00,                              /* bInterfaceNumber */
  0x00,                              /* bAlternateSetting */
  0x02,                              /* bNumEndpoints */ ------ количество конечных точек
  USB_DEVICE_CLASS_HUMAN_INTERFACE,  /* bInterfaceClass */
  HID_SUBCLASS_NONE,                 /* bInterfaceSubClass */
  HID_PROTOCOL_NONE,                 /* bInterfaceProtocol */
  0x04,                              /* iInterface */
/* HID Class Descriptor */
/* HID_DESC_OFFSET = 0x0012 */
  HID_DESC_SIZE,                     /* bLength */
  HID_HID_DESCRIPTOR_TYPE,           /* bDescriptorType */
  WBVAL(0x0100), /* 1.00 */          /* bcdHID */
  0x00,                              /* bCountryCode */
  0x01,                              /* bNumDescriptors */
  HID_REPORT_DESCRIPTOR_TYPE,        /* bDescriptorType */
  WBVAL(HID_REPORT_DESC_SIZE),       /* wDescriptorLength */
/* Endpoint, HID Interrupt In */
  USB_ENDPOINT_DESC_SIZE,            /* bLength */
  USB_ENDPOINT_DESCRIPTOR_TYPE,      /* bDescriptorType */
  USB_ENDPOINT_IN(1),                /* bEndpointAddress */
  USB_ENDPOINT_TYPE_INTERRUPT,       /* bmAttributes */
  WBVAL(64),                         /* wMaxPacketSize */ //РАЗМЕР 64 БАЙТА
  1,//0x08,            /* 32ms */    /* bInterval */          /* on High_speed 2^(bInterval-1) */
//------ ставим время 1с.
//------ дописываем пару строк для OUT отправки данных
  USB_ENDPOINT_DESC_SIZE,            /* bLength */
  USB_ENDPOINT_DESCRIPTOR_TYPE,      /* bDescriptorType */
  USB_ENDPOINT_OUT(2),               /* bEndpointAddress */
  USB_ENDPOINT_TYPE_INTERRUPT,       /* bmAttributes */
  WBVAL(64),                         /* wMaxPacketSize */
  1,//0x09,            /* 32ms */    /* bInterval */          /* on High_speed 2^(bInterval-1) */
/* Terminator */
  0                                  /* bLength */
}; 

Чтобы не запутаться в IN и OUT, есть аналогия с ПК, IN – входные данные в ПК, OUT – выходные данные из ПК. Сейчас необходимо описать работу конечных точек. Конечную точку номер 1, я буду использовать для записи данных в ПК, конечную точку 2 будем использовать для чтения данных из ПК.
usbuser.c

#if USB_CONFIGURE_EVENT
void USB_Configure_Event (void) {
  if (USB_Configuration) {                 
				USB_WriteEP(USB_ENDPOINT_IN(1), HIDVariablesIN, sizeof(HIDVariablesIN));
	}
}
#endif


void USB_EndPoint1 (U32 event) {
switch (event) {
		case USB_EVT_IN:
			USB_WriteEP(USB_ENDPOINT_IN(1), HIDVariablesIN, sizeof(HIDVariablesIN));
			break;
	}

}


void USB_EndPoint2 (U32 event) {
U32 size_OUT;
	switch (event) {
			case USB_EVT_OUT:	
      size_OUT = USB_ReadEP(USB_ENDPOINT_OUT(2), HIDVariablesOUT);
			memcpy(HIDVariablesIN, HIDVariablesOUT, size_OUT);
	}
}

Наша программа будет принимать данные с ПК и отправлять через USB HID их назад. Далее скачиваем кросс платформенную либу для USB  HID (https://github.com/signal11/hidapi) , и меняем исходник.

#include <stdio.h>
#include <wchar.h>
#include <string.h>
#include <stdlib.h>
#include "hidapi.h"
// Headers needed for sleeping.
#ifdef _WIN32
	#include <windows.h>
#else
	#include <unistd.h>
#endif

#define VID 0xC251
#define PID 0x2301

int main(int argc, char* argv[])
{
	FILE *fp;
	fp = fopen("report.txt", "w");
	int res;
	unsigned char buf[64];
	#define MAX_STR 255
	wchar_t wstr[MAX_STR];
	hid_device *handle;
	int i;

#ifdef WIN32
	UNREFERENCED_PARAMETER(argc);
	UNREFERENCED_PARAMETER(argv);
#endif

	struct hid_device_info *devs, *cur_dev;
	
	if (hid_init())
		return -1;

	devs = hid_enumerate(0x0, 0x0);
	cur_dev = devs;	
	while (cur_dev) {
		printf("Device Found\n  type: %04hx %04hx\n  path: %s\n  serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
		printf("\n");
		printf("  Manufacturer: %ls\n", cur_dev->manufacturer_string);
		printf("  Product:      %ls\n", cur_dev->product_string);
		printf("  Release:      %hx\n", cur_dev->release_number);
		printf("  Interface:    %d\n",  cur_dev->interface_number);
		printf("\n");
		cur_dev = cur_dev->next;
	}
	hid_free_enumeration(devs);

	// Set up the command buffer.
	memset(buf,0x00,sizeof(buf));
	buf[0] = 0x01;
	buf[1] = 0x82;
	

	// Open the device using the VID, PID,
	// and optionally the Serial number.
	////handle = hid_open(0x4d8, 0x3f, L"12345");
	handle = hid_open(VID, PID, NULL);
	if (!handle) {
		printf("unable to open device\n");
 		return 1;
	}

	// Read the Manufacturer String
	//wstr[0] = 0x0000;
	res = hid_get_manufacturer_string(handle, wstr, MAX_STR);
	if (res < 0)
		printf("Unable to read manufacturer string\n");
	printf("Manufacturer String: %ls\n", wstr);

	// Read the Product String
	wstr[0] = 0x0000;
	res = hid_get_product_string(handle, wstr, MAX_STR);
	if (res < 0)
		printf("Unable to read product string\n");
	printf("Product String: %ls\n", wstr);

	// Read the Serial Number String
	wstr[0] = 0x0000;
	res = hid_get_serial_number_string(handle, wstr, MAX_STR);
	if (res < 0)
		printf("Unable to read serial number string\n");
	printf("Serial Number String: (%d) %ls", wstr[0], wstr);
	printf("\n");

	// Read Indexed String 1
	wstr[0] = 0x0000;
	res = hid_get_indexed_string(handle, 1, wstr, MAX_STR);
	if (res < 0)
		printf("Unable to read indexed string 1\n");
	printf("Indexed String 1: %ls\n", wstr);

	// Set the hid_read() function to be non-blocking.
	hid_set_nonblocking(handle, 0);
	
	// Read requested state. hid_read() has been set to be
	// non-blocking by the call to hid_set_nonblocking() above.
	// This loop demonstrates the non-blocking nature of hid_read().
	res = 0;
	int t;
	int inc_array = 0;

	char array_char[5][64] = {
		" mcu.by_1",
		" mcu.by_2",
		" mcu.by_3",
		" mcu.by_4",
		" mcu.by_5",
	};
	int k;
	for(t=0; t<1000; t++) {
		
 		memcpy(buf, array_char[inc_array], sizeof(array_char[inc_array]));
		for (k = 0; k < 3; k++) {
			res = hid_write(handle, buf, sizeof(buf));
			#ifdef WIN32
			Sleep(1);
			#else
			usleep(1*1000);
			#endif
		}
		fprintf(fp,"IN: "" ");printf("IN: "" ");
		printf("%s\n",buf); fprintf(fp,"%s\n",buf);
		
		if (inc_array < 4)  inc_array++;
		else inc_array = 0;

		unsigned char outbuf[64] = {0};
		for (k = 0; k < 3; k++) {
			res = hid_read(handle, outbuf, 64);
				#ifdef WIN32
				Sleep(1);
				#else
				usleep(1*1000);
				#endif
		}
		fprintf(fp,"OUT: "" ");printf("OUT: "" ");
		for (i = 0; i < res; i++){
			printf("%c", outbuf[i]);
			fprintf(fp,"%c", outbuf[i]);
		}
		printf(" ""\n");fprintf(fp," ""\n");
	}

	fclose(fp);
	hid_close(handle);

	hid_exit();

#ifdef WIN32
	system("pause");
#endif

	return 0;
}

Я делаю трижды read и write, только для примера.
Итого:

Всем пока!) продолжим следующий раз.