Horti'Buddy - épisode 4 : Interfacer l'Arduino et le RaspberryPi avec I2C

Tuesday, April 21, 2015

Article dédié à la communication entre l’Arduino et le Raspberry Pi. On va d’abord ce documenter sur les différentes méthodes puis mettre en œuvre une d’entre elles. Je pense que c’est la partie la plus sioux du projet, miam !

Introduction

Pour le moment notre petit projet n’est qu’un bête site PHP/MySQL totalement coupé de la réalité. On aimerait, dans un premier temps, remonter les infos climatiques de nos différents espaces de culture. L’Arduino est doué pour utiliser des sondes mais il faut mettre en place le tuyau pour remonter ces valeurs dans notre outil web !

Les méthodes disponibles

Port série en USB

Voici un exemple mais j’ai rapidement écarté cette option, ça semble compliquer de passer des commandes complexes :
http://gladysproject.com/news/connecter-un-arduino-au-raspberry-pi

Protocole SPI

De loin, ça ressemble fort à I2C mais avec un câble dédié émission et un autre dédié réception, full duplex quoi. Il y a des limites mais visiblement le débit est bien meilleur et les infos transmises plus riches. Je vais voir si l’I2C montre rapidement ses limites, je pense surtout lorsqu’il y aura beaucoup de périph à checker sur l’Arduino. Le SPI serait une solution de repli intéressante au cas où.

Dans les topics de comparaison SPIvsI2C, on lit souvent que SPI est plus accessible mais ça semble demander plus de hardware. Je n’ai pas trop comprit le fonctionnement. Je le testerai peut être plus tard, en solution de repli.

Protocole XPL

J’ai croisé quelques geeks qui étaient à fond dans ce protocole. A première vue, c’est orienté domotique et pas nécessairement Arduino. C’est plutôt une architecture distribuée avec des briques autonomes. Si j’ai bien suivit, il n’y a pas de brique Linux (pour le LAMP).

Protocole I2C

On est rapidement orienté vers ce protocole dans nos recherches, il y a de nombreux exemples d’interface entre le RPi et l’Arduino. Certains sont très poussés, je trouve, comme ce robot pilotable depuis un smartphone :
https://goddess-gate.com/projects/fr/raspi/raspiduinorover

Je me suis basé sur son script Arduino pour écrire le mien :
https://github.com/aboudou/raspiduinorover/blob/master/Arduino/Arduino.ino

Ce tuto est intéressant car ciblé bus I2C et les commandes de base Wire :
http://www.pihomeserver.fr/2013/08/13/raspberry-pi-home-server-arduino-lier-les-deux-via-bus-i2c/

Ce tuto rentre dans le détail des registres, technique utilisée d’une certaine façon par la suite :
http://mchobby.be/wiki/index.php?title=ArduPi-I2C + les autres chapitres

Quel débit ? Il y aurait trois modes : 100kbits/s 400kbits/s et 3.4Mbits/s. Mais pas vu comment configurer cela. J’ai un petit doute sur les perfs du bordel, à voir pendant les tests.

Alimenter l’Arduino depuis le Raspberry

Avant de mettre en œuvre une de ces méthodes, il faut alimenter notre Arduino. Le Raspberry propose une patte +5V et une patte de masse, ça devrait suffir pour nos premiers bricolages. L’Arduino ou le Raspberry peuvent fournir du +5/GND au reste du monde si ils ont une source d’alim dédié à l’un d’entre eux. On peut aussi alimenter les deux via les pattes +5V et GND avec une alimentation commune, c’est surement cette solution que je retiendrais par la suite.

Pour le moment, le Raspberry est alimenté via l’USB et propage le 5V entre sa patte +5V et la patte Vin de l’Arduino, simple pour commencer :

Mise en place bus I2C

On va explorer ce protocole qui me semble parfait pour notre besoin. Le RPi sera le maitre du bus, il y aura un ou plusieurs Arduino esclaves. Le bus I2C peut avoir une bonne longueur si on utilise une sorte de répéteur, enfin un ampli quoi. Chaque membre du bus à une adresse, l’Arduino et le RPi ont des libs qui savent causer I2C. J’ai pas vérifié sur Pidora, peut être une mauvaise surprise (Dev I2C activé par défaut et paquet i2c-utils dispo dans les dépôts). La techno semble accessible, il y a deux méthodes : read et write en gros. La grosse zone floue est le passage de paramètres et donc le pilotage de l’arduino et de ces PIN.

Bref, niveau hardware il y a juste deux fils à ajouter clock et data :

Maintenant, il va falloir produire du code :S

Pré-requis Arduino

Là, on arrive aux choses sérieuses, surtout vu mon expérience avec les Arduino, c’est à dire walloo :D Bon à priori, c’est vraiment très accessible et j’arrive mieux à imaginer l’interaction entre les deux boiboites suite à mes lectures. On va d’abord jeter un coup d’oeil à la lib Wire utilisée par beaucoup de monde ainsi qu’aux bases de scripting sur Arduino. On laisse de coté la sonde DHT22 mais si ça se goupille bien, j’ajouterai cette dernière dans les essais (j’ai un doute sur le pull-up interne).

Initialement, j’avais imaginé téléverser le code Arduino depuis le Raspberry. Comme ça, on pouvait ajouter dans le site web des éléments pour modifier le code Arduino, l’uploader et reset l’Arduino. Mais visiblement, l’upload de code compilé est réservé à la phase de conception, pas très stable en mode “run” à priori. Le code chargé sur l’Arduino sera donc statique dans le sens où il ne sera pas modifié au fil de l’eau (j’y pensais pour les seuils de ventilation/température pour les intracteurs/extracteurs).

Du coup, on sera sur un mode plus classique, deux briques logicielles indépendantes qui communiquent entre elles des messages évolués. D’où l’utilisation de Wire qui possède une ordre de lecture et un ordre d’écriture, à nous de jouer sur les valeurs échangées pour programmer nos opérations.

Pour programmer et compiler, on passe par une sorte d’éclipse pour Arduino, il y a même une version Linux ! C’est dans cet éditeur qu’on installera les libs nécessaires, pas sur l’Arduino directement. Le script une fois compilé est poussé sur l’Arduino via le port USB. Donc on installe cet env de développement très simplement, suffit de télécharger le binaire et de l’exécuter.

On télécharge l’archive via un navigateur car il y a des rewrites/javascripts qui empêchent de WGET simplement :
http://arduino.cc/download_handler.php?f=/arduino-1.6.3-linux64.tar.xz

Puis en root :

mv /home/<mon_user>/Download/arduino-1.6.3-linux64.tar.xz /tmp/
cd /tmp
tar xvf arduino-1.6.3-linux64.tar.xz
mv arduino-1.6.3 /usr/local/

Enfin dans un shell en tant que votre utilisateur habituel :

/usr/local/arduino-1.6.3/arduino

Hop, l’outil se lance nickel ;-)

Pré-requis I2C sur le Raspberry Pi

Premièrement, on vérifie dans /dev la présence du bus I2C. A priori, sur Pidora, le module I2C n’est pas blacklisté, donc pas besoin de l’activer explicitement :

ll /dev/i2c*

Vous devez voir deux devices : i2c-0 et i2c-1.

Puis on installe les utilitaires pour I2C :

yum install i2c-tools

On a ainsi les binaires i2cdetect, i2cget et i2cset qui nous serviront par la suite.

Protocole maison

De mes lectures, il n’est pas possible de passer un paramètre avec l’event onRequest (lecture coté RPi), comment récupérer la valeur d’un de nos capteurs ? On va devoir concevoir des instructions complexes se basant sur les deux events à notre disposition : onRequest (Arduino balance des octets) et onReceive (Arduino reçoit des octets). Créer un sorte de protocole de communication en quelques sortes, coooooool :D

J’imagine un système en deux phases : Le RPi demande une opération à l’Arduino (onReceive), par exemple “lire sonde A et stocker la valeur”, puis le RPi demande la dernière valeur stockée (onRequest). Le RPi sait quelle valeur va avec quelle opération. Ça semble être une bonne approche car l’Arduino peut recevoir plusieurs octets avec onReceive et peut émettre lui aussi plusieurs octets avec onRequest.

En fouillant internet dans ce sens, on trouve des infos sur une méthode qui utilise des registres, c’est très proche de ce que je veux faire :) En gros on pilote un pointeur avec onReceive et on récupère la valeur qui correspond au registre d’adresse = pointeur. Pour mes consignes d’asservissement je vais avoir besoin de stocker plusieurs valeurs, donc au final, l’utilisation de registres semble obligatoire.

Essai 1 - En 2 étapes

Mon premier essai n’a pas fonctionné, mais m’a permis de mieux comprendre le fonctionnement de ce genre de communication. L’idée est d’exécuter une opération avec des paramètres, puis de récupérer le résultat dans un second temps. Vu du Raspberry, il s’agit d’appeler la commande i2cset puis la commande i2cget :

i2cset -y <id_bus> <id_slave> <key> <value>
i2cget -y <id_bus> <id_slave>

L’option -y permet d’éviter de confirmer l’envoi de la commande. L’id de bus peut être déterminé avec la commande i2cdetect. Je voulais passer mes trois paramètres avec une commande i2cset, exécuter l’opération, puis déclencher la réponse avec la commande i2cget, simple nan ? Ben raté, ça ne fonctionnera pas car j’ai trop de paramètres à passer :S

En effet, la commande set est conçue pour pousser un octet seulement, 2 octets au mieux (mode “w” pour word). Or, dans mon modèle, j’ai trois paramètres que je comptais passer simultanément en plus de déclencher le traitement. La deuxième étape étant de renvoyer le résultat stocké tout bêtement. J’ai beaucoup de mal coté Arduino à décoder le passage de paramètres, car la commande i2cset travaille avec un couple “clé:valeur” alors que de l’autre coté, dans onReceive, je veux “param1:param2:param3”.

Essai 2 - En 4 étapes

On garde la même idée de fond mais on va changer deux choses dans la forme. Premièrement, nos 3 paramètres (commandId, pinId, pinValue) seront passés en trois appels à la commande i2cset. Le traitement de l’opération est maintenant déclenchée lorsque on appelle la commande i2cget. Du coup, on est dans le cadre de la technique des registres citée plus haut. Ça me plait bien d’avoir des infos en retour d’un traitement, la commande i2cget le permet.

Il y a plus de requêtes, mais on reste proche de l’essai précédent :

Cette base semble viable. Pour le moment nous n’avons que deux commandes bidons, retourner la valeur pinId et retourner la valeur pinValue. Je vous liste les blocks de code mais la référence est le code sur GitHub :
https://github.com/ggambini/hortibuddy_arduino-i2c

Communication I2C

La partie I2C est très très simple, on crée une connexion et on définit nos deux fonctions de callback. C’est facile mais du coup limité, en particulier la taille des messages. Résumé du code pour l’I2C :

#include <Wire.h>
// I2C address
#define I2C_ADDRESS 0x03
void setup() {
  // On passe en slave sur le bus I2C avec l'adresse I2C_ADDRESS
  Wire.begin(I2C_ADDRESS);
 
  // Fonctions de callback pour l'I2C
  Wire.onReceive(getParameter);
  Wire.onRequest(execCommand);
}

C’est tout xD

Gestion des commandes

Le gros du traitement sera dans les fonctions dédiées aux commandes. Les deux fonctions ne font que de l’aiguillage, de variables pour la première, d’exécution de commande pour la seconde.

// OPERATION REG 
  byte commandId;
  int pinId;
  byte pinValue;
  byte paramId;
  byte paramData;
  int result;

// GetParameter - OnReceive
// Stock octet2 dans var d'index octet1
void getParameter(int nbOctets) {

  // Lecture - Octet1=Id, Octet2=data
  paramId = Wire.read();
  paramData = Wire.read();
  
  if (paramId == 0x01) {
    // Set commandId
    commandId = paramData;
  }else if (paramId == 0x02) {
    // Set pinId
    pinId = paramData;
  }else if (paramId == 0x03) {
    // Set pinValue
    pinValue = paramData;
  }
 
}

// ExecCommand - OnRequest
// Execute la fonction de nom commandId, renvoi le resultat
void execCommand() {

  switch (commandId) {
    case 0x01 :
      getPinId();
      break;  
    case 0x02 :
      getPinValue();
      break;
    case 0x03 :
      getTempDht22();
      break;
    case 0x04 :
      //getHumiDht22
      break;
    case 0x05 :
      getLightModule();
      break;
  }

  Wire.write(result);
  result = 0x00;
}

Upload de notre sketch

L’outil a créé un dossier Arduino/ dans votre home/, c’est ici qu’il sauvegardera les sketchs (= scripts pour Arduino) et c’est ici qu’il trouvera les libs supplémentaires. On va cloner le projet Git de notre script :

cd /home/<mon_user>/Arduino/
git clone https://github.com/ggambini/hortibuddy_arduino-i2c

J’ai eu un message d’erreur lors de l’upload du code, accès refusé sur le dev correspondant à l’USB où est branché l’Arduino. Il y a une conf pour pouvoir écriture sur le port USB depuis l’IDE lancé en tant que <mon_user>. En root, lancez les commandes suivantes :

usermod -a -G dialout <mon_user>
chmod a+rw /dev/ttyACM3

On coupe l’alim du RPi puis on alimente l’Arduino en USB. On upload le sketch arduino depuis le repo local GitHub. On débranche l’USB de l’Arduino. On alimente ensuite le Raspberry et on ouvre une session SSH en root.

Test du “protocole”

La commande i2cdetect doit trouver l’Arduino sur le bus :

i2cdetect -y 1

On voit notre Arduino sur l’adresse 0x03 :

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          03 -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Pour nos essais, on va définir les paramètres pinId et pinValue puis demander la valeur de ces paramètres. Dans un shell sur le Raspberry, on utilise les commandes i2c.

On met la valeur 0x11 dans le registre 0x02 c’est à dire pinId :

i2cset -y 1 0x03 0x02 0x11

On met la valeur 0x13 dans le registre 0x03 c’est à dire pinValue :

i2cset -y 1 0x03 0x03 0x13

On met la valeur 0x01 dans le registre 0x01, qui correspond à la commande getPinId :

i2cset -y 1 0x03 0x01 0x01

On fait exécuter la commande qui doit nous renvoyer pinId :

i2cget -y 1 0x03
0x11

On change de commande pour getPinValue et on réexécute :

i2cset -y 1 0x03 0x01 0x02
i2cget -y 1 0x03
0x13

Et mais on dirait que ça marche !?!!!! Bon pour le moment, on travaille avec des octets tout bête.

Remonter des infos d’une sonde

Encouragé par les premiers résultats, je vais essayer un cas un peu plus concret, lire la température sur une sonde DHT22. Après pas mal d’essais, le code semble bon mais j’ai une erreur au moment d’interroger la sonde. Je n’ai pas de résistance pour faire le pull-up indiqué dans la doc, l’erreur survient visiblement lors de la lecture.

Ça m’embête d’être coincé comme ça, dur d’avoir du log quand on a que l’I2C. Du coup, on va utiliser un capteur beaucoup plus simple, une sonde de luminosité. Il embarque en plus de la photo résistance, un comparateur LM393. Cet ensemble renvoi qu’une seule valeur binaire, 0 si lumière et 1 si ombre. Pauvre en info mais utile pour débuter :D

On alimente le capteur et on le relie à une entrée numérique sur l’Arduino :

On ajoute une petite fonction dans notre code Arduino, on l’appellera avec commandId=0x05 :

void getLightModule() {
  pinMode(pinId, INPUT);
  result = digitalRead(pinId);
}

On upload le code, on éteint l’Arduino et on rebranche le RPi. Sur le RPi, dans un shell, on va tester notre fonction. D’abord on définit pinId et commandId, puis on fait exécuter.

On définit pinId=2 sur le slave 3 du bus 1 :

i2cset -y 1 3 2 2

On définit commandId sur le même slave :

i2cset -y 1 3 1 5

On exécute la fonction getLightModule :

i2cget -y 1 3
0x00

Le capteur est éclairé, donc la valeur est cohérente. On va pas s’enflammer avant de tester avec le capteur dans le noir. On occulte la photo résistance. Il y a une led sur le miens qui indique l’état :)

On réexécute la fonction getLightModule (commandId et pinId toujours en mémoire) :

i2cget -y 1 3
0x01

Le capteur indique qu’il est à l’ombre, le potentiomètre sur la platine permet de régler le seuil. On peut recommencer des relevés, en pleine lumière et à l’ombre, les infos remontent ! Ça marche, yeah !! Je détaillerai dans la conclusion, mais pouah j’en ai chié et je suis content d’enfin réussir à lire une vraie valeur sur le RPi !!!

Conclusion

Quel pavé ! Ca fait quelques temps qu’on avait pas vu un article de cette taille :D Et je n’ai effleuré qu’un dixième du sujet, on a rapidement envi d’automatiser tout et rien ! J’ai bien kiffé le mini défi mais ça m’a demandé pas mal de réflexion … L’accessibilité laisse rapidement la place à une certaine complexité, on n’est plus habitué aux systèmes très proches de la couche physique (genre pousser quelques octets sur un bus LOL).

Il ne faut pas se laisser abuser par cette électronique minimaliste (par rapport à un PC), l’Arduino est très flexible ! Au final, j’ai passé une majeur partie du temps à réfléchir et me documenter, pour très peu de code produit xD La doc n’est pas très détaillée mais les nombreux tutos répondent à toutes nos questions.

Le bus I2C correspond bien aux besoins du projet, je reste sur ce choix après ces tests. Je pourrais ainsi utiliser plusieurs Arduino si je me retrouve limité en entrées-sorties ou en distance de câble pour les sondes. Il faut maintenant coder la classe PHP qui appellera i2cset et i2cget. Ainsi que l’interface pour configurer tout ça et le cron.

Références

Détails des entrés/sortie du Raspberry Pi model B :
http://developer-blog.net/wp-content/uploads/2013/09/raspberry-pi-rev2-gpio-pinout.jpg

Détails des entrés/sortie d’ARduino Mega 2560 :
http://f1mvp.perso.sfr.fr/Robotic/arduino.htm

Une bonne documentation du bus I2C avec Arduino :
http://mchobby.be/wiki/index.php?title=ArduPi-I2C

Un projet complet Arduino et RPi avec code source bien beau :
https://goddess-gate.com/projects/fr/raspi/raspiduinorover

La police d’écriture utilisée sur les photos (pas de note de licence sur son site nicksfonts.com à l’heure actuelle) :
http://www.dafont.com/fr/dymaxion-script.font

Les schémas ont été réalisés avec Dia, les sources sont dispos ici :
hortibuddy-wire1.dia
hortibuddy-wire2.dia
hortibuddy-i2c_proto1.dia
hortibuddy-i2c_proto2.dia

ArticleJardinGeekarduinoi2ci2cgeti2csetmega 2560onReceiveonRequestraspberry piWire.h
Le contenu de ce site est sous licence Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)

Gamb

DIY

Projet houblonnière : Premières expériences

Pommeau de vitesse sous pression