Delphi – komunikacja USB HID

Wstęp

W tym artykule pokażę aplikacje na PC, która komunikuje się z urządzeniem przez USB w klasie HID.
W systemach embedded występują 2 główne sposoby komunikacji przez USB pomiędzy mikrokontolerem a komputerem:
– za pomocą klasy CDC (Comunication Device Class) – tz. wirtualny port szeregowy
– za pomocą klasy HID ( Human Interface Device) – klasa, która jest wykorzystywana w komunikacji klawiatura, myszka, gamepad…

Używanie wirtualnego portu szeregowego

Powiedzmy, że chcemy skomunikować się za pomocą wirtualnego portu szeregowego, naszą aplikacje na PC a jakimś urządzeniem w którym zaimplementowaną wirtualny port szeregowy ( odpowiedni driver lub implementacja w mikrokontrolerze). Pierwszym krokiem będzie zainstalowanie sterowników. W zależności od producenta mikrokontrolera ( w którym zaimplementowane klasę CDC ) lub drivera (np FT232, CP210x.. ) musimy zainstalować różne sterowniki. Jeśli komputer poprawnie wykryje port COM, to w aplikacji możemy skonfigurować parametry transmisji: numer portu COM – który będzie może być różny na różnych komputerach, prędkość transmisji, ilość bitów, parzystość itd… Następnie, należy otworzyć portu. Tu ważna uwaga – dwie aplikacje nie mogą w tym samym czasie korzystać z tego samego portu szeregowego. Jeśli jedna z aplikacji otworzy port, to wtedy dla drugiej aplikacji, ten port jest zajęty.

Używanie USB HID

Jeżeli chodzi o komunikację za pomocą USB HID, to tu mamy o wiele wygodniej! Nie trzeba instalować żadnych sterowników, ponieważ  w każdym systemie są one zainstalowane fabrycznie. Nie trzeba konfigurować parametrów transmisji oraz otwierać portów. Całość działa na zasadzie Plug&Play – tak jak podłączenie myszki przez USB do komputera – myszka po kilku chwilach będzie poprawnie działała bez jakiejkolwiek konfiguracji. Jest jeszcze dodatkowy plus – możliwe jest aby kilka aplikacji mogło korzystać z tego samego urządzenia HID w tym samym czasie.

Dwa najważniejsze parametry ( z mojego punktu widzenia ) USB HID:
– maksymalna długość jednej paczki danych – 64 bajty
– najmniejszy odstęp pomiędzy paczkami – 1ms

Testowanie

Po instalacji Delphi, nie znajdziemy tam żadnego komponentu do komunikacji USB HID. Odpowiedni komponent znajdziemy w bardzo fajnej i rozbudowanej bibliotece JEDI JCL i JVCL. Można je ściągnąć z http://cc.embarcadero.com/Item/30553  i  http://cc.embarcadero.com/Item/30554. Są to wersje z instalatorem w pliku exe do najnowszej wersji Embarcadero Delphi 10.1 Berlin. Po instalacji znajdziemy tam komponent TJvHidDeviceController, którego użyjemy do komunikowania się po HID. Urządzeniem, z którym będziemy się komunikować będzie płytka z STM32F429 Disco, którą zaprogramowałem tak  wysyła nam paczkę danych gdy naciśniemy przycisk, oraz gdy otrzyma odpowiednią komendę to zaświeci diodę LED. W deskryptorze ustawiłem maksymalną dla USB HID rozmiar paczki danych tj. 64 bajty oraz odstęp pomiędzy paczkami minimalny jaki się da: 1ms.

Program w Delphi

Program składa się z 4 głównych pól:
– listy urządzeń USB,
– szczegółowych informacji na temat wybranego urządzenia USB,
– podglądu danych wysyłany i odbieranych,
– pola do wysyłania danych.

 

 

 

 

 

 

 

 

Program opiera się na zdarzeniach, wywoływanych przez komponent TJvHidDeviceController. Pierwsze co musimy oprogramować to zdarzenie OnDeviceChange:

procedure TForm1.JvHidDeviceDeviceChange(Sender: TObject);
var
  Dev: TJvHidDevice;
  I: Integer;
begin
  for I := 0 to lbDeviceList.Items.Count - 1 do
  begin
    Dev := TJvHidDevice(lbDeviceList.Items.Objects[I]);
    Dev.Free;
  end;
  lbDeviceList.Clear;
  JvHidDevice.Enumerate;
  if lbDeviceList.Items.Count > 0 then lbDeviceList.ItemIndex := 0;
end;

Na początku usuwamy wszystkie obiekty TJvHidDevice z listy obiektów komponentu ListBox oraz czyścimy listę wszystkich urządzeń na liście. Następnie wywołujemy funkcje JvHidDevice.Enumerate.

Zdarzenie  OnEnumerate:

function TForm1.JvHidDeviceEnumerate(HidDev: TJvHidDevice; const Idx: Integer): Boolean;
var
  N: Integer;
  Dev: TJvHidDevice;
begin
  // jezeli urzadzenia posiada nazwe
  if HidDev.ProductName <> '' then
   N := lbDeviceList.Items.Add(Format('%-30.30s', [HidDev.ProductName]) + Format(' VID=%.4x PID=%.4x', [HidDev.Attributes.VendorID,
    HidDev.Attributes.ProductID]))
  else
    N := lbDeviceList.Items.Add(Format('%-30.30s', ['No name device!']) + Format(' VID=%.4x PID=%.4x', [HidDev.Attributes.VendorID,
     HidDev.Attributes.ProductID]));

 JvHidDevice.CheckOutByIndex(Dev, Idx);
 lbDeviceList.Items.Objects[N] := Dev;
 Result := True;
end;

W tym zdarzeniu następuje proces enumeracji. Dodajemy nowego obiekt do listy urządzeń. Następnie wywołujemy funkcje JvHidDevice.CheckOutByIndex(Dev, Idx) która indeksuje urządzenia USB wewnątrz klasy. Na końcu dodajemy nowy obiekt do listy obiektów urządzeń USB.

W zdarzeniu OnClick listy urządzeń pobieramy szczegółowe dane o urządzeniu USB. Tworzymy tyle pól Edit w GroupBoxie “Send data” ile bajtów wyjściowych ma urządzenie. Na końcu zapisujemy wybrany obiekt USB do zmiennej globalnej.

procedure TForm1.lbDeviceListClick(Sender: TObject);
var
  Dev: TJvHidDevice;
  I: Integer;
begin
  Dev := lbDeviceList.Items.Objects[lbDeviceList.ItemIndex] as TJvHidDevice;
  HistoryListBox.Clear; 
  VendorName.Caption := Dev.VendorName;
  ProductName.Caption := Dev.ProductName;
  SerialNo.Caption := Dev.SerialNumber;
  Vid.Caption := IntToHex(Dev.Attributes.VendorID, 4);
  Pid.Caption := IntToHex(Dev.Attributes.ProductID, 4);
  Vers.Caption := IntToHex(Dev.Attributes.VersionNumber, 4);
  if Dev.Caps.InputReportByteLength  then InputLen.Caption := IntToStr(Dev.Caps.InputReportByteLength - 1) 
  else InputLen.Caption := '0';
  if Dev.Caps.OutputReportByteLength  then OutputLen.Caption := IntToStr(Dev.Caps.OutputReportByteLength - 1) 
  else OutputLen.Caption := '0';
  if Dev.Caps.FeatureReportByteLength then FeatureLen.Caption := IntToStr(Dev.Caps.FeatureReportByteLength - 1) 
  else FeatureLen.Caption := '0';
  for I := Low(Edits) to High(Edits) do Edits[I].Visible := False;
  for I := 0 to Dev.Caps.OutputReportByteLength - 2 do Edits[I].Visible := True;
  SelectedDvice := Dev;
end;

Najważniejszym zdarzeniem jest odbiór danych. Sprawdzamy czy otrzymane dane są z wybranego urządzenia a następnie zapisujemy dane do terminala.

procedure TForm1.JvHidDeviceDeviceData(HidDev: TJvHidDevice; ReportID: Byte;
const Data: Pointer; Size: Word);
var
  I: Integer;
  Str: string;
begin
  if HidDev = SelectedDvice then
  begin
    Str := Format('[R][T: %d ms]: ',[DateUtils.MilliSecondsBetween(Now, Later)]);
    Later := Now;
    for I := 0 to Size - 1 do Str := Str + Format('%.2x ', [Cardinal(PAnsiChar(Data)[I])]);
    HistoryListBox.ItemIndex := HistoryListBox.Items.Add(Str);
  end;
end;

Filtrowanie jest konieczne, gdyż te zdarzenie jest wywoływane gdy jakiekolwiek urządzenie USB HID wyśle pakiet danych.
Ostatnim elementem programu jest obsługa przycisku “Send”:

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  Buf: array [0 .. 64] of Byte;
  Written: Cardinal;
  ToWrite: Cardinal;
  Str: string;
begin
  if Assigned(SelectedDvice) then
  begin
    ToWrite := SelectedDvice.Caps.OutputReportByteLength;
    for I := 1 to ToWrite - 1 do
    begin
    Buf[I] := StrToIntDef('$' + Edits[I - 1].Text, 0);
    Edits[I - 1].Text := Format('%.2x', [Buf[I]]);
  end;
  SelectedDvice.WriteFile(Buf, ToWrite, Written);
  Str := '[W]: ';
  for I := 0 to Written - 1 do
  Str := Str + Format('%.2x ', [Buf[I]]);
  HistoryListBox.ItemIndex := HistoryListBox.Items.Add(Str);
  end;
end;

Tutaj następuje przepisanie wartości z pól edit do tabeli bufora danych, oraz wysłanie danych za pomocą metody WriteFile.

Podsumowanie

Działanie programu prezentuje filmik poniżej:

 

Komunikacja z urządzeniami przez USB HID  za pomocą komponentu TJvHidDeviceController jest proste, czytelne i co najważniejsze działa bez najmniejszych problemów! Stanowi bardzo ciekawą alternatywę do najbardziej popularnej metody komunikacji urządzeń z komputerem – wirtualny port szeregowy.

 

 

Posted in Artykuły.

Leave a Reply

Your email address will not be published. Required fields are marked *