V-USB и libusb: обмен с устройством USB HID с помощью управляющих сообщений (USB control messages) |
![]() |
Добавил(а) microsin |
В статье разбирается метод использования функций usbFunctionSetup (из библиотеки V-USB) и usb_control_msg (из библиотеки libusb) для организации обмена между устройством USB HID (работает на основе библиотеки V-USB) программой на компьютере (ПО хоста, работает на основе библиотеки libusb). Если коротко - библиотеки V-USB и libusb позволяют разрабатывать USB устройства на обычных микроконтроллерах AVR компании Atmel и писать кроссплатформенное (Windows, MAC OS, *nix) ПО хоста для них. Подробнее см. ссылки. Обмен данными с помощью управляющих сообщений (control messages) удобен в том случае, когда не нужно передавать большой объем данных за один раз (до 4 байт). Все обмены информацией в обоих направлениях (и от устройства USB HID к ПО хоста, и обратно от ПО хоста к USB HID устройству) происходят только по инициативе хоста. Для передачи данных от устройства USB HID ПО хоста использует запрос со значением CUSTOM_RQ_GET_STATUS, а для отправки данных в USB устройство - запрос со значением CUSTOM_RQ_SET_STATUS (для этого в ПО хоста применяется вызов функции usb_control_msg с соответствующими параметрами). В ответ firmware USB HID внутри своей функции usbFunctionSetup должно проанализировать значение пришедшего запроса, и в зависимости от него либо получить данные (в ответ на запрос CUSTOM_RQ_SET_STATUS), либо подготовить данные для отправки (в ответ на запрос CUSTOM_RQ_GET_STATUS). Разберем методику обмена на примере hid-custom-rq (этот пример можно скачать в архиве avr-usb-russian.rar [3]). В этом примере ПО хоста управляет светодиодом, подключенным к ножке микроконтроллера (см. для примера схему макетной платы AVR-USB-MEGA16), либо считывает его состояние. И в том и в другом случае передается только один байт, что вполне достаточно для такой задачи. Я рассмотрю только те участки кода примера, которые нужно будет модифицировать в случае создания собственного программного обеспечения. [Отправка данных от ПО хоста к USB HID устройству] В этом случае мы управляем состоянием светодиода. В ПО хоста для отправки байта применяется вызов функции usb_control_msg с параметром CUSTOM_RQ_SET_STATUS (см. модуль examples\hid-custom-rq\commandline\set-led.c, тело функции main): usb_dev_handle *handle = NULL; int cnt, isOn; char buffer[4]; cnt = usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, CUSTOM_RQ_SET_STATUS, isOn, 0, buffer, 0, 5000); if(cnt < 0) { fprintf(stderr, "USB error: %sn", usb_strerror()); } Теперь подробно о назначении каждой переменной. handle - хендл открытого на доступ устройства USB HID. Тут не рассмотрен код, открывающий устройство USB HID и получающий ненулевое значение переменной handle, поскольку в нем мы ничего не меняем - все тупо оставляем так, как в примере. Назначение параметров функции usb_control_msg (см. документацию по функции usb_control_msg libusb Developers Guide site:libusb.sourceforge.net): 1. usb_dev_handle *dev. Здесь используется значение переменной handle - передается идентификатор открытого USB HID устройства. Получают валидный handle после однократного вызова функции usbOpenDevice (из модуля opendevice.c). В устройстве USB HID запрос CUSTOM_RQ_SET_STATUS обрабатывает код внутри тела функции usbFunctionSetup. Этот код зажигает или гасит светодиод: usbMsgLen_t usbFunctionSetup(uchar data[8]) { usbRequest_t *rq = (void *)data; if((rq -> bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_VENDOR) { if(rq->bRequest == CUSTOM_RQ_SET_STATUS) { if(rq -> wValue.bytes[0] & 1) { /* зажжем светодиод */ LED_PORT_OUTPUT |= _BV(LED_BIT); } else { /* погасим светодиод */ LED_PORT_OUTPUT &= ~_BV(LED_BIT); } } else if(rq -> bRequest == CUSTOM_RQ_GET_STATUS) { static uchar dataBuffer[1]; /* буфер должен оставаться валидным при выходе из usbFunctionSetup */ dataBuffer[0] = ((LED_PORT_OUTPUT & _BV(LED_BIT)) != 0); usbMsgPtr = dataBuffer; /* говорим драйверу, какие данные вернуть */ return 1; /* говорим драйверу послать 1 байт */ } } else { /* вызовы запросов USBRQ_HID_GET_REPORT и USBRQ_HID_SET_REPORT не реализованы, * поскольку мы их не вызываем. Операционная система также не будет обращаться * к ним, потому что наш дескриптор не определяет никакого значения. */ } return 0; /* default для нереализованных запросов: не возвращаем назад данные хосту */ } Из кода видно, что анализируются данные поля wValue структуры usbRequest_t. Значение wValue равно значению параметра 4 в вызове функции usb_control_msg, в нашем примере тут передается значение переменной isOn. Размер поля wValue 16 бит, здесь можно передать 2 байта полезной информации. Еще 2 байта можно передать через поле wIndex (соответствует параметру 5 int index в функции usb_control_msg). Возможно, что еще 2 байта можно передать через поле wLength, но я не пробовал. [Отправка данных от USB HID устройства к ПО хоста] В этом случае мы считываем состояние светодиода. В ПО хоста для получения байта, в котором записано состояние светодиода, применяется вызов функции usb_control_msg с параметром CUSTOM_RQ_GET_STATUS (см. модуль examples\hid-custom-rq\commandline\set-led.c, тело функции main): cnt = usb_control_msg(handle, Возвращенное значение переменной cnt, равное нулю и меньше нуля сигнализирует об ошибке, в случае возврата 1 и более означает, что запрос обработан успешно. Реально возвращается один байт, поэтому cnt должно быть равно 1 (оно как раз равно значению, которое возвращает функция usbFunctionSetup при обработке запроса CUSTOM_RQ_GET_STATUS: return 1). Полезные данные передаются в первом байте buffer. Назначение переменных в вызове функции usb_control_msg: 1. usb_dev_handle *dev. Тут ничего не поменялось, все то же самое, как в случае отправки запроса CUSTOM_RQ_SET_STATUS. На стороне USB HID устройства запрос CUSTOM_RQ_GET_STATUS обрабатывает простой код в теле функции usbFunctionSetup: .. else if(rq->bRequest == CUSTOM_RQ_GET_STATUS) { static uchar dataBuffer[1]; /* буфер должен оставаться валидным при выходе из usbFunctionSetup */ dataBuffer[0] = ((LED_PORT_OUTPUT & _BV(LED_BIT)) != 0); usbMsgPtr = dataBuffer; /* говорим драйверу, какие данные вернуть */ return 1; /* говорим драйверу послать 1 байт */ } .. В памяти выделяется однобайтовый буфер dataBuffer, в который записывается состояние светодиода. Указатель на этот буфер передается через значение переменной usbMsgPtr. Оператор return 1 говорит драйверу о том, что передаем 1 байт (он попадает в буфер buffer в ПО хоста). Реально можно передать 4 байта, если выделить буфер dataBuffer[1], и вернуть заместо 1 значение 4 (сделать return 4). Вячеслав, автор примера программы на Visual Basic (см. ссылку [5]) сообщает, что буфер (dataBuffer и buffer) может быть и размером в 8 байт. Все! Больше ничего не надо знать, чтобы организовать простой работоспособный обмен данными до 4 байт в обе стороны. Еще раз повторю, что отправка данных в USB устройство происходит в запросе CUSTOM_RQ_SET_STATUS через параметры 4 value и 5 index вызова функции usb_control_msg (в USB HID устройстве эти данные попадают в соответствующие поля структуры usbRequest_t), а получение данных от USB устройства происходит через запросCUSTOM_RQ_GET_STATUS и выделенный буфер данных. [Смена параметров USB устройства - имя устройства, его VID и PID] Эти параметры задаются в файле usbconfig.h. При компиляции он общий как для firmware (основано на библиотеке V-USB), так и для ПО хоста (основано на библиотеке libusb). Параметры (имя устройства, VID и PID) менять совершенно необязательно, все и так будет работать, но это может понадобиться, если Вы хотите получить уникальное устройство, либо хотите обеспечить работу нескольких Ваших устройств USB одновременно на одном компьютере. О назначении всех параметров можете прочитать комментарии в файле usbconfig.h на русском языке, если скачаете русифицированный пакет V-USB (см. ссылку 3). Обращаю Ваше внимание, что после внесения изменений в файл usbconfig.h необходимо перекомпилировать как firmware, так и ПО хоста. [Ссылки] 1. libusb Developers Guide site:sourceforge.net - документация по библиотеке libusb. |