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 😉