STM32 и USB-HID — это просто

  • Tutorial
На дворе 2014 год, а для связи микроконтроллеров с ПК самым популярным средством является обычный последовательный порт. С ним легко начать работать, он до примитивности прост в понимании — просто поток байт.
Однако все современные стандарты исключили COM порт из состава ПК и приходится использовать USB-UART переходники, чтобы получить доступ к своему проекту на МК. Не всегда он есть под рукой. Не всегда такой переходник работает стабильно из-за проблем с драйверами. Есть и другие недостатки.
Но каждый раз, когда заходит разговор о том, применять USB или последовательный порт, находится множество поклонников логической простоты UART. И у них есть на то основания. Однако, хорошо ведь иметь альтернативу?

Меня давно просили рассказать как организовать пакетный обмен данными между ПК и МК на примере STM32F103. Я дам готовый рабочий проект и расскажу как его адаптировать для своих нужд. А уж вы сами решите — нужно оно вам или нет.

У нас есть плата с современным недорогим микроконтроллером STM32F103C8 со встроенной аппаратной поддержкой USB, я рассказывал о ней ранее


Я сказал, что у последовательного порта есть и другие недостатки:
-часто COM порт отсутствует в ПК или ноутбуке
-питание устройству нужно подавать отдельно
-даже при наличии COM порта в ПК необходимо согласовывать уровни сигналов: ПК использует интерфейс RS232 с дифференциальными уровнями сигналов +15В и -15В, а микроконтроллеры — TTL уровни (+5В, +3.3В, униполярные).
-часто в системе образуются десятки виртуальных COM портов и найти тот порт, что соответствует вашему устройству может оказаться непросто.
В свою очередь USB с нами уже многие годы и имеет свои преимущества:
-Возможность подачи питания от HOST устройства
-Удобная реализация пакетного обмена
-Возможность одновременного подключения к устройству несколькими программами
-Возможность однозначной идентификации подключенного устройства
-Аппаратная поддержка во многих современных МК, что исключает необходимость переходников
Функционал USB чрезвычайно богатый, но это порождает проблему — разобраться не так просто, как с последовательным интерфейсом. Есть отдельный класс устройств — USB-HID, которые не требуют установки драйверов, специально предназначены для взаимодействия с человеком и различными устройствами ввода-вывода. Идеально для организации обмена данными с МК. Лично мне нравится пакетный режим обмена. Это удобная абстракция. К тому же разбирать пакетные сообщения несколько проще и удобнее, чем работать с простым потоком байт.

Выбор профиля HID


USB-HID — довольно обширный класс устройств, поэтому прежде всего придется выбрать какое именно устройство мы будем создавать.
Мы можем создать эмуляцию клавиатуры, мыши, джойстика и других устройств ввода, а можем создать свое устройство, чтобы не зависеть от довольно жестких рамок стандарта и свободно обмениваться данными с ПК.
Я расскажу как cделать Custom HID device. Это дает максимальную свободу. Чтобы не затягивать статью, постарюсь рассказать максимально кратко — описаний стандарта в сети и без меня много, но лично мне они слабо помогли, когда понадобилось решить конкретную задачу.

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


Я использую EmBlocks для разработки под STM32. Вы можете использовать любую удобную среду, проект не очень сложно адаптировать.
К базовой структуре проекта добавлены:
  • Папка USB-FS с библиотекой «STM32F10x, STM32L1xx and STM32F3xx USB-FS-Device Driver» версии 4.0.0.
  • В папках Inc и Src файлы:
    platform_config.h — здесь собраны определения, касающиеся конкретной платы и семейства МК
    stm32_it.h, stm32_it.c — здесь определены обработчики прерываний
    usb_conf.h, usb_endp.c — здесь определяются конечные точки (Endpoint), размеры и адреса их буферов, функции-обработчики
    usb_desc.h, usb_desc.c — здесь собрана информаци о самом устройстве — как оно будет определяться при подключении к ПК и определены размеры и формат пакетов данных
    hw_config.c — здесь собрана вся работа с отправкой данных на ПК
    hw_config.h, usb_istr.h, usb_prop.h, usb_pwr.h
    usb_istr.c, usb_prop.c, usb_pwr.c — нужны для работы USB-FS библиотеки, но лезть в них необязательно

Все эти файлы мы добавляем в любой проект, использующий USB.

Инициализация USB


Для корректной работы USB модуля важна частота работы МК. Далеко не все частоты позволяют правильно задать тактирование USB. В нашем случае используется кварцевый генератор на 8МГц и МК работает на частоте 72МГц, а USB модуль на 48МГц.
В main.c достаточно включить всего несколько строк кода
main.c
/* Includes ------------------------------------------------------------------*/
#include "hw_config.h"
#include "usb_lib.h"
#include "usb_pwr.h"

/* Private variables ---------------------------------------------------------*/
__IO uint8_t PrevXferComplete = 1;

int main(void)
{
  Set_System();

  USB_Interrupts_Config();

  Set_USBClock();

  USB_Init();


  while (1)
  {

    if (bDeviceState == CONFIGURED)
    {
      if (PrevXferComplete)
      {
        RHIDCheckState();
      }
    }
  }
}


В функции Set_System() производится настройка пина подтяжки линии D+ к питанию для программного подключения/отключения устройства от ПК (в нашей плате не используется), настраивается прерывание и инициализируются светодиоды и кнопки для демонстрационного проекта.
В USB_Interrupts_Config() настраиваются прерывания в зависимости от семейства МК (поддерживаются F10x, F37x, L1x).
Функция USB_Init() запускает работу USB модуля. Если временно нужно отключить для отладки работу с USB, просто закомментируйте эту строку.
Далее в бесконечном цикле проверяется, удалось ли сконфигурировать USB модуль при подключении к ПК. Если все сработало верно и устройство успешно подключилось, ПК включен и не находится в режиме энергосбережения, то состояние будет CONFIGURED.
Далее проверяется, была ли закончена предыдущая передача данных в ПК и если да, то готовится к отправке новый пакет в функции RHIDCheckState()

Размер пакета и частота передачи


USB-HID девайс не может сам инициировать передачу, т.к. координацией шины занимается host устройство — ПК. Поэтому при подготовке USB дескриптора нашего устройства, мы пишем, как часто нужно опрашивать наше устройство. По спецификации максимальная частота опроса — 1кГц и максимальный размер передаваемого за раз пакета — 64 байта. Если этого недостаточно — придется использовать другие режимы работы — вроде USB bulk, но там уже без драйверов не обойтись.
За настройку взаимодействия с ПК отвечают 3 дескриптора:
Дескриптор устройства
/* USB Standard Device Descriptor */
const uint8_t RHID_DeviceDescriptor[RHID_SIZ_DEVICE_DESC] =
  {
		    RHID_SIZ_DEVICE_DESC,         // общая длина дескриптора устройства в байтах
		    USB_DEVICE_DESCRIPTOR_TYPE, // bDescriptorType - показывает, что это за дескриптор. В данном случае - Device descriptor
		    0x00, 0x02,                 // bcdUSB - какую версию стандарта USB поддерживает устройство. 2.0

			// класс, подкласс устройства и протокол, по стандарту USB. У нас нули, означает каждый интерфейс сам за себя
		    0x00,                       //bDeviceClass
		    0x00,                       //bDeviceSubClass
		    0x00,                       //bDeviceProtocol

		    0x40,                       //bMaxPacketSize - максимальный размер пакетов для Endpoint 0 (при конфигурировании)

			// те самые пресловутые VID и PID,  по которым и определяется, что же это за устройство.
		    0x83, 0x04,                 //idVendor (0x0483)
		    0x11, 0x57,                 //idProduct (0x5711)

		    DEVICE_VER_L, DEVICE_VER_H,                 // bcdDevice rel. DEVICE_VER_H.DEVICE_VER_L  номер релиза устройства

			// дальше идут индексы строк, описывающих производителя, устройство и серийный номер.
			// Отображаются в свойствах устройства в диспетчере устройств
			// А по серийному номеру подключенные устройства с одинаковым VID/PID различаются системой.
		    1,                          //Index of string descriptor describing manufacturer
		    2,                          //Index of string descriptor describing product
		    3,                          //Index of string descriptor describing the device serial number
		    0x01                        // bNumConfigurations - количество возможных конфигураций. У нас одна.
  }
  ; /* CustomHID_DeviceDescriptor */


В комментариях все довольно прозрачно. Обратите внимание на DEVICE_VER_L, DEVICE_VER_H — это константы из usb_desc.h, которые вы можете изменить для идентификации версии своего устройства.

Дескриптор конфигурации (описывает возможности устройства)
/* USB Configuration Descriptor */
/*   All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */
const uint8_t RHID_ConfigDescriptor[RHID_SIZ_CONFIG_DESC] =
  {
		    0x09, 			// bLength: длина дескриптора конфигурации
		    USB_CONFIGURATION_DESCRIPTOR_TYPE, // bDescriptorType: тип дескриптора - конфигурация
		    RHID_SIZ_CONFIG_DESC, 0x00, // wTotalLength: общий размер всего дерева под данной конфигурацией в байтах

		    0x01,         // bNumInterfaces: в конфигурации всего один интерфейс
		    0x01,         // bConfigurationValue: индекс данной конфигурации
		    0x00,         // iConfiguration: индекс строки, которая описывает эту конфигурацию
		    0xE0,         // bmAttributes: признак того, что устройство будет питаться от шины USB
		    0x32,         // MaxPower 100 mA: и ему хватит 100 мА

				/************** Дескриптор интерфейса ****************/
				0x09,         // bLength: размер дескриптора интерфейса
				USB_INTERFACE_DESCRIPTOR_TYPE, // bDescriptorType: тип дескриптора - интерфейс
				0x00,         // bInterfaceNumber: порядковый номер интерфейса - 0
				0x00,         // bAlternateSetting: признак альтернативного интерфейса, у нас не используется
				0x02,         // bNumEndpoints - количество эндпоинтов.

				0x03,         // bInterfaceClass: класс интерфеса - HID
				// если бы мы косили под стандартное устройство, например клавиатуру или мышь, то надо было бы указать правильно класс и подкласс
				// а так у нас общее HID-устройство
				0x00,         // bInterfaceSubClass : подкласс интерфейса.
				0x00,         // nInterfaceProtocol : протокол интерфейса

				0,            // iInterface: индекс строки, описывающей интерфейс

					// теперь отдельный дескриптор для уточнения того, что данный интерфейс - это HID устройство
					/******************** HID дескриптор ********************/
					0x09,         // bLength: длина HID-дескриптора
					HID_DESCRIPTOR_TYPE, // bDescriptorType: тип дескриптора - HID
					0x01, 0x01,   // bcdHID: номер версии HID 1.1
					0x00,         // bCountryCode: код страны (если нужен)
					0x01,         // bNumDescriptors: Сколько дальше будет report дескрипторов
						HID_REPORT_DESCRIPTOR_TYPE,         // bDescriptorType: Тип дескриптора - report
						RHID_SIZ_REPORT_DESC,	0x00, // wItemLength: длина report-дескриптора


					/******************** дескриптор конечных точек (endpoints) ********************/
					0x07,          // bLength: длина дескриптора
					USB_ENDPOINT_DESCRIPTOR_TYPE, // тип дескриптора - endpoints

					0x81,          // bEndpointAddress: адрес конечной точки и направление 1(IN)
					0x03,          // bmAttributes: тип конечной точки - Interrupt endpoint
					wMaxPacketSize, 0x00,    // wMaxPacketSize:  Bytes max
					0x20,          // bInterval: Polling Interval (32 ms)

          0x07,	/* bLength: Endpoint Descriptor size */
          USB_ENDPOINT_DESCRIPTOR_TYPE,	/* bDescriptorType: */
            /*	Endpoint descriptor type */
          0x01,	/* bEndpointAddress: */
            /*	Endpoint Address (OUT) */
          0x03,	/* bmAttributes: Interrupt endpoint */
          wMaxPacketSize,	/* wMaxPacketSize:  Bytes max  */
          0x00,
          0x20,	/* bInterval: Polling Interval (32 ms) */
}
  ; /* RHID_ConfigDescriptor */


Здесь стоит обратить внимание на константу wMaxPacketSize — она определяет максимальный размер пакета, которым мы будем обмениваться с ПК. Проект так настроен, чтобы при ее изменении менялись и размеры буферов. Но не забывайте, что больше 0x40 по стандарту указывать не стоит. С этой константой будьте осторожны — если передаваемый пакет будет отличаться по размеру — будут проблемы!
Следующая за ним константа с комментарием bInterval — это период опроса устройства в миллисекундах. Для нашего устройства задано 32мс.

Дескриптор репорта (описывает протокол)
const uint8_t RHID_ReportDescriptor[RHID_SIZ_REPORT_DESC] =
  {
    0x06, 0x00, 0xff,              // USAGE_PAGE (Generic Desktop)
    0x09, 0x01,                    // USAGE (Vendor Usage 1)
    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, 0x01,                    //   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 (2)
    0x09, 0x02,                    //   USAGE (Vendor Usage 2)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0xb1, 0x82,                    //   FEATURE (Data,Var,Abs,Vol)
    0x85, 0x02,                    //   REPORT_ID (2)
    0x09, 0x02,                    //   USAGE (Vendor Usage 2)
    0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)

    0x85, 0x03,                    //   REPORT_ID (3)
    0x09, 0x03,                    //   USAGE (Vendor Usage 3)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //   LOGICAL_MAXIMUM (255)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, RPT3_COUNT,                    //   REPORT_COUNT (N)
    0xb1, 0x82,                    //   FEATURE (Data,Var,Abs,Vol)
    0x85, 0x03,                    //   REPORT_ID (3)
    0x09, 0x03,                    //   USAGE (Vendor Usage 3)
    0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)

    0x85, 0x04,                    //   REPORT_ID (4)
    0x09, 0x04,                    //   USAGE (Vendor Usage 4)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, RPT4_COUNT,                    //   REPORT_COUNT (N)
    0x81, 0x82,                    //   INPUT (Data,Var,Abs,Vol)
    0xc0                           // END_COLLECTION
}


Это самый важный дескриптор — он описывает протокол обмена и функционал устройства. Его формирование — не самая простая задача. Если допустить ошибку при формировании дескриптора — устройство перестанет работать. Формат дескриптора очень жесткий. Есть даже специальная утилита HID Descriptor tool. А в корне проекта лежит файл «RHID.hid» с описанным выше дескриптором для редактирования в этой утилите. Но если вы не понимаете, что делаете, лучше не лезть.
Для простоты я сделал две константы:
RPT3_COUNT — размер OUTPUT буфера в байтах для передачи пакета в МК (в примере — 1 байт)
RPT4_COUNT — размер INPUT буфера в байтах для передачи пакета в ПК (в примере — 4 байта)
Размер любого из этих буферов не должен превышать wMaxPacketSize. Меньше — можно.
Кстати, превратить Custom HID в другой HID девайс, например, клавиатуру или джойстик можно фактически только переписав ReportDescriptor и изменив класс и подкласс устройства в дескрипторе конфигурации.

Что такое Report


Хост (ПК) и девайс (МК) обмениваются пакетами данных заранее оговоренной структуры — report. Пакетов может быть весьма много, их можно предусмотреть на все случаи жизни — например пакет с данными о каких-то событиях в устройстве, пакет с данными, которые запрашивал ПК, пакет с командой для МК. Все, что угодно. Но структура всех пакетов должна быть описана в структуре RHID_ReportDescriptor.
ПК и МК различают репорты по ID, который идет первым байтом в пакете.
В нашем примере 4 типа репортов:
  • REPORT_ID = 1 и 2 — команда МК включить/выключить LED1/LED2. Содержит поле размером 1 бит с желаемым состоянием светодиода и поддерживает отправку как методом SET_REPORT так и методом SET_FEATURE (об этом чуть позже).
  • REPORT_ID = 3 — передает один байт в МК. Просто, чтобы показать, как передать данные МК. Мы будем передавать положение ползунка.
  • REPORT_ID = 4 — это репорт для передачи данных ПК. Возвращает информацию о текущем состоянии светодиодов, кнопок (если они есть) и возвращает переданный в репорте с ID=3 байт, чтобы показать, что данные приняты.

Если вы не до конца разобрались в том, как формировать дескриптор репортов, то просто меняйте константы RPT3_COUNT и RPT4_COUNT, устанавливая нужный размер исходящих и входящих (с точки зрения ПК) пакетов. Остальные репорты можно просто не трогать, они не помешают. Не забывайте, что первым байтом должен быть ID репорта.

Цикл обмена


Итак, мы сконфигурировали наше устройство, установив PID, VID, номер версии, настроили размеры входящих и исходящих пакетов и готовы к работе.
Каждые 32мс, как мы и просили в дескрипторе конфигурации, хост будет нас опрашивать и в функции RHIDCheckState мы проверяем — если у нас есть, что отправить, то формируем пакет данных для хоста.
RHIDCheckState - функция отправки данных
/*******************************************************************************
* Function Name : RHIDCheckState.
* Description   : Decodes the RHID state.
* Input         : None.
* Output        : None.
* Return value  : The state value.
*******************************************************************************/
uint16_t btn1_prev, btn2_prev;
uint8_t Buffer[RPT4_COUNT+1];
uint8_t RHIDCheckState(void)
{
    uint16_t btn1=0, btn2=0;
    btn1 = GPIO_ReadInputDataBit(BTN1_PORT, BTN1_PIN);
    btn2 = GPIO_ReadInputDataBit(BTN2_PORT, BTN2_PIN);
    Buffer[0] = 4;
    Buffer[1] = btn1;
    Buffer[2] = btn2;
    Buffer[3] = (GPIO_ReadInputDataBit(LED_PORT, LED1_PIN) | GPIO_ReadInputDataBit(LED_PORT, LED2_PIN)<<1);

    /* Reset the control token to inform upper layer that a transfer is ongoing */
    PrevXferComplete = 0;

    /* Copy buffer date info in ENDP1 Tx Packet Memory Area*/
    USB_SIL_Write(EP1_IN, Buffer, RPT4_COUNT+1);
    /* Enable endpoint for transmission */
    SetEPTxValid(ENDP1);

    return (btn1 | btn2<<1);
}


Массив uint8_t Buffer[RPT4_COUNT+1] определен как размер полезных данных входящего (рассматривается всегда с точки зрения хоста) пакета + байт ID. Это важно — если размер буфера будет отличаться — будут проблемы. Поэтому для изменения размеров буфера редактируйте значение константы в usb_desc.h.
В функции мы собираем данные в пакет, устанавливаем флаг PrevXferComplete = 0, говорящий о том, что данные отправляются и вызываем функциии библиотеки USB_SIL_Write и SetEPTxValid для отправки данных хосту.
Все, на этом передача данных хосту закончена.

С приемом данных немного сложнее — есть два способа послать данные девайсу — один из них заключается в использовании описанных в дескрипторе репорта возможностей устройства (Features), с соответствующими параметрами посредством функции SET_FEAUTRE. Это некоторая абстракция, для красивого управления устройством с кучей функций, чтобы можно было вызывать осмысленные функции, а не просто слать поток байт.
Второй способ — это работа с устройством как с файлом — просто записываем в него пакет как в файл. Этот метод называется SET_REPORT. На деле работает чуть-чуть медленнее.
Наше устройство поддерживает оба метода, о чем мы и сказали хосту в дескрипторе репортов.

Обработка SET_FEATURE

Данные, отправленные методом SET_FEAUTRE обрабатываются в usb_prop.c

функция HID_Status_In
/*******************************************************************************
* Function Name  : HID_Status_In.
* Description    : HID status IN routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void HID_Status_In(void)
{
  BitAction Led_State;

  if (Report_Buf[1] == 0)
  {
    Led_State = Bit_RESET;
  }
  else
  {
    Led_State = Bit_SET;
  }

  switch (Report_Buf[0])
  {
    case 1: /* Led 1 */
     if (Led_State != Bit_RESET)
     {
       GPIO_SetBits(LED_PORT,LED1_PIN);
     }
     else
     {
       GPIO_ResetBits(LED_PORT,LED1_PIN);
     }
     break;
    case 2: /* Led 2 */
     if (Led_State != Bit_RESET)
     {
       GPIO_SetBits(LED_PORT,LED2_PIN);
     }
     else
     {
       GPIO_ResetBits(LED_PORT,LED2_PIN);
     }
      break;
    case 3: /* Led 1&2 */
       Buffer[4]=Report_Buf[1];
     break;
  }
}


Здесь мы проверяем первый байт в репорте и в соответствии с ним обрабатываем остаток пакета — управляем светодиодами или просто берем байт, отправленный нам хостом и кладем в пакет для последующей отправки обратно в функции RHIDCheckState.
Под Report_Buf зарезервировано wMaxPacketSize байт, чтобы влез любой пакет, который нам отправит хост.

Данные, отправленные методом SET_REPORT обрабатываются в usb_endp.c
функция EP1_OUT_Callback
/*******************************************************************************
* Function Name  : EP1_OUT_Callback.
* Description    : EP1 OUT Callback Routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void EP1_OUT_Callback(void)
{
  BitAction Led_State;

  /* Read received data (2 bytes) */
  USB_SIL_Read(EP1_OUT, Receive_Buffer);

  if (Receive_Buffer[1] == 0)
  {
    Led_State = Bit_RESET;
  }
  else
  {
    Led_State = Bit_SET;
  }


  switch (Receive_Buffer[0])
  {
    case 1: /* Led 1 */
     if (Led_State != Bit_RESET)
     {
       GPIO_SetBits(LED_PORT,LED1_PIN);
     }
     else
     {
       GPIO_ResetBits(LED_PORT,LED1_PIN);
     }
     break;
    case 2: /* Led 2 */
     if (Led_State != Bit_RESET)
     {
       GPIO_SetBits(LED_PORT,LED2_PIN);
     }
     else
     {
       GPIO_ResetBits(LED_PORT,LED2_PIN);
     }
      break;
    case 3: /* Led 1&2 */
        Buffer[4]=Receive_Buffer[1];
     break;
  }

  SetEPRxStatus(ENDP1, EP_RX_VALID);
}


Здесь почти то же самое, только нужно самостоятельно забрать данные вызовом USB_SIL_Read(EP1_OUT, Receive_Buffer) и в конце сообщить, что мы закончили вызовом SetEPRxStatus(ENDP1, EP_RX_VALID);

Настраивать устройство, передавать и принимать данные в пакетах нужного размера с нужной нам периодичностью мы научились.
Собираем проект и прошиваем в устройство.
Работать, это будет примерно так:


Проект поддерживает взаимодействие с утилитой USB HID Demonstrator от ST Microelectronics.
Страница Device capabilities отображает возможности, описанные в Report Descriptor.
Input/Output transfer позволяет вручную поотправлять данные девайсу и посмотреть пакет, который от него приходит.
Graphic view позволяет управлять светодиодами, чекбоксами Led 1, Led 2, настроив соответствующий им Report ID, а также передавать байт ползунком (ReportID=3)


Также я написал маленькую демо-софтинку, которая автоматически определяет подключение к компу и отключение нашего девайса по его VID и PID, отображает статус — подключено/отключено индикатором рядом с чекбоксом Auto Connect

Радиокнока Send using позволяет выбрать метод отправки данных девайсу.
Report: отображает полученный от девайса пакет побайтно, начиная с ReportID.
Щелкая по светодиодам ниже — управляем светодиодами девайса. Их состояние отображает текущее состояние девайса. Считывается из репорта от девайса.
Перемещая ползунок, мы отправляем Report с ID=3 и значением, соответствующим позиции ползунка. Девайс вернет это значение в 4 байте репорта.
В выпадающем комбобоксе отображаются HID девайсы, найденные в системе и если найден наш девайс, то отображается его название.

Скачать все, что необходимо, можно на GitHub. В составе:
DT — HID Descriptor tool
tstHID-STM32F103 — проект для EmBlocks
USB HID Demonstrator — утилита от ST Microelectronics
HIDSTM32.exe — моя демо-софтинка на Delphi аналогичного фукнционала, но не требующая настройки

Если остались вопросы — пишите в комментариях. Постараюсь ответить. Я постарался не утопить суть в куче мелочей, чтобы сложилось общее понимание. Остальное уже можно понять, изучая проект. Но если вам нужно быстро сделать свое устройство, а лезть в дебри некогда — все, что вам нужно, я описал.

P.S. По умолчанию при уходе хоста в режим энергосбережения, девайс засыпает вместе с ним, а если подключить девайс к спящему ПК, то он тоже уйдет в слип. Поэтому если мы просто воткнем в девайс блок питания или запитаем от батареи, то работать он не будет, считая, что подключен к спящему ПК (пакетов конфигурации то от БП не придет точно). Я изменил библиотеку так, чтобы устройство работало и при подключении просто БП. Поэтому девайс будет работать как при подключениии к ПК так и автономно. (У меня ушло немало времени, чтобы разобраться с этим.)
Метки:
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 104
  • +3
    Наконец-то! Спасибо большое!!!
    • +1
      Да все никак времени не было. На статью ушло больше 4 часов плюс сам проект подготовить к публикации.
      Надеюсь будет полезна.
    • 0
      Лучше уж тогда в TCP сразу
      • +1
        Если вы об Ethernet функционале, то его в STM32F103 нет.
        • 0
          Ну понятное дело, просто мы в свое время долго с разными такими конвертерами возились и в итоге отошли в пользу Serial-IP конвертеров, как наиболее стабильных и предсказуемых.
          • +3
            есть у меня такой. Еще не возился с ним. Но я говорю в статье об элегантном способе взаимодействия с ПК. Без дополнительных микросхем, конвертеров и лишних задержек. Максимально нативный интерфейс для современного компьютера. К тому же МК с поддержкой USB экстремально дешевы.
            TCP/IP нельзя назвать ни простым ни дешевым ни быстрым протоколом. Особенно в вариации со всякими промежуточными конвертерами.
            Для специфических случаев, когда нужно передавать данные удаленно — это то, что нужно. А при локальном подключении — удобнее USB. Никакой возни с сетевыми адресами, все прозрачно и автоматически. Для пользователя — жутко удобно. А для промышленного применения вообще лучше CAN. Но там без CAN трансивера не обойтись.
            • 0
              >TCP/IP нельзя назвать ни простым ни дешевым ни быстрым протоколом.

              На фоне изначального посыла о том, как хорош COM-порт это несколько слишком сильное заявление.

              Как и утверждение о том, что " для промышленного применения вообще лучше CAN".

              Ну и с локальным подключением не все так розово и пушисто. 3 метра или 5 метров — это локально или уже удалённо?
              • 0
                Я не говорил, что COM порт хорош. я говорю о том, что у него много приверженцев. Ну нравится он людям, что тут поделать. Я не очень любли потоки. Предпочитаю пакеты. Но у меня для этого свои причны.
                >3 метра или 5 метров — это локально или уже удалённо?
                Тут все определяется стандартами. 5 метров для USB — допустимо, но кабель нужно очень качественный. Да и питание по нему подавать уже нежелательно. Вообще же граничные случаи на то и граничные, чтобы рассматривать их более внимательно и с учетом остальных факторов.
                • 0
                  Ну, у вас пост начинается с COM-порта и кабеля USB-to-COM… кстати, неработающего (это который блестючий), так что дальше невольно про COM помнишь…

                  >Ну нравится он людям, что тут поделать. Я не очень любли потоки. Предпочитаю пакеты.
                  а какие проблемы обмениваться пакетами по COM/UART? Я, честно говоря, тезиса «потоки vs пакеты» не понимаю.
                  • +1
                    Если я правильно понял автора, то он имеет в виду под «потоком» тот (довольно неприятный) факт, что из последовательного порта данные считываются в режиме «байт за байтом» в, условно говоря, бесконечном цикле. И проверка «пришел ли уже весь пакет данных или дальше ждать?» осуществляется в момент приема каждой порции. И порция может содержать данные и для двух (или более) пакетов, и надо реально байт за байтом в ней рыться…

                    А в случае пакетной передачи по USB ресивер просто получает сообщение «пакет пришел целиком».
                    • +2
                      Именно так. Это очень существенный недостаток потоков. Бесконечный цикл не обязателен — есть прерывания. Но это никак не упрощает задачу.
                      • 0
                        > И порция может содержать данные и для двух (или более) пакетов

                        позвольте, такая фигня происходит только в том экзотическом случае, когда наши люди вешают на один COM-порт несколько устройств, или в еще более экзотическом случае, когда «с другой стороны» вешают многозадачный PC с необсемафоренным обменом.
                        А во всех остальных случаях — создать пакет с заголовком, длиной, данными и контрольной суммой — плевое дело. А если участь аппаратные буферы на 16 байт и в PC и в МК и буфер драйвера в PC, да смешные по нынешним временам скорости обмена…
                      • +1
                        Выявление в потоке пакета — это дополнительная задача, требующая буферизации, выявления начала и конца пакета, проверки на целостность, введения доп. символов в поток для надежного определения заголовка. Все это надо программировать, это ест ОЗУ, ресурсы МК и время программиста. Если вы всего этого не делаете — обмен нельзя считать надежным. Пакетный обмен в USB все это делает прозрачно для программиста. Все данные уже разложены по нужны полям пакета, просто рассматриваешь полученный пакет как struct. Это ОЧЕНЬ удобно. При отправке то же самое — достаточно записать данные в поля структуры простым присваиванием и отправить буфер одной командой. Об остальном позаботится модуль USB. Причем буферы у него аппаратные.
                        Если вам это непонятно, то либо вы не делаете всего этого, что вообще-то странно, либо вам нравится каждый раз изобретать велосипед.
                        При работе с пакетами переменной длины в потоке нужно вводить какие-то идентификаторы длины пакета, выяснять считан ли весь пакет, не пропали ли байты и т.п.
                        При работе же с пакетами в USB достаточно определить какие поля нужны и какого размера. Это вся подготовка к работе.
                        • +1
                          >Выявление в потоке пакета — это дополнительная задача, требующая буферизации, выявления начала и конца пакета,
                          очень сложная задача, ага.

                          Сколько лет функции gets(), напомнить?
                          — Нет, я конечно понимаю, что вы имеете ввиду случай, когда много устройств сидят на одной шине, но это не случай COM/UART в стандартном виде.

                          > Все это надо программировать, это ест ОЗУ, ресурсы МК и время программиста.
                          насчет ресурсов и ОЗУ в МК — ровно наоборот, достаточно собрать пример «хеллоу ворлд» для UART и для USB.

                          >USB. Причем буферы у него аппаратные.
                          вы не поверите, у UARTов тоже
                          • 0
                            Нет, вы не понимаете. Речь вовсе не о том, что несколько устройств на одной шине.
                            Кроме того — текстовый протокол в обмене между устройствами я считаю глупостью. Я его все равно читать не буду, а для девайса экономнее и быстрее использовать двоичный протокол.
                            Большинство проектов для МК вообще не включают реализацию обработки строк. Если от устройства не требуется выводить текст для человека, то это лишний расход ОЗУ, flash памяти и процессорного времени.
                            К тому же алгоритм обработки текста всегда более неуклюж чем для бинарных данных.
                            Я никого не призываю поступать именно так. Нравится вам заниматься половину времени над проектом всякой посторонней работой, не влияющей на результат — на здоровье, пишите протоколы и обработку потоков.
                            Я их уже написал для себя, но все равно использовать не люблю — родная поддержка пакетного обмена куда как элегантнее. Точно так же как не люблю дрыганье ножками для реализации протоколов типа SPI, I2C, и программный UART. У них всегда есть неудобства и программные ограничения, которые рано или поздно вылезают боком.
                            • 0
                              Правильно ли я понимаю, что в случае пакетного подхода в USB HID размер пакета строго фиксирован?

                              Но ведь тогда, справедливости ради, и через последовательный порт при использовании фиксированного размера пакетов данные читать не сильно сложнее. Зато вот сделать пакет произвольного размера в случае USB HID не получится, в отличие от…
                              • 0
                                Пакета с конкретным ID да, но идентификаторов может быть сотни, на каждый тип используемого пакета. А можете задать пакет максимального используемого размера сделать поле с идентификатором своего типа пакета, который позволит рассматривать его как нужного формата структуру.
                                При использовании последовательного порта — сильно сложнее. Вообще говоря программа нелинейная, поэтому по приходу каждого байта нужно анализировать не только его, но и байты до него, уже находящиеся в буфере, а также вводить машину состояний — принимаем пакет, ждем пакет, ждем поле длины пакета, ждем контрольную сумму, ой, пропустили из-за того, что кабель выдрали и снова вставили или питание девайса моргнуло и теперь мы принимаем с середины пакета. Я в свое время занимался разработкой системы телеметрии и телеуправления Ташметрополитена и у меня было 13 географически распределенных стоек с оборудованием, связанных модемами USRCourier. Кабельные линиии были не лучшего качества, помехи, возможные потери байт, а тайминг на весь цикл опроса станций и ответа — не более 4с. Вместе с повторами.
                                Сейчас такое легко встречается при наличии помех по питанию, наводок от моторов рядом (управление станком ЧПУ, например) и т.п.
                                В академическом случае на столе все работает хорошо. Но протокол должен быть надежным и если часть этой работы можно выполнить аппаратно, лучше делать ее аппаратно. К тому же это проще и удобнее.
                                Кстати, комментарии для того и нужны, чтобы учиться друг у друга и обсуждать.
                                Если можете посоветовать набор библитек для пакетной связи через COM порт для Delphi + C/C++ на ПК + С/С++ на МК, чтобы обмен был бинарным, с задаваемым размером пакета и передать было достаточно указатель на буфер и размера буфера, остальное библиотека должна сделать сама. По приему пакета она должна вызвать мою функцию для обработки принятого пакета. Если будут функции, сигнализирующие об ошибках на линии — отлично.
                                • 0
                                  Увы, готовую библиотеку порекомендовать не могу. В тех проектах, где я данные через последовательный порт читал, логику на стороне хоста сам реализовывал (обычно — в отдельном треде, в последнее время — с помощью Qt и механизма сигналов/слотов). А на стороне MK другие люди данные готовили, уж и не знаю, как именно…
                                  • +1
                                    Вот именно, так обычно и бывает :) Библиотек много, а один фиг — вручную велосипедить.
                                  • 0
                                    Возможно, вам будет интересен протокол Modbus RTU.
                                    • 0
                                      Да, смотрел его. На мой взгляд его реализация немного геморройная — нужно мерять таймауты между символами, значит нужно кроме UART задействовать еще и таймер. А со стороны ПК вообще нет поддержки ни ModBus ни RS485. Поэтому применение — только в сети МК.
                                • 0
                                  ???

                                  >Кроме того — текстовый протокол в обмене между устройствами я считаю глупостью.

                                  Еще со времен модемов 1200 бод ээээ… дай бог памяти без MNP никаких проблем с нетекстовым обеном не было.

                                  >Большинство проектов для МК вообще не включают реализацию обработки строк. Если от устройства не требуется выводить текст для человека, то это лишний расход ОЗУ, flash памяти и процессорного времени

                                  еще раз повторю — вопрос расхда памятей решается сборкой проекта с использованием и неиспользованием и USB

                                  >Точно так же как не люблю дрыганье ножками для реализации протоколов типа SPI, I2C, и программный UART. У них всегда есть неудобства и программные ограничения, которые рано или поздно вылезают боком.

                                  оно давно уже аппаратное, в том же вашем STM32F103C8
                                  • 0
                                    Именно, поэтому и работа в пакетном режиме должна быть аппаратной, раз такая возможность есть. Если бы был модуль, который реализует всю грязную работу по пакетному обмену — я бы им пользовался. Для CAN и USB это делается средствами периферии аппаратно.
                                    Вообще я предвидел, что основной спор будет на тему — UART vs HID :) типа «чем вам не угодил простой UART».
                                    Так вот — каждый пользуется тем, что ему больше нравится. Я люблю комфорт и функциональность. Всем, кому это интересно, я предложил попробовать с минимальными усилиями. Если вам не нужно — пользуйтесь UART
                                    • 0
                                      >Вообще я предвидел, что основной спор будет на тему — UART vs HID :) типа «чем вам не угодил простой UART».

                                      А чего тут спорить? Ваши возражения против UART, (как и Ethernet) несколько не соответствуют реальности. Только и всего. Совершенно не в плане религиозных войн.
                                      Всякий интерфейс имеет свою область применимости. Вот у меня, например, в одном из проектов после дрыгания одной ногой через 6 мкс я с другой ноги считываю 10 байт, причем обе ноги — оптоизолированные. Могу ли я сделать что-то аналогичное с USB? Очевидно, что не могу, хотя бы потому что для USB задействовано больше ног ;-)
                                      • +1
                                        А причем тут USB? Ну получили вы свои 10 байт, а данные ПК как отдать? вот тут придет очередь USB. Пакуем их в пакет и отправляем. Если надо, предварительно обрабатываем. Для того МК и нужен.
                                        • 0
                                          Не, USB не наш выбор ;-)
                                          Или тот же ЦОМ-Порт, ибо так исторически сложилось, или эзернеты.
                                          По крайней мере для околопромышленного применения, где видео не надо передавать, а вот думателем думать надо достаточно серьёзно и поэтому полученные за 6 мкс 10 байт никуда передавать не нужно, а вот всякие опторазвязки от напряжений и наводок очень сильно желательно.

                                          Опять-таки — не в порядке религиозных войн, а только обмена опытом

                      • 0
                        Завязались в свое время на ethernet и в процессе отладки я попробовал через wifi цепляться к контроллеру, оказалось в целом жутко удобно и главное, что пропускной способности хватает и расстояние до девайса может быть значительным.
                • 0
                  Я вот почитываю всякие статьи, обычно дело заканчивается классом HID. Вот задумался над классом CDC, в частности виртуальным COM-портом. Причем главным требованием является работа без использования сторонних драйверов, сейчас на винде писать свой драйвер геморрой (требуется подпись). Может кто-нибудь даст ссылочку на реализацию CDC класса виртуального COM-порта с использованием стандартного драйвера usbser.sys? Или объяснит, что с HID классом работать проще и удобней?
                  • +2
                    Виртуальный комп порт имеет недостаток — он работает в таком же пакетном режиме, а не в потоковом, как реальный COM порт, соответсвенно те же скорости и задержки. Но ко всему прочему потребует драйвер скорее всего. Под стандартный драйвер не видел реализаций.
                    На мой взгляд с HID работать гораздо приятнее и удобнее — никаких номеров портов, каждый девайс можно автоматически подцеплять при подключении к компу. Не нужно сканить порты на появление и исчезновение, не нужно пытаться аккуратно закрыть порт при отключении девайса. Можно подключать столько прог к одному девайсу, сколько нужно. Девайс сам поставляет данные по мере их наличия (прерываниями заниматся USB шина) а не приходится опрашивать по таймеру, в итоге получается, что софтина на ПК занята только обработкой полезных данных и ровно тогда, когда они появляются, а не по таймеру, пытаясь угадать как же часто нужно опрашивать устройство, чтобы скорость реакции была приличная и нагрузка не слишком возросла.
                    На мой взгляд для связи ПК-МК последовательный порт крайне редко бывает оправдан.
                    Поэтому я никак не могу понять этой инерции мышления — любой интерфейс превращать в убогую трубу с байтами, даже когда возможностей куда больше.
                    • 0
                      Здесь вопрос стоит так: как получить максимальную пропускную способность, при использовании стандартных USB драйверов Windows.
                      • 0
                        К слову, (реальная) максимальная пропускная способность USB куда выше, чем у UART (тот же STM32F103, если не ошибаюсь, максимально разгоняет USART до 8 мегабит, и то, вряд ли терминал ПК протащит такую скорость, и провод нужно очень короткий и экранированный). Для USB гораздо меньше проблем в этом плане (хотя и STM32 не будет работать в High-speed, Full-speed с потолком в 12 Мб/с он протащит без особых проблем). Только это уже не HID-устройство будет, как автор сказал, а несколько другое.

                        Как мне кажется, UART очень хорошо использовать внутрисхемно (для соединения нескольких чипов) и для соединения с ПК какого-нибудь простого контроллера (типа AVR; хотя туда есть программный V-USB, не всегда разумно его использовать). Где можно использовать USB — почему бы и нет? Вопрос только в том, насколько часто ваше устройство должно связываться с ПК (мне очень часто приходится делать устройства, которые после однократной настройки по тому же UART уходят в «свободное плавание», там городить USB-стек смысла не вижу).
                        • +1
                          A USB это все тот же UART только с одним финтом — применено специальное кодирование чтобы снизить частоту на шине в двое по сравнению с тем же UART при аналогичной битовой скорости. Проблема классического UART лишь в том что источник тактовых импульсов для него должен быть в 8-16 раз больше скорости передачи, естественно эта частота берется с тактовых импульсов контроллера.
                          Ну и узкое место — это линия передачи если приделать к UART драйвер симметричной линии, то расстояние передачи будет таким же как у USB на аналогичной скорости. Просто USB это дальнейшее развитие UART на которое навешали столько наворотов, что он уже сам на себя не похож и это только в аппаратной части.
                  • 0
                    А еще COM порт не дает нужных показателей скорости. Кстати, а как там дела у usb-hid? Или в случае необходимости большой пропускной способности только ethernet остается?
                    • 0
                      В случае необходимости в большой скорости можно использовать другие профили USB. Скоростные параметры шины давно известны.
                      • 0
                        Но, если мне память не изменяет, то для других профилей нужен vendor id, который не так то просто получить.
                        • +1
                          VendorID нужен в любом случае, особенно если вы делаете коммерческое устройство. Но есть приличное количество VID и PID которые безопасно можно использовать. Главное, чтобы устройство с такими идентификаторами не было.
                        • 0
                          Вроде мегабит ограничение протокола, да?
                          • 0
                            У самой USB шины как минимум 12 мБит.
                            • 0
                              А у масс-трансфера HID — 1Мбит
                              • 0
                                Да. Но никто не заставляет использовать именно HID, если нужная действительно высокая скорость. Реально это нужно крайне редко, там можно и над драйвером поработать
                      • 0
                        Забавно читать такие статьи в стиле «Все просто! 1. рисуем два кружочка, 2. рисуем остаток совы»

                        Под потоковый обмен уже столько готового софта (причем, кроссплатформенного) написано, что это не проблема совершенно. А вот как ваши пример под линукс адаптировать — придется голову поломать. Жаль, что создатели USB не предусмотрели профиля «дуплексный асинхронный сокет», хотя архитектура именно под него заточена.
                        • +1
                          Остаток совы за вас уже нарисован :) Так что осталось именно два кружочка нарисовать. Хотите разобраться во всем самостоятельно — вам не в статью для начинающих, но проект тоже может быть полезен для изучения.
                          Под потоковый обмен написано всего много, но почему то как доходит до дела, выясняется, что либо слишком тяжелая библиотека, либо не компилируется совместно с другим софтом, либо не умеет чего-то, либо работает без DMA, а надо с ним, либо неудобна в работе, либо лицензия не та. Проходили. В итоге все равно приходится изобретать велосипед. Каждый раз.
                        • +1
                          Я вот собирал проект для F3-Discovery: github.com/dccharacter/AHRS
                          Там можно дефайнами переключить проброску данных либо через HID-Joystick, либо через CDC.
                          Там же питоновский визуалайзер лежит: github.com/dccharacter/AHRS/blob/master/myVisualisation.py
                          За код ногами не бейте…
                          • 0
                            Ок, понял, что немного не в тему.
                            • 0
                              Ну почему же. Может кому пригодится.
                              Кстати, с алгоритмами Мэджвика и Махони есть взаимопонимание?
                              • 0
                                В смысле? Есть ли понимание, как они работают? Неа :-) Но знаю где почитать общую теорию АХРС-а. Суть понятна, чтобы вникнуть в детали нужно неплохое знание математики и тригонометрии. У меня, к сожалению, с математикой как у первокурсника после первого семестра — так себе…
                                С кватернионами вроде чуть-чуть разобрался — вон написал визуалайзер, но потом опять все позабыл… Тоже знаю где почитать.
                        • 0
                          Вопрос, наверное, глупый, но можно ли используя класс USB-HID реализовать отправку данный о паре десятков энкодеров и паре десятков кнопок и переключателей?

                          Интересуюсь с целью самостоятельной сборки:
                          image
                          Только без дисплеев.
                          Возможно и:
                          image
                          Так же без приборов и дисплеев
                          • +1
                            Без проблем. А что именно пытаетесь сделать, виртуальную кабину самолета?
                            • 0
                              Идей две —
                              1. Стенд для ВУЗа и ПО имитирующее работу определенной системы самолета, например для демонстрации запуска и опробования двигателей. Элементы управления снаружи, приборы через проектор.
                              2. Настольный вариант панели, чтобы связать с симулятором (XPlane, MS FSX)
                              • 0
                                Для этой задачи подходит идеально.
                          • 0
                            Кто-нибудь встречал в продаже готовые дополнительные кнопки? Интересуют:
                            1. Отдельные большие кнопки (размером со спичечный коробок).
                            2. Переключатель (2-х, 3-х позиционный) чтобы на ощупь определялась позиция, желательно с программным переключением положения. (под переключатель раскладки клавиатуры)
                            3. Блоки по 4-5 кнопок в ряд небольшого размера (чтобы пальцами одной руки можно было нажимать)
                            • 0
                              Отдельные большие кнопки — точно бывают, обычно квадратные (такие ставят на всякие игровые автоматы).
                              Что имеется в виду под «программным переключением положения»? Переключатели бывают, движковые, например (на игрушках разных обычно ставят). У него позиция чётко видна, можно сверху более аккуратную ручку установить.
                              Вот про блоки кнопок — надо искать. Сейчас на ЧиДе посмотрю, там всё слишком дорого, конечно, но зато есть очень грамотный каталог, можно хотя бы представить, что бывает.
                              • 0
                                Под программным переключением я подразумеваю возможность задать положение кнопки из программы. Т. е. в кнопке должен быть какой-нибудь минимальный привод.
                                • 0
                                  Ой, вот это точно в дефиците. Впрочем, кнопок я пока тоже не нашёл соответствующих, но есть прямая отсылка на китайских поставщиков типа dealextreme или satistronics.

                                  Насчёт «программно переключаемого свитча» я бы предложил какое-нибудь touch-решение с led-индикацией, но для этого нужно либо самому разбираться, либо призывать радиолюбителей, кто с этим работал. Тут не помешает навык работы с МК, и всё зависит от того, куда всё это добро нужно подключать.
                            • 0
                              Возможно, кто-нибудь сможет подсказать, в каком документе можно узреть значения всех полей HID-дескриптора? В официальной документации я утонул, но ничего вразумительного не нашел.

                              Я пытаюсь сгенерировать самый тупой дескриптор для передачи 64 байт, но я никак не могу понять, какие поля нужно указывать? Нужно ли указывать и REPORT_SIZE и REPORT_COUNT? LOCAL_MAXIMUM — это максимум для одного байта или для всех?
                              • 0
                                Разумеется, все нужно указывать. Возьми за основу мой проект — там есть переменные
                                #define RPT3_COUNT 0x01 //PC->STM32
                                #define RPT4_COUNT 0x04 //STM32->PC

                                Они прописаны в дескрипторе.
                                • 0
                                  Я, к сожалению, не могу прямо взять ваш проект, у меня плата с STMF407. Но вы же дескриптор не из пальца высосали? Поделитесь, чем вы пользовались, чтобы его составить, пожалуйста.
                                  • 0
                                    Дескриптор для любой платы одинаковый.
                                    Читайте внимательно, я выложил все инструменты.
                                    Вам нужен DT — HID Descriptor tool
                                    • 0
                                      Да, как-то не подумал, что можно только дескриптор взять. Спасибо.
                                      HID Descriptor tool облегчает дело, спору нет, но из нее совершенно непонятно, какое поле что означает.
                                      • 0
                                        Это основная проблема usb вообще. Информации в доступном виде нет, сама шина очень сложна. Те описания, что есть- фрагментарны или очень запутаны. Поэтому я и предложил проект-шаблон.
                                        • 0
                                          То есть вы его по кусочкам собирали? Жаль, жаль.
                                          • 0
                                            Да, собирал по частям. Единого документа не встречал.
                                            • 0
                                              Интересно, как интерфейс с такой отвратительной спецификацией стал таким популярным. Жуть.
                                              • 0
                                                Классика же, сначала садим на иглу а потом все оказываются должны и отказаться нет возможности.
                                                • 0
                                                  Спецификация отвратительная, зато возможности очень обширные, стандарт живет уже много лет и сохраняет обратную совместимость, аналогов у него с таким функционалом просто нет.
                                                  • 0
                                                    к эзернету только питание прикрутить на разъеме и новый стек при необходимости — получилось бы не хуже(при необходимости обеспечивать расстояние передачи больше 10м и можно было бы легко поднять скорость).
                                                    • 0
                                                      Если навесить на Ethernet функционал usb, автоопределение, автоконфигурацию, то usb и получится, только с неудобным разъемом.
                                      • 0
                                        (deleted)
                                      • 0
                                        А вы не поделитесь примером для stmf407?
                                        • 0
                                          Вот. Только пример custom hid я тут уже разворошил, а где оригинал брал — не помню.
                                          • 0
                                            Спасибо. Попробуем выковырять
                                          • 0
                                            Не поделюсь :) Потому что не писал для F407 этого примера. А писать с нуля вам столько же сколько и мне. Принцип тот же.
                                            • 0
                                              Уже выложили для discovery
                                              А так у меня сейчас даже родные примеры от st не заводятся.
                                              В системе не появляться новое usb устройство
                                              • 0
                                                У меня заводятся, но там есть косяк, который пока руки не доходят исправить — надо отлаживать.
                                                • 0
                                                  У меня вот эти stm32_f105-07_f2_f4_usb-host-device_lib.zip

                                                  Выбираю свою версию, собираю, зашиваю и понимаю что устройства в системе нет.
                                                  Нужно править все, т.к. эта версия не под discovery, LED я нашел, но сходу смог включить только 2 из 4. И уперся для начала в это. Хотя выставил все параметры для LED одинаковые, кроме номера ножки GPIO (PD12-PD15)
                                                  код
                                                  #define LEDn                             4
                                                   
                                                  #define LED1_PIN                         GPIO_Pin_12
                                                  #define LED1_GPIO_PORT                   GPIOD
                                                  #define LED1_GPIO_CLK                    RCC_AHB1Periph_GPIOD  
                                                   
                                                  #define LED2_PIN                         GPIO_Pin_13
                                                  #define LED2_GPIO_PORT                   GPIOD
                                                  #define LED2_GPIO_CLK                    RCC_AHB1Periph_GPIOD  
                                                   
                                                  #define LED3_PIN                         GPIO_Pin_14
                                                  #define LED3_GPIO_PORT                   GPIOD
                                                  #define LED3_GPIO_CLK                    RCC_AHB1Periph_GPIOD  
                                                   
                                                  #define LED4_PIN                         GPIO_Pin_15
                                                  #define LED4_GPIO_PORT                   GPIOD
                                                  #define LED4_GPIO_CLK                    RCC_AHB1Periph_GPIOD

                                    • 0
                                      Кстати, платка исходно фирмы LC-Tech
                                      www.lctech-inc.com/Hardware/
                                      У них много всего вкусного, но к сожалению китайские (пере-)продавцы
                                      торгуют в основном только их самой дешовой платкой STM32F103C8T6.
                                      • 0
                                        Да, к сожалению, есть сложившиеся популярные платы и камни, не всегда самые правильные, но они сильно дешевле всех остальных из-за массовости.
                                        • 0
                                          Можно перепаять чип F1xx на более мощный, они совместимы по ножкам :)

                                          Еще мне кажется полезно указать источник софта STSW-STM32121
                                          STM32F10x, STM32L1xx and STM32F3xx USB full speed device library (UM0424)
                                          www.st.com/web/en/catalog/tools/PF258157
                                          Там кстати внутри самая новейшая либа StdPeriph аж версии V3.6.1
                                          • 0
                                            Кстати — новое не значит лучше. У меня например с новой версией SPL отказался работать LCD на ILI9xxx серии — FSMC некорректно инициализируется и все тут. Пока не оставил этот модуль от старой библиотеки, экран оставался пустой.
                                            • 0
                                              Забавно. Может поменяли формат вызова (добавли/сменили параметры) или может быть ошибка. В любом случае, лучше сделать багрепорт в STM.
                                              • 0
                                                Честно говоря понятия не имею куда его направлять. К тому же не факт, что проблема не в LCD контроллере.
                                      • 0
                                        У кого-нибудь получалось запустить на STM32F103 USB от внутреннего генератора (HSI)? Интрересует, возможно ли это в принципе, или можно не пытаться, даже на low speed? В datasheet сказано, что для использования USB должен быть включен PLL и HSE, но судя по схеме системы тактирования, PLL может тактироваться также и от HSI, хоть частота и не будет строго 48 МГц. У меня в тестовом проекте PLL судя по коду запускается на частоте ± 48 МГц, но при подключении к компу даже нет виндового звука подключения нового устройства к USB, который, как я понимаю, должен быть сразу после подключения, еще до начала опроса дескрипторов и выбора конфигурации устройства. Вот думаю, проблема ли у меня в прошивке или в самой плате где-то закралась… У кого есть заведомо рабочая плата с STM32F103, отпишитесь, если вдруг получится запустить USB от HSI, или хотя бы получить звук подключения устройства :)
                                        • 0
                                          USB — только с кварцем. внутренний генератор не обладает достаточной точностью чтобы поддержать работу по USB — где-то сразу происходит сбой из-за несоответствия скорости и устройство игнорируется.
                                          • 0
                                            Я тоже так думал, но в русскоязычном интернете нашел одно упоминание, что якобы у кого-то получилось запустить от внутреннего генератора — тут, поэтому решил уточнить, а то мало ли, может в datasheet-е пишут, что оно не работает, а реально работает, хотя бы на Low Speed, которого мне было бы достаточно. С отсутствием реакции на подключение устройства разобрался — у меня не было подтягивающего резистора, т.к. когда я проектировал плату, то смотрел, к своему сожалению, в основном сюда, а там нету ни резистора, ни упоминания о том, что для USB требуется внешний кварц, поэтому я подумал что в контроллере есть внутренний подтягивающий резистор. Как выяснилось, в некоторых контроллерах STM32 он действительно есть, но не в моем STM32F103.

                                            Теперь при подключении винда пишет, что подключенное устройство не опознано (сбой запроса дескриптора устройства). Подозреваю, что это из-за несоответствия частоты — попозже попробую воткнуть кварцевый генератор.

                                            Интересно, что, например, софтверная библиотека V-USB для AVR может работать от 12.8 МГц встроенного RC-генератора, видимо стандарт USB 1.1 не так прихотлив к точности может работать на более низкой частоте…
                                            • 0
                                              это называется просто везёт. На некоторых экземплярах будет работать на других нет. Чуть нагрелся и отвалился от ухода частоты… это никуда не годится!

                                              Можно конечно калибровать генератор(есть ли такая функция?) но опять же нужна опорная частота для калибровки, хотябы часовой кварц. Или слушать USB-шину и по импульсам опроса калибровать генератор, а дальше поехали…
                                              Но эти усилия по преодолению проблем которые создаём сами себе не стоят отказа от кварцевого резонатора.
                                              • 0
                                                Согласен, что никуда не годится. Таил надежду, что оно может относительно стабильно работать без кварца, т.к. изначально по причине невнимательного чтения документации не предусмотрел его в схеме, и воткнуть кварц в уже разведенную плату, конечно, сложнее :) Теперь, видимо, все-таки придется производить хирургическое вмешательство.
                                                • 0
                                                  есть плоские кварцы в SMD-исполнении.
                                              • 0
                                                По поводу V-USB, там есть спец функция которая калибрует встроенный RC-генератор как раз по USB посылкам от компа.
                                                osccal.c
                                                /* Функция калибрует RC-генератор. Наш опорный источник точного времени -
                                                 *  сигнал SOF, Start Of Frame (один бит SE0), повторяющийся каждую милисекунду
                                                 *  сразу после состояния USB RESET. Мы сначала делаем двоичный поиск величины
                                                 *  OSCCAL, и затем оптимизируем эту величину поиском соседних значений.
                                                 *  Этот алгоритм может также использоваться для калибровки RC-генератора напрямую
                                                 *  до 12 MHz (не вовлекается ФАПЧ, таким образом эту технику можно использовать
                                                 *  почти на ВСЕХ AVR), но это слишком вне спецификации величины OSCCAL и необходимой
                                                 *  точности для тактов на 12 МГц! Используйте RC-генератор, калиброванный на 12 МГц
                                                 *  только для экспериментов!
                                                 */
                                                


                                                pastebin.com/7geg5rXU

                                                А потом калибровочные данные сохраняет в еепром, и при загрузки извлекает их из еепрома.
                                                • 0
                                                  не покатит. рекалибровку надо делать постоянно. Частота плывёт от температуры и напряжения питания(стабилизатор нагревается и напряжение изменяется в процессе работы).
                                                  • 0
                                                    Так она делается по reset'у usb
                                                    У нормального стабилизатора параметры не текут =)
                                                    Только если вы в -40 подключаете USB девайс, то да за время работы он прогреется =)
                                                    А так на моей практике (100+ V-USB устройств на AtTinyX5 без кварца) ни одного проблемного не было.
                                                    Были разные девайсы, и которые годами данные снимают к роутеру подключенные, и постоянно перетыкаемые. Все работает в лучшем виде.

                                                    osccal.h
                                                    /* Имя: osccal.h
                                                     * Автор: Christian Starkjohann
                                                     * Перевод: microsin.ru 
                                                     * Дата создания: 2008-04-10
                                                     * Табуляция: 4
                                                     * Copyright: (c) 2008 by OBJECTIVE DEVELOPMENT Software GmbH
                                                     * Лицензия: GNU GPL v2 (см. License.txt) или проприетарная (CommercialLicense.txt)
                                                     * Ревизия: $Id: osccal.h 553 2008-04-17 19:00:20Z cs $
                                                     */
                                                    
                                                    /*
                                                    Основное описание:
                                                    Этот модуль содержит функцию, которая калибрует внутренний RC-генератор AVR,
                                                    чтобы CPU работал на частоте F_CPU (F_CPU является макропределением, которое 
                                                    должно быть задано при компиляции модуля. Лучший выбор - передача этого макро
                                                    через командную строку компилятора). Эталон времени - частота фрейма USB 
                                                    1 кГц, доступная немедленно после состояния USB RESET. Отсчет времени производится
                                                    путем подсчета циклов CPU, при этом все прерывания должны быть запрещены, пока работает
                                                    калибровочная функция. Для измерений времени низкого уровня вызывается функция 
                                                    usbMeasureFrameLength(). Эта функция должна быть разрешена в usbconfig.h 
                                                    путем задания USB_CFG_HAVE_MEASURE_FRAME_LENGTH в 1.
                                                    
                                                    Используемый алгоритм:
                                                    calibrateOscillator() сначала делает двоичный поиск в регистре OSCCAL для 
                                                    наилучшего совпадения с частотой тактового генератора. Затем она делает поиск
                                                    следующей соседней (частоты?) для нахождения наименьшей девиации тактовой 
                                                    частоты. Это гарантирует наилучшее соответствие между соседними величинами,
                                                    но для генераторов версии 5 (у которых прерывающаяся взаимосвязь между
                                                    OSCCAL и частотой) лучшее соответствие может быть доступно в другом диапазоне
                                                    OSCCAL.
                                                    
                                                    Ограничения:
                                                    Этот алгоритм калибровки может пробовать величины OSCCAL до 192, даже если
                                                    оптимальная величина далеко меньше 192. Таким образом, может быть превышена 
                                                    допустимая тактовая частота CPU в разработках с низковольтным питанием!
                                                    Точность зависит от OSCCAL простив частотной зависимости генератора.
                                                    Типичная точность для ATMega168 (вычисленная от OSCCAL против F_RC диаграмма
                                                    в даташите) должна быть в диапазоне 0.4%. Только версия 16.5 МГц AVR-USB 
                                                    (которая использует ФАПЧ в приемнике данных) может допускать такую девиацию!
                                                    Все другие частотные модули требуют точности 0.3%.
                                                    */
                                                    
                                                    #ifndef __OSCCAL_H_INCLUDED__
                                                    #define __OSCCAL_H_INCLUDED__
                                                    
                                                    void calibrateOscillator(void);
                                                    /* Эта функция калибрует генератор RC, чтобы CPU работал на частоте F_CPU.
                                                     * Она ДОЛЖНА быть вызвана немедленно после окончания состояния USB RESET!
                                                     * Запретите все прерывания во время вызова! Рекомендуется сохранить 
                                                     * результирующую величину в EEPROM так, чтобы хорошее предполагаемое 
                                                     * значение было доступно после следующего сброса.
                                                     */
                                                    
                                                    void    usbEventResetReady(void);
                                                    
                                                    #endif /* __OSCCAL_H_INCLUDED__ */
                                                    


                                                    Вот конкретно мой девайс который как только через *опу не собирали, а он прекрасно работает. =)
                                                  • 0
                                                    Все оказалось проще — у меня подтягивающий резистор был припаян к D-, как предписывает спецификация для Low-Speed устройств. Перепаял резистор на D+, и теперь всё работает, как с внешним кристаллом, так и без него — от внутреннего генератора :)
                                              • 0

                                                Извиняюсь за некропостинг. У ST есть некоторое количество чипов, поддерживающих crystal-less режим, они аппаратно калибруют генератор по SOF-пакету. Из тех, что знаю — это серии STM32F0x2 и STM32F0x8.

                                              • –1
                                                интересно почему устройство не подключается в линаксе (по виндой работает)

                                                $ dmesg
                                                [ 433.425184] hub 4-1:1.0: connect-debounce failed, port 6 disabled
                                                [ 435.731027] hub 4-1:1.0: connect-debounce failed, port 6 disabled
                                                [ 438.037839] hub 4-1:1.0: connect-debounce failed, port 6 disabled
                                                [ 440.344716] hub 4-1:1.0: connect-debounce failed, port 6 disabled
                                                [ 454.185890] hub 4-1:1.0: connect-debounce failed, port 6 disabled
                                                [ 456.492745] hub 4-1:1.0: connect-debounce failed, port 6 disabled
                                                [ 458.799587] hub 4-1:1.0: connect-debounce failed, port 6 disabled
                                                • 0
                                                  У меня есть вопрос к автору, и боюсь, это совсем глупый вопрос.

                                                  Купил именно такую плату. Подключаю к компьютеру, и получаю сообщение Windows, что устройство опознать не удалось, и вероятно, оно работает неправильно. Дискавери тот честно говорит, «я — st-link», а как должен рапортовать этот девайс?

                                                  И что, собственно мне, как совсем зелёному новичку в микроконтроллерах, теперь со всем этим делать?
                                                  • 0
                                                    Разумеется. Этот USB не для программирования платы, а штатный вывод USB модуля периферии микроконтроллера.
                                                    Вы можете сконфигурировать устройство как вам захочется — эмулировать клавиатуру, мышь, флешку, Custom HID устройство или любое другое. но для этого нужно писать программу.
                                                    Пустой МК не будет никак опознаваться, потому что он вообще не отвечает компьютеру на запросы по шине USB, а модуль USB в нем просто выключен.
                                                    Уровень зелени новичков бывает сильно разный. Если работали с Arduino и имеете понятие о программировании МК — будет проще. Можно посоветовать CooCox, например, для старта. (В CooCox просто некоторые вещи новичку сделать будет проще, а не потому, что среда хорошая.) А потом выберете себе среду получше на свой вкус. Я использую EmBlocks.
                                                    Немного статей по STM32 можно найти у меня на сайте по тегу STM32.
                                                    Там есть моргание светодиодом, небольшое описание работы с IDE Emblocks (разных версий), устройство, работающее с USB, прошивка через USART.
                                                    Порог входа в STM32 немного выше, чем в AtmegaXX8, но просто потому, что МК намного более нафаршированный, можно настроить практически все на свете, куча полезных фич и режимов, которые я, к примеру, даже наполовину не знаю досконально. Но все их знать и не обязательно. Вполне можно разрабатывать качественные и надежные устройства и обучаться постепенно.
                                                  • 0
                                                    Спасибо за статью!
                                                    Скажите, а нет ли ссылки на документацию по библиотеке USB входящей в состав STM32CubeF1?
                                                    Дело в том, что там по пути STM32Cube_FW_F1\Middlewares\ST\STM32_USB_Device_Library расположена библиотека, которая судя по сообщению на сайте, заменяет USB-FS. ST нас призывает использовать куб для новых проектов. Для куба есть примеры(мышь я успешно запустил даже), но дело в том, что для USB-FS есть подробное описание UM0424, а для библиотеки входящей в состав куба я ничего подобного не нашел.
                                                    Это очень странно конечно, но факт. Может быть кто поможет?
                                                    • 0
                                                      Призывать то призывает и куб вроде неплох, но интегрировать его в простые проекты очень сложно. Он рассчитан на RAD разработку, без оптимизации по размеру кода, нет генератора для Emblocks например, импортировать проект, созданный для keil не получается — очень много ошибок. Документации и опыта по Cube тоже кот наплакал пока. Вот они и пытаются создать сообщество, подталкивая к использованию инструмента.
                                                      Но SPL как работала так и работает и вполне подходит.
                                                      • 0
                                                        Немножечко там с оптимизацией проблемы есть, я согласен. Но вполне себе годится. У меня сейчас проектик на stm32f103c8t. usb custom hid, таймеры, ADC, DMA, прерывания, GPIO естественно и конфигурирование тактирования — все с использованием куба. Кода самого куба получилось где-то килобайт 15. Из них около 10 приходится на ЮСБ стек. Так что можно себя нормально чувствовать и на 64кб камнях. Документация есть на все кроме USB почему-то.

                                                        Генератора на емблокс может и нет, но есть cube mx, который может сгенерить проект потыкав мышкой.

                                                        Я использую gcc, slickedit как редактор и makefile. Немного хардкорно, но все работает:)
                                                    • 0
                                                      А можете показать исходники HIDSTM32.exe?
                                                      • 0
                                                        Пытаюсь заставить код работать на контроллере STM32F103RCT6 (отладочная плата Port103R).
                                                        Но получаю только «Сбой запроса дескриптора USB-устройства».
                                                        Есть предположения, почему так может быть?
                                                        • 0
                                                          На точно такой же отладочной плате как у автора (тоже с STM32F103C8T6) – такая же ошибка. Попробовал даже делать всё, как автор написал: установил EmBlocks и скомпилировал в точности код из репозитория. Ничего не помогает.
                                                          Как задебажить?

                                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.