Stm32 и usb hid это просто

Обновлено: 03.07.2024

stm32f4discovery и usb hid-устройство - быстрый старт.

stm32f4discovery и usb hid-устройство - быстрый старт.


Для работы только USB, очевидно, что не нужны "usart и spi". "tim" тоже, в общем-то, не понадобится.
"hid/src/usbd_hid_core.c" - тут самое важное - определение нашего USB, заголовки. Я поменял HID_MOUSE_ReportDesc на свой. Во-первых, предложенный в примере вариант мыши избыточен, а Во-вторых, мой с комментариями . Пример мыши типа Logitec

В "hid/inc/usbd_hid_core.h" надо поменять длину заголовка, который только что поменяли на

В "hid/src/usbd_hid_core.c" находится и функция "USBD_HID_Setup", в которой можно дописать свои доп. обработчики, вроде ответа на "HID_REQ_GET_REPORT" и т.п., или изменить отправку заголовка "HID_MOUSE_ReportDesc" на какой-то другой - все тут!
В "usbd_desc.c" находятся такие важные элементы как VID, PID и PRODUCT_*_STRING. Можно менять на что-то иное. Не забудьте только, что VID и PID надо покупать или использовать предоставленные, но помним, что их все используют!
В "stm32f4xx_it.c" Я заменил функцию вычисления сдвига курсора на пример из v-usb:

void SysTick_Handler(void)
uint8_t *buf;

if (TimingDelay != 0x00)
TimingDelay--;
>


Обработка "TimingDelay" тоже не нужна, но используется в другом месте - это далее.
Теперь начнем заполнять функцию "main()"

/* SysTick end of count event each 1ms */
RCC_GetClocksFreq(&RCC_Clocks);
SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);


Мы должны сперва проинициализировать тактирование нашего процессора, так чтобы заработал USB, тут есть масса тонкостей. Поскольку USB должен работать на 48MHz, что зависит и от др. настроек, поэтому не удивляйтесь, если при подключении к компу вы увидите "Устройство не опознано!" - у Вас, скорее всего, неверное тактирование. Чтобы наверняка вливаем из примера к себе файл "system_stm32f4xx.c" и будет нам счастье.
SysTick инициализируем на срабатывание прерывания 1 раз в одну милисекунду. Это самый удобный вариант для всех модулей, которые Вы возможно будете еще где использовать.
Например, реализация задержки:

void Delay(__IO uint32_t nTime)
TimingDelay = nTime;


1-й косяк примера. В примере SysTick настраивается на 10мсек., а в "Delay" это никак не упоминается и не корректируется, а ведь мы привыкли, что "nTime" для данной функции в милисекундах.
Добавим также функцию (ну и некоторые определения):

//uint16_t PrescalerValue = 0;

void Delay(__IO uint32_t nTime);

volatile __IO uint32_t TimingDelay;
volatile __IO uint32_t UserTimingDelay=0;

/* Private function prototypes -----------------------------------------------*/
static uint32_t USBConfig(void);
/* . */
static uint32_t USBConfig(void)
USBD_Init(&USB_OTG_dev,
USB_OTG_FS_CORE_ID,
&USR_desc,
&USBD_HID_cb,
&USR_cb);


Теперь в "main()" можно добавить "USBConfig();" и у нас ничего не компилируется. А просто в настройках компилятора надо еще проинициализировать константу "USE_USB_OTG_FS", например так "-DUSE_USB_OTG_FS;". Или другую "USE_USB_OTG_HS" - это просто выбор типа USB: Full speed или High Speed.
Теперь все должно компилироваться. Если нет, то скорее всего Вы не почистили все лишнее от использования "LIS302DL". Поэтому я предлагаю к использванию целиком мой пример для Coocox, в котором как и ранее используется USART и подключена SD карта по SPI2 (см. более ранние посты), зато из файлов для USB вырезано все к нему не относящееся.
2-й косяк примера. Я долго не мог понять почему SysTick не срабатывает как я хочу раз в 1мс. Оказалось, что есть еще файлик "usbd_usr.c", в котором при инициализации USB дергается функция "USBD_USR_Init". Вообще файлик видимо сделан для дополнительных пользовательских обработчиков, но что-то смысла использования именно его не вижу, чаще нужно что-то еще, собственно он есть. В общем, там вызывается:

void USBD_USR_Init(void)
<
/* Setup SysTick Timer for 40 msec interrupts
This interrupt is used to probe the joystick */
if (SysTick_Config(SystemCoreClock / 24))
<
// Capture error
while (1);
>
>


Ой, а это же перенастройка таймера на 41,(6) милисекунд. Какие там обещанные "40 msec"? Считать видимо не умеют, частота-то нашего МК 168MHz. Но блин, ЗАЧЕМ тогда инициализировали ранее на "10 msec"? Кто в лес, кто по дрова! Комментируем все это или удаляем!
В примере также есть файлик "selftest.c", который я не копировал. Ну не нужен он. Но там есть интересные функции. Например, "USB_Test()".

/**
* @brief Test USB Hardware.
* The main objectif of this test is to check the hardware connection of the
* Audio and USB peripheral.
* @param None
* @retval None
*/
void USB_Test(void)
GPIO_InitTypeDef GPIO_InitStructure;

/* GPIOA, GPIOC and GPIOD clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOC | \
RCC_AHB1Periph_GPIOD, ENABLE);

/* GPIOD Configuration: Pins 5 in output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);

/* Turn LED8 ON using PD5 */
GPIO_ResetBits(GPIOD, GPIO_Pin_5);

/* GPIOC Configuration: Pin 0 in output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);

/* GPIOA Configuration: Pin 9 in input pull-up */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Turn LED7 ON using PC0 (5v) */
GPIO_ResetBits(GPIOC, GPIO_Pin_0);

/* Waiting delay 10ms */
Delay(1);

if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_9) == Bit_RESET)
Fail_Handler();
>

/* GPIOA Configuration: Pins 10 in output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Waiting delay 10ms */
Delay(1);

/* Check the ID level without cable connected */
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10) == Bit_RESET)
Fail_Handler();
>

/* Turn LED7 OFF using PC0 */
GPIO_SetBits(GPIOC, GPIO_Pin_0);

/* GPIOA Configuration: Pins 11, 12 in input pull-up */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* GPIOA Configuration: Pin 9 in output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA, GPIO_Pin_9);

/* Waiting delay 10ms */
Delay(1);

/* Check PA11 and PA12 level without cable connected */
if ((GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11) == Bit_RESET) || \
(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12) == Bit_RESET))
Fail_Handler();
>

/* GPIOA Configuration: Pins 12 in input pull-up */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* GPIOA Configuration: Pin 11 in output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA, GPIO_Pin_11);

/* Waiting delay 10ms */
Delay(1);

/* Check PA12 level without cable connected */
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12) == Bit_RESET)
Fail_Handler();
>

/* GPIOA Configuration: Pins 11 in input pull-up */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* GPIOA Configuration: Pin 12 in output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA, GPIO_Pin_12);

/* Waiting delay 10ms */
Delay(1);

/* Check PA12 level without cable connected */
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11) == Bit_RESET)
Fail_Handler();
>

/* GPIOA Configuration: Pins 9 in output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Turn LED7 ON using PA9 */
GPIO_SetBits(GPIOA, GPIO_Pin_9);

/* Turn Green LED ON: signaling Audio USB Test part1 PASS */
STM_EVAL_LEDOn(LED4);

/* Waiting User Button is pressed */
while (STM_EVAL_PBGetState(BUTTON_USER) == Bit_RESET)
<>

/* Waiting User Button is Released */
while (STM_EVAL_PBGetState(BUTTON_USER) != Bit_RESET)
<>

/* Turn Green LED OFF: signaling the end of Audio USB Test part1 and switching to
the part2 */
STM_EVAL_LEDOff(LED4);

/* Turn LED7 OFF using PA9 */
GPIO_ResetBits(GPIOA, GPIO_Pin_9);

/* Turn LED8 OFF using PD5 */
GPIO_SetBits(GPIOD, GPIO_Pin_5);

/*********************************** USB Test *******************************/
/* Check the ID level with cable connected */
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10) != Bit_RESET)
Fail_Handler();
>

/* GPIOA Configuration: Pins 11, 12 in input pull-down */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* GPIOA Configuration: Pin 9 in output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_9);

/* Waiting delay 10ms */
Delay(1);

/* Check PA11 and PA12 level with cable connected */
if ((GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11) == Bit_RESET) || \
(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12) == Bit_RESET))
Fail_Handler();
>

/* GPIOA Configuration: Pins 9, 12 in input pull-down */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* GPIOA Configuration: Pin 11 in output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_11);

/* Waiting delay 10ms */
Delay(1);

/* Check PA9 and PA12 level with cable connected */
if ((GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_9) == Bit_RESET)|| \
(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12) == Bit_RESET))
Fail_Handler();
>

/* GPIOA Configuration: Pins 9, 11 in input pull-down */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* GPIOA Configuration: Pin 12 in output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_12);

/* Waiting delay 10ms */
Delay(1);

/* Check PA9 and PA12 level with cable connected */
if ((GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_9) == Bit_RESET)|| \
(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11) == Bit_RESET))
Fail_Handler();
>

/* GPIOA Configuration: Pins 11, 12 in input pull-down */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* GPIOA Configuration: Pin 9 in output push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Turn LED7 OFF using PA9 */
GPIO_ResetBits(GPIOA, GPIO_Pin_9);
>

void Fail_Handler(void)
/* Erase last sector */
FLASH_EraseSector(FLASH_Sector_11, VoltageRange_3);
/* Write FAIL code at last word in the flash memory */
FLASH_ProgramWord(TESTRESULT_ADDRESS, ALLTEST_FAIL);

while(1)
/* Toggle Red LED */
STM_EVAL_LEDToggle(LED5);
Delay(5);
>
>

Объясню, что она делает. Прежде чем инициализировать USB данная функция проверяет, что USB разъем не подключен, используя свойства схемы подключения USB на Discovery. Затем, ждет пока пользователь нажмет кнопку, но нажать он ее должен только после подключения шнурка USB. Как подключили шнурок от девайса к компу, нажмем кнопку и теперь эта функция начнет проверять подключили вы устройство к хосту. В случае, если что-то подключено или не подключено не вовремя, программа впадет в ступор на функции "Fail_Handler()".
Повторю, что данная функция ВООБЩЕ НЕ НУЖНА, но можно использовать как пример проверок подключения . Если Вы просто проинициализируете устройство USB, то не важно подключено оно или нет к хосту. Он просто начнет работать, а если подключите его к компу, то оно определится и будет работать, а вся эта доп. логика только сбивает пользователя. Ну какой дружественный интерфейс, когда надо знать за ранее процедуру включения проводка USB? Мы же привыкли, когда надо - включить, а когда не надо - выключить, а тут еще и в нужный момент кнопку надо нажать, а если что-то забыли, то все - намертво зависший контроллер.

Это пример чисто usb hid-устройства, а именно мышки. Легко теперь переделать на что-то другое, т.к. лишнего нет, но мы уже знаем, что и где надо искать в примерах, если что-то не получается
STM32, очень неплохо справляется со своими задачами

UPD: В проект включена библиотека от ChaN`а для поддержки файловой системы FAT. Подразумевается, что если подключить по SPI2 флешку, то ее можно будет прочитать или на нее что-то записать, а вся отладочная информация пойдет на настроенный UART. К самому примеру с мышкой не имеет никакого отношения. Получить доступ, эта библиотека, к файловой системе компа по интерфейсу USB мыши также никак не сможет Все что ее касается, а также все что касается UART, можно смело удалять и на работу мыши это никак не скажется!

stm32f4discovery и usb hid устройство быстрый старт

Помогите пожалуйста с внешним прерыванием Проблема в том, что при возникновении внешнего прерывания процессор на него реагирует, но не переходит в обработчик прерываний, а зависает где то И в основной цикл не возвращается. Может кто знает в чем дело?

RCC->APB2ENR = RCC_APB2ENR_IOPCEN;
RCC->APB2ENR = RCC_APB2ENR_IOPAEN;

GPIOC->CRH = GPIO_CRH_MODE9;
GPIOC->CRH &= GPIO_CRH_CNF9;

GPIOA->CRL = GPIO_CRL_MODE0;
GPIOA->CRL &= GPIO_CRL_CNF0_0;

RCC->APB2ENR = RCC_APB2ENR_AFIOEN;
AFIO->EXTICR0 = AFIO_EXTICR1_EXTI0_PA;

EXTI->IMR = EXTI_IMR_MR0;
EXTI->EMR = EXTI_EMR_MR0;

GPIOC->BSRR = GPIO_BSRR_BS8;
for volatile int x=0; x<100000; x
GPIOC->BRR = GPIO_BRR_BR8;
for volatile int x=0; x<100000; x


В чём проблема-то?

Начну с низкоуровневой части. Сколько я ни видел статей на эту тему, ни в одной так нормально и не разобраны были следующие моменты:

Вот по этим пунктам я постараюсь вкратце пробежаться, объяснить, что и как необходимо сделать, чтобы устройство работало корректно, и показать это на практике. Базовые понятия работы USB HID устройств я не буду объяснять, про это уже много и доступно писали до меня.

Теперь про саму библиотеку. В оригинальной и потом чуть допиленной мною было ещё очень немало багов и недочётов, которые мне довольно сильно мешали в моём неспешном проекте CV Meter (да-да, он НЕ заброшен, и уже много чего поменялось со времён той статьи, постараюсь опубликовать наработки в виде статьи + новой версии в ближайшие месяцы), что только добавляло непонимания или горения пятой точки от того, что работало оно совсем не так, как задумывалось. И в какой то момент меня это всё достало, что и вылилось в полном пересмотре библиотеки, написании своего тестового ПО (UsbHidDemonstrator от ST меня также не устраивал своими ограничениями по VID\PID и логикой работы).


Открываем STM32CubeMX и включаем тактирование, SPI, USB и несколько GPIO на выход и один на вход для кнопки:


Тактирование у меня от кварцевого резонатора 16 МГц, да, он перепаян (для своего удобства, т. к. в проектах часто ставится именно такой номинал), на плате же изначально идёт 25 МГц, учитывайте это (просто поменяйте настройки тактирования):


SPI никаких особенностей не имеет, просто Master Transmit.


Настройки интерфейса можно не трогать, т. к. ничего дополнительного не потребуется:


Не забываем включить реализацию различных классов USB устройств (библиотека от ST):


И выбираем именно USB Custom HID (а не просто USB HID):


В настройках нас интересуют три параметра:


Далее заходим в настройки дескриптора описания устройства:


В целом и всё, можно сгенерировать проект:


Теперь об небольших изменениях в сгенерированном проекте. Открываем файл Src\usbd_custom_hid_if.c и добавляем объявление на обработчик входящих репортов:

Далее редактируем (точнее, добавляем, изначально дескриптор пуст) дескриптор возможностей устройства:

Примечание: для удобства я пишу рядом с каждым объявлением типа репорта количество байт, которое содержит это описание, чтобы потом можно было быстро посчитать и отредактировать объявление USBD_CUSTOM_HID_REPORT_DESC_SIZE .

И ближе к концу этого файла вставляем вызов обработчика репортов:

Примечение: в обработчике CUSTOM_HID_OutEvent_FS параметр event_idx является Report ID полученного репорта, который содержится в hhid->Report_buf[0].

В файле Src\usbd_desc.c можно отредактировать VID, PID, описания устройства.

В файле Inc\usbd_conf.h содержится, собственно, USBD_CUSTOM_HID_REPORT_DESC_SIZE, задающее размер дескриптора возможностей, а также можно проверить значения USBD_CUSTOMHID_OUTREPORT_BUF_SIZE и CUSTOM_HID_FS_BINTERVAL.

Репорты, размером > 64 байта будут автоматически нарезаны и склеены, от вас не требуется ничего делать.

Также стоит отметить два важных замечания по поводу Report ID, которые часто упускают в других туториалах:

  • Если в дескрипторе НЕ указано значение Report ID для какого-либо типа репорта, то будут пропускаться все, а если указано, то репорты, не соответствующие этому значению, для конкретного типа будут игнорироваться автоматически, как на стороне устройства, так и на стороне ПК.
  • Исходя из первого замечания, выходит, что часто, не указывая значение Report ID для любого типа репорта, можно подумать, что выделяемый буфер (к примеру, 32 байта) полностью и есть данные, которые отправляются\принимаются, но на самом деле первый байт в репорте всегда = Report ID (т. е. фактически полезной нагрузки в репорте 31 байт), и если он не используется по своему прямому назначению, то да, его как бы можно использовать в качестве байта данных.

Ну и файл Src\main.c, добавляем обработчик репортов с дополнительными переменными:

И в главном цикле добавляем отображение данных на экране (счетчики отправленных и принятых репортов, а также применение цветов на тестовую строку) и обработку нажатия на кнопку (отправка Input Report):

Минимально-простой функционал для тестирования реализован.

Итак, теперь библиотеку можно подключать фактически из любого места (обычно я во ViewModel это делаю), раньше же только из класса окна можно было:

Сам обработчик MainWindow_Loaded назначается так:

Появился статичный метод GetDevicesList(), который возвращает список доступных устройств в виде List<HIDDeviceAvalible>:

И теперь можно подключаться к устройству различными способами: VID/PID, Device Path или экземпляр HIDDeviceAvalible.

Из ключевых особенностей это, вроде, всё.

И время проверить это дело на практике с примером приложения, которое отображает список доступных устройств, краткие возможности выбранного устройства и позволяет отправлять\принимать репорты.

Про реализацию приложения особо нечего сказать, почти всё стандартно, могу отметить только то, что при приёме Input Report намеренно сделано окно в 100 мс (по коду смотрите DispatcherTimer refreshtimer), которое отбрасывает все Input Report, которые пришли на время действия этого окна, сделано это для того, чтобы интерфейс не залипал, если устройство шлёт репорты каждые 10 мс или даже чаще.

Выглядит приложение вот так:


Выбираю из списка доступных устройств созданный только что пример:


После подключения к нему отображаются размеры репортов (если какой-то тип репорта не поддерживается, его размер = 0):


И тут мы видим размеры репортов, которые больше на +1, чем объявлено в CUSTOM_HID_ReportDesc_FS, всё сходится!

Переключаемся на вкладку с RAW данными:


Здесь в целом ничего замудрёного, сверху Input Report, который обновляется при поступлении нового репорта от устройства, и если значение байта в одной конкретной позиции отличается от значения байта в той же позиции, то он подсвечивается красным.

Примечание: да, тут видно, что Input Report содержит далеко не только счетчик в первых байтах, который мы пишем туда в прошивке, а ещё какие-то статичные значения в последующих байтах. Догадались, почему так? ;)

В динамике это выглядит так:

И отправка репорта в устройство, для наглядного примера прошивка разбирает пришедший Output\Feature Report следующим образом:


Примечание: реализация Feature Report не совсем полноценная в прошивке, не приходит ответ от устройства, но это уже оставлю для тех, кому это действительно нужно, сделать это несложно, но решение пока не буду выкладывать.

На этом всё. Мог что-то забыть или ошибиться, буду рад, если напишете об этом. Спасибо, что прочитали! ;)

Ряд микроконтроллеров STM32 имеют на борту USB интерфейс для связи с компьютерами. Как правило, удобнее всего использовать предоставляемый компаний ST Microelectronics драйвер класса CDC (Communication Device Class ). Он позволяет использовать на стороне компьютера UART через USB и не требует установки драйверов. Со стороны STM32 при этом требуется только поменять операции вывода данных, остальное делается самостоятельно. Причём скорость такого соединения может быть практически любой, поддерживаемой компьютером.

Однако ряд разработок, особенно, когда приходишь в другую компанию, где используется HID Class (Human Interface Device), в случае разработки новой версии устройства требуется поддерживать ранее выбранный интерфейс. Что, собственно, и случилось. Примеры проектов от самой ST, которые они дают при загрузке STM32 Cube MX и IDE, как обычно, дали только минимальное понимание, но не раскрыли, что и как надо делать. Я когда-то разбирался с USB, даже писал собственный драйвер, но это было так давно… Остались только общие воспоминания. Посему пришлось искать дополнительную информацию, чтобы получить стартовую точку.

Первое найденное было видеороликом на youtube в стиле HID за 5 минут :-) Автор даёт доступ к своему коду на GitHub. Всё, типа круто, красиво, просто вставляйте к себе и всё будет чудесно. Судя по отзывам под роликом, некоторым этого хватило. Изучив исходники понял, что минимального прозрения не наступило, да и уровень полученной информации мал для того, чтобы решить поставленную задачу. Но закомство с этим материалом было явно полезным. Решение вопроса с использованием кубика (STM32Cube MX) мне лично импонирует больше, чем другие подходы, поскольку позволяет отвлечься от ряда низкоуровневых операций и генерация проекта всегда происходит в одном стиле. Соответственно, изучение этого примера показало, на какие файлы надо обратить внимание, где и что надо поменять или добавить, какие функции использовать для получения и отправки данных именно для нашей выбранной среды программирования.

Следующий поиск оказался весьма удачным. Хабр — известный сайт, на котором можно найти много полезного по разной электронной тематике. Нашлась там и статья STM32 и USB-HID — это просто. Я не являюсь постоянным клиентом Хабра и не знаю автора этой статьи RaJa, но на мой взгляд это очень хорошая статья, описывающая основные положения работы HID интерфейся. Без её прочтения читать дальше здесь бессмысленно, поскольку далее будут, в основном, комментарии для адаптации кода к среде разработки STM32IDE/STM32CubeMX + Atollic TrueStudio. (Далее STM32IDE). Да и столь популярный в 2014 году и реально очень неплохой проект EmBlocks, увы, умер.

Первое, что необходимо решить — как тестировать вновь создаваемое устройство. Лет… дцать назад я использовал для этого анализатор и синтезатор трафика USB — очень полезные, но дорогие игрушки :-) Сейчас у меня такой возможности нет, да и должен же быть более простой путь. Тем более для простого стандартного интерфейса без написания собственного драйвера. Авторы обоих рассмотренных выше проектов пошли самы простым для них путём — написание простой программы на известных им языках. Но автор статьи на Хабре сделал очень правильный шаг — он написал свой проект, совместимый с программой ST HID Demonstrator (ссылка есть в статье), позволяющей поуправлять нашим устройством, как графически, так и послать свои данные и посмотреть, что пришло от нашего устройства. Фактически программа может использоваться и в дальнейшем для отладки будущей программы на выбранном микроконтроллере.

Своё ознакомление с проектом для HID я осуществлял с платой STM32L476 Discovery. Плата, вообще говоря, может быть любой, где USB интерфейс микроконтроллера физически подключён к отдельному разъёму USB. Есть у меня и Nucleo 32 с STM32L4, но там один разъём USB тспользуется и для программирования/отладки, и для связи с хостом, что добавляет интриги в интерфейс и может служить источником дополнительных непоняток. Оно нам надо?

Итак, комментарии и дополнения к статье по привязке HID к STM32IDE примерно по тем же шагам, как и в хабровской статье.

Структура проекта

В STM32IDE структура всех проектов задаётся при генерации проекта из среды назначения функциональности пинов и пользователю о том заботиться не надо. В частности, в кубике (что отдельном STM32Cube MX, что в встроенном в STM32IDE) активируем USB, как Device, и добавляем Middleware USB Custom HID.

Заходим в Clock Configuration. Вполне вероятно, что могут быть проблемы с системными частотами, которые маркируются малиновым цветом.


Рис. 3 Возможные проблемы по установке частот

Если так, нажимаем Resolve Clock Issues и, скорее всего, всё будет настроено на максимальные частоты. Главное — USB Clock будет выставлен на 48 МГц. Надо заметить, что в семействе STM32L4 генератор на 48МГц имеет автоподстройку по SOF (Start Of Frame), что позволяет создавать USB устройства без внешнего кварца/генератора. Если, конечно, остальной дизайн допускает использование некварцованных генераторов. Для других семейств не проверял, поскольку для моего текущего проекта был выбран именно L4. Только надо отметить, что при использовании USB есть некоторая минимальная частота работы микроконтроллера. Я делал прикидку для другого проекта, где надо общаться с хостом и при этом потреблять минимум тока. Задачи простые, не требуют большой скорости и я хотел запустить МК на 8МГц. Оказалось, что меньше 14МГц при подключении USB ставить не могу, RCC не позволяет. Пришлось остановиться на следующем круглом значении 16МГц.

Собственно, настройка аппаратной части USB и выбор файлов, отвечающих за базовую функциональность этого интерфейса на на этом закончены. Вся остальная периферия, находящаяся на выбранной плате настраивается автоматически при её выборе на старте проекта. Сохраняем, генерим проект и переходим к «программированию» в сравнении с описанным на Хабре проектом.

Это страшное слово Descriptor

Дескриптор от RajaДескриптор от STФайл в проекте
RHID_DeviceDescriptorUSBD_FS_DeviceDescusbd_desc.c
RHID_ConfigDescriptorUSBD_CUSTOM_HID_CfgFSDescusbd_customhid.c
RHID_ReportDescriptorCUSTOM_HID_ReportDesc_FSusbd_custom_hid_if.c

Поскольку для простоты сейчас будем работать только с ST HID Demonstrator, то не мудрствуя лукаво я просто скопировал содержимое RHID_ReportDescriptor в соответствующее место моего проекта. Только подставил свои константы на место длины. Надо отметить, что надо точно посчитать количество байтов в этом дескрипторе (в этом проекте 79) и убедиться, что именно это значение стоит в Class Parameters. Не больше и не меньше. Иначе хост не опознает подключённое устройство. Проверено :-)

Далее заходим в файл usbd_customhid.h и меняем значения CUSTOM_HID_EPIN_SIZE и CUSTOM_HID_EPOUT_SIZE на 0x40U. Честно говоря, немного напрягает то, что ST не даёт альтернатив смене значения по умолчанию 2 на другое значение и далее в коде с использованием этих констант стоит комментарий, что не более 2х байт. Но, с другой стороны, это было рекомендовано в первом найденном описании и, вообще говоря, установка такого значения выглядит достаточно логично. Иначе в чём отличие CustomHID от обычного? Проблема в том, что при регенерации проекта из кубика, что на этапе первичного кода происходит довольно часто, это значение не сохраняется и его надо восстанавливать ручками. Для этого я себе в main вывел строку warning, чтобы не забывать проверить эти константы. Возможно я ошибаюсь, и в дальнейшем всё окажется проще. Но в такой конфигурации работает :-)

Цикл обмена (пишем/читаем)

Для выдачи данных на хост всё достаточно аналогично описанию на Хабре. Только название функции другое: USBD_CUSTOM_HID_SendReport(). Все остальные реомендации из той статьи подходят по полной программе.

А вот чтение здесь интереснее, чем на Хабре. И на самом деле несколько проще. Обработка принятого массива происходит в usbd_custom_hid_if.c / static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state).

В этом тестовом проекте я не заморачивался с обработкой входных параметров и следуя своей обычной практике минимальности времени обработки прерываний, просто копирую полученные данные в заранее определённый массив и устанавливаю флаг готовности данных от USB

Ну, а собственно «сбор данных» (нажатие кнопок джойстика) и реакция на полученные от хоста данные в этом прото проекте делаю внутри бесконечного цикла в main.c Всё просто :-) В этом прото проекте нет разделения реакции на SET_FEATURE и SET_REPORT, с этим надо будет разобраться далее, в реальном проекте. Компилируем, запускаем, подключаем к хосту и там должен появиться новый CustomHID от STMicroelectronics.

Звпускаем на хосте USB HID Demonstrator. На плате, с которой я запускал этот проект, не имеет органов для работы с Variable Inputs/Outputs, поэтому в разделе Graphic customization были убраны соответствующие назначениями, оставлено 5 кнопок и назначены ID, определённые в проекте: 1, 2 для Output report (входные данные для ST) и 4 для Input Report (выход от ST).


Рис. 4 Настройка демонстратора

Моей задачей для этого проекта было управлять парой светодиодов на плате, что стало работать сразу, как эта программа обнаружила подключенную плату, и включать «лампочки» этой платы при нажатии различных кнопок джойстика на плате, а вот здесь сразу не получилось. При указанных настройках все пять лампочек одновременно зажигались при нажатии на центр джойстика. Остальные кнопки не отображались. При этом, если перейти на Input/Otput transfer, то данные были вполне ожидаемы. Т.е. сам интерфейс работает, но отображение в программе на хосте не отвечает моим запросам. Слава богу ST предоставляетс исходники, а в соседнем кубике сидит программист нашей группы, пишущий в том числе и софт для компьютеров. В общем, он подправил одну функцию и сгенерил исполняемую программу. Всё стало работать, как хотелось. Конечно, можно было бы на каждую кнопку создать свой report с уникальным номером, что исходно и предусмотрено. В этом случае было бы достаточно посылать по одному байту для каждой кнопки, но мой проект предусматривает многобайтный отчёт. Исходник подправленной функции и подправленный исполняемый файл можно скачать по ссылке ниже.

На этом, пожалуй, всё. Если у Вас есть такая же плата 32L476GDISCOVERY, то для начала можно просто скачать мой прото проект, адаптированный для него демонстратор и исходник изменённой функции по этой ссылке. Исходный USB HID Demonstrator скачивается с сайта STM, инсталлируется и его исполняемый файл заменяется моим. Импортируете в STM32IDE мой проект, компилируете и должны получить работающую базу для своих проектов. Если у Вас другая плата, то адаптируете «сбор информации» и включение светодиодов под свою плату.

Для дальнейшей работы обязательно прочтите указанную статью RaJa с Хабра. Она даст понимание того, что и как должно быть сделано для других проектов с USB HID интерфейсом. А ещё лучше начать с неё :-)

И при выборе класса устройства для Вашего проекта надо учитывать следующее: минимальный период опроса HID устройств — 1ms. И если я правильно помню, это скорее пожелание системе от внешнего устройства. В стандартном HID устройстве за один кадр (frame) передаётся только два байта, т.е. скорость обмена не более 2 кбайт/с. В Custom HID на
Full Speed (12 мбит/с) объём данных отчёта (report) - не более 64 байт, т.е. скорость обмена с Вашим HID не более 64 кбайт/с. Для High Speed (480 мбит/с) — максимальный объём данных 512 байт (512 кбайт/с). Не будь у меня ограничения совместимости с предыдущим софтом, используемым в компании, использовал хотя бы CDC.

У меня изучение статей и адаптация под мои хотелки заняло три дня. Описание заняло больше :-) Надеюсь, что у тех, кто воспользуется этой статьёй, аналогичный процесс займёт не более одного дня. Комментируйте, спрашивайте. Что смогу — отвечу. Что не смогу, вместе поищем решение.

STM32F4xx исходный код библиотеки usb подробный пользовательский HID

Сначала я перечислю несколько отличных справочных документов:

Keil USB documentation

Введение в базовые знания USB



Некоторые общие вопросы

Сколько там конечных точек

В микросхемах серии STM32F407 / 417/27/37/29/39 количество двунаправленных EP, включая EP0, составляет всего 4 вместо 16. Этому месту следует уделить особое внимание, но для общности программы в HAL_PCD_IRQHandler опрашиваются 16 конечных точек.

Для получения более конкретной и подробной информации рекомендуется обратиться к "STM-AN4879》,

Проблема направления передачи

Он специально указан здесь, потому что передача всегда относится к HOST. Для устройства IN означает вывод на HOST (IN - это ввод для HOST, а вывод для DEVICE) OUT принимает.

В заголовок

В исходном коде HID код инициализации выглядит следующим образом:

hUsbDeviceFS

Тип структурной переменной - USBD_HandleTypeDef,

/* USB Device Core handle declaration. */
USBD_HandleTypeDef hUsbDeviceFS;

Обратите внимание, что это глобальная переменная, используемая нашим USB, которая сохраняет всю информацию о жизненном цикле USB.

USBD_HandleTypeDef

Каждая конечная точка (ep_in / ep_out) имеет свою собственную конфигурацию, тип - USBD_EndpointTypeDef,

USBD_EndpointTypeDef(ep_in/ep_out)

USBD_CUSTOM_HID

Тип структурной переменной - USBD_ClassTypeDef, который определяется следующим образом:

Зарегистрировать маршрут

Эта структурная переменная передается
USBD_RegisterClass(&hUsbDeviceFS, &USBD_CUSTOM_HID);
Зарегистрируйтесь в глобальных переменных
USBD_HandleTypeDef hUsbDeviceFS;
, который часто называют
hUsbDeviceFS->pClass (pdev->pClass)

USBD_ClassTypeDef

Тип структуры определяется как USBD_ClassTypeDef:

DataIn, DataOut - функция передачи данных

Из определения этих двух переменных видно, что соответствующие функции ввода и вывода

Основной маршрут звонка:

Также видно, что его отправка и получение обрабатываются в функции прерывания HAL_PCD_IRQHandler.

hpcd_USB_OTG_FS

Тип структурной переменной:

Среди них PCD означает аппаратный контроллер: устройство / драйвер периферийного контроллера.

Зарегистрировать маршрут

Маршрут регистрации для этой структурной переменной:

USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
==>
USBD_LL_Init (pdev); где pdev - hUsbDeviceFS
где,
hpcd_USB_OTG_FS.pData = pdev;
pdev->pData = &hpcd_USB_OTG_FS;

так что,
hpcd_USB_OTG_FS.pData = &hUsbDeviceFS;
hUsbDeviceFS->pData = &hpcd_USB_OTG_FS;

PCD_HandleTypeDef

Глобальная переменная hpcd_USB_OTG_FS, определенная в usb_conf.c, которая инициализируется в USBD_LL_Init

Соответственно, каждая конечная точка IN_ep / OUT_ep также имеет свою конфигурацию, тип - PCD_EPTypeDef,

PCD_EPTypeDef (IN_ep/OUT_ep)

Несколько концепций передачи данных USBx

USBx, широко используемый в библиотеке, относится к базовым адресам USB_OTG_FS и USB_OTG_HS регистров конфигурации режима передачи FS и HS.

Обратите внимание, что адреса регистров USB_OTG_FS и USB_OTG_HS в STM32F4xx могут относиться к адресам границ регистров таблицы 10 таблицы данных STM32F4xx. Фактические физические адреса определены следующим образом:

Выберите EndPoint и перенесите данные

USBx_INEP(i)

Здесь мы берем USB_OTG_FS в качестве примера для объяснения,

Итак, USBx_INEP (0) - это OTG_FS_DIEPCTL0, 0x50000900

USBx_OUTEP(i)

Точно так же давайте посчитаем, к какому USBx_OUTEP относится, предполагая, что x = 3,

Следовательно, USBx_OUTEP (15) = 0x50000B00U + 3 * 0x20

В функции прерывания приема и отправки HAL_PCD_IRQHandler первый проход

USB_ReadDevOutEPInterrupt (или USB_ReadDevInEPInterrupt)
Определите, из какой конечной точки исходит прерывание (всего 16),
USB_ReadDevOutEPInterrupt или USB_ReadDevInEPInterrupt для подтверждения следующего кода:

epint = USB_ReadDevOutEPInterrupt(hpcd->Instance, epnum);
epint = USB_ReadDevInEPInterrupt(hpcd->Instance, epnum);

Конкретный код реализации выглядит следующим образом:

Настройки трансмиссии

Маршрут звонка

Режим передачи и объем данных устанавливаются при инициализации USB.Маршрут вызова функции следующий:

OTG_FS_IRQHandler --> HAL_PCD_IRQHandler --> HAL_PCD_SetupStageCallback --> USBD_LL_SetupStage --> USBD_StdDevReq --> USBD_SetConfig --> USBD_SetClassConfig --> USBD_CUSTOM_HID_Init --> USBD_LL_OpenEP --> HAL_PCD_EP_Open

Примечание: pClass в USBD_SetClassConfig упоминал ранее, что if (pdev-> pClass-> Init (pdev, cfgidx) вызывает USBD_CUSTOM_HID_Init.

Размер передаваемых данных

USBD_LL_OpenEP устанавливает номер конечной точки, такой как CUSTOM_HID_EPIN_ADDR, режим передачи, такой как USBD_EP_TYPE_INTR, и размер тома передачи данных, такой как USB_FS_MAX_PACKET_SIZE (настраиваемый). Также обратите внимание, что в режиме VCP / CDC режим передачи часто устанавливается на USBD_EP_TYPE_BULK.

USBD_CUSTOM_HID_Init

В этой функции USBD_LL_OpenEP полезно только одно предложение,

Так что действительно работает только функция HAL_PCD_EP_Open.

HAL_PCD_EP_Open

О конкретных настройках заботится HAL_PCD_EP_Open, где maxpacket в IN_ep / OUT_ep в PCD_HandleTypeDef - это максимально допустимый объем передачи, а mps - это значение максимального размера пакета.

Примечание. В исходной подпрограмме STM ep-> maxpacket = CUSTOM_HID_EPIN_SIZE = 2k; то есть максимальный объем данных каждого пакета равен 2k.

Функция передачи

USBD_CUSTOM_HID_DataIn

Здесь снова, относительно устройства, IN означает отправку. Как упоминалось ранее, функция отправки - USBD_CUSTOM_HID_DataIn,

Однако обратите внимание, что этот USBD_CUSTOM_HID_DataIn, похоже, ничего не делает, поэтому давайте еще раз подумаем об этом: где данные и как они отправляются?

Фактически, эта функция вызывается во время процесса отправки прерывания HAL_PCD_IRQHandler, мы увидим позже

Фактический маршрут отправки (этап подготовки данных)

Фактический маршрут отправки выглядит следующим образом:

USBD_CUSTOM_HID_SendReport -->USBD_LL_Transmit–>HAL_PCD_EP_Transmit -->USB_EPStartXfer

USBD_CUSTOM_HID_SendReport

Функция USBD_LL_Transmit, которую он вызывает, полезно только одно предложение

HAL_PCD_EP_Transmit

USB_EPStartXfer

В этой функции в основном устанавливаются USB_OTG_DOEPTSIZ_XFRSIZ и USB_OTG_DOEPTSIZ_PKTCNT,

USBx_INEP(ep->num)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_PKTCNT & (((ep->xfer_len + ep->maxpacket -1U)/ ep->maxpacket) << 19U)) ;

Возьмем предыдущий пример, maxpacket = CUSTOM_HID_EPIN_SIZE = 2, что означает, что может быть передано максимум 2 КБ. В этом примере xfer_len = 5 = XFRSIZ байтов,

PKCNT = (ep->xfer_len + ep->maxpacket -1U) / ep->maxpacket = 6/2 = 3

Наконец вызов прерывания

USBx_INEP(ep->num)->DIEPCTL |= (USB_OTG_DIEPCTL_CNAK | USB_OTG_DIEPCTL_EPENA);

Для отправки данных необходимо понимать, что, когда данные отправляются вне контроля пользователя, это можно увидеть с помощью инструмента захвата пакетов, что связано с конкретной ситуацией связи хоста.

Step-by-step

HAL_PCD_DataInStageCallback(hpcd, epnum);
Здесь hpcd - это hpcd_USB_OTG_FS,

USBD_LL_DataInStage((USBD_HandleTypeDef*)hpcd->pData, epnum, hpcd->IN_ep[epnum].xfer_buff);

USBD_LL_DataInStage(&hUsbDeviceFS, epnum, hpcd_USB_OTG_FS.IN_ep[epnum].xfer_buff);
с
pdev->pClass->DataIn(pdev, epnum);

Пока все готово, и тогда функция прерывания HAL_PCD_IRQHandler вызовет PCD_WriteEmptyTxFifo для передачи данных.

if ((epint & USB_OTG_DIEPINT_TXFE) == USB_OTG_DIEPINT_TXFE) PCD_WriteEmptyTxFifo(hpcd, epnum);
>

И PCD_WriteEmptyTxFifo, наконец, вызывает функцию USB_WritePacket.

USB_WritePacket(USBx, ep->xfer_buff, epnum, len, hpcd->Init.dma_enable);

PCD_WriteEmptyTxFifo

Обратите особое внимание на то, что len32b здесь имеет дело с 32-битным выравниванием.В справочном руководстве 34.16 OTG_FS регистры управления и состояния все регистры, включая DFIFO, требуют выравнивания 32-битного блока.

Последний send-USB_WritePacket

Как и в случае с предыдущей функцией, здесь также обрабатывается 32-битное выравнивание.Каждый раз USBx_DFIFO отправляет 4 байта данных, а последние менее 4 байтов также отправляются с 4 байтами. Из функций PCD_WriteEmptyTxFifo и USB_WritePacket видно, что чем больше параметр maxpacket, тем выше скорость отправки, поскольку это может значительно сэкономить время определения размера данных.

Другой момент заключается в том, что объем данных, отправленных за один раз, не имеет ничего общего с maxpacket (на самом деле, правильность отправки и получения больше зависит от дескриптора отчета HID). Функция maxpacket заключается только в том, чтобы определять размер данных, записываемых в DFIFO каждый раз. Готовый еще будет рассылаться до конца. Что касается того, когда и сколько чип отправит после записи в FIFO, все это связано с конструкцией оборудования и не может контролироваться пользователем.

По сути, то, сколько данных может быть записано в DFIFO за один раз, определяется USB_OTG_DTXFSTS_INEPTFSAV, поэтому его необходимо постоянно проверять в программе. while (((USBx_INEP(epnum)->DTXFSTS & USB_OTG_DTXFSTS_INEPTFSAV) > len32b) ,

USBx_DFIFO (i) - регистр передачи данных

Точно так же давайте посчитаем, к какому USBx_DFIFO относится, предполагая, что i = 1,

Следовательно, USBx_DFIFO (1) = 0x50001000U + 1 * 0x1000

Это в точности карта регистра доступа Data FIFO (DFIFO), упомянутая в руководстве 34.6.1.

Функция приема

USBD_CUSTOM_HID_DataOut

Прием относительно простой, поэтому не буду вдаваться в подробности. Исходный код также прост для понимания.

Понять дескрипторы слова конфигурации

USBD_FS_DeviceDesc

CUSTOM_HID_ReportDesc_FS

  • В каждой транзакции максимальный пакет для полной скорости составляет всего 64 байта, а максимум для низкой скорости - 8 байтов; каждая миллисекунда (за миллисекунду) инициирует транзакцию, поэтому количество байтов, передаваемых в секунду, FullSpeed ​​составляет около 64 КБ, LowSpeed ​​составляет около 8 КБ. ,
  • Только ввод может быть отправлен конвейером прерывания, а вывод и функция должны быть инициированы хостом через конвейер управления. (HID1.11chapter5.6)
  • Если REPORT_ID используется в дескрипторе отчета, первым байтом буфера данных USB-отправки должен быть REPORT_ID, чтобы сообщить системе Windows / Linux, к какому идентификатору принадлежат данные, в противном случае система не сможет нормально получить данные;
  • Когда нижний компьютер выгружает данные, он должен передавать в соответствии с количеством байтов, указанным в дескрипторе отчета. Если он был задан как 64 байта, и у нижнего компьютера есть только 10 байтов для отправки на хост в определенное время, то количество байтов в фактически отправленном буфере также должно быть 64 байта, а не только 10 байтов. Что касается завершения этих 64 байтов, не имеет значения, что это за контент. Только в этом случае Readfile компьютера верхнего уровня сможет правильно получать данные.

В приведенном выше исходном коде мы настроили 3 светодиода, которые управляются главным компьютером (USB HID Demonstrator, предоставленный официальным веб-сайтом ST), и определили ключ на плате разработки, а затем определили 3 63 (включая report_ID, фактически 64 А) байтовый канал связи.

В Windows эта информация получается с помощью следующей структуры, такой как количество каналов (я не знаю, как это перевести, английский - это возможности), максимальное количество байтов связи и т. Д.

Последнее в принципе не составляет труда, оцените и выложите исходный код!

Читайте также: