Bluetooth HC-05 envoi des commandes String

Bonjour, ceci est mon premier post sur un forum, excusez moi d'avance si tout n'est pas clair.

Je construit actuellement un R2D2 taille réelle et je souhaite utiliser un module HC05 ainsi qu'une application développé sur MIT App Inventor pour pouvoir avoir plusieurs centaines de commandes disponibles.

En suivant tuto, cours et vidéo youtube, j'ai réussi à faire fonctionner le module et ma carte Mega ensemble, cependant, les char ne me permettent pas d'aller plus loin que '9', j'ai donc voulu envoyer des String via bluetooth pour pouvoir passer plus de commandes.

Voilà mon problème, je reçois exactement la valeur String qui m'interesse mais le code ne réagi pas à mon if, je ne comprend pas ce que j'ai fait de mal, si quelqu'un pourrait m'éclairer :slight_smile:

Mon code

#include "SoftwareSerial.h"

SoftwareSerial bluetooth(1, 0); // broshes TX, RX

void setup() {
 bluetooth.begin(9600);
}

void loop() {
 if (bluetooth.available()) {
   String val = bluetooth.readStringUntil('\n');
   Serial.println(val);
   if (val == "F02") {
     Serial.println("Command F");
   }
 }
}

La partie sur App Inventor

questionforum

Le retour actuel de mon moniteur

Voilà, merci d'avance a ceux qui prendrons le temps de m'aider :slight_smile:

:warning:
Post mis dans la mauvaise section, on parle anglais dans les forums généraux. déplacé vers le forum francophone.

Merci de prendre en compte les recommandations listées dans Les bonnes pratiques du Forum Francophone

Bonjour yann377

Essaies la fonction trim(), ça nettoie des caractères "parasites"

 if (bluetooth.available()) {
   String val = bluetooth.readStringUntil('\n');
   val.trim();
   Serial.println(val);
   if (val == "F02") {
     Serial.println("Command F");
   }

Autre test à faire est d'imprimer ta variable ainsi:
Serial.println("*" + val + "*");
Ainsi tu verra si elle est "parasitée".

MIT App Inventor, le pied :wink:

Cordialement
jpbbricole

Bonjour UKHeliBob, mes excuses je ferais mieux la prochaine fois :smile:

Bonjour jpbbricole,

Merci pour vos recommandations, mes malheureusement cela ne fonctionne toujours pas, j'ai pourtant mis à jour mon code comme suis :

#include "SoftwareSerial.h"

SoftwareSerial bluetooth(1, 0); // broshes TX, RX

void setup() {
 bluetooth.begin(9600);
}

void loop() {
 if (bluetooth.available()) {
   String val = bluetooth.readStringUntil('\n');
   val.trim();
   Serial.println("*" + val + "*");
   if (val == "f02") {
     Serial.println("Command F");
   }
 }
}

Même après plusieurs tentatives, rien, j'ai essayé de déplacé les broches RX et TX, rien, j'ai essayé les différentes méthodes read(), readString(), readStringUntil(\n) et ... rien ! Je ne comprend pas pourquoi j'ai bien ma valeur retourné dans ma console mais que je ne rentre pas dans ma boucle :pensive:

Pourtant on vois bien ici que j'ai bien le retour 'f02', j'ai essayé aussi avec et sans f Majuscule aucune variations :thinking:

Je ne pense pas que le soucis proviendrais de MIT App Inventor, ayant suivis plusieurs tuto YouTube, la méthode est toujours assez similaire.

Cordialement
Yann377

Salut Yann377,

juste par curiosité, si tu fait un Serial.println(val); il te renvoie quoi?

Juste une remarque, quand je lit la doc de SoftwareSerial.h, il l'écrive tjs entre <> et pas avec des "".
Je suis plutot débutant, mais est-ce que cela peut avoir un rapport (ou c'est hors sujet?:slight_smile: )
Tu en pense quoi jpbbricole?

Bonjour jon_01

Quand c'est avec des "" le compilateur va d'abord chercher la bibliothèque dans le même répertoire du sketch, avec <>, dans le répertoire par défaut, en général libraries.

Cordialement
jpbbricole

1 Like

Oupsss, j'avais pas vu!
SoftwareSerial bluetooth(1, 0);
Attention 1 et 0 sont les ports série de l'interface USB, donc de la console, pas conseillé.

Pourquoi utiliser SoftwareSerial sur un Mega qui a 3 ports série de libres:
image

Autre problème, tu testes
if (val == "f02")
mais tu envoie depuis MIT
image
Tu dois tester:
if (val == "F02")
C'est sensible majuscules/minuscules :wink:

Je te mets ton programme adapté au Mega, sur Serial3:

void setup() 
{
	Serial.begin(115200);
	Serial3.begin(9600);     // Tx du Bluetooth sur 15 et Rx du Bluetooth sur 14 du Mega
}

void loop() 
{
	if (Serial3.available()) 
	{
		String val = Serial3.readStringUntil('\n');
		val.trim();
		val.toUpperCase();     // Tout en majuscules
		//Serial.println("*" + val + "*");
		if (val == "F02") {
			Serial.println("Command F");
		}
	}
}

C'est testé "en vrai"

A+
Cordialement
jpbbricole

Sauveur !!! Merci infiniment :heart: Je peux reprendre la programmation de tout mon R2, merci 1000fois :grin:

Autant pour moi pour les ports 0 et 1, dans mon idée originale je voulais utiliser les ports 0 et 1 pour le bluetooth et utiliser les autres ports de série pour dialoguer avec d'autres Arduino (c'est qu'il en faut beaucoup pour 1 R2D2 :sweat_smile: ), ça va d'ailleurs être la suite de mes recherches maintenant :grin:

Merci encore :grin:

A bientôt
Cordialement
Yann377

Salut, j'avais suivis un tuto qui mettait la lib entre "", j'ai remis entre <>, Merci de la remarque :grin:

Bonsoir yann377

Pourquoi il t'en faut beaucoup?
Quel est le rôle de tout ces Arduino?

Tu peux utliser SoftwareSerial pour gagner un port série, mais il n'est pas nécessaire que ce soit un port série, n'importes quel port fait l'affaire, y compris les A0, A1...

Il y a, peut être une solution i2C.

Bonne soirée
jpbbricole

Re jpbbricole,

L'objectif de mon R2 est de le rendre le plus proche de celui du film (hors la partie ou il vole cela va de soit :sweat_smile: ) du coup j'ai besoin de beaucoup de ports pour contrôler tout les servos, led, moteurs, esc et j'en passe pour le faire fonctionner, d'où le fait d'utiliser une Arduino qui va contrôler toutes les autres (un peu comme une pieuvre ou la Mega serait au centre) :grin:

De plus, je m'était dit à l'origine que, qui dit plusieurs Arduino, dit plusieurs thread de code donc un R2 qui pourrait faire plusieurs choses à la fois et ne pas avoir l'air procédural comme chez certains R2Builders :grin:

Par contre, si je comprend bien ce que tu viens de dire, je ne suis pas restreint uniquement au port de communication pour obtenir plus de Serial ? si c'est le cas, alors une seule Mega me sera largement suffisante pour contrôler toutes les Uno R3. J'ai regardé tout à l'heure la suite de ma démarche, j'ai trouvé un post qui expliquait que je n'avais qu'a rajouté un Serial et lui faire un print pour envoyer ma commande, et rebelote de l'autre coté pour récupérer la commande entré :grin:

Mon idée de départ aurait été de finir avec un code un peu comme ça :grin:

#include <SoftwareSerial.h>

SoftwareSerial pied(3, 2);
SoftwareSerial jambes(5, 4);
SoftwareSerial dome(7, 6);
SoftwareSerial face_avant(9, 8);
SoftwareSerial face_arriere(11, 10);
SoftwareSerial son(13, 12);
SoftwareSerial bras_sup(31, 30);
SoftwareSerial bras_gauche(33, 32);
SoftwareSerial bras_droit(35, 34);
// et d'autres au besoin 

void setup() 
{
  Serial.begin(115200);
  Serial3.begin(9600);     // Tx du Bluetooth sur 15 et Rx du Bluetooth sur 14 du Mega
  pied.begin(9600);
  jambes.begin(9600);
  dome.begin(9600);
  face_avant.begin(9600);
  face_arriere.begin(9600);
  son.begin(9600);
  bras_sup.begin(9600);
  bras_gauche.begin(9600);
  bras_droit.begin(9600);
}

void loop() 
{
  if (Serial3.available()) 
  {
    String val = Serial3.readStringUntil('\n');
    val.trim();
    val.toUpperCase();     // Tout en majuscules
    String carte_concernee = val.substring(0, 1);
    //Serial.println("*" + val + "*");
    if (carte_concernee == "F") {
      Serial.println("Commande pour la carte F");
      pied.print(val);
    }
    if (carte_concernee == "L") {
      Serial.println("Commande pour la carte L");
      jambes.print(val);
    }
    if (carte_concernee == "H") {
      Serial.println("Commande pour la carte H");
      dome.print(val);
    }
    // et ainsi de suite pour le reste
  }
}

C'est une idée ! Je n'ai pas encore testé si cela fonctionne :sweat_smile: , je suis encore assez peu expérimenté avec les Arduino et j'ai souvent tendance à vouloir travailler comme je le ferai en python ou JS :joy: .Peut être que cette idée est aberrante mais tant que j'ai pas essayé je peux pas savoir :joy:, je testerai ça demain si tu as des recommandations je suis preneur :grinning:

Je vais me pencher aussi sur la technique i2C pour voir si cela est plus simple :grinning:

Bonne soirée
Yann377

Bonjour yann377

Tu y sera obligé, SoftwareSerial c'est 2 ports max. :face_with_diagonal_mouth:
Si tu veux un exemple i2C, c'est volontier.

Cordialement
jpbbricole

Bonjour jpbbricole,

Ah ok, bon bah ça m'évite un test inutile :sweat_smile: , du coup je suis preneur pour un exemple i2C :grin:

Merci d'avance,
Cordialement
Yann377

Bonjour yann377

Oui!

Je te fais ça, mais tu dois attendre 2 ou 3 jours :wink:

Bonne journée
jpbbricole

1 Like

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.
image

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

Bonsoir jpbbricole, désolé de ne pas être revenu vers toi plus tôt, j'avais des partiels en début de semaine :sweat_smile: .

Merci infiniment pour ton exemple, il est très complet, ça me permet d'avancer et c'est très agréable. J'aurais cependant quelques questions :grin:.

Avoir du code qui fonctionne c'est bien, le comprendre c'est mieux :joy:, du coup j'ai voulu réécrire le code de mon coté à partir des librairies et de la doc Arduino (l'objectif est de comprendre étapes par étapes l'ensemble du processus que tu as fait, si je me contente de lire et appliquer ton exemple j'ai peur de ne pas pouvoir le débuguer en cas de problèmes :grin: ).

J'ai du mal a comprendre la partie envoi et réception des commandes, s'il serait possible de revenir la dessus ça m'aiderait beaucoup :grin:.

J'ai compris que du côté émetteur que la commande d'envoi était Wire.write(la commande, la taille de la commande).
Et j'ai également compris que du côté récepteur la commande de réception était Wire.read() ou Wire.readBytes() dans ton exemple.

La partie que je ne comprend actuellement pas est la différence entre Wire.read() et Wire.readBytes() ainsi que les paramètres que l'on passe dans la dernière commande.

Dans le code que j'ai construit à partir des exemples et de la documentation, j'arrive bien à passer des commande mais les caractères reçu ne sont pas bon, je suppose donc que j'ai du louper une étape importante :grin:.

Mon code émetteur :

#include <Wire.h>
#include <SoftwareSerial.h>

void setup()
{
  Wire.begin(); // join i2c bus (address optional for master)
  Serial.begin(115200);
  Serial3.begin(9600);     // Tx du Bluetooth sur 15 et Rx du Bluetooth sur 14 du Mega
}

void loop(){
  if (Serial3.available()){
    String val = Serial3.readStringUntil('\n');
    val.trim();
    val.toUpperCase();     // Tout en majuscules
    String carte_concernee = val.substring(0, 1);
    String commande = val.substring(0, 3);
    if (carte_concernee == "F") {
      Serial.println("Commande pour la carte F");
      Wire.beginTransmission(4); // transmit to device #4
      Wire.write(commande.c_str(), sizeof commande);   // sends one byte
      Wire.endTransmission();    // stop transmitting
      delay(500);
    }
  }
}

Mon code récepteur :

#include <Wire.h>

void setup()
{
  Wire.begin(4);                // join i2c bus with address #4
  Wire.onReceive(receiveEvent); // register event
  Serial.begin(9600);           // start serial for output
}

void loop()
{
  delay(100);
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany)
{
  while(1 < Wire.available()) // loop through all but the last
  {
    char c = Wire.read(); // receive byte as a character
    Serial.print(c);         // print the character
  }
  int x = Wire.read();    // receive byte as an integer
  Serial.println(x);         // print the integer
}

Et le retour que j'obtiens dans mon terminal:
Terminal

Côté MIT App Inventor, j'ai mis à jour mes commandes en mettant la première lettre en Majuscule
MITApp

Ces deux bout de codes sont produits à partir des exemples fournit par Arduino, je ne veux m'en servir que pour mieux comprendre la méthode que tu as fait, j'utilise déjà ce que tu m'a donné, cette partie est purement à but éducatif :grinning:.

Si tu peux m'aider à mieux comprendre, cela m'aiderait beaucoup.
Merci d'avance.

Cordialement,
Yann377

Bonjour yann377

C'est le but de mon "don" de programme, provoquer des questions :wink:

.read() et .write() ne concernent qu'un byte qui vaut de 0 à 255, c'est à dire que ces 2 fonctions ne peuvent pas transmettre ou recevoir la "star" des variables Arduino, le int qui est formé de 2 bytes.

Pour envoyer/recevoir des données plus complexes y compris des structures qui sont un assemblage de divers type de variables, il y a .readBytes() et .writeBytes(), dont le 2ème paramètre est la longueur en bytes des données transmises.

Je regarde ton programme :wink:

A+
jpbbricole

1 Like

Bonjour yann377

Je n'ai pas beaucoup de temps ce matin, dans le fond, ton programme fonctionne, j'ai fait un peu de "cosmétique", des 2 côtés.

Ta réception était normale
image
C'est sa mise en forme qui posait problème.
A la réception de la commande F01 j'ai 01%, j'ai pas compris pourquoi ce % :woozy_face:, je te laisse chercher :wink:
Attention, le programme côté émetteur, pour des raisons pratiques, j'ai tout fait dans Serial (à la place de Sérial3), donc les commandes sont à introduire dans le moniteur série (115200) de l'IDE.

L'émetteur:

#include <Wire.h>
//#include <SoftwareSerial.h>

void setup()
{
	Wire.begin(); // join i2c bus (address optional for master)
	Serial.begin(115200);
	//Serial3.begin(9600);     // Tx du Bluetooth sur 15 et Rx du Bluetooth sur 14 du Mega
}

void loop(){
	if (Serial.available()){
		String val = Serial.readStringUntil('\n');
		val.trim();
		val.toUpperCase();     // Tout en majuscules
		String carte_concernee = val.substring(0, 1);     // Le premier caractère
		String commande = val.substring(1);     // Tout ce qui est après le premier caractère
		if (carte_concernee == "F") {
			Serial.println("Commande pour la carte F commande " + commande);
			Wire.beginTransmission(4); // transmit to device #4
			Wire.write(commande.c_str(), sizeof(commande));   // Envoi de la commande
			Wire.endTransmission();    // stop transmitting
			delay(500);
		}
	}
}

Le récepteur:

#include <Wire.h>

void setup()
{
	Wire.begin(4);                // join i2c bus with address #4
	Wire.onReceive(receiveEvent); // register event
	Serial.begin(9600);           // start serial for output
}

void loop()
{
	//delay(100);
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany)
{
	String commande = "";
	while(Wire.available()) // loop through all but the last
	{
		char c = Wire.read(); // receive byte as a character
		commande += c;
		//Serial.print(c);         // print the character
	}
	Serial.println(commande);
	//int x = Wire.read();    // receive byte as an integer
	//Serial.println(x);         // print the integer
}

Je te laisse jouer au jeux des 7 différences :wink:

Cordialement
jpbbricole