Arduino Leonardo : USB Device

Il est aujourd’hui possible d’émuler un périphérique USB HID avec les carte Arduino UNO grâce a au framework LUFA. Néanmoins cela nécessite de flasher la carte et donc un programmateur externe et cela conduit également a la perte du bootloader Arduino.
Des informations sur le framework LUFA ICI et un exemple d’utilisation avec une Arduino Uno ICI

La nouvelle carte Arduino Leonardo possède également une gestion USB HID. Celle-ci est intégrée dans l’IDE. Nous allons l’utiliser pour communique avec serveur Raspberry Pi fonctionnant sous Raspbian. Cela permet de créer des périphériques HID sans devoir flasher le firmeware comme c’est le cas pour la librairie LUFA et d’utilise l’IDE de DEV Arduino.

Un thread intéressant à propos de LUFA et l’Arduino Leonardo :

It’s disappointing to see Arduino move away from LUFA in only one generation of hardware
revisions, but I understand that they need to more tightly integrate the USB code with their custom platform to make it more useful to the masses, and portable to the new expanded range of Arduino architectures.

On peut donc penser qu’il y aura de plus en plus de projet USB grâce à la nouvelle génération de carte Arduino Leonardo. Il existe déjà quelque exemple dans l’IDE 1.01 (dans le menu Fichier -> Exemple -> Leonardo) pour la gestion d’un clavier et d’une souris.

Je vais utiliser un des exemple “clavier” et la lib libusb pour essayer de communiquer entre mon raspberry pi et mon arduino Leonardo. Voilà le code Leonardo qui se contente de faire clignoter une LED s’il reçoit qq chose sur le port USB :

/* 
 Blink LED when receive something from USB
 */

//Define the pin number
const int _OutPinLedTest = 7;
const int PIN_BOUTON_ON = 4;
const int PIN_BOUTON_OFF = 3;

int _ledState=0;

void setup() {
  //defined input button
  pinMode(_OutPinLedTest,OUTPUT);
  pinMode(PIN_BOUTON_ON,INPUT);
  pinMode(PIN_BOUTON_OFF,INPUT);
  //Active resistor as pull up
  digitalWrite(PIN_BOUTON_ON,HIGH);
  digitalWrite(PIN_BOUTON_OFF,HIGH);
  // open the serial port:
  Serial.begin(9600);
  // initialize control over the keyboard:
  Keyboard.begin();
}

void flashPin(int pin, int times, int wait) 
{
  for (int i = 0; i < times; i++) {
    digitalWrite(pin, HIGH);
    delay(wait);
    digitalWrite(pin, LOW);

    if (i + 1 < times) {
      delay(wait);
    }
  }
}

void switchLedStatus(int pin) 
{
  if(_ledState == 0) {
    digitalWrite(pin, HIGH);
    _ledState = 1;
    delay(100);
  }else{
    digitalWrite(pin, LOW);
    _ledState = 0;
    delay(100);

  }
}

void loop() {
  // check for incoming serial data:
  if (Serial.available() > 0) {
    // read incoming serial data:
    char inChar = Serial.read();
    // Type the next ASCII value from what you received:
    flashPin(_OutPinLedTest, 10, 600);
    //Keyboard.write(inChar+1);
  }  
  //Read button status
  int aInputDigitalValue = digitalRead(PIN_BOUTON_ON);
  //Reset the counter if button is press
  if (aInputDigitalValue == LOW)
  {
    flashPin(_OutPinLedTest, 10, 600);
  }
  aInputDigitalValue = digitalRead(PIN_BOUTON_OFF);
  //Reset the counter if button is press
  if (aInputDigitalValue == LOW)
  {
    switchLedStatus(_OutPinLedTest);
  }
}

Cote Raspberry Pi avant de connecter la carte :

pi@raspberrypi ~ $ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp.
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 007: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 008: ID 04f2:1013 Chicony Electronics Co., Ltd

Après connexion avec la Carte Leonardo (sur un HUB USB externe) :

pi@raspberrypi ~ $ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp.
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 007: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 008: ID 04f2:1013 Chicony Electronics Co., Ltd
Bus 001 Device 010: ID 2341:8036 Arduino SA

La carte Leonardo est détecte ! Voyons voir dans le détail :

Bus 001 Device 010: ID 2341:8036 Arduino SA
Couldn't open device, some information will be missing
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x2341 Arduino SA
  idProduct          0x8036
  bcdDevice            1.00
  iManufacturer           1
  iProduct                2
  iSerial                 0
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength          100
    bNumInterfaces          3
    bConfigurationValue     1
    iConfiguration          0
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              500mA
    Interface Association:
      bLength                 8
      bDescriptorType        11
      bFirstInterface         0
      bInterfaceCount         2
      bFunctionClass          2 Communications
      bFunctionSubClass       2 Abstract (modem)
      bFunctionProtocol       1 AT-commands (v.25ter)
      iFunction               0
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         2 Communications
      bInterfaceSubClass      2 Abstract (modem)
      bInterfaceProtocol      0 None
      iInterface              0
      CDC Header:
        bcdCDC               1.10
      CDC Call Management:
        bmCapabilities       0x01
          call management
        bDataInterface          1
      CDC ACM:
        bmCapabilities       0x06
          sends break
          line coding and serial state
      CDC Union:
        bMasterInterface        0
        bSlaveInterface         1
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0010  1x 16 bytes
        bInterval              64
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass        10 CDC Data
      bInterfaceSubClass      0 Unused
      bInterfaceProtocol      0
      iInterface              0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x83  EP 3 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        2
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.01
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength     101
         Report Descriptors:
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x84  EP 4 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1

Pour mieux comprendre à quoi correspondent ces informations vous pouvez jeter à la présentation de la norme USB disponible ICI. Le schéma suivant permet de voir les différents “composants” d’un périphérique USB :

Et si on adapte a l’Arduino Leonardo :

Si ma mémoire est bonne (la dernière fois que j’ai utilisé la lib libusb été à l’époque où j’utilisai les microcontroleur PIC avec une communication Linux USB – PIC 18f4550) il nous faut ces 2 informations :

idVendor           0x2341 Arduino SA
idProduct          0x8036

Cote Raspbian

J’ai trouvé un vieux code C++ que j’utilisai pour communiquer avec les PIC 18f4550 qui fonctionne avec la lib libusb. On doit donc commencer par l’installer :

pi@raspberrypi ~/USB_Leonardo $ sudo apt-get install libusb-1.0-0-dev

Ensuite….un petit tour sur la doc de la lib disponible ICI pour vérifier les dernières mise a jour/info. Ensuite j’ai mis à jour mon vieux programme (ca n’a pas bcp change en deux…) pour que l’utilisateur puisse changer d’interface et de configuration sans devoir tout recompiler. Voilà le programme :

#include <errno.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
//C++ migration
#include <iostream>

#include <stdlib.h>
#include <libusb-1.0/libusb.h>

#define VERSION "0.1.0"

#define VENDOR_ID 9025
//CWA
#define LINUX
#define PRODUCT_ID 32822

// HID Class-Specific Requests values. See section 7.2 of the HID specifications
#define HID_GET_REPORT                0x01
#define HID_GET_IDLE                  0x02
#define HID_GET_PROTOCOL              0x03
#define HID_SET_REPORT                0x09
#define HID_SET_IDLE                  0x0A
#define HID_SET_PROTOCOL              0x0B
#define HID_REPORT_TYPE_INPUT         0x01
#define HID_REPORT_TYPE_OUTPUT        0x02
#define HID_REPORT_TYPE_FEATURE       0x03

#define CTRL_IN                LIBUSB_ENDPOINT_IN|LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE
#define CTRL_OUT       LIBUSB_ENDPOINT_OUT|LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE

const static int PACKET_CTRL_LEN=2; 

const static int PACKET_INT_LEN=2;
const static int INTERFACE=2;
const static int ENDPOINT_INT_IN=0x81; /* endpoint 0x81 address for IN */
const static int ENDPOINT_INT_OUT=2; /* endpoint 1 address for OUT */
const static int TIMEOUT=5000; /* timeout in ms */

void bad(const char *why)
{
    fprintf(stderr,"Fatal error> %s\n",why);
    exit(17);
}

static struct libusb_device_handle *devh = NULL;

static int find_lvr_hidusb(void)
{
    devh = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID);
    return devh ? 0 : -EIO;
}

int main(void)
{
    //Store the interface number to use
    int aInterface;
    std::cout << "Please enter an interface ID (integer value): ";
    std::cin >> aInterface;
    std::cout << "The interface used will be : " << aInterface <<std::endl;

    //Store the configuration number to use
    int aConfiguration;
    std::cout << "Please enter an configuration ID (integer value): ";
    std::cin >> aConfiguration;
    std::cout << "The configuration used will be : " << aConfiguration <<std::endl;

    int r = 1;
    unsigned char *data = new unsigned char[1]; //data to write
    data[0]=0x00; //some dummy values
    //for (int compt=0; compt<63;compt++)
    //data[compt]=0x00; //some dummy values
    int actual; //used to find out how many bytes were written
    int actual2; //used to find out how many bytes were written

    r = libusb_init(NULL);
    if (r < 0) {
        fprintf(stderr, "Failed to initialise libusb\n");
        exit(1);
    }
    printf("Successfully initialise libusb\n");

    libusb_set_debug(NULL,4);

    r = find_lvr_hidusb();
    if (r < 0) {
        fprintf(stderr, "Could not find/open LVR Generic HID device\n");
        goto out;
    }
    printf("Successfully find the LVR Generic HID device\n");

#ifdef LINUX
    printf("LINUX - we need to see if a kernel driver is active on the interface.\nIf yes we have to detach it to be able to claim the interface\n");

    r = libusb_kernel_driver_active(devh, aInterface);

    if (r==0)
    {
        printf("No Kernel Driver active on this interface\n");
    }
    else
    {
        printf("Kernel Driver already active on this interface - we have to detach it to be able to claim the interface\n");
        r = libusb_detach_kernel_driver(devh, aInterface);  
        if (r < 0) 
        {
            fprintf(stderr, "libusb_detach_kernel_driver error %d\n", r);
            goto out;
        }
        printf("Kernel Driver detach\n");
    }

#endif

    /*r =  libusb_reset_device(devh);
    if (r < 0) {
        fprintf(stderr, "libusb_set_configuration error %d\n", r);
        goto out;
    }
    printf("Successfully reset usb \n");*/

    r = libusb_set_configuration(devh, aConfiguration);
    if (r < 0) {
        fprintf(stderr, "libusb_set_configuration error %d\n", r);
        goto out;
    }
    printf("Successfully set usb configuration 1\n");

    r = libusb_claim_interface(devh, aInterface);
    if (r < 0) {
        fprintf(stderr, "libusb_claim_interface error %d\n", r);
        goto out;
    }
    printf("Successfully claimed interface\n");
    r = libusb_bulk_transfer(devh, ENDPOINT_INT_OUT, data, 1, &actual2, 0);  
    if(r == 0 && actual2 == 1) //we wrote the 4 bytes successfully 
    {
        fprintf(stderr, "Successfully perform bulk transfer \n");
    }
    else
    {
    fprintf(stderr, "libusb_bulk_transfer error %d\n", r);
        goto out;
    }

    //r = libusb_bulk_transfer(devh, ENDPOINT_INT_OUT, data, 64, &actual, 0); 
    r = libusb_interrupt_transfer(devh, ENDPOINT_INT_OUT, data, 1, &actual, 0); 
    if(r == 0 && actual == 1) //we wrote the 4 bytes successfully 
    {
        fprintf(stderr, "Successfully perform interupt transfer \n");
    }
    else
    {
    fprintf(stderr, "libusb_interrupt_transfer error %d\n", r);
        goto out;
    }

    libusb_release_interface(devh, 0);
out:
    //     libusb_reset_device(devh);
    libusb_close(devh);
    libusb_exit(NULL);
    return r >= 0 ? r : -r;

}

Voilà ce que ça donne au final :

pi@raspberrypi ~/USB_Leonardo $ sudo ./test
Please enter an interface ID (integer value): 1
The interface used will be : 1
Please enter an configuration ID (integer value): 1
The configuration used will be : 1
Successfully initialise libusb
Successfully find the LVR Generic HID device
LINUX - we need to see if a kernel driver is active on the interface.
If yes we have to detach it to be able to claim the interface
No Kernel Driver active on this interface
Successfully set usb configuration 1
Successfully claimed interface
Successfully perform bulk transfer
[timestamp] [threadID] facility level [function call] <message>
--------------------------------------------------------------------------------
[ 0.000000] [000007ca] libusbx: error [submit_bulk_transfer] submiturb failed error -1 errno=22
libusb_interrupt_transfer error -1

et une led qui clignote 😉

2 thoughts on “Arduino Leonardo : USB Device

  1. Pingback: Site Web Jquery Mobile | Djynet

  2. Pingback: Merge ! | Djynet

Comments are closed.