USB mit Windows-Programmen nutzen

in Arbeit

am Beispiel des PIC18F2550


zurück zu 18F-Interfaces , zurück zu 16-Bit-Kern-PICs , PIC-Prozessoren , Elektronik , Homepage

Einleitung
USB-Hardware der PIX18Fxx5x

Alternative
 

Ein einfaches USB-Beispiel-Übungsprojekt



Einleitung

Im Windows-PC vermitteld der Device-Treiber zwischen dem Anwendungsprogramm und dem USB-Interface. Die Programmierschnittstelle stellt in der Regel eine DLL bereit. Im Fall des Microchip Custom Driver (verfügbar auf der Microchip Homepage) ist das die mpusbapi.dll (C:\MCHPFSUSB\Pc\Mpusbapi\Dll) .

Die DLL wurde in C geschrieben. Sie verwendet einige Daten-Typen, deren Aufbau in der C-Header-Datei mpusbapi.h (C:\MCHPFSUSB\Pc\Mpusbapi\Dll\Borland_C)
beschrieben sind. Wer seine Anwendungssoftware auch in C schreiben will, fügt diese Header-Datei einfach seinem Projekt zu, und hat damit alle Daten-Typen und Konstanten
definiert. Ich arbeite mit Delphi, und habe eine Unit (usbdll.pas) zusammengestellt, die die Daten-Typen, die Konstanten und die Funktionsaufrufe der DLL enthält. Diese Unit
basiert auf Informationen von http://www.sixca.com/delphi/article/microchip_usb.html.
 
unit usbdll;

interface

uses
  Windows, Dialogs, Sysutils;

//based on http://www.sixca.com/delphi/article/microchip_usb.html

Const
  MAXSIZE           = 64;
  MPUSB_FAIL        = 0;
  MPUSB_SUCCESS     = 1;
  MP_WRITE:DWORD    = 0;
  MP_READ:DWORD     = 1;
  MAX_NUM_MPUSB_DEV = 127;
 

type
  DWORD  = LongInt;
  PCHAR8 = array[0..MAXSIZE] of char;
  PBYTE  = array[0..MAXSIZE] of BYTE;
  PDWORD = array[0..MAXSIZE] of DWORD;
  PVOID  = Pointer;
  UINT   = Cardinal;
 

var
  vid_pid     : PCHAR8 = 'vid_04d8&pid_000c';
  out_pipe    : PCHAR8 = '\MCHP_EP1';
  in_pipe     : PCHAR8 = '\MCHP_EP1';
  myOutPipe   : THANDLE;
  myInPipe    : THANDLE;
  isConnected : boolean;
 

// Version der mpusbapi.dll auslesen
function _MPUSBGetDLLVersion():DWORD; 
             stdcall;external 'mpusbapi.dll';

// Anzahl der zu vid&pid passenden USB-Devices
function _MPUSBGetDeviceCount(pVID_PID:PCHAR8):DWORD; 
             stdcall;external 'mpusbapi.dll';

function _MPUSBOpen(instance:DWORD;pVID_PID:PCHAR8;
             pEP:PCHAR8;dwDir:DWORD;dwReserved:DWORD):
             THANDLE;stdcall;external 'mpusbapi.dll';

function _MPUSBClose(handle:THANDLE):DWORD; 
             stdcall;external 'mpusbapi.dll';

function _MPUSBRead(handle:THANDLE;var pData:PBYTE;
             dwLen:DWORD;var pLength:DWORD;
             dwMilliseconds:DWORD):DWORD;stdcall;
             external 'mpusbapi.dll';

function _MPUSBReadInt(handle:THANDLE;
             var pData:PBYTE;dwLen:DWORD;
             var pLength:PDWORD;
             dwMilliseconds:DWORD):DWORD; 
             stdcall;external 'mpusbapi.dll';

function _MPUSBWrite(handle:THANDLE;pData:PBYTE;
             dwLen:DWORD;
             var pLength:DWORD;
             dwMilliseconds:DWORD):DWORD; 
             stdcall;external 'mpusbapi.dll';

function SendReceivePacket(SendData:PBYTE;SendLength:DWORD;var ReceiveData:PBYTE;
         var ReceiveLength:DWORD;SendDelay:UINT;ReceiveDelay:UINT):DWORD; stdcall;
 

implementation
 

procedure CheckInvalidHandle();
begin
  if(GetLastError=ERROR_INVALID_HANDLE) then
  begin
        _MPUSBClose(myOutPipe);
        _MPUSBClose(myInPipe);

        myInPipe:=INVALID_HANDLE_VALUE;
        myOutPipe:=INVALID_HANDLE_VALUE;
  end
  else
    ShowMessage('Error Code :'+inttostr(GetLastError()));
end;
 
 

function SendReceivePacket(SendData:PBYTE;SendLength:DWORD;var ReceiveData:PBYTE;
         var ReceiveLength:DWORD;SendDelay:UINT;ReceiveDelay:UINT):DWORD; stdcall;
var
  SentDataLength:DWORD ;
  ExpectedReceiveLength:DWORD;
begin
  result:=100;
  ExpectedReceiveLength:= ReceiveLength;
  if((myOutPipe <> INVALID_HANDLE_VALUE) and (myInPipe <> INVALID_HANDLE_VALUE)) then begin
    if(_MPUSBWrite(myOutPipe,SendData,SendLength,SentDataLength,SendDelay)<>0) then
    if(_MPUSBRead(myInPipe,ReceiveData,ExpectedReceiveLength,ReceiveLength,ReceiveDelay)<>0)then begin
      if(ReceiveLength = ExpectedReceiveLength) then begin
        Result:=1; // Success
        exit;
      end else
      if(ReceiveLength < ExpectedReceiveLength) then begin
        Result:=2; // incorrect receive length
        exit;
      end
    end else
    CheckInvalidHandle()
    else
    CheckInvalidHandle()
  end else begin
    Result:=0; // Failed
  end;
end;
 

initialization

end.
 

Diese DLL stellt für Delphi die folgenden Funktionen der mpusbapi.dll zur Verfügung:

Außerdem definiert sie eine neue Funktion, die man alle Lese- und Schreib-Operationen einfacher erledigen kann:
nach oben

_MPUSBGetDLLVersion
function _MPUSBGetDLLVersion():DWORD;

Mit dem Aufruf dieser Funktion kann man prüfen, ob die mpusbapi.dll geladen ist, und welche Version der DLL bereitsteht. Die Funktion benötigt keine Übergabeparameter, und liefert eine vorzeichenlose 32-Bit-Zahl als DLL-Versionsnummer zurück. Bei der Version 1 der DLL erhält man 0x00010000.
Ein typischer Aufruf könnte z.B. so aussehen:
 
.
memo1.Lines.add('_MPUSBGetDLLVersion : '+inttohex(_MPUSBGetDLLVersion,8));
.

nach oben


_MPUSBGetDeviceCount
function _MPUSBGetDeviceCount(pVID_PID:PCHAR8):DWORD;

Mit dem Aufruf dieser Funktion kann man prüfen, ob das eigene USB-Device von Windows richtig erkannt wurde.  Dazu muss man der Funktion einen Pointer auf einen nullterminierten String übergeben. Der String enthält die VID (vendor ID) und die PID (product ID) des USB-Device. Die Funktion liefert die Zahl der gefundenen Devices zurück, auf die VID und PID passen. Normalerweise ist das 0x00000001.

Der nötige Pointer auf den String ist in dieser Unit schon definiert. Er heißt vid_pid und trägt die Microchip-VID 0x04D8 und die PID 0x000C. Wer eine andere PID einsetzen will, muss den Eintrag anpassen. In der Grundeinstellung ist die mpusbapi.dll nur für die PIDs 0x000B und 0x000C verantwortlich. Beim Einsatz anderer PIDs ist der entsprechende Eintrag in der mchpusb.inf anzupassen.
 
.
memo1.Lines.add('_MPUSBGetDeviceCount : '+inttohex(_MPUSBGetDeviceCount(vid_pid),8));
.

nach oben


SendReceivePacket
function SendReceivePacket(SendData:PBYTE;SendLength:DWORD;var ReceiveData:PBYTE;
         var ReceiveLength:DWORD;SendDelay:UINT;ReceiveDelay:UINT):DWORD; stdcall;

Das ist eine universelle Schreib-und-Lese-Funktion, die ich aus http://www.sixca.com/delphi/article/microchip_usb.html übernommen habe. Sie schreibt in den out-Endpunkt und liest aus dem in-Endpunkt. Dabei kümmert sie sich auch gleich um allen Kleinkram. Die Funktion benötigt 6 Übergabeparameter:
 
Parameter Bedeutung
SendData:PBYTE; SendData ist ein 64 Byte langes Array. In ihm legt man for dem Funktionsaufruf beginnend mit dem Index 0 die Daten ab, die man zum out-Endpunkt übertragen möchte.
SendLength:DWORD; Das ist die Anzahl der Bytes in SendData, die zum out-Endpunkt geschrieben werden sollen. Sollen gar keine Daten zum Device geschrieben werden, so ist hier 0 einzutragen.
var ReceiveData:PBYTE; ReceiveData ist ein 64 Byte langes Array. In ihm befinden sich nach dem Funktionsaufruf die aus dem in-Endpunkt gelesenen Daten.
var ReceiveLength:DWORD; Vor dem Funktionsaufruf wird hier eingetragen, wieviele Bytes aus dem in-Endpunkt gelesen werden sollen.
Nach dem Funktionsaufruf steht hier die Zahl der wirklich gelesenen Bytes.
SendDelay:UINT; Das ist die Time-out-Zeit in Millisekunden. Nach dieser Zeit wird das Übertragen von Daten zum out-Endpunkt abgebrochen, auch wenn noch nicht alle Daten übertragen wurden.
ReceiveDelay:UINT Das ist die Time-out-Zeit in Millisekunden. Nach dieser Zeit wird das Lesen von Daten aus dem in-Endpunkt abgebrochen, auch wenn noch nicht alle Daten gelesen wurden.

Darüberhinaus liefert sie als Ergebnis einen 32-Bit-Zahlenwert, der Informationen darüber enthält, ob der Aufruf erfolgreich war:


Vor der Nutzung dieser Funktion ist aber etwas Vorarbeit und hinterher etwas Aufräumen nötig. Insbesondere sind die USB-Ports zu öffnen und zu schließen. Im Folgenden Beispiel soll ein einzelnes Byte mit dem Wert "1" in das USB-Device geschrieben werden:
 
.
procedure TForm1.USB_Sende_Test;
var
   selection   : DWORD;
   send_buf    : PBYTE;
   receive_buf : PBYTE;
   RecvLength  : DWORD;
   CurrentCMD  : BYTE;
begin
  //instance ist warscheinlich 0, kann aber auch einen anderen Wert haben
  selection:=0;

  //pipes anfordern
  myOutPipe:= _MPUSBOpen(selection,vid_pid,out_pipe,MP_WRITE,0);
  myInPipe:= _MPUSBOpen(selection,vid_pid,out_pipe,MP_READ,0);
  //habe ich pipes bekommen?
  if ((myOutPipe = INVALID_HANDLE_VALUE) or (myInPipe = INVALID_HANDLE_VALUE)) then
  begin
    Memo1.lines.add('USB Error, no pipes');
    exit;
  end;

  //Daten in den Puffer
  send_buf[0]:=1; 
  RecvLength:=0;  // ich will ja nichts lesen
  SendReceivePacket(send_buf,1,receive_buf,RecvLength,100,100);

  _MPUSBClose(myOutPipe);
  _MPUSBClose(myInPipe);
  myInPipe:= INVALID_HANDLE_VALUE;
  myOutPipe:=INVALID_HANDLE_VALUE;
end;  //USB_Sende_Test
.

Das Beispiel geht davon aus, dass dieses Device das einzige mit seiner VID&PID ist, und deshalb beim Durchzählen den selection-Wert "0" bekommen hat. Rein Theoretisch könnte es auch einen anderen selection-Wert bekommen, das ist mir aber noch nie passiert.

nach oben

Alternative

Eine Alternative zu meiner DLL ist ist die USB-API von Gerhard Burger. Er hat sie auf seiner Homepage veröffentlicht. Dort findet sich auch eine passende Testplatine mit einem PIC18F4550, bei der es sich um eine vereinfachte Eigenbauversion des USB-Demo-Boards von Microchip handelt.  

nach oben

Hinweis von Jan per Email

..ich habe vor einiger Zeit angefangen, mich mit der USB-Schittstelle der PICs zu beschäftigen. ... Allerdings hatte ich am Anfang einige Probleme, die ich aber inzwischen anscheinend behoben habe und möchte dir von meinen Erfahrungen berichten.

Windows-Anwendungen programmiere ich in letzter Zeit fast nur noch in Lazarus (basierend auf Freepascal, Delphi-ähnlich). Ich scheiterte aber daran, die Funktionen der "mpusbapi.dll" mit Hilfe deiner "usbdll.pas" einzubinden. Das Compilieren klappte wunderbar, doch schon ein Aufruf von "_MPUSBGetDeviceCount(vid_pid)" brachte mir eine Zugriffsverletzung. Ich habe dann alles mögliche versucht und einige Stunden lang im Internet recherchiert. Mit Delphi 7 hatte ich das gleiche Problem. Allerdings funktionierte dein "win_usbtest" einwandfrei auch nach neuem Compilieren. Mir ist aufgefallen, dass in deinem Projekt einige Compiler-Einstellungen von meinen Standart-Werten abweichen. Da ich aber in Lazarus programmieren wollte, wo ich diese Einstellungen nicht finden konnte, musste ich weitersuchen.
Dann habe ich die Lösung glücklicherweise noch gefunden: Die Funktionen der "mpusbapi.dll" musste ich mit der Aufrufkonvention "cdecl" (statt "stdcall") aufrufen.
Nach den Informationen, die ich nebenbei gefunden habe, verlangt die Aufrufkonvention "stdcall" von der aufgerufenen Funktion, dass diese am Ende ihren Stack wieder aufräumt. Durch "cdecl" weiß der Compiler, dass die aufrufende Funktion diese Aufgabe übernehmen muss.
Nachdem ich diese Angabe in der "usbdll.pas" geändert habe, funktionieren die Aufrufe in Lazarus und auch in Delphi 7 (mit Standart-Einstellungen) ohne Fehler und ich konnte sogar schon erste Daten austauschen.

nach oben

zurück zu 18F-Interfaces , zurück zu 16-Bit-Kern-PICs , PIC-Prozessoren , Elektronik , Homepage


Quellen:
- Microchip,
- USB2.0-Spezifikation
- http://www.sixca.com/delphi/article/microchip_usb.html

Autor: sprut
erstellt: 02.03.2006
letzte Änderung 24.02.20010