[UPDATED] Using AVRUSB with Delphi TJvHidDevice

General discussions about V-USB, our firmware-only implementation of a low speed USB device on Atmel's AVR microcontrollers
Post Reply
mschumann
Posts: 14
Joined: Thu Jan 29, 2009 8:01 pm

[UPDATED] Using AVRUSB with Delphi TJvHidDevice

Post by mschumann » Tue Feb 03, 2009 4:51 pm

[UPDATE]
I now found a better solution using the way shown by example HID_DATA from VUSB. It also works well with TJvHidDevice but one has to use the getFeature/setFeature methods for data transfer instead of reports. This doesnt trigger the events of the component so you have to poll it with a timer. In my current development (HTPC) this works absolutely fine.
[/UPDATE]

Getting the ingenious USBDRV up and running on the ATMEGA was a snap. Since I am bound to Delphi 7 on the host side I had quite a few frustrating nights trying to get things up and running with LibUSB. It crashed often when disconnecting the device and so on. Then I discovered TJvHidDevice and tried this one since it does not need any additional drivers on the PC. It connected to my device but It took me some time to figure out how to communicate,. Maybe it saves time and frustration for someone out there so heres my solution:

This device descriptor made it possible to communicate in 8 byte packtets which seems to be the upper limit for low speed devices.

Code: Select all

#define USB_BUFFER_SIZE 8
PROGMEM char usbHidReportDescriptor[29] = {
    0x06, 0xA0, 0xFF,   // USAGE_PAGE (Vendor Defined page 0xA1)
    0x09, 0x01,         // USAGE (Vendor Usage 0x01)
    0xA1, 0x01,         // COLLECTION (Application)
                        // Global items
    0x15, 0x00,         //   LOGICAL_MINIMUM(0)
    0x26, 0xFF, 0x00,   //   LOGICAL_MAXIMUM(255)
    0x75, 0x08,         //   REPORT_SIZE (8 Bits)
    0x95, USB_BUFFER_SIZE,    //   REPORT_COUNT (8  8 Bits, 8 Bytes)
                        // INPUT
    0x09, 0x03,         //   USAGE (Vendor Usage 0x03)
    0x81, 0x02,         //   INPUT (Data,Variable,Abs)
                        // OUTPUT
    0x09, 0x04,         //   USAGE (Vendor Usage 0x04)
    0x91, 0x02,         //   OUTPUT(Data, Variable,Abs
                        // Feature
    0x09, 0x05,         //   USAGE (Vendor Usage 0x05)
    0xB1, 0x02,         //   Feature (Data, Variable,Abs)
    0xC0                // END_COLLECTION
};


On the AVR I Implemented only these functions

Code: Select all

static uchar rest, curpos, currep;
usbMsgLen_t usbFunctionSetup(uchar data[8]) {
usbRequest_t    *rq = (void *)data;
    // HID-Class Request
    if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) { 
   if(rq->bRequest == USBRQ_HID_SET_REPORT) {

    // Receiving more thana single byte needs usbFunctionWrite()
      curpos = 0;               
        rest = rq->wLength.word; 
        // Limit size
        if(rest > sizeof(usbrec)) rest = sizeof(usbrec);
        // flag usage of usbFunctionWrite()
      return USB_NO_MSG;                   
      }
   }
    return 0;
}

uchar usbFunctionWrite(uchar *data, uchar len)
{
   uchar i;
    if(len > rest) len = rest;               
    rest -= len;
    for(i = 0; i < len; i++)
        usbrec[curpos++] = data[i];
    if (rest == 0) {
   
       // Process results
       
       // Write the buffer to my lcd display
        write(0,0,0,usbrec);
        
     return 1;
   } 
   else
      return 0; 
}


Transferring data to the host is done this way

Code: Select all

usbSetInterrupt(usbcmd, sizeof(usbcmd));  // usbcmd is char[8]


This works well and even triggers the onData Event of the delphi component! Here are the important parts of the delphi side:

Code: Select all

...
  private
    { Private-Deklarationen }
    dev: TJvHidDevice;
...
const
    USB_BUFFER_SIZE = 8;
...
type
  TLCD = packed record
    ID: byte;
    text: array[0..USB_BUFFER_SIZE-1] of char;
  end;
...
procedure TForm1.log(s: string);
begin
  ListBox1.ItemIndex := ListBox1.Items.Add(s);
end;
...
procedure TForm1.HIDCtlDeviceChange(Sender: TObject);
var
  x: integer;
  s: string;
begin
  with hidCtl do begin
    // check if owned device was plugged out
    if Assigned(Dev) and not Dev.IsPluggedIn then begin
      // give back device
      s := dev.ProductName;
      CheckIn(Dev);
      log(s + ' disconnected');
      dev := nil;
    end;
    // no device connected
    if not Assigned(Dev) then
      // take over device
      if CheckOutByID(Dev, $16C0, $05DF) then begin
        log(dev.ProductName + ' connected');
      end;
  end;
end;
...
procedure TForm1.Button1Click(Sender: TObject);
var
 rep:TLCD;

begin
  if assigned(dev) then begin
    rep.id:=0;
    strpcopy(rep.text,'Hallo');
    dev.setOutputReport(rep,sizeOf(rep));
  end;
end;
...
procedure TForm1.HIDCtlDeviceData(HidDev: TJvHidDevice; ReportID: Byte;
  const Data: Pointer; Size: Word);
var
  buffer: array[0..100] of char;
  I: integer;
  s:string;
begin
     Move(Data^, buffer[0], Size);
     s:='';
     for i:=0 to size-1 do s:=s+', '+intToStr(ord(buffer[i]));
     log(s);
end;
Last edited by mschumann on Fri Jun 05, 2009 12:01 am, edited 1 time in total.

dts
Posts: 1
Joined: Fri May 15, 2009 1:50 pm

Re: Using AVRUSB with Delphi TJvHidDevice

Post by dts » Fri May 15, 2009 2:01 pm

Hello!
i was trying to use your code to communicate with Microchip PIC18F67J50 controller with HID demo firmware.
I got as far as "Simple HID Device demo connected" message.
When i try to use SetOutputReport function i get "CRC error" message.
With WriteFile i get error also.
Byte is not sent.
Hardware works with Microchip demo application. It sends 64 byte array with first byte set to $80, i watched with USB Monitor. Actually 1 byte is enough.

Here are my write procedures:

procedure TForm1.btnOutReportClick(Sender: TObject);
var
Err: integer;
begin
if Assigned(CurrentDevice) then
begin
Buf[0] := $80;
if not CurrentDevice.SetOutputReport(Buf[0], 1) then
begin
Err := GetLastError;
ListBox1.Items.Add(Format('SET REPORT ERROR: %s (%x)', [SysErrorMessage(Err), Err]));
end
else
ListBox1.Items.Add(IntToHex(Buf[0], 2));
end;
end;

procedure TForm1.btnWriteClick(Sender: TObject);
var
Towrite, Written: Cardinal;
Err: integer;
begin
if Assigned(CurrentDevice) then
begin
Buf[0] := $80;
Towrite := 64;
// HidCheck(CurrentDevice.WriteFile(Buf, Towrite, Written));
if not CurrentDevice.WriteFile(Buf, Towrite, Written) then
ListBox1.Items.Add(Format('%s (%x)', [SysErrorMessage(Err), Err]))
else
ListBox1.Items.Add(IntToStr(Written));
end;
end;

Other stuff is same as yours.
What do you think could be wrong?

Best regards,
dts

Post Reply