В статье разбирается метод использования функций 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, поскольку в нем мы ничего не меняем - все тупо оставляем так, как в примере. cnt - количество байт, пришедших от устройства USB HID в ответ на запрос CUSTOM_RQ_SET_STATUS. В данном случае значение cnt просто сигнализирует об наличии ошибки, если cnt отрицательно. isOn - передаваемый байт полезной информации. Если он равен 0, то светодиод погаснет, если 1, то загорится. buffer - просто тупая переменная, которая тут никак не используется. При отправке запроса CUSTOM_RQ_SET_STATUS состояние данных буфера buffer на входе в функцию usb_control_msg может быть произвольным, и никакого значения эти данные не имеют. На выходе из функции usb_control_msg буфер buffer также не анализируется, никаких полезных данных в нем нет.
Назначение параметров функции 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). 2. int requesttype, тип запроса. Его значение равно USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT. Указывается конечная точка USB устройства USB_ENDPOINT_OUT. 3. int request, значение запроса. Тут подставляется значение CUSTOM_RQ_SET_STATUS, что означает, что мы передаем данные в запросе. 4. int value. В этом параметре можно передать максимум два байта (т. е. младшие 16 бит 32-разрядного параметра int value). В нашем примере тут передается значение переменной isOn, несущей информацию о том, что нужно светодиод выключить (isOn=0) или включить (isOn=1). 5. int index. Здесь передается ноль, и в нашем примере этот параметр не используется, однако тут можно передать еще 2 байта полезных данных. Таким образом, с помощью запроса CUSTOM_RQ_SET_STATUS можно передать максимум 4 байта полезной информации от ПО хоста в USB HID устройства (в значениях параметров 4 value и 5 index). 6. char *bytes. Указатель на буфер, выделенный в памяти. В случае запроса CUSTOM_RQ_SET_STATUS этот буфер никак не используется, и нет никакой возможности его использовать для передачи информации. 7. int size. Тут передается размер буфера. В случае запроса CUSTOM_RQ_SET_STATUS всегда передается 0, другое значение не дает никакого эффекта. 8. int timeout. Таймаут, в течение которого должен прийти ответ. Стоит значение 5000, наверное это означает 5000 миллисекунд).
В устройстве 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, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,CUSTOM_RQ_GET_STATUS, 0, 0, buffer, sizeof(buffer), 5000);
if(cnt < 1)
{
if(cnt < 0)
fprintf(stderr, "USB error: %sn", usb_strerror());
else
fprintf(stderr, "only %d bytes received.n", cnt);
}
else
printf("LED is %sn", buffer[0] ? "on" : "off");
Возвращенное значение переменной cnt, равное нулю и меньше нуля сигнализирует об ошибке, в случае возврата 1 и более означает, что запрос обработан успешно. Реально возвращается один байт, поэтому cnt должно быть равно 1 (оно как раз равно значению, которое возвращает функция usbFunctionSetup при обработке запроса CUSTOM_RQ_GET_STATUS: return 1). Полезные данные передаются в первом байте buffer. Назначение переменных в вызове функции usb_control_msg:
1. usb_dev_handle *dev. Тут ничего не поменялось, все то же самое, как в случае отправки запроса CUSTOM_RQ_SET_STATUS. 2. int requesttype, тип запроса. Его значение равно USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN. Тут отличие только в том, что указывается конечная точка USB_ENDPOINT_IN, а не USB_ENDPOINT_OUT. 3. int request, значение запроса. Тут подставляется значение CUSTOM_RQ_GET_STATUS, что означает, что мы передаем запрос на получение данных. 4. int value. Передается 0, параметр не используется. 5. int index. Передается 0, параметр не используется. 6. char *bytes. Указатель на буфер, выделенный в памяти. В случае запроса CUSTOM_RQ_GET_STATUS этот буфер используется для переноса данных от USB устройства к ПО хоста. В нашем примере передается только один байт, несущий информацию о состоянии светодиода. О том, что байт только один, говорит return 1 на выходе из usbFunctionSetup в случае обработки запроса CUSTOM_RQ_GET_STATUS (см. код устройства USB HID, модуль main.c). 7. int size. Тут передается размер буфера. В случае запроса CUSTOM_RQ_GET_STATUS передается значение 4, отражающее реальный размер буфера. 8. int timeout. Тут все то же самое.
На стороне 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. 2. В русской википедии кратко объясняется, что это такое V-USB. 3. avr-usb-russian.rar - библиотека V-USB с русскими комментариями и документацией. 4. Разработка устройства USB - как начать работу с библиотеками V-USB и libusb. Пошаговое руководство, как установить весь нужный софт для работы с библиотеками V-USB и libusb и компилировать примеры. 5. AVR-USB-MEGA16: управление устройством USB из GCC, Visual Studio CPP, VB6. 6. Общение с контроллером шагового двигателя по USB site:speleoastronomy.org. |
Комментарии
microsin: очень просто. Вы наверное догадываетесь, что 16-битная переменная это всего лишь 2 байта? Если да, то просто создайте буфер размером в 2 байта, поместите туда старший и младший байты 16-битной переменной, и верните из функции usbFunctionSetu p не 1, как приведено в примере в статье, а 2:
..
else if(rq->bRequest == CUSTOM_RQ_GET_S TATUS)
{
static uchar dataBuffer[2];
dataBuffer[0] = младший_байт_16_битной_переме нной;
dataBuffer[1] = старший_байт_16_битной_переме нной;
usbMsgPtr = dataBuffer;
return 2;
}
..
RSS лента комментариев этой записи