Работа с USB HID в Python на примере STM32
Чаще всего для передачи данных между компьютером и каким-либо устройством на микроконтроллере используют старый добрый Serial интерфейс (он же UART/USART, он же COM-порт). Но есть ещё один удобный протокол для обмена информации - USB HID. Для него не требуется установка драйверов в большинстве современных операционных систем, нет необходимости настройки номера порта (как в случае с Serial) - обращение к устройству происходит по VendorId и ProductId, которые прописываются в самом устройстве. А передача данных облегчается за счёт пакетной передачи данных - нет необходимости вручную «отлавливать» начало и конец передачи в потоке данных.
Рассмотрим обмен данными между программой на Python и микроконтроллером STM32, который имеет полноценный USB-интерфейс и может «прикидываться» практически любым устройством.
STM32
Для теста я взял свою отладочную плату на базе STM32F103VET6, на которой уже есть всё необходимое:
Сделаем управление светодиодами, которые расположены на плате:
Подтяжка линии D+ USB шины включается с помощью управления выводом PC13:
Код я написал в Arduino IDE:
- usb-hid-stm32.ino
/* Arduino IDE 2.3.6 STM32F1xx/GD32F1xx board v2022.9.26 Chip: STM32F103VET6 */ #include <USBComposite.h> // Идентификаторы устройства #define VENDORID 0x0005 #define PRODUCTID 0x0001 #define SERIALSTRING "0001" #define MANUFACTURER "Corp" #define PRODUCT "ProductName" // Пины светодиодов #define LED1_PIN PC6 #define LED2_PIN PC7 #define LED3_PIN PD13 #define LED4_PIN PD6 // Управление подтяжкой USB #define USB_PULLUP_PIN PC13 // Размеры пакетов данных #define TXSIZE 64 #define RXSIZE 64 uint8_t LED[4] = {LED1_PIN, LED2_PIN, LED3_PIN, LED4_PIN}; USBHID HID; HIDRaw<TXSIZE,RXSIZE> raw(HID); uint8 rxbuf[RXSIZE]; uint8 txbuf[TXSIZE]; const uint8_t reportDescription[] = { HID_RAW_REPORT_DESCRIPTOR(TXSIZE,RXSIZE) }; void setup() { pinMode(LED1_PIN, OUTPUT); pinMode(LED2_PIN, OUTPUT); pinMode(LED3_PIN, OUTPUT); pinMode(LED4_PIN, OUTPUT); pinMode(USB_PULLUP_PIN, OUTPUT); digitalWrite(USB_PULLUP_PIN, LOW); USBComposite.setManufacturerString(MANUFACTURER); USBComposite.setProductString(PRODUCT); USBComposite.setVendorId(VENDORID); USBComposite.setProductId(PRODUCTID); USBComposite.setSerialString(SERIALSTRING); HID.begin(reportDescription, sizeof(reportDescription)); // Здесь программа будет ждать подключения компьютера. Можно закомментировать. while (!USBComposite); raw.begin(); } void loop() { if (raw.getOutput(rxbuf)) // Если получен пакет данных { for (int i=0; i<4; i++) { if (rxbuf[i] > 0) digitalWrite(LED[i], HIGH); else digitalWrite(LED[i], LOW); } for (int i=0;i<TXSIZE;i++) txbuf[i] = i; raw.send(txbuf, TXSIZE); // Отправляем ответ } }
Python
Устанавливаем библиотеку hidapi:
pip install hidapi==0.15.0
- main.py
import hid import time class HIDDevice: def __init__(self, vendor_id=None, product_id=None, path=None): """ Инициализация HID-устройства. Можно указать vendor_id и product_id или сразу path устройства. """ self.vendor_id = vendor_id self.product_id = product_id self.path = path self.device = None self.is_opened = False def find_device(self): """Поиск HID-устройства по VID и PID или по пути""" devices = hid.enumerate(self.vendor_id, self.product_id) if self.vendor_id and self.product_id else hid.enumerate() if self.path: device_info = next((dev for dev in devices if dev['path'] == self.path.encode()), None) elif self.vendor_id and self.product_id: device_info = next((dev for dev in devices if dev['vendor_id'] == self.vendor_id and dev['product_id'] == self.product_id), None) else: device_info = None if device_info: print(f"Найдено устройство: {device_info['product_string']}") return device_info else: print("Устройство не найдено.") return None def open(self): """Открытие HID-устройства""" if self.is_opened and self.device: print("Устройство уже открыто.") return True device_info = self.find_device() if not device_info: return False try: self.device = hid.device() self.device.open_path(device_info['path']) self.is_opened = True print(f"Устройство открыто: {device_info['product_string']}") return True except Exception as e: print(f"Ошибка при открытии устройства: {e}") return False def close(self): """Закрытие HID-устройства""" if self.device and self.is_opened: self.device.close() self.is_opened = False print("Устройство закрыто.") def write(self, data): """Отправка данных в HID-устройство (Output Report)""" if not self.is_opened: print("Устройство не открыто.") return False try: sent = self.device.write(data) print(f"Отправлено {sent} байт: {data}") return sent except Exception as e: print(f"Ошибка при отправке: {e}") return 0 def read(self, size=64, timeout_ms=None): """Чтение данных из HID-устройства (Input Report)""" if not self.is_opened: print("Устройство не открыто.") return None try: if timeout_ms is not None: data = self.device.read(size, timeout_ms) else: data = self.device.read(size) if data: print(f"Получены данные: {data}") return data except Exception as e: print(f"Ошибка при чтении: {e}") return None def get_serial_number(self): """Получение серийного номера устройства""" if self.is_opened: return self.device.get_serial_number_string() return None def get_manufacturer(self): """Получение производителя устройства""" if self.is_opened: return self.device.get_manufacturer_string() return None def get_product(self): """Получение названия продукта""" if self.is_opened: return self.device.get_product_string() return None def main(): device = HIDDevice(vendor_id=0x0005, product_id=0x0001) if device.open(): print("Производитель: ", device.get_manufacturer()) print("Продукт: ", device.get_product()) print("Серийный номер: ", device.get_serial_number()) # Отправка данных device.write([0x00, 0x01, 0x00, 0x00, 0x00]) time.sleep(1) device.write([0x00, 0x00, 0x01, 0x00, 0x00]) time.sleep(1) device.write([0x00, 0x00, 0x00, 0x01, 0x00]) time.sleep(1) device.write([0x00, 0x00, 0x00, 0x00, 0x01]) time.sleep(1) device.write([0x00, 0x01, 0x01, 0x01, 0x01]) # Чтение данных data = device.read(64, timeout_ms=1000) # таймаут 1 секунда if data: print("Прочитано:", data) device.close() if __name__ == '__main__': main()
Найдено устройство: ProductName Устройство открыто: ProductName Производитель: Corp Продукт: ProductName Серийный номер: 0001 Отправлено 65 байт: [0, 1, 0, 0, 0] Отправлено 65 байт: [0, 0, 1, 0, 0] Отправлено 65 байт: [0, 0, 0, 1, 0] Отправлено 65 байт: [0, 0, 0, 0, 1] Отправлено 65 байт: [0, 1, 1, 1, 1] Получены данные: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63] Прочитано: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63] Устройство закрыто.

