STM32 USB, QT, Libusb, шаблон проекта


На стороне контроллера используется стандартная библиотека от ST («Custom_HID»), на ПК установлен Libusb (windows сборка — «libusb-win32»). Обмен происходит 50-байтовыми пакетами, используются две конечные точки.
Передается ряд простых параметров:
— системное время на плате
— напряжение после 3,3v LDO стабилизатора и входное 5v
— температура с внутреннего «temperature sensor» (ADC_Channel_16)
— условный серийный номер устройства
Так как о самом USB в связке с stm32 уже многократно писали на хабре и здесь, сразу приведу код:
Контроллер
Дескриптор немного изменен, описано два репорта:
/* CustomHID_ConfigDescriptor */
const uint8_t CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
{
0x06, 0x00, 0xff, // USAGE_PAGE (Generic Desktop)
0x09, 0x01, // USAGE (Vendor Usage 1)
// System Parameters
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x01, // REPORT_ID (1)
0x09, 0x01, // USAGE (Vendor Usage 1)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 50, // REPORT_COUNT (1)
0xb1, 0x82, // FEATURE (Data,Var,Abs,Vol)
0x85, 0x01, // REPORT_ID (1)
0x09, 0x01, // USAGE (Vendor Usage 1)
0x91, 0x82, // OUTPUT (Data,Var,Abs,Vol)
0x85, 0x02, // REPORT_ID (4)
0x09, 0x02, // USAGE (Vendor Usage 4)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 50, // REPORT_COUNT (N)
0x81, 0x82, // INPUT (Data,Var,Abs,Vol)
0xc0 // END_COLLECTION
};
Также параметр «wMaxPacketSize:» увеличен до 0x40 (Max).
int main(void)
{
/* настройка USB из примеров */
Set_System();
USB_Interrupts_Config();
Set_USBClock();
USB_Init();
/* настройка RTC, установка начального времени и т.п.*/
SetParam();
/* настройка АЦП */
ADC_Config();
/* всего одна задача */
xTaskCreate(USB_Response,0, configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );
vTaskStartScheduler();
while (1){}
}
Задача, подготавливаются и отправляются данные:
#define COEFF_0 (float)3.20
#define COEFF_1 (float)10.8
#define V25 (float)1.38
#define AVG_SLOPE (float)4.3
#define BUF_SIZE 50
void USB_Response(void *pvParameters)
{
uint8_t i, i2;
uint8_t UsbTrans[BUF_SIZE];
float tempFloatPar;
char *PointChar;
sMain_Setting * PointMainSetting = NULL;
for(;;)
{
/* Update Value ADC */
PointMainSetting = (sMain_Setting*)&Main_Setting;
for(i=0;i<ADC_CHANELS;i++)
{
if(i==0)
{
PointMainSetting->ADC_Values[i] = ((COEFF_0 * ADC_Result[i])/4095);
}
if(i==1)
{
PointMainSetting->ADC_Values[i] = ((COEFF_1 * ADC_Result[i])/4095);
}
if(i==2)
{
PointMainSetting->ADC_Values[i] = ((V25 -((ADC_Result[i])* VREF)/4095)*1000/AVG_SLOPE)+25.0;
}
i =0;
/* Send Pack USB */
UsbTrans[i++]= USB_CurrentIndex.USB_Index_Catalog;
UsbTrans[i++]= USB_CurrentIndex.USB_Index_SubCatalog;
UsbTrans[i++]= USB_CurrentIndex.USB_Index_Param_One;
UsbTrans[i++]= USB_CurrentIndex.USB_Index_Param_Two;
switch(USB_CurrentIndex.USB_Index_Catalog)
{
case U_CATLOG_SYS_PAR:
if(USB_CurrentIndex.USB_Index_Param_One == U_SYS_PAR1)
{
/*Date-Time, Value ADC, SerialNumber */
UpdateCalendar();
UsbTrans[i++]=Main_Setting.Data_Time.year & 0x00FF;
UsbTrans[i++]=Main_Setting.Data_Time.month;
UsbTrans[i++]=Main_Setting.Data_Time.day;
UsbTrans[i++]=Main_Setting.Data_Time.hour;
UsbTrans[i++]=Main_Setting.Data_Time.minutes;
UsbTrans[i++]=Main_Setting.Data_Time.seconds;
/* get ADC 3,3 and 5 v */
for(i2=0;i2<ADC_CHANELS;i2++)
{
tempFloatPar = Main_Setting.ADC_Values[i2];
PointChar = (char*)&tempFloatPar;
UsbTrans[i++]= *PointChar; PointChar ++;
UsbTrans[i++]= *PointChar; PointChar ++;
UsbTrans[i++]= *PointChar; PointChar ++;
UsbTrans[i++]= *PointChar;
}
/* Serial Number */
PointChar = (char*)&Main_Setting.SerialNumberDev;
UsbTrans[i++] = *PointChar; PointChar ++;
UsbTrans[i++] = *PointChar; PointChar ++;
UsbTrans[i++] = *PointChar; PointChar ++;
UsbTrans[i++] = *PointChar; PointChar ++;
}
break;
}
PrevXferComplete = 0;
USB_SIL_Write(EP1_IN, (uint8_t*)&UsbTrans, BUF_SIZE);
SetEPTxValid(ENDP1);
vTaskDelay(1);
}
}
Если все правильно, (после прошивки) при включении должно обнаружится новое USB-HID устройство.
VID-PID оставлен по умолчанию (0x483,0x5750)

Для работы с классом HID устройств на стороне ПК — существует несколько известных библиотек:
— HID API
— HID.dll
— USB HID for C#
— Libusb
Остановился на Libusb по причине кроссплатформености (хотя отличия под Win32 и Linux есть).
Скачав последную версию Libusb, распаковав, в каталоге «libusb\bin» обнаружил:

inf-wizard.exe — «конструктор» драйверов для устройств
в папках amd64-ia64-x86 находятся два системных файла (libusb0.dll и libusb0s.sys)
— libusb0.dll нужно скопировать в папку C:\Windows\System32
— libusb0.sys в C:\Windows\System32\drivers
Дальше необходимо установить «фильтр» на наше устройство.
Для этого запустим «install-filter-win.exe», выберем устройство по VID-PID номеру

После установки, устройство в системе будет видится как принадлежащее к «libusb-win32 devices»,
чтобы высвечивалось его название — для него необходимо создать inf файл, через «inf-wizard.exe».

После установки:

QT:
В первую очередь добавить пути к распакованному Libusb (в pro файл проекта)
LIBS += C:/libusb/lib/gcc/libusb.a
INCLUDEPATH += C:/libusb/libusb/include/
DEPENDPATH += C:/libusb/libusb/include/
PRE_TARGETDEPS+= C:/libusb/lib/gcc/libusb.a
В исходнике понадобится всего один дополнительный инклуид:#include "C:\libusb\include/lusb0_usb.h"
Определения:// Device vendor and product id.
#define MY_VID 0x0483
#define MY_PID 0x5750
// Device endpoint(s)
#define EP_IN 0x81
#define EP_OUT 0x01
// Device of bytes to transfer.
#define BUF_SIZE 50
// Device configuration and interface id.
#define MY_CONFIG 1
#define MY_INTF 0
Все остальное:
usb_dev_handle *dev = NULL;
usb_dev_handle *open_dev(void);
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
Timer.setInterval(1000);
connect(&Timer, SIGNAL(timeout()), this, SLOT(ReadyCheck()));
Timer.start();
usb_init();
usb_find_busses();
usb_find_devices();
if (!(dev = open_dev()))
{
printf("error opening device: \n%s\n", usb_strerror());
QMessageBox::information(NULL, "Error", "Not Found Device!");
return;
}
else
{
printf("success: device %04X:%04X opened\n", MY_VID, MY_PID);
Connect_status = true;
}
if (usb_set_configuration(dev, MY_CONFIG) < 0)
{
printf("error setting config #%d: %s\n", MY_CONFIG, usb_strerror());
usb_close(dev);
QMessageBox::information(NULL, "Error", "error setting config");
return;
}
else
{
printf("success: set configuration #%d\n", MY_CONFIG);
}
if (usb_claim_interface(dev, 0) < 0)
{
printf("error claiming interface #%d:\n%s\n", MY_INTF, usb_strerror());
usb_close(dev);
QMessageBox::information(NULL, "Error", "error claiming interface");
return;
}
else
{
printf("success: claim_interface #%d\n", MY_INTF);
}
}
void MainWindow::ReadyCheck()
{
int ret=0;
QString StrTemp;
char USB_data[BUF_SIZE];
if (!dev)
{
Connect_Stat->setStyleSheet("background-color: red");
usb_close(dev);
printf("Done.\n");
}
ret = usb_bulk_read(dev, EP_IN, USB_data, BUF_SIZE, 2000);
if (ret < 0)
{
printf("error reading:\n%s\n", usb_strerror());
Connect_Stat->setText("Not connection");
Connect_Stat->setStyleSheet("background-color: red");
Lab_VID_PID_num->setText("-");
Lab_VID_PID_num->setStyleSheet("color: rgb(0, 0, 0)");
}
else
{
printf("success: bulk read %d bytes\n", ret);
Connect_Stat->setText("Connect");
Connect_Stat->setStyleSheet("background-color: green");
Lab_VID_PID_num->setText("0x" + QString::number(MY_VID,16) + " 0x"+QString::number(MY_PID,16));
Lab_VID_PID_num->setStyleSheet("color: rgb(100, 0, 0)");
/* Parsing Read Data */
switch(USB_data[0])
{
case U_CATLOG_SYS_PAR:
/* Year, Month, Day */
Lab_SysTimeData->setText(QString::number(2000 + USB_data[4],10) + "-");
Lab_SysTimeData->setText(Lab_SysTimeData->text() + QString::number(USB_data[5],10)+ "-");
Lab_SysTimeData->setText(Lab_SysTimeData->text() + QString::number(USB_data[6],10)+ "-");
Lab_SysTimeData->setText(Lab_SysTimeData->text() + QString::number(USB_data[7],10)+ "-");
Lab_SysTimeData->setText(Lab_SysTimeData->text() + QString::number(USB_data[8],10)+ "-");
Lab_SysTimeData->setText(Lab_SysTimeData->text() + QString::number(USB_data[9],10));
/* ADC Values */
float TempFloat=0;
char * pChar=0;
pChar = (char*)&TempFloat;
*pChar = USB_data[10]; pChar ++;
*pChar = USB_data[11]; pChar ++;
*pChar = USB_data[12]; pChar ++;
*pChar = USB_data[13];
Lab_Value_ADC_Pow->setText(QString::number(TempFloat,'g', 4)+"v, ");
pChar = (char*)&TempFloat;
*pChar = USB_data[14]; pChar ++;
*pChar = USB_data[15]; pChar ++;
*pChar = USB_data[16]; pChar ++;
*pChar = USB_data[17];
Lab_Value_ADC_Pow->setText(Lab_Value_ADC_Pow->text() + QString::number(TempFloat,'g', 4)+"v,");
pChar = (char*)&TempFloat;
*pChar = USB_data[18]; pChar ++;
*pChar = USB_data[19]; pChar ++;
*pChar = USB_data[20]; pChar ++;
*pChar = USB_data[21];
Lab_Temperature->setText(QString::number(TempFloat,'g', 4)+"°c");
/* Serial number */
pChar = (char*)&ret;
*pChar = USB_data[18]; pChar ++;
*pChar = USB_data[19]; pChar ++;
*pChar = USB_data[20]; pChar ++;
*pChar = USB_data[21];
Lab_SerialNum_Info->setText(QString::number(ret, 10));
break;
}
}
}
usb_dev_handle * open_dev(void)
{
struct usb_bus *bus;
struct usb_device *dev_local;
for (bus = usb_get_busses(); bus; bus = bus->next)
{
for (dev_local = bus->devices; dev_local; dev_local = dev_local->next)
{
if (dev_local->descriptor.idVendor == MY_VID
&& dev_local->descriptor.idProduct == MY_PID)
{
dev = usb_open(dev_local);
return usb_open(dev_local);
}
}
}
return NULL;
}
Отправка с ПК на плату в этом примере не используется, хотя это можно использовать для переключения отсылаемого контента (максимальные 64 байта накладывают ограничения).
Для отправки можно использовать функцию «usb_usb_bulk_write()», в микроконтроллере возникнет прерывание и из обработчика можно забрать пришедшие данные и в зависимости от содержимого — поменять отсылаемые на ПК данные.
void EP1_OUT_Callback(void)
{
uint8_t Data[BUF_MAX];
USB_SIL_Read(EP1_OUT, Data);
SetEPRxStatus(ENDP1, EP_RX_VALID);
}
Для разбора и поиска ошибок очень помог UsbLyzer (USB сниффер).
Он выдает полную информацию по USB устройству (Endpoint, Report, длины и т.д.), + наглядно отображает принятые-отправленные пакеты и их содержимое.

- +7
- 11 сентября 2014, 16:50
- khomin
- 2
Файлы в топике:
Firmware.zip, qt.zip
Какая максимальная скорость передачи данных (размер пакета и минимальный интервал передачи) через USB HID?
Low-speed, 10—1500 Кбит/c
Максимальный пакет — 64 байта (первый байт — дескриптор, считай 63).
интервал затрудняюсь ответить…
Максимальный пакет — 64 байта (первый байт — дескриптор, считай 63).
интервал затрудняюсь ответить…
п. 5.6 Using USB terminology, a device may send or receive a transaction every USB
frame (1 millisecond). A transaction may be made up of multiple packets (token,
data, handshake) but is limited in size to 8 bytes for low-speed devices and 64
bytes for high-speed devices.
www.usb.org/developers/hidpage/HID1_11.pdf
frame (1 millisecond). A transaction may be made up of multiple packets (token,
data, handshake) but is limited in size to 8 bytes for low-speed devices and 64
bytes for high-speed devices.
www.usb.org/developers/hidpage/HID1_11.pdf
А, да, еще размер пакета на LS ограничен. На FS ЕМНИП тоже 64 байта.
Насчет опроса каждые 8 фреймов — это ограничение, вероятно, не со спекой связано, а с виндой. Ее HID драйвер опрашивает LS устройства каждые 8 фреймов (125Гц). Геймерские мыши с увеличенной частотой опроса (250-500-1000Гц) то ли патчат драйвер в винде, то ли ставят свой.
Насчет опроса каждые 8 фреймов — это ограничение, вероятно, не со спекой связано, а с виндой. Ее HID драйвер опрашивает LS устройства каждые 8 фреймов (125Гц). Геймерские мыши с увеличенной частотой опроса (250-500-1000Гц) то ли патчат драйвер в винде, то ли ставят свой.
Вы использовали устаревшую libusb версии 0.1, а в linux скорее всего смотрели версию 1.0. Поэтому и увидели различия. На текущий момент существует libusb-1.0 для windows и для linux (и еще для ряда платформ) они абсолютно идентичны, код переносится без проблем. Вообще библиотеке очень мощная, хотя и пришлось повозиться пока подключал к builder 6.
connect(&Timer, SIGNAL(timeout()), this, SLOT(ReadyCheck()));
А почему вы не используете новый синтаксис для connect()? ИМХО, он безопаснее и удобнее. Или вы ориентируетесь на Qt4?
Qt5 ещё пока нету. Когда KDE будет — будет смысл перелезать. А там и вкусности constexpr из 14 С++ с assert'ами могут запилить в Qt и сигнал-слотовая систем наконец будет разрешаться в компил-тайме.
Qt5 ещё пока нету. Когда KDE будет — будет смысл перелезать.
А кокой смысл ориентироваться на KDE? KDE — по сути, дополнительный фреймворк, который базируется на Qt. В силу своего масштаба — он достаточно «консервативен», и сильно отстает (по времени внедрения) от развития Qt.
А там и вкусности constexpr из 14 С++ с assert'ами могут запилить в Qt
Это больше зависит от компилятора: вы и сейчас можете использовать, например, анонимный код (С++11), хотя внутренняя реализация Qt5 придерживаются (ЕМНИП) стандарта С++98
сигнал-слотовая систем наконец будет разрешаться в компил-тайме
Здесь не понял. Ведь динамическое связывание — это именно основная идея «сигналов и слотов».
А теперь немного хорошек:
Одно из свойств С++ — полиморфизм и наследование — порождает классы с виртуальными членами. Виртуальные члены порождают рантайм накладки — vtable.
А хорошие компилляторы способны соптимизировать вызов виртуальной функции потомка через обращенияе к предку не используя vtable, разрешая всё в compile-time.
Теперь представим с вами, что connect — это constexpr-выражение, которое на стадии компилляции может понять, что on_button1Click — это слот только для сигнала button1, и не генерить Qt'шный рантайм.
Да и много из moc-обработки, делаемое сейчас препроцессором, может перейти в constexpr + немного шаблонов, что повысит информативность выдачи отладчика и сделает код более прозрачным для компиллятора и его оптимизаций.
Одно из свойств С++ — полиморфизм и наследование — порождает классы с виртуальными членами. Виртуальные члены порождают рантайм накладки — vtable.
А хорошие компилляторы способны соптимизировать вызов виртуальной функции потомка через обращенияе к предку не используя vtable, разрешая всё в compile-time.
Теперь представим с вами, что connect — это constexpr-выражение, которое на стадии компилляции может понять, что on_button1Click — это слот только для сигнала button1, и не генерить Qt'шный рантайм.
Да и много из moc-обработки, делаемое сейчас препроцессором, может перейти в constexpr + немного шаблонов, что повысит информативность выдачи отладчика и сделает код более прозрачным для компиллятора и его оптимизаций.
А хорошие компилляторы способны соптимизировать вызов виртуальной функции потомка через обращенияе к предку не используя vtable, разрешая всё в compile-time.
Разве что в каких-то очень предельно упрощенных случаях. Идея полиморфизма именно в динамике, когда на стадии компиляции не известно ни компилятору, ни даже программисту, который писал этот код, какой именно вариант переопределенного метода вызывать.
connect — это constexpr-выражение, которое на стадии компилляции может понять, что on_button1Click — это слот только для сигнала button1
Это не constexpr — я могу соединять сигнал от кнопки соединять с разными слотами (динамически, в зависимости от логики), потом вызвать disconnect() и связать все по другому. И т. д. В общем случае, я динамически могу пересвязывать сигналы (для условного, on_button1Click()) и компилятор никак это не может оптимизировать на этапе компиляции.
Разве что в каких-то очень предельно упрощенных случаях.и 99 % реальных
constexpr — это weak деректива. Поэтому если компиллятор не сможет на стадии сборки разрешить connect — оно останется в рантайме. Но вот если сможет (вызовы данной функции данного экземпляра, которые встречаются в приложении происходят с констснтными параметрами) — то сам бог велел разрешать в compile-time.
А как определяете пропажу устройства во время работы в винде?
Вопрос не праздный: лезть в винду не хочу, а иногда проблемы у моих устройств под виндой из-за кривых операторов/кабалей возникают.
Вопрос не праздный: лезть в винду не хочу, а иногда проблемы у моих устройств под виндой из-за кривых операторов/кабалей возникают.
а что надо поменять в усб конфиге, чтобы виделось 2 разных устройства как в мфу принтер и сканер?
- kalobyte-ya
- 13 сентября 2014, 17:10
- ↓
Комментарии (45)
RSS свернуть / развернуть