Bus I2C, liaison NXT carte Arduino, partie 1.

Le bus I2C est un bus de données série synchrone bidirectionnel half-duplex. Plusieurs équipements peuvent se connecter au bus I2C. Les échanges ont toujours lieu entre un seul maître et un ou plusieurs esclaves toujours à l’initiative du maître. Après cette définition quelque peu « Wikipédiaine » et avoir vu quelques articles sur le net, je me suis décidé à relier une brique NXT à une carte Arduino.

Le matériel:

  • Une carte Arduino UNO REV3
  • Une brique NXT
  • Un cable de liaison LEGO sacrifié
  • 2 résistances de 47 K

La connexion entre le NXT et la carte Arduino est réalisée par deux lignes, une ligne de données bidirectionnelles SDA et une ligne d’horloge de synchronisation bidirectionnelle SCL. Les masses doivent être communes.

Comme je ne possède pas de connecteur NXT, j’ai sacrifié un câble en le coupant d’un côté. Le câble contient 6 fils, un vert, un bleu, un jaune, un noir, un blanc et un rouge. Les fils qui m’intéressent sont les fils bleu (SDA NXT), jaune (SCL NXT), vert (4,3V venant du NXT) et le rouge (GND).

Plan de cablâge:

Ne pas oublier de mettre les deux résistances car l’I2C sur le NXT nécessite des résistances de pull-up. Les valeurs typiques à utiliser sont de 82K, mais n’en ayant pas sous la main, j’ai mis des résistances de 47K. Vous trouverez plus d’informations sur le bus I2C à cette adresse.

Le programme:

Chaque périphérique I2C a une adresse unique, il faut fixer l’adresse de l’esclave connecté sur le port S1 du NXT (carte Arduino) à 0x0A. Petite particularité, il existe deux versions du bus I2C, la version 7 bits et la version 8 bits. LEGO a choisi d’utiliser la version 7 bits, avec un décalage vers la gauche afin de faire apparaître le dernier bit correspondant à l’action effectuée, lecture (valeur 1) ou écriture (valeur 0).

Programme Arduino:

// A4 - SDA
// A5 - SCL
#include
byte read_register = 0x00;
String valeur,valeur1;

void setup()
{
 Wire.begin(0x0A); // Adresse carte Arduino
 Wire.onRequest(requestEvent); // Envoi data
 Wire.onReceive(receiveI2C); // Reception data
 Serial.begin(9600);
}

void loop()
{
 valeur = String(analogRead(A0),DEC); // conversion entrée ana A0 de Int en String
 valeur1 = String(analogRead(A1),DEC);// conversion entrée ana A1 de Int en String
 delay(100);
}

// Demande du NXT la procédure est lancée
void receiveI2C(int bytesIn)
{
 read_register = bytesIn;
 while(1 < Wire.available())
 {
 read_register = Wire.read();
 Serial.print(read_register);
 }
 int x = Wire.read(); // Lecture octet du NXT
 Serial.println(x); // Affichage octet du NXT sur moniteur série
}
// procédure envoie des valeurs ana A0 ou A1 au NXT pour affichage
void requestEvent()
{
 if(read_register == 0x01){
 int v= analogRead(A0);
 Serial.println(v);
 char buffer[32];
 valeur.toCharArray(buffer, 10); // conversion string en char
 Wire.write(buffer); // envoi de la réponse au NXT pour affichage
 }
 if(read_register == 0x02){
 int v= analogRead(A1);
 Serial.println(v);
 char buffer[32];
 valeur1.toCharArray(buffer, 10); // conversion string en char
 Wire.write(buffer); // envoi de la réponse au NXT pour affichage
 }

}

Ligne 9: affectation de l’adresse à la carte Arduino (esclave), 0x0A qui correspond en binaire à 0000 1010. Dans le programme RobotC avec le décalage vers la gauche et ajout du zéro (écriture au niveau de l’esclave) on a l’adresse 0x14 correspondant en binaire à 0001 0100.

Dans la boucle Loop, je lis les entrées analogiques A0 et A1 toutes les 100ms. Dès que le NXT envoie une demande, la procédurevoid receiveI2C(int bytesIn) est lancée, elle récupère la demande du NXT, ici 0x01 ou 0x02. En fonction de la valeur choisie, la procédure void requestEvent() envoie au NXT l’information demandée.

Programme RobotC:

#pragma config(Sensor, S1,TIR,sensorI2CCustom)

#define ARDUINO_ADDRESS 0x14 // adresse carte Arduino
#define ARDUINO_PORT S1

ubyte I2Cmessage[22];
char I2Creply[20];
string str1;
string str2;
string str3;
string strtotal;
int valeur = 0;
int j = 1;

void i2c_read_registers_text(ubyte register_2_read, int message_size, int return_size){
 memset(I2Creply, 0, sizeof(I2Creply));
 message_size = message_size+3;

I2Cmessage[0] = message_size;
 I2Cmessage[1] = ARDUINO_ADDRESS;
 I2Cmessage[2] = register_2_read;
 sendI2CMsg(S1, &I2Cmessage[0], return_size);
 wait1Msec(20);

readI2CReply(ARDUINO_PORT, &I2Creply[0], return_size);

int i = 0;
 while(true){
 writeDebugStream("%c", I2Creply[i]);
 str1 = I2Creply[0]; // Mise en memoire des valeurs pour affichage
 str2 = I2Creply[1]; // Mise en memoire des valeurs pour affichage
 str3 = I2Creply[2]; // Mise en memoire des valeurs pour affichage
 wait1Msec(50);
 i++;

if(I2Creply[i] == 0){
 if (j == 1){
 strtotal = str1 + str2; // Mise en forme pour affichage
 strtotal = strtotal + str3; // Mise en forme pour affichage
 valeur = atoi(strtotal); // convertion string vers int avec atoi()
 nxtDisplayString(2, "Input A0:%3u", valeur); // affichage ecran NXT
 }
 if (j == 2){
 strtotal = str1 + str2; // Mise en forme pour affichage
 strtotal = strtotal + str3; // Mise en forme pour affichage
 valeur = atoi(strtotal); // convertion string vers int avec atoi()
 nxtDisplayString(4, "Input A1:%3u ", valeur); // affichage ecran NXT
 }
 if (j == 1){
 j = 2;
 }
 else{
 j = 1;
 }

break;
 }
 }
 writeDebugStreamLine(" ");
}

task main()
{
 while(true){
 if (j == 1){
 i2c_read_registers_text(0x01, 0,3); // Demande valeur input A0 3 octets
 wait1Msec(5000);
 }
 if (j == 2){
 i2c_read_registers_text(0x02, 0, 3); // Demande valeur input A1 3 octets
 wait1Msec(5000);
 }
 }
}

En lançant les deux programmes, on obtient:

De gauche à droite, on a le moniteur série de l’IDE Arduino, la fenêtre de debug de RobotC et l’affichage sur le NXT. On obtient des valeurs cohérentes à quelques pions près.

Le test est concluant et est fonctionnel, je vais approfondir avec l’utilisation des sorties numériques de la carte Arduino.

Quelques articles intéressants sur l’I2C du NXT: article 1 et article 2.

Version optimisée du code Arduino:


// A4 - SDA
// A5 - SCL
#include <Wire.h>
byte read_register = 0x00;
String valeur,valeur1;
char buffer[32], buffer1[32];

void setup()
{
Wire.begin(0x0A); // Adresse carte Arduino
Wire.onRequest(requestEvent); // Envoi data
Wire.onReceive(receiveI2C); // Reception data
Serial.begin(9600);
}

void loop()
{
valeur = String(analogRead(A0),DEC); // conversion entrée ana A0 de Int en String
valeur.toCharArray(buffer, 10); // conversion string en char
valeur1 = String(analogRead(A1),DEC);// conversion entrée ana A1 de Int en String
valeur1.toCharArray(buffer1, 10); // conversion string en char
delay(100);
}

// Demande du NXT la procédure est lancée
void receiveI2C(int bytesIn)
{
read_register = bytesIn;
while(1 < Wire.available())
{
read_register = Wire.read();
}
}
// procédure envoie des valeurs ana A0 ou A1 au NXT pour affichage
void requestEvent()
{
if(read_register == 0x01){
Wire.write(buffer); // envoi de la réponse au NXT pour affichage
}
if(read_register == 0x02){
Wire.write(buffer1); // envoi de la réponse au NXT pour affichage
}
}

6 réflexions sur « Bus I2C, liaison NXT carte Arduino, partie 1. »

  1. Excellent ça !!! Je suis pile poil dedans avec de nombreux capteurs I²C, des Arduino et des Raspberry PI. Je trouve ça super cool. Je vais me permettre quelques suggestions d’améliorations de ton code Arduino.
    * Attention avec les interruptions : il ne faut pas faire de Serial.print à ce moment car potentiellement ça peut planter l’Arduino (les dernières versions de Serial utilisent une interruption et donc ça entre en conflit (pas d’interruption dans une interruption)).
    * De manière globale, le code d’une interruption doit être le plus court possible et ne pas utiliser Serial (ligne 29, 32, 39 et 46) ni utiliser Delay. Dans l’idée, il faut positionner une variable pour savoir qu’une interruption a eu lieu et traiter ça dans Loop(). De même, il vaut mieux envoyer la dernière valeur lue que d’effectuer une lecture pendant cette interruption ligne 38 et 45. En plus tu le fais bien car c’est bien valeur et valeur1 que tu envoies (et pas v que tu viens juste de lire sur ces 2 lignes 38 et 45) et dont tu ne fais rien (à part utiliser Serial).
    * Histoire de bien optimiser le tout, je sortirais la déclaration du tableau de la procédure (ligne 40 et 47) et je préparerais le buffer (du coup il en faut 2) dans Loop(). Comme ça lors de l’interruption, il n’y a qu’à envoyer le buffer 1 ou 2 en fonction de ce qui est demandé. Cette valeur demandée est d’ailleurs lue ligne 28 et pas 31 comme ton commentaire l’indique. La preuve: tu utilises bien ad_register ligne 37 et 44 (et pas cette variable x).
    * Côté RobotC je ne maîtrise pas, je déclarerais tout de même les tableaux lignes 6 et 7 de longueur 32 (à la place de 22 et 20) qui est la taille maxi d’une trame I²C (histoire d’être certain qu’il n’y ait pas de dépassement de capacité).

  2. Merci Mr BORIS pour ce commentaire, il y a beaucoup de Serial.println pour vérifier les données envoyées. Je viens de remettre un version optimisée.

  3. Hello, salut Boris, c’est Dada,
    Bonjour Ubik, on ne se connait pas, mais j’ai vu ton pseudo quelques fois sur play-different si je ne me trompe…

    J’ai récupéré il y a peu un set mindstorm, je me renseigne donc…et suis tombé par un heureux hasard sur ce blog en me documentant sur les interactions mindstorm-arduino en I2c !

    Une question pour les experts : existe-t-il à votre connaissance un block lego NXT-G I2C déja prêt à l’emploi pour remplacer le code ROBOTc afin de d’utiliser le firware d’origine Lego?

    J’ai vu ça, mais je ne sais pas trop si ça ferait l’affaire, qu’en pensez vous ?
    http://www.quantumtorque.com/content/view/28/29/

    http://www.teamhassenplug.org/NXT/NXTGAdditions.html
    (IIcRead et IIcWrite).

    A+
    David

    1. Il n’existe pas de bloc Lego pour remplacer le code NXT, mais il faudrait tester avec les blocs IIC_Read.zip et IIC_Write.zip et voir ce que tu peux faire.

      Ubik75

Répondre à ubik75 Annuler la réponse

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *