Bonjour yann377
Je t’ai fait un exemple de mise en réseau de plusieurs Arduino et, ce, par un bus i2C.
Il y a un Arduino maître (MM) et des Arduino esclaves (MS), le tout, adresse et noms est géré dans le fichier commun ARDFR_yann377_R2D2_Common.h.
Chaque MS a un nom :
char* msPseudo[] = {"TETE", "BRAG","BRAD", "PIEDG", "PIEDD"}; // Pseudo des MS
Et une adresse :
int msAddresses[] = {64, 65, 66, 67, 68}; // Adresses des MS
Pour ajouter un MS, il suffit de mettre son nom et son adresse dans les tableaux respectifs.
L’adresse du MS s’introduit, au début, au moyen de la commande MSADRESSE64, par exemple et est sauvée en EEPROM.
Cette « maquette » de MS est équipée pour gérer des LED (3), des LED RGB (4 de type WS2812) et des servo (2). Tout est extensible, les paramètres sont en tableaux.
Ces périphériques sont commandables depuis la ligne de commande (à 115200), les commandes reconnues sont :
MSADRESSE65
par ex. Adresse i2c du module
LEDnnnv
= LED numéro de 0 ä 999 valeur 0 ou 1 LED0011
LRGBnnnv
= LED numéro de 0 ä 999 valeur selon enum lgrbCoulIndex LRGB0034
SERVOnnna
= Servo numéro de 0 ä 999 angle a
Voir void cmdExecute()
.
Au premier lancement du MS, il y aura, dans le moniteur :
Adresse de MS 444 0x1bc INCONNUE!!!!
Adresses connues
TETE 64 0x40
BRAG 65 0x41
BRAD 66 0x42
PIEDG 67 0x43
Il faudra introduire l’adresse du MS par MSADRESSE64 par exemple.
Si le MS a déjà une adresse il doit y avoir :
MS TETE adresse = 64 0x40
Taille des trames: 13
Debut programme
Le MM peut commander les périphériques d’un MS, via le bus i2C en faisant précéder la commande du nom du MS. Ainsi, pour mettre ON la LED 1 de BRAD, il faut envoyer BRAD>LED0011 ou mettre le servo 0 de PIEDD à 75° PIEDDservo00075. Maximum 20 caractères (char command[20];
).
Il y a un affichage LCD i2C sur le MM, son adresse est dans la variable :
const int lcdI2cAddress = 0x3f; // Adresse de l'affichage LCD 20x4 ou 0x27
Dans la « mécanique » de communication entre MM et MS, il y a un type de trame de données dans le sens MM > MS (i2cDataMmFrame
) et un autre dans le sens MS > MM (i2cDataSlFrame
), si nécessaire, on pourra revenir en détail sur ces données échangées.
Pour installer ce réseau, il y a 3 fichiers, le sketch du MM ARDFR_yann377_R2D2_MM.ino
, le sketch du MS ARDFR_yann377_R2D2_MS.ino
et le fichier de paramètres commun ARDFR_yann377_R2D2_Common.h
.
Dans le fichier Kit R2D2.zip
, il y a ces fichiers dans leur répertoire respectifs.
A l’ouverture du sketch du MM ou du MS, il y a au tout début, cette ligne :
#include <ARDFR_yann377_R2D2_Common.h>
Cliques à droite sur cette ligne et choisi Go to Definition
, ce qui ajoute un onglet qui donne accès au fichier commun.

Le programme du MM :
/*
Name: ARDFR_yann377_R2D2_MM.ino
Created: 01.03.2024
Author: jpbbricole/yann377
https://forum.arduino.cc/t/bluetooth-hc-05-envoi-des-commandes-string/1228717/14
Remarque: Commande d'accessoires du robot R2D2
*/
#include <ARDFR_yann377_R2D2_Common.h> // Paramètre communs MM et MS
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
//------------------------------------- i2C network
int i2cNetPacketIndex = 0;
unsigned long netScanTempo = 250; // Interrogation du réseau
unsigned long netScanMillis = millis(); // Interrogation du réseau chrono
//------------------------------------- i2C Data
i2CpacketCommandsDef i2cDataMmFrame; // Trame de données du MM vers MS
i2CpacketDef i2cDataSlFrame[msNumber];
String i2cPayloadMaStr = "";
//------------------------------------- Module Slave MS
i2cModuleDef msInUse; // MS en cours d'utilisation
//---------------------- Réception de commandes du moniteur
bool cmdRxNew = false; // Si une nouvelle commande a été reçue
String cmdRXTexte = ""; // Texte de la commande
//------------------------------------- LCD
const int lcdI2cAddress = 0x3f; // Adresse de l'affichage LCD 20x4 ou 0x27
const boolean displClsEol = true; // Si effacement fin de ligne
const int lcdI2cRowsNbr = 4; // Affichage LCD nombre de lignes
const int lcdI2cColsNbr = 20; // Affichage LCD nombre de colonnes
LiquidCrystal_I2C lcd(lcdI2cAddress,lcdI2cColsNbr, lcdI2cRowsNbr);
String displTextLine1 = "", displTextLine2 = "", displTextLine3 = "", displTextLine4 = "";
const String displaySeparator = "-----------------------------------------------";
void setup()
{
Serial.begin(115200);
Wire.begin();
lcdInitialisation();
Serial.println(F("\nR2D2 MM"));
displTextLine1 = "R2D2 MM"; lcdlDisplayFull();
msTopologyList();
msSelection(msAindex);
}
void loop()
{
//---------------------- Monitor commands
if (cmdRxNew) // Arduino IDE monitor
{
cmdExecute(cmdRXTexte);
cmdRXTexte = "";
cmdRxNew = false;
}
}
//===================================== Module slave (MS)
void msSelection(int msIndex)
{
msInUse.indexInNet = msIndex;
msInUse.i2cAdresse = msAddresses[msIndex];
msInUse.pseudo = msPseudo[msIndex];
}
void msSelectionByPseudo(String msFind)
{
int msFound = msGetIndexByPseudo(msFind);
if (msFound != -1) // Si pseudo trouvé
{
msSelection(msFound);
}
else
{
Serial.println("\n" + displaySeparator);
Serial.println("MS pseudo: " + msFind + " INCONNU!!!");
Serial.println(displaySeparator + "\n");
}
}
//------------------------------------- Retourne l'index du MS en fonction de son pseudo
int msGetIndexByPseudo(String pseudoFind)
{
pseudoFind.toUpperCase();
int retVal = -1;
for(int i = 0; i < msNumber; i++)
{
String arrayPseudo = (String)msPseudo[i];
arrayPseudo.toUpperCase();
if (arrayPseudo == pseudoFind)
{retVal = i;}
}
return retVal;
}
//------------------------------------- Liste la topologie du reseau
void msTopologyList()
{
Serial.println("\n" + displaySeparator);
Serial.println("Topologie du reseau");
Serial.println(displaySeparator);
for(int i = 0; i < msNumber; i++)
{
Serial.println((String)msPseudo[i] + "\t" + String(msAddresses[i]) + "\t0x" + String(msAddresses[i], HEX));
}
Serial.println(displaySeparator + "\t");
}
void msSendCommand(int msIndex, String msCmd)
{
Serial.println("Send " + msCmd + " to " + msPseudo[msIndex]);
msSelection(msIndex);
i2cSlSendFrame(msInUse.i2cAdresse, i2cFrFctCommand, msCmd);
}
//===================================== Trames i2C (Frames)
//------------------------------------- i2cSlave send data
void i2cSlSendFrame(int msAddress, int frFunction, String plString)
{
byte i2Cheader = 1; // Start
plString.toCharArray(i2cDataMmFrame.command, sizeof(i2cDataMmFrame.command)+1);
i2cDataMmFrame.frameIndex ++;
i2cDataMmFrame.function = frFunction;
Wire.beginTransmission (msAddress);
Wire.write(i2Cheader); // Pour differencier du scan i2c qui vaut -1
Wire.write((byte *)&i2cDataMmFrame, sizeof i2cDataMmFrame);
Wire.endTransmission ();
}
//------------------------------------- i2cSlave request data du MA selectionne (msIndex)
void i2cSlRequestData(int msIndex)
{
i2cNetPacketIndex ++;
Serial.println(msInUse.pseudo + " <Requ> ");
Wire.requestFrom(msAddresses[msIndex], sizeof i2cDataSlFrame[msIndex]);
Wire.readBytes((byte*)&i2cDataSlFrame[msIndex], sizeof i2cDataSlFrame[msIndex]);
Serial.println("\t" + msInUse.pseudo + " <Answ> ");
displTextLine2 = (String)msPseudo[msAindex] + " " + String(i2cDataSlFrame[msAindex].packetPayloadInt[0]);
displTextLine3 = (String)msPseudo[msBindex] + " " + String(i2cDataSlFrame[msBindex].packetPayloadInt[0]);
}
//------------------------------------- Commandes
void cmdExecute(String cmdStr)
{
int flagPos = 0;
String msPseudo = "";
String msCommand = "";
String cmdError = "";
int msIndex = 0;
cmdStr.toUpperCase();
flagPos = cmdStr.indexOf(">"); // Si c'est une commande pour un MS
if (flagPos > 2)
{
msPseudo = cmdStr.substring(0, flagPos);
msIndex = msGetIndexByPseudo(msPseudo);
if (msIndex > -1) // Pseudo connu
{
msCommand = cmdStr.substring(flagPos +1);
msSendCommand(msIndex, msCommand); // modules MS sur i2C
}
else
{
cmdError = msPseudo;
}
}
else
{
cmdError = cmdStr;
}
if (cmdError != "")
{
Serial.println(cmdStr + " !!!!");
}
}
//---------------------------------------------------------------------------------------
// Réception de commandes depuis le moniteur
//---------------------------------------------------------------------------------------
void serialEvent() // IDE monitor
{
if (Serial.available())
{
cmdRXTexte = Serial.readStringUntil('\n');
// Nettoyage des caractères indésirables, CR, LF, TAB... et les espace de début et de fin
cmdRXTexte.trim();
cmdRxNew = true;
}
}
//===================================== Affichage LCD
void lcdInitialisation()
{
lcd.init(); // LCD initialisation
lcd.noBacklight();
delay(500);
lcd.backlight();
}
//------------------------------------- Affichage des lignes de l'affichage
void lcdlDisplayFull()
{
lcdDisplayColRow(0, 0, displTextLine1, displClsEol);
lcdDisplayColRow(0, 1, displTextLine2, displClsEol);
lcdDisplayColRow(0, 2, displTextLine3, displClsEol);
lcdDisplayColRow(0, 3, displTextLine4, displClsEol);
}
//---------------------------------------------------------------------------------------
// Diverses routines pour faciliter l'usage de l'affichage LCD
//---------------------------------------------------------------------------------------
String lcdEmptyLine = " ";
//---------------------- Effacement écran
void lcdCls()
{
lcd.clear();
}
//---------------------- Effacement de la ligne rowNum
void lcdClsRow(int rowNum)
{
lcdDisplayColRow(0, rowNum, lcdEmptyLine, !displClsEol);
}
//---------------------- Afficher texte Colonne Ligne
void lcdDisplayColRow(int lcdCol, int lcdRow, String lcdText, boolean clsEolIf)
{
if (clsEolIf)
{
int emptyLen = lcdCol + lcdText.length();
lcdText += lcdEmptyLine.substring(emptyLen);
}
lcd.setCursor(lcdCol,lcdRow);
lcd.print(lcdText);
}
Le programme de MS :
/*
Name: ARDFR_yann377_R2D2_MS.ino
Created: 01.03.2024
Author: jpbbricole/yann377
https://forum.arduino.cc/t/bluetooth-hc-05-envoi-des-commandes-string/1228717/14
Remarque: Commande d'accessoires du robot R2D2
Reconnait les commandes MSadresse64
*/
#include <ARDFR_yann377_R2D2_Common.h> // Paramètre communs MM et MS
#include <Wire.h>
#include <Adafruit_NeoPixel.h> // Gestion des LED RGB Neopixel https://github.com/adafruit/Adafruit_NeoPixel
#include <Servo.h>
#include <EEPROM.h> // Pour la sauvegarde des paramètres
//------------------------------------- MS
i2cModuleDef MS; // Création de l'objet Module Slave
//------------------------------------- i2C
i2CpacketDef i2cDataFrameMs; // Paquet de données du slave vers MM
i2CpacketCommandsDef i2cCommandFrameMm; // Paquet de données du master (commandes)
//String i2cPayloadSlStr = "";
volatile int i2cActivityCpt = 0; // Compteur d'activité sur bus i2C
const int activityLedPin = LED_BUILTIN; // Patte du bus de la LED "locale"
//---------------------- LED
const int ledPin[] = {5, 6, 7}; // Pin des LED
const int ledNombre = sizeof(ledPin) / sizeof(ledPin[0]);
const int ledEtatOn = HIGH; // Etat pour allumer une LED
//------------------------------------- LED RGB (lrgb)
const int lrgbNombre = 4; // Nombre de LED RGB
const int lrgbBusPin = 8; // Connexion bus
Adafruit_NeoPixel lrgb = Adafruit_NeoPixel(lrgbNombre, lrgbBusPin, NEO_GRB + NEO_KHZ800); // Création de l'objet Neopixel lrgb
enum lgrbCoulIndex {lrgbCoulEteint, lrgbCoulVert, lrgbCoulBleue, lrgbCoulRouge, lrgbCoulOrange, lrgbCoulJaune, lrgbCoulBlanc, lrgbnCoulNombre};
uint32_t lrgbColors[lrgbnCoulNombre]; // Valeurs RGB pour les couleurs (Tableau)
const int lrgbBrightMax = 40; // LED Luminosité maximum
//------------------------------------- Servo
const int servoPin[] = {9, 10}; // Pin des Servo
const int servoNombre = sizeof(servoPin) / sizeof(servoPin[0]);
Servo servo[servoNombre]; // Création des objets servo
//---------------------- Réception de commandes du moniteur
bool cmdNew = false; // Si une nouvelle commande a été reçue
String cmdTexte = ""; // Texte de la commande
//------------------------------------- EEPROM
#define eePromOffset 0 // Position en EEPROM du premier paramètre
struct setupStructureDef
{int i2cAddress;};
setupStructureDef setupParams;
const String displaySeparator = "-----------------------------------------------";
void setup()
{
Serial.begin(115200);
setupRestore(); // Lecture du setupo en memoire
lrgbInitialisation(); // Initialisation des LED RGB
lrgbAllumer(0, lrgbCoulRouge, 0);
msInitialisation();
Wire.begin(MS.i2cAdresse); // join i2c bus with address i2cSlaveAddress
Wire.onRequest(i2cRequestEvent); // register event
Wire.onReceive(i2cReceivedFrame); // register Rx commande
//--------------------------------- Entrées/sorties
pinMode(activityLedPin, OUTPUT);
digitalWrite(activityLedPin, 0);
for (int l = 0; l < ledNombre; l ++)
{
pinMode(ledPin[l], OUTPUT);
analogWrite(ledPin[l], !ledEtatOn); // Eteindre les LED
}
for (int s = 0; s < servoNombre; s ++) // Initialisation des servo
{
servo[s].attach(servoPin[s]);
}
delay(500);
Serial.println("\n" + displaySeparator);
Serial.println("MS " + MS.pseudo + " adresse = " + " " + String(MS.i2cAdresse) + " 0x" + String(MS.i2cAdresse, HEX));
Serial.println("Taille des trames: " + String(sizeof(i2cDataFrameMs)+1));
Serial.println(displaySeparator);
lrgbAllumer(0, lrgbCoulVert, 500);
Serial.println(">>>> Debut programme >>>>");
}
void loop()
{
//--------------------------------- Ecoute du port serie
serialEvent();
if (cmdNew) // Si une nouvelle commande depuis le moniteur
{
cmdExecute(cmdTexte);
cmdTexte = "";
cmdNew = false;
}
//--------------------------------- Led d'activite du bus i2C (activityLedPin)
if (i2cActivityCpt > 2)
{
digitalWrite(activityLedPin, !digitalRead(activityLedPin));
i2cActivityCpt = 0;
}
}
//------------------------------------- Module Slave
void msDataUpdate()
{
int ledStatus = 0;
for (int l = 0; l < ledNombre; l ++)
{
ledStatus += (digitalRead(ledPin[l]) << l);
}
Serial.println(ledStatus);
i2cDataFrameMs.packetPayloadInt[0] = 12;
i2cDataFrameMs.packetPayloadInt[1] = ledStatus;
i2cDataFrameMs.packetPayloadInt[2] = 0;
}
void msInitialisation()
{
MS.i2cAdresse = setupParams.i2cAddress;
int msIndex = msAddressGetIndex(MS.i2cAdresse);
if (msIndex > -1)
{
MS.indexInNet = msAddressGetIndex(MS.i2cAdresse);
MS.pseudo = msPseudo[MS.indexInNet]; // Nom du MS
}
else
{
Serial.println("\n" + displaySeparator);
Serial.println("Adresse de MS " + String(MS.i2cAdresse) + " 0x" + String(MS.i2cAdresse, HEX) + " INCONNUE!!!!");
msAddressKnowed();
delay(3000);
}
}
//------------------------------------- Retourne l'index du MS en fonction de son adresse i2C
int msAddressGetIndex(int wAddress)
{
int retVal = -1;
for(int i = 0; i < msNumber; i++)
{
if (msAddresses[i] == wAddress)
{retVal = i;}
}
return retVal;
}
//------------------------------------- Retourne l'index du MS en fonction de son adresse i2C
void msAddressKnowed()
{
Serial.println(displaySeparator);
Serial.println("\nAdresses connues");
for(int i = 0; i < msNumber; i++)
{
Serial.println((String)msPseudo[i] + " " + String(msAddresses[i]) + " 0x" + String(msAddresses[i], HEX));
}
Serial.println(displaySeparator + "\n");
}
//------------------------------------- i2C
//------------------------------------- Appelé a chaque request du MM et renvoie la structure i2cDataSlFrame
void i2cRequestEvent()
{
msDataUpdate(); // Remise a jour des donnees du MS
Wire.write((byte *)&i2cDataFrameMs, sizeof i2cDataFrameMs);
i2cActivityCpt ++;
}
//------------------------------------- Appelé a chaque réception d'une trame dpuis le MM
void i2cReceivedFrame(int rxBytes)
{
char i2Cheader = Wire.read(); // Premier caractere, pour scanner i2C
if (i2Cheader != -1) // Si pas byte de test pour scan i2C
{
Wire.readBytes((byte*)&i2cCommandFrameMm, sizeof i2cCommandFrameMm);
Serial.println("Rx packetIndex\t\t" + String(i2cCommandFrameMm.command));
cmdExecute((String)i2cCommandFrameMm.command);
}
else
{
Serial.println("Scan i2C\t" + String(MS.i2cAdresse) + "\t0x" + String(MS.i2cAdresse, HEX));
}
}
/*------------------------------------- Commandes
Commandes reconnues:
MSADRESSE65 par ex. Adresse i2c du module
LEDnnnv = LED numéro de 0 ä 999 valeur 0 ou 1 LED0011
LRGBnnnv = LED numéro de 0 ä 999 valeur selon enum lgrbCoulIndex LRGB0034
SEERVOnnna = Servo numéro de 0 ä 999 angle a
les commandes ne sont pas sensibles à la casse
*/
void cmdExecute(String commande)
{
commande.toUpperCase(); // Tout en majuscules
commande.replace(" ", ""); // Supprimer les espaces
Serial.println("Execution de : " + commande);
if (commande.startsWith(F("MSADRESSE"))) // Adresse du MS
{
commande.replace(F("MSADRESSE"), ""); // Supprimer le texte
setupParams.i2cAddress = commande.toInt();
setupSave();
}
else if (commande.startsWith(F("LED"))) // Commande LED
{
commande.replace(F("LED"), "");
ledSet(commande);
}
else if (commande.startsWith(F("LRGB"))) // Commande LED RGB
{
commande.replace(F("LRGB"), "");
lrgbSet(commande);
}
else if (commande.startsWith(F("SERVO"))) // Commande Servo
{
commande.replace(F("SERVO"), "");
servoSet(commande);
}
else if (commande.startsWith("TEST")) // Pour essais
{
Serial.println("Commande Test = " + String(commande));
}
else
{
Serial.print(F("Commande inconnue!!! ")); Serial.println(commande);
}
}
//------------------------------------- LED
void ledSet(String param)
{
int ledNum = param.substring(0,3).toInt();
int ledValeur = param.substring(3).toInt();
Serial.println("\nLED " + String(ledNum) + " " + String(ledValeur));
digitalWrite(ledPin[ledNum], (ledValeur == 1) ? ledEtatOn : !ledEtatOn);
}
//------------------------------------- LED RGB (lrgb)
void lrgbSet(String param)
{
int ledNum = param.substring(0,3).toInt();
int coulValeur = param.substring(3).toInt();
Serial.println("\nLED RGB " + String(ledNum) + " " + String(coulValeur));
lrgbAllumer(ledNum, coulValeur, 0);
}
//------------------------------------- Servo
void servoSet(String param)
{
int servoNum = param.substring(0,3).toInt();
int servoPos = param.substring(3).toInt(); // Position de servo
Serial.println("\nLServo " + String(servoNum) + " " + String(servoPos));
servo[servoNum].write(servoPos);
}
//------------------------------------- Setup
void setupRestore()
{
EEPROM.get(eePromOffset, setupParams);
}
void setupSave()
{
EEPROM.put(eePromOffset, setupParams);
}
//------------------------------------- LED RGB (lrgb)
void lrgbInitialisation()
{
lrgbColors[lrgbCoulRouge] = lrgb.Color(255, 0, 0); // Définition des couleurs
lrgbColors[lrgbCoulVert] = lrgb.Color(0, 255, 0);
lrgbColors[lrgbCoulBleue] = lrgb.Color(0, 0, 255);
lrgbColors[lrgbCoulJaune] = lrgb.Color(255, 255, 0);
lrgbColors[lrgbCoulOrange] = lrgb.Color(255, 165, 0);
lrgbColors[lrgbCoulBlanc] = lrgb.Color(255, 255, 255);
lrgbColors[lrgbCoulEteint] = lrgb.Color(0, 0, 0);
lrgb.begin();
lrgb.setBrightness(lrgbBrightMax);
}
void lrgbAllumer(int led, int couleurIndex, int timeDispl)
{
lrgb.setPixelColor(led, lrgbColors[couleurIndex]);
lrgb.show();
if (timeDispl > 0) // Si timeDispol > 0 millisec.
{
delay(timeDispl);
lrgb.setPixelColor(led, lrgbColors[lrgbCoulEteint]);
lrgb.show();
}
}
//---------------------------------------------------------------------------------------
// Réception de commandes depuis le moniteur
//---------------------------------------------------------------------------------------
void serialEvent() // IDE monitor
{
if (Serial.available())
{
cmdTexte = Serial.readStringUntil('\n');
// Nettoyage des caractères indésirables, CR, LF, TAB... et les espace de début et de fin
cmdTexte.trim();
cmdNew = true;
}
}
Le fichier commun :
//------------------------------------ Topologie
enum msIndexes {msAindex, msBindex, msCindex, msDindex, msNumber}; // Index des MS
char* msPseudo[] = {"TETE", "BRAG","BRAD", "PIEDG", "PIEDD"}; // Pseudo des MS
int msAddresses[] = {64, 65, 66, 67, 68}; // Adresses des MS
//------------------------------------- Structure des paramètres d'un MS
struct i2cModuleDef
{int i2cAdresse; int indexInNet; String pseudo;};
//------------------------------------- Structure des paquets de donnees transmises sur le bus i2c max 32 bytes
struct __attribute__((packed, aligned(4))) i2CpacketDef // Si ESP32 dans reseau i2C
//struct i2CpacketDef
{
uint8_t packetIndex;
uint8_t packetFunction;
int packetPayloadInt[4];
};
//-------------------------------------- Trame de commandes du MM > MS
struct __attribute__((packed, aligned(4)))i2CpacketCommandsDef
{
byte frameIndex;
byte function;
char command[20];
};
////------------------------------------ Fonction des trames i2C
enum i2cFrameFunctionIndex {i2cFrFctCommand, i2cFrFctNombre};
Le kit :
Kit R2D2.zip (23.3 KB)
Le montage de développement:
A ta disposition pour toutes questions.
A+
Cordialement
jpbbricole