Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: STM32F4Discovery USB_HID, посылка данных в PC
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
ilkz
Добрый день!

Понадобилось организовать обмен данными стмки с пк на основе USB_HID.
За основу взял темплейт от TM с его либами.

В целом, туда-сюда пакетики бегают, но есть несколько крайне непонятных для меня моментов:
1. Коллбек USBD_HID_DataOut. Насколько я понял, прием данных происходит именно в нем. Но откуда достать сами данные? Во всех примерах используют переменную USB_Rx_Buffer, которая объявляется в том же файле. Но она нигде не заполняется, а при попадании в коллбек данные в ней чудесным образом оказываются. Как так?

2. Непонятно поведение: если буфер объявить внутри коллбека, то вместо значений из буфера отсылается мусор. Если же объявление сделать глобальным, то отсылаются верные значения. Почему так?

3. В коллбек вписана отправка ответного пакета. Но иногда (в основном после включения) ответный пакет отсылается лишь с 3-го раза. Почему такое может возникать?

Спасибо.
Kabdim
1. Очевидно её заполняет обработчик прерывний USB который не был найден.
2. Видимо функция сохраняет адрес буфера для последующего прерывания. В итоге если буфер лежит в стеке, то на момент реального использвоания там уже может быть мусор.
3. Могут быть разные причины.
ilkz
Цитата(Kabdim @ Aug 26 2015, 13:55) *
1. Очевидно её заполняет обработчик прерывний USB который не был найден.
2. Видимо функция сохраняет адрес буфера для последующего прерывания. В итоге если буфер лежит в стеке, то на момент реального использвоания там уже может быть мусор.
3. Могут быть разные причины.


1. Так ведь обращение к ней нигде не производится. Я даже указателя нигде на нее не нашел. Может кто знает сей фокус?
2. Более-менее понятно.
3. Пока тоже неясно, разбираюсь.

Появился еще один вопрос:
Для отправки данных используется функция USBD_HID_SendReport(&USB_OTG_dev, buff, count).
И для того, чтобы послать буфер в 64 байта надо указывать count равным 65, а не 64 как казалось бы... Никто с таким не сталкивался?
Golikov A.
64 байта - граница пакета, должна быть снабжена пустым пакетом с 0 данными, может дело в этом?

еще не забывайте что есть ДМА для заполнения массивов....
ilkz
Цитата(Golikov A. @ Aug 26 2015, 19:39) *
64 байта - граница пакета, должна быть снабжена пустым пакетом с 0 данными, может дело в этом?


Не совсем понял: т.е., чтобы отослать 64 байта, надо отослать 65 с последним нулевым? Или в 64 байтах последний болжен быть нулевой?

Хорошо, давайте разбираться детально sm.gif

Вот есть такой код отправки ответа:
Код
#define buf_size  8
uint8_t buf[buf_size];

static uint8_t  USBD_HID_DataOut (void  *pdev, uint8_t epnum) {
  uint16_t USB_Rx_Cnt;
  uint8_t i = 0;
  
  /* Get the received data buffer and update the counter */
  USB_Rx_Cnt = ((USB_OTG_CORE_HANDLE*)pdev)->dev.out_ep[epnum].xfer_count; // pointer to usb packet

  if(USB_Rx_Buffer[1] == 0x01) {
    TM_DISCO_LedOn(LED_GREEN);
  }
  else if(USB_Rx_Buffer[1] == 0x00) {
    TM_DISCO_LedOff(LED_GREEN);
  }
  
  for(i = 0; i<buf_size; i++) buf[i] = i;
  TM_USB_HIDDEVICE_SendCustom(buf, buf_size);
  
  /* Prepare Out endpoint to receive next packet */
  DCD_EP_PrepareRx(pdev, HID_OUT_EP, USB_Rx_Buffer, MAX_DATA_LENGTH);
  
  return USBD_OK;
}


Т.е., в ответ на прием пакета я отправляю 8 байт со счетчиком внутри.
TM_USB_HIDDEVICE_SendCustom() - это просто обертка над USBD_HID_SendReport(&USB_OTG_dev, buff, count).

Я ожидаю, что на пк примется этот 8-байтный ответ, однако ничего не принимается. Смотрим сниффером что получается:
Нажмите для просмотра прикрепленного файла
Ответы приходят (правда почему-то только с 3-го раза после включения девайса), длина 8 байт, вроде все как заказывали.


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

Однако, принимающая прога на компе в упор ничего не видит.
Вот репу уже сломал, прям руки опускаются. И что-то мне подсказывает что дело, скорее всего, в дескрипторах. Вроде я их пилил-пилил, но может чего не допилил. Посмотрите опытным взглядом:
Нажмите для просмотра прикрепленного файла
Там по идее заложено 64 байта туда, 64 байта обратно и все.
ilkz
Опа-ля, йа креведко розобралсо.

Дело было вот в чем:
1. Если мы хотим отправить N Байт, то реально надо отправить на 1 байт больше, т.е. N+1.
2. В дескрипторе репорта должно быть указано количество отправляемых элементов (REPORT_COUNT) равное N.
3. Первым байтом в отправляемых данных должен быть REPORT ID.
4. И усё.

Теперь понятны слова Golikov A. про 65 байт.

Осталось понять кто заполняет USB_Rx_Buffer.
Сергей Борщ
Цитата(ilkz @ Aug 27 2015, 09:06) *
1. Если мы хотим отправить N Байт, то реально надо отправить на 1 байт больше, т.е. N+1.
....
Теперь понятны слова Golikov A. про 65 байт.
Вы поняли неправильно. Конечная точка имеет ограничение на длину пакета. В вашем случае оно равно 64 байта. Если вам нужно передать больше - вы передаете несколько пакетов. Но как вторая сторона определит, что вы уже закончили передачу? А очень просто: если пришел пакет с длиной, меньшей, чем максимальная длина для этой рабочей точки, то он последний. А что делать, если вам надо передать ровно столько данных, какова максимальная длина пакета? Получив такой пакет вторая сторона будет ждать продолжения. Поэтому передающая в этом и только в этом случае посылает следом пакет с нулевой длиной, т.е. без данных (zlp, zero-length payload). Но это все внутренние дела стека., вас они волновать не должны.

REPORT ID - это совсем другая опера. Если вы в дескрипторах сообщили, что будете отсылать несколько разных типов сообщений (reports), то вы должны в начале каждого передавать дополнительный байт с идентификатором. Если вы анонсировали только один вид сообщений, то второй стороне не нужно отделять его от других и этот байт с идентификатором не передается.
ilkz
Цитата(Сергей Борщ @ Aug 27 2015, 09:25) *
Вы поняли неправильно. Конечная точка имеет...

REPORT ID - это совсем другая опера. Если вы в дескрипторах сообщили, что будете отсылать несколько разных типов сообщений (reports), то вы должны в начале каждого передавать дополнительный байт с идентификатором. Если вы анонсировали только один вид сообщений, то второй стороне не нужно отделять его от других и этот байт с идентификатором не передается.



С первым понятно, спасибо за внятное разъяснение.
Со вторым: если в репорте используется один IN и один OUT, то это считается за два вида сообщений или за один?
Сергей Борщ
Цитата(ilkz @ Aug 27 2015, 09:39) *
Со вторым: если в репорте используется один IN и один OUT, то это считается за два вида сообщений или за один?
Рапорты считаются отдельно для каждой конечной точки. IN и OUT - разные конечные точки, между разными конечными точками рапорты идентифицирутся по номеру конечной точки.
ilkz
Сергей Борщ, ясно.

Последний пока что вопрос: кто же все-таки заполняет USB_Rx_Buffer?
Пока что единственное что я нашел связанное с буфером приема/передачи - это указатель xferr_buff в структуре USB_OTG_hc (файл usb_core.h). Но как-то оно не то...
Сергей Борщ
Цитата(ilkz @ Aug 27 2015, 12:05) *
Последний пока что вопрос: кто же все-таки заполняет USB_Rx_Buffer?
Заполняется она в прерывании. Где-то в процессе инициализации конечной точки в стек передается адрес и размер этого буфера. Но это уже не ко мне. Я вместо этого ужаса написал свой стек на плюсах. Поищите по файлам все упоминания USB_Rx_Buffer, наверняка наткнетесь на функцию инициализации. А дальше уже копайте от нее.
ilkz
Искал - пусто.

Сергей Борщ, хотите прикол?
Было (это единственное объявление данной переменной на весь проект, сидит в том же файле что и код ниже):
Код
__ALIGN_BEGIN uint8_t USB_Rx_Buffer   [MAX_DATA_LENGTH] __ALIGN_END;


Меняю на:
Код
__ALIGN_BEGIN uint8_t SuperMegaBuffer   [MAX_DATA_LENGTH] __ALIGN_END;


, соответственно, в коллбеке приема:
Код
static uint8_t  USBD_HID_DataOut (void  *pdev, uint8_t epnum) {
  uint16_t USB_Rx_Cnt;
  uint8_t i = 0;
  
  USB_Rx_Cnt = ((USB_OTG_CORE_HANDLE*)pdev)->dev.out_ep[epnum].xfer_count; // pointer to usb packet

  USB_Rx_Buffer[0] = 0x01;
  TM_USB_HIDDEVICE_SendCustom(USB_Rx_Buffer, 65);

  DCD_EP_PrepareRx(pdev, HID_OUT_EP, USB_Rx_Buffer, MAX_DATA_LENGTH);
  return USBD_OK;
}


меняю на:
Код
static uint8_t  USBD_HID_DataOut (void  *pdev, uint8_t epnum) {
  uint16_t USB_Rx_Cnt;
  uint8_t i = 0;
  
  USB_Rx_Cnt = ((USB_OTG_CORE_HANDLE*)pdev)->dev.out_ep[epnum].xfer_count; // pointer to usb packet
  
  SuperMegaBuffer[0] = 0x01;
  TM_USB_HIDDEVICE_SendCustom(SuperMegaBuffer, 65);
  
  DCD_EP_PrepareRx(pdev, HID_OUT_EP, SuperMegaBuffer, MAX_DATA_LENGTH);
  return USBD_OK;
}


и все, больше ничего и нигде не трогаю. И что же я вижу? А то, что чудесным, мать его, образом - в SuperMegaBuffer оказываются принятые данные. КАААК?
Сергей Борщ
Цитата(ilkz @ Aug 27 2015, 12:54) *
КАААК?

Видимо как-то отсюда:
Цитата(ilkz @ Aug 27 2015, 12:54) *
Код
  TM_USB_HIDDEVICE_SendCustom(USB_Rx_Buffer, 65);
Тут же вы передаете адрес этого буфера стеку. Копайте эту функцию.
ilkz
Цитата(Сергей Борщ @ Aug 27 2015, 14:53) *
Видимо как-то отсюда:
Тут же вы передаете адрес этого буфера стеку. Копайте эту функцию.


Весь цимес в том, что если убрать строки
Код
  TM_USB_HIDDEVICE_SendCustom(USB_Rx_Buffer, 65);
  DCD_EP_PrepareRx(pdev, HID_OUT_EP, USB_Rx_Buffer, MAX_DATA_LENGTH);


,то волшебный буфер все равно оказывается заполнен принятыми данными )))
Сергей Борщ
Цитата(ilkz @ Aug 27 2015, 16:18) *
Весь цимес в том, что если убрать строки
Чудес не бывает sm.gif Не верю. Что-то вы не перекомпилировали или не то зашили
Golikov A.
Там данные из USB не по ДМА заполняются?
Вы где то в структуре настройки этот буфер передаете, а дальше ДМА на него настраивается и поехали, не?
ilkz
Думаю, это единственное логичное объяснение. Но мне не удалось найти где делается эта привязка. Но очень хочется разобраться.
Выкладываю проект, посмотрите по возможности незамыленным взглядом (сабж сидит в usbd_hid_core.c).
Нажмите для просмотра прикрепленного файла

UPD: И еще, почему-то после первого приема данных буфер - пустой (это видно по мемори дампу). Начиная со второго приема - он заполняется валидными данными.
Он был не готов принимать после инициализации. Проблема решилась добавлением DCD_EP_PrepareRx(pdev, HID_OUT_EP, rxbuf, MAX_DATA_LENGTH); в USBD_HID_Init() в место после открытия ендпоинтов.

UPD2: Возможно, становится понятно поведение с "магическим" запонением приемного буфера. Насколько реален такой сценарий:
1. Объявлена переменная-буфер buf.
2. Т.к., во время инициализации не делается DCD_EP_PrepareRx(..., buf, ...), то буфер не трогается и он по дефолту равен нулю.
3. Возникает событие приема: буфер равен нулю (т.к. его никто не трогал).
4. Что-то делается в обработчике.
5. Последняя строчка - это как раз DCD_EP_PrepareRx(..., buf, ...), т.е. тут уже нормально передается ссылка на этот несчастный буфер.
6. Ну и на последующих приемах он уже получается прилинкован к приемнику, который заполняет его валидными данными.

Я прав или не?
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.