Author Topic: Linux USB Identification & I/O  (Read 14954 times)

ashgill

  • Newbie
  • *
  • Posts: 13
Linux USB Identification & I/O
« on: March 04, 2013, 01:08:39 am »
Greetings.

I've been working on getting one of my DigiSparks communicating with my Linux system via USB. So far, I've got to the point where it now shows up when I run the "lsusb" command.

However, the output is a little unexpected:

Code: [Select]
[root@dev ~]# lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 003: ID 27b8:01ed 
Bus 001 Device 004: ID 154b:005b PNY
Bus 001 Device 006: ID 16c0:05df Van Ooijen Technische Informatica Masterkit MA901 radio
[root@dev ~]#

I believe that device 006 is my digispark, and the vendor/device IDs correspond to the ones in the sample Python code, but the Linux Kernel Driver database/USB device database seems to think it is a radio of some sort.

Will this cause any problems going forward?

Also, does anyone have any pointers to example Linux C code to write/read from the Digispark via USB? I've got libusb installed, but I've never written any software that uses that library before, so some examples would always help.
« Last Edit: March 04, 2013, 02:02:01 am by ashgill »

ashgill

  • Newbie
  • *
  • Posts: 13
Re: Linux USB Identification & I/O
« Reply #1 on: March 04, 2013, 02:00:40 am »
Here's my progress so far in figuring out libusb:

Code: [Select]
#include <stdio.h>
#include <usb.h>

int main (int argc, char **argv)
{
  struct usb_bus *bus = NULL;
  struct usb_device *digiSpark = NULL;
  struct usb_device *device = NULL;

  printf("Detecting USB devices...\n");

  // Initialize the USB library
  usb_init();

  // Enumerate the USB device tree
  usb_find_busses();
  usb_find_devices();

  // Iterate through attached busses and devices
  bus = usb_get_busses();
  while(bus != NULL)
  {
     device = bus->devices;
     while(device != NULL)
     {
        // Check to see if each USB device matches the DigiSpark Vendor and Product IDs
        if((device->descriptor.idVendor == 0x16c0) && (device->descriptor.idProduct == 0x05df$
        {
           printf("Detected DigiSpark\n");
           digiSpark = device;
        }

        device = device->next;
     }

     bus = bus->next;
  }

  // If a digiSpark was found
  if(digiSpark != NULL)
  {
     // FIXME - Send a string to the DigiSpark

  }

  return 0;
}

This can be compiled and run using the following commands (assuming you have libusb installed):

Code: [Select]
[root@dev ~]# cc test.c -lusb -o test
[root@dev ~]# ./test
Detecting USB devices...
Detected DigiSpark
[root@dev ~]#

Next step - to get writing data to the digispark working...

ashgill

  • Newbie
  • *
  • Posts: 13
Re: Linux USB Identification & I/O
« Reply #2 on: March 04, 2013, 02:40:44 am »
Here's a dump of the Digispark USB interface:

Code: [Select]
Dev #8: digistump.com - DigiUSB
  wTotalLength:         34
  bNumInterfaces:       1
  bConfigurationValue:  1
  iConfiguration:       0
  bmAttributes:         80h
  MaxPower:             50
    bInterfaceNumber:   0
    bAlternateSetting:  0
    bNumEndpoints:      1
    bInterfaceClass:    3
    bInterfaceSubClass: 0
    bInterfaceProtocol: 0
    iInterface:         0
      bEndpointAddress: 81h
      bmAttributes:     03h
      wMaxPacketSize:   8
      bInterval:        10
      bRefresh:         0
      bSynchAddress:    0

ashgill

  • Newbie
  • *
  • Posts: 13
Re: Linux USB Identification & I/O
« Reply #3 on: March 04, 2013, 03:07:18 am »
Next challenge: It looks like the Linux HID module was attaching to the USB device, so I kept getting error -16 when trying to claim the interface.

Code: [Select]
[root@dev ~]# lsmod
Module                  Size  Used by
ipv6                  259694  14
usbhid                 24972  0
hid                    63533  1 usbhid
mv_cesa                 9128  0
autofs4                22074  2
[root@dev ~]# rmmod hid
rmmod: ERROR: Module hid is in use by: usbhid
[root@dev ~]# rmmod usbhid
[root@dev ~]# rmmod hid
[root@dev ~]# lsmod
Module                  Size  Used by
ipv6                  259694  14
mv_cesa                 9128  0
autofs4                22074  2
[root@dev ~]#

Unloading the modules eliminated the error when claiming the interface.
« Last Edit: March 04, 2013, 03:34:30 am by ashgill »

ashgill

  • Newbie
  • *
  • Posts: 13
Re: Linux USB Identification & I/O
« Reply #4 on: March 04, 2013, 03:11:20 am »
Now I just need to figure out the correct parameters to usb_control_msg...


The usbdevice.py file that comes with the Digispark isn't very clear to me (most likely this is because I don't know Python). Perhaps the ruby gem will be more understandable.

ashgill

  • Newbie
  • *
  • Posts: 13
Re: Linux USB Identification & I/O
« Reply #5 on: March 04, 2013, 03:31:42 am »
No luck so far...

Here's my current code to write a character via a control request:

Code: [Select]
  // If a digiSpark was found
  if(digiSpark != NULL)
  {
     int result = 0;
     int stringLength = 0;
     usb_dev_handle *devHandle = NULL;

     if(argc > 1)
     {
        stringLength = strlen(argv[1]);

        devHandle = usb_open(digiSpark);

        if(devHandle != NULL)
        {
           result = usb_claim_interface(devHandle, 0);
           if(result < 0) printf("Error %i claiming Interface 0\n", result);

           printf("Writing character \"%c\" to DigiSpark\n", argv[1][0]);
           result = usb_control_msg(devHandle, 0, 0x09, 0, argv[1][0], NULL, 0, 1000);
           if(result < 0) printf("Error %i writing to USB device\n", result);

           result = usb_release_interface(devHandle, 0);
           if(result < 0) printf("Error %i releasing Interface 0\n", result);
           
           usb_close(devHandle);
        }
     }
  }

No errors, but no results either.

Here's the code I'm running on the digispark:

Code: [Select]
#include <DigiUSB.h>

void setup() {
  // Use the LED for temporary testing
  // Long blink indicates startup
  pinMode(0, OUTPUT);
  digitalWrite(0, HIGH);
  delay(2000);
  digitalWrite(0, LOW);

  DigiUSB.begin();
}

void get_input() {

  int lastRead;
  // when there are no characters to read, or the character isn't a newline
  while (1==1) {
    if(DigiUSB.available()){
      //something to read
      lastRead = DigiUSB.read();
//      DigiUSB.write(lastRead);
     
      if(lastRead == 'a')
      {
        // Turn the LED on
        digitalWrite(0, HIGH);
        break;
      } 

      if(lastRead == 'b')
      {
        // Turn the LED off
        digitalWrite(0, LOW);
        break;
      } 
    }
    // refresh the usb port
    DigiUSB.refresh();
    delay(10);

  }

}


void loop() {
  //print output
  DigiUSB.println("Waiting for input...");
  //get input
  get_input();

}

I'm going to sleep on it for now — suggestions, corrections and pointers in the right direction are gladly welcomed!

ashgill

  • Newbie
  • *
  • Posts: 13
Re: Linux USB Identification & I/O
« Reply #6 on: March 04, 2013, 09:19:05 am »
It took a little sleuthing, but I was able to narrow down my problem to how I'm using the usb_control_msg call to write a character to the digispark.

The ruby gem sets the following parameters that are used to call usb_control_msg:

Code: [Select]
bRequest: 0x09
wIndex: character.ord
wValue: 0
bmRequestType: LIBUSB::REQUEST_TYPE_CLASS | LIBUSB::RECIPIENT_DEVICE

(note that there is a bug in the gem, as dataOut should be set to 1 in the control_transfer for getc. It only works because LIBUSB::ENDPOINT_OUT resolves to 0x00)

libusb call takes the following parameters:

Code: [Select]
int usb_control_msg(usb_dev_handle *dev, int requesttype, int request, int value, int index, char *bytes, int size, int timeout);

I'm able to resolve all of the parameters except for the requesttype...

Code: [Select]
result = usb_control_msg(devHandle, ????, 0x09, 0, 'a', NULL, 0, 1000);

The python code does the same thing:

Code: [Select]
REQUEST_TYPE_SEND = usb.util.build_request_type(usb.util.CTRL_OUT,
                                                usb.util.CTRL_TYPE_CLASS,
                                                usb.util.CTRL_RECIPIENT_DEVICE)

In the Linux Kernel, these values seem to be defined as preprocessor macros:

Code: [Select]
#define USB_DIR_OUT                     0               /* to device */
#define USB_TYPE_CLASS                  (0x01 << 5)
#define USB_RECIP_DEVICE                0x00

Assuming that these were the correct values, then I use (0x01 << 5) for the requesttype, and it worked!

ashgill

  • Newbie
  • *
  • Posts: 13
Re: Linux USB Identification & I/O
« Reply #7 on: March 04, 2013, 09:22:30 am »
Here's the full source code that works with the above posted code running on the DigiSpark:

Code: [Select]
#include <stdio.h>
#include <string.h>
#include <usb.h>

int main (int argc, char **argv)
{
  struct usb_bus *bus = NULL;
  struct usb_device *digiSpark = NULL;
  struct usb_device *device = NULL;

  printf("Detecting USB devices...\n");

  // Initialize the USB library
  usb_init();

  // Enumerate the USB device tree
  usb_find_busses();
  usb_find_devices();

  // Iterate through attached busses and devices
  bus = usb_get_busses();
  while(bus != NULL)
  {
     device = bus->devices;
     while(device != NULL)
     {
        // Check to see if each USB device matches the DigiSpark Vendor and Product IDs
        if((device->descriptor.idVendor == 0x16c0) && (device->descriptor.idProduct == 0x05df))
        {
           printf("Detected DigiSpark... ");
           digiSpark = device;
        }

        device = device->next;
     }

     bus = bus->next;
  }

  // If a digiSpark was found
  if(digiSpark != NULL)
  {
     int result = 0;
     int stringLength = 0;
     int numInterfaces = 0;
     struct usb_dev_handle *devHandle = NULL;
     struct usb_interface_descriptor *interface = NULL;

     if(argc > 1)
     {
        stringLength = strlen(argv[1]);
       
        devHandle = usb_open(digiSpark);
         
        if(devHandle != NULL)
        {
           result = usb_set_configuration(devHandle, digiSpark->config->bConfigurationValue);
           if(result < 0) printf("Error %i setting configuration to %i\n", result, digiSpark->config->bConfigurationV$
       
           numInterfaces = digiSpark->config->bNumInterfaces;
           interface = &(digiSpark->config->interface[0].altsetting[0]);
           printf("Found %i interfaces, using interface %i\n", numInterfaces, interface->bInterfaceNumber);
   
           result = usb_claim_interface(devHandle, interface->bInterfaceNumber);
           if(result < 0) printf("Error %i claiming Interface %i\n", result, interface->bInterfaceNumber);
 
           printf("Writing character \"%c\" to DigiSpark.\n", argv[1][0]);
           result = usb_control_msg(devHandle, (0x01 << 5), 0x09, 0, argv[1][0], 0, 0, 1000);
           if(result < 0) printf("Error %i writing to USB device\n", result);

           result = usb_release_interface(devHandle, interface->bInterfaceNumber);
           if(result < 0) printf("Error %i releasing Interface 0\n", result);

           usb_close(devHandle);
        }
     }
  }     
       
  return 0;
}

Sending 'a' turns the LED on, and sending 'b' turns it off.

Code: [Select]
[root@dev ~]# ./test a
Detecting USB devices...
Detected DigiSpark... Found 1 interfaces, using interface 0
Writing character "a" to DigiSpark.
[root@dev ~]# ./test b
Detecting USB devices...
Detected DigiSpark... Found 1 interfaces, using interface 0
Writing character "b" to DigiSpark.
[root@dev ~]#

ashgill

  • Newbie
  • *
  • Posts: 13
Re: Linux USB Identification & I/O
« Reply #8 on: March 04, 2013, 10:28:54 pm »
Several more observations...

The above code also works on MacOSX 10.6 in addition to Linux, but claiming the interface and releasing the interface returns errors -13 and -2 respectively. This may be due to driver conflicts — I need to try the programatic device unload code that's included with libusb and see if that helps.

Also, I'm beginning to think that I'm actually coding against libusb 0.x rather than libusb 1.0... I'll have to dig into that in more detail.

So, here's some working code that reads data back from the Digispark:

Code: [Select]
// Try to read from the digispark
printf("Read from Digispark: ", thechar);
while(thechar != 0)
{
thechar = 0;
result = usb_control_msg(devHandle, (0x01 << 5) | 0x80, 0x01, 0, 0, &thechar, 1, 1000);
if(result < 0)
{
printf("Error %i reading from USB device\n", result);
break;
}
else
{
printf("%c", thechar);
}
}
printf("\n", thechar);

digistump

  • Administrator
  • Hero Member
  • *****
  • Posts: 1465
Re: Linux USB Identification & I/O
« Reply #9 on: March 05, 2013, 11:07:51 am »
ashgill-


I just wanted to pop in here and say - this is really awesome work you are doing!


To answer the VID/PID question:


The VID/PID used by DigiUSB is a public set that can be used by anyone (the bootloader has a set we actually bought, though it is free for use with the bootloader) so the hardware that comes up in linux must have submitted their driver attached to the public VID/PID (bad!) - but it shouldn't cause any issues.

ephphatha

  • Newbie
  • *
  • Posts: 6
Re: Linux USB Identification & I/O
« Reply #10 on: March 06, 2013, 06:40:02 am »
Several more observations...

The above code also works on MacOSX 10.6 in addition to Linux, but claiming the interface and releasing the interface returns errors -13 and -2 respectively. This may be due to driver conflicts — I need to try the programatic device unload code that's included with libusb and see if that helps.

Also, I'm beginning to think that I'm actually coding against libusb 0.x rather than libusb 1.0... I'll have to dig into that in more detail.


libusb 0.1 and libusb 1.0 have vastly different APIs, so they're not compatible without significant changes to the method signatures. As for the errors you're getting, you can call libusb_error_name(int error_code) with the error code to get a string describing the error code in (some) detail as it appears you're using libusb 1.0. Nope, you're using libusb 0.1. I would suggest re-targeting libusb 1.0.


I've actually been working on a c++ program that sends strings to the digispark to be displayed that I haven't got anywhere near finished yet, but I found the following links really helpful:
http://www.usbmadesimple.co.uk/ums_4.htm
http://www.beyondlogic.org/usbnutshell/usb4.shtml#Interrupt


Also, using usbview.exe it appears the digispark defines one interface in addition to interface 0, both appear to support interrupt transfers (but not bulk transfers as the digispark appears as a USB1.1 device).


Edit: Anyone planning on interacting with a digispark using windows should use libusbx. This supports the driver that comes with the digispark, libusb requires its own special driver to be loaded over the top of the normal digispark driver making development a pain.
« Last Edit: March 06, 2013, 06:49:25 am by ephphatha »