STM32 та USB-HID

На подвір'ї 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 - досить великий клас пристроїв, тому перш за все доведеться вибрати який саме пристрій ми будемо створювати. Ми можемо створити емуляцію клавіатури, миші, джойстика та інших пристроїв введення, а можемо створити свій пристрій, щоб не залежати від досить жорстких рамок стандарту та вільно обмінюватись даними з ПК. Я розповім як зробитиCustom HIDdevice. Це дає максимальну свободу. Щоб не затягувати статтю, постараюсь розповісти максимально коротко — описів стандарту в мережі і без мене багато, але мені вони слабо допомогли, коли знадобилося вирішити конкретне завдання.

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

Я використовую EmBlocks для розробки під STM32. Ви можете використовувати будь-яке зручне середовище, проект не дуже складно адаптувати. До базової структури проекту додано:

Всі ці файли ми додаємо до будь-якого проекту, який використовує USB.

Ініціалізація USB

Для коректної роботи USB модуляважлива частота роботи МК. Не всі частоти дозволяють правильно задати тактування USB. У нашому випадку використовується кварцовий генератор на 8МГц та МК працює на частоті 72МГц, а USB модуль на 48МГц. У main.c достатньо включити лише кілька рядків коду

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

USB-HID девайс неспроможна сам ініціювати передачу, т.к. Координацією шини займається host пристрій - ПК. Тому при підготовці USB дескриптора нашого пристрою ми пишемо, як часто потрібно опитувати наш пристрій. За специфікацією максимальна частота опитування - 1кГц і максимальний розмір пакета, що передається за один раз, - 64 байти. Якщо цього недостатньо – доведеться використовувати інші режими роботи – на зразок USB bulk, але там уже без драйверів не обійтися. За налаштування взаємодії з ПК відповідають 3 дескриптори:

Це найважливіший дескриптор - він описує протокол обміну та функціонал пристрою. Його формування - не найпростіше завдання. Якщо припуститися помилки при формуванні дескриптора - пристрій перестане працювати. Формат дескриптора дуже твердий. Є навіть спеціальна утиліта 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_ > Якщо ви не до кінця розібралися в тому, як формувати дескриптор репортів, просто змінюйте константи RPT3_COUNT і RPT4_COUNT, встановлюючи потрібний розмір вихідних і вхідних (з точки зору ПК) пакетів. Решту репортів можна просто не чіпати, вони не завадять. Не забувайте, що першим байтом має бути ID репорту.

Цикл обміну

Отже, ми налаштували наш пристрій, встановивши PID, VID, номер версії, налаштували розміри вхідних та вихідних пакетів та готові до роботи. Кожні 32мс, як ми і просили в дескрипторі конфігурації, хост буде опитувати нас і в функції RHIDCheckState ми перевіряємо - якщо у нас є, що відправити, то формуємо пакет даних для хоста.

Масив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

Тут ми перевіряємо перший байт у репорті і відповідно до нього обробляємо залишок пакету - керуємо світлодіодами або просто беремо байт, відправлений нам хостом і кладемо в пакет для подальшого відправлення назад у функції RHIDCheckState. Під Report_Buf зарезервовано wMaxPacketSize байт, щоб вліз будь-який пакет, який нам відправить хост.

Дані, надіслані методом SET_REPORT, обробляються в usb_endp.c

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

Налаштовувати пристрій, передавати та приймати дані в пакетах потрібного розміру з потрібною періодичністю ми навчилися. Збираємо проект і прошиваємо у пристрій. Працювати, це буде приблизно так:

Проект підтримує взаємодію з утилітою USB HID Demonstrator від STMicroelectronics. Сторінка Device capabilities відображає можливості, описані в Report Descriptor. Input/Output transfer дозволяє вручну надсилати дані девайсу і подивитися пакет, який від нього надходить. Graphic view дозволяє керувати світлодіодами, чекбоксами Led 1, Led 2, налаштувавши відповідний їм Report >

Також я написав маленьку демо-софтинку, яка автоматично визначає підключення до комп'ютера і відключення нашого девайса за його VID і PID, відображає статус - підключено/відключено індикатором поряд з чекбосом Auto Connect РадіокнокаSend usingдозволяє вибрати метод відправки даних девайсу.Report:відображає отриманий від девайса пакет побайтно, починаючи з ReportID. Клацнувши по світлодіодах нижче - керуємо світлодіодами девайса. Їхній стан відображає поточний стан девайса. Зчитується з репорту від девайсу. Переміщаючи повзунок, ми відправляємо Report з > У комбобоксі, що випадає, відображаються HID девайси, знайдені в системі і якщо знайдено наш девайс, то відображається його назва.

Завантажити все, що потрібно, можна на GitHub. У складі:DT- HID Descriptor tooltstHID-STM32F103- проект для EmBlocksUSB HID Demonstrator- утиліта від ST MicroelectronicsHIDSTM32.exe— моя демо-софтинка на Delphi аналогічного фукціонала, але не потребує налаштування