python:rabota_s_usb_hid_v_python_na_primere_stm32

Работа с USB HID в Python на примере STM32

FIXME

Чаще всего для передачи данных между компьютером и каким-либо устройством на микроконтроллере используют старый добрый Serial интерфейс (он же UART/USART, он же COM-порт). Но есть ещё один удобный протокол для обмена информации - USB HID. Для него не требуется установка драйверов в большинстве современных операционных систем, нет необходимости настройки номера порта (как в случае с Serial) - обращение к устройству происходит по VendorId и ProductId, которые прописываются в самом устройстве. А передача данных облегчается за счёт пакетной передачи данных - нет необходимости вручную «отлавливать» начало и конец передачи в потоке данных.

Рассмотрим обмен данными между программой на Python и микроконтроллером STM32, который имеет полноценный USB-интерфейс и может «прикидываться» практически любым устройством.

Для теста я взял свою отладочную плату на базе 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);  // Отправляем ответ
  }
}

Устанавливаем библиотеку 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]
Устройство закрыто.
  • python/rabota_s_usb_hid_v_python_na_primere_stm32.txt
  • Последнее изменение: 12.03.2026 23:23
  • r0wbh