Nous allons voir un aspect fondamental de la programmation : la possibilité d'insérer des mini-programmes dans les programmes de façon à créer de nouvelles actions. Vous avez déjà utilisé de nombreuses fois les fonctions. Lesquelles ? Les fonctions setup etloop. Mais nous allons voir aujourd'hui comment créer vos propres fonctions si l'action que vous voulez effectuer n'est pas gérée par l'une des innombrables commandes disponibles dans Arduino.
Une fonction est un ensemble d'instructions qui seront effectuées lorsqu'on va appeler la fonction. Aucune ligne de code de la fonction ne sera exécutée tant que la fonction n'aura pas été rendue active par son appel. Un petit exemple pour la forme. Nous allons voir que la fonction créée permet de faire avancer le robot.
J'ai comme d'habiture rajouté quelques capteurs et actionneurs que vous pourriez vouloir utiliser pour votre mini-projet. Si vous ne les avez pas cablé, pensez à supprimer la déclaration de leur variable-objet.
#include "MeOrion.h"
#include <SoftwareSerial.h>
Me7SegmentDisplay disp(PORT_4);
MeInfraredReceiver capteurIR(PORT_6);
MeUltrasonicSensor ultraSensor(PORT_7); /* Ultrasonic module can ONLY be connected to port 3, 4, 6, 7, 8 of base shield. */
MeDCMotor motor3(M1);
MeDCMotor motor4(M2);
uint8_t motorSpeed = 100;
void avanceEtStop()
{
Serial.println("Ceci est un message automatique pour dire qu'on est dans la fonction avanceEtStop.");
motor3.run(motorSpeed);
motor4.run(motorSpeed);
delay(500);
motor3.stop();
motor4.stop();
}
void setup()
{
capteurIR.begin();
Serial.begin(9600);
Serial.println("Ceci est un message automatique pour dire qu'on est dans la fonction setup.");
avanceEtStop();
delay(1000);
}
void loop()
{
Serial.println("Ceci est un message automatique pour dire qu'on est dans la fonction loop.");
avanceEtStop();
delay(1000);
}
01° Tester le code. Que constatez-vous de nouveau avec d'aller plus loin ? Regardez l'ordre du code donné et le résulat dans la console :
Ceci est un message automatique pour dire qu'on est dans la fonction setup.
Ceci est un message automatique pour dire qu'on est dans la fonction avanceEtStop.
Ceci est un message automatique pour dire qu'on est dans la fonction loop.
Ceci est un message automatique pour dire qu'on est dans la fonction avanceEtStop.
Ceci est un message automatique pour dire qu'on est dans la fonction loop.
Ceci est un message automatique pour dire qu'on est dans la fonction avanceEtStop.
Ceci est un message automatique pour dire qu'on est dans la fonction loop.
...CORRECTION...
On constate qu'on n'effectue pas les actions de la fonction avanceEtStop avant de rentrer dans les autres fonctions setup et loop.
Les lignes de code sous void avanceEtStop() ne sont exécutées que si on tape avanceEtStop() dans l'une des deux autres fonctions principales !
En plus, on constate qu'on peut exécuter plusieurs fois le code en tapant plusieurs fois avanceEtStop() et contrairement à une boucle, les exécutions ne se font pas nécessairement à la chaîne les unes des autres.
Les premières lignes sont habituelles : on définit les différentes bibliothèques, variables globales et variables-objets rattachés aux capteurs et actionneurs.
#include "MeOrion.h"
#include <SoftwareSerial.h>
Me7SegmentDisplay disp(PORT_4);
MeInfraredReceiver capteurIR(PORT_6);
MeUltrasonicSensor ultraSensor(PORT_7); /* Ultrasonic module can ONLY be connected to port 3, 4, 6, 7, 8 of base shield. */
MeDCMotor motor3(M1);
MeDCMotor motor4(M2);
uint8_t motorSpeed = 100;
Nous arrivons ensuite à une chose nouvelle : la zone de déclaration d'une fonction qui ne se nomme ni setup ni loop. On commence ici par la ligne void avanceEtStop() {
où tous les éléments sont importants :
void avanceEtStop() {
: On commence par le mot void qui indique qu'on va créer une fonction qui ne renvoie rien (void, pour vide/néant en anglais).void avanceEtStop() {
: On place un espace entre void et le nom de la fonction.void avanceEtStop() {
: On donne le nom de la fonction, avanceEtStop ici, suivi de parenthèses ().void avanceEtStop() {
: on finit la déclaration du nom par une accolade ouverte { pour signaler que la suite va être composée des actions à effectuer (c'est le même principe qu'avec le if, le while, le for...).Point important : lorsque vous voulez utiliser la fonction, il ne faut pas juste donner son nom : il faut rajouter les parenthèses après son nom. Il faut donc utiliser avanceEtStop().
On peut donc utiliser de telles fonctions pour effectuer des tâches répétitives (et exactement identiques) qu'on devrait taper sinon à plusieurs endroits dans le code. On notera que si on devait les faire à la suite directe les unes des autres, les boucles FOR ou WHILE conviennent également.
Un autre intêret est de créer un code qui sera ensuite réutilisable ailleurs simplement en faisant un copier/coller de la déclaration de la fonction.
Nous verrons plus tard, qu'on peut même faire mieux et se créer ses propres bibliothèques ou modules de fonctions. Vous l'avez déjà fait en fait :
#include "MeOrion.h"
#include <SoftwareSerial.h>
Et oui, cela va simplement aller chercher (importer dans votre code) des bibliothèques de fonctions préinstallées sur votre ordinateur !
Cela vaut donc le coup d'encapsuler vos bouts de code dans des fonctions : il est plus que probable que vous aurez à réutiliser plus tard telle ou telle action.
Et si on veut faire des choses un peu différentes ? On peut aussi ?
Oui, on peut transmettre des données aux fonctions de façon à ce qu'elles utilisent des contenus venant de l'extérieur. Lors de la déclaration de la fonction, il suffit de placer entre les parenthèses les paramètres attendus : on doit préciser le type du paramètre (int, uint8_t, float, char, bool, ...) suivi du nom qu'on veut donner au paramètre.
void bougeEtStop(int vitesseMoteurGauche, int vitesseMoteurDroite, int tempo)
{
Serial.println("Ceci est un message automatique pour dire qu'on est dans la fonction bougeEtStop.");
motor3.run(vitesseMoteurGauche);
motor4.run(vitesseMoteurDroite);
delay(tempo);
motor3.stop();
motor4.stop();
}
Cette fois, on devra fournir les données lors de l'appel de bougeEtStop :
Pour faire l'appel de la fonction dans le programme, il faut maintenant lui fournir les données supplémentaires qui seront transmises lors de l'exécution de la fonction : par exemple :
bougeEtStop(100,100,500);
02° Comparer l'appel de la fonction avec sa déclaration initiale. Que va contenir ici le paramètre vitesseMoteurGauche, le paramètre vitesseMoteurGauche et le paramètre tempo ?
...CORRECTION...
APPEL :
bougeEtStop(100,100,500);
DECLARATION :
void bougeEtStop(int vitesseMoteurGauche, int vitesseMoteurDroite, int tempo)
En comparant, on voit donc que le premier 100 sera affecté au premier paramètre vitesseMoteurGauche.
Le seconde 100 sera affecté à vitesseMoteurDroite.
Le troisième, 500, sera affecté au paramètre tempo.
La correction groupé des questions 3-4-5-6 est sous la question 6.
03° Intégrer la fonction bougeEtStop au code de la question 1. Le robot doit avoir le même comportement que précédemment.
04° Modifier les appels à la fonction bougeEtStop : on doit lui transmettre la variable motorSpeed lorsqu'on affecte un contenu aux paramètres controlant les moteurs. Le robot doit avoir le même comportement que précédemment.
Conclusion : on peut envoyer le contenu d'une variable en paramètre. C'est pratique.
05° Faire des tests pour vérifier que le paramètre vitesseMoteurGauche permet bien de contrôler la chenille de gauche. Il suffit de mettre 0 sur l'autre vitesse. Modifier si besoin l'ordre des moteurs dans
motor3.run(vitesseMoteurGauche);
motor4.run(vitesseMoteurDroite);
06° Créer 4 fonctions avance, recule, tourneGauche et tourneDroite: elles doivent simplement faire appel à la fonction bougeEtStop en lui procurant les bonnes valeurs pour que le robot fasse l'action désirée.
Correction globale :
#include "MeOrion.h"
#include <SoftwareSerial.h>
Me7SegmentDisplay disp(PORT_4);
MeInfraredReceiver capteurIR(PORT_6);
MeUltrasonicSensor ultraSensor(PORT_7); /* Ultrasonic module can ONLY be connected to port 3, 4, 6, 7, 8 of base shield. */
MeDCMotor motor3(M1);
MeDCMotor motor4(M2);
uint8_t motorSpeed = 100;
void bougeEtStop(int vitesseMoteurGauche, int vitesseMoteurDroite, int tempo)
{
Serial.println("Ceci est un message automatique pour dire qu'on est dans la fonction bougeEtStop.");
motor3.run(vitesseMoteurGauche);
motor4.run(vitesseMoteurDroite);
delay(tempo);
motor3.stop();
motor4.stop();
}
void avance()
{
bougeEtStop(motorSpeed, motorSpeed, 500);
}
void recule()
{
bougeEtStop(-motorSpeed, -motorSpeed, 500);
}
void tourneGauche()
{
bougeEtStop(motorSpeed, 0, 500);
}
void tourneDroite()
{
bougeEtStop(0, motorSpeed, 500);
}
void setup()
{
capteurIR.begin();
Serial.begin(9600);
Serial.println("Ceci est un message automatique pour dire qu'on est dans la fonction setup.");
avance();
delay(1000);
}
void loop()
{
Serial.println("Ceci est un message automatique pour dire qu'on est dans la fonction loop.");
recule();
avance();
tourneGauche();
tourneDroite();
delay(1000);
}
Pour ceux qui aiment bien les termes précis, sachez que paramètre est le nom donné à ce qu'on place entre parenthèses lors de la définition de la fonction. Ce sont des "conteneurs" qui ne contiennent rien avant qu'on utilise concrétement la fonction lors d'un appel. On nomme les données fournies lors de l'appel les arguments :
Lors de l'appel :
bougeEtStop(100,100,500);
100,100 et 500 sont des arguments. On transmet les arguments.
Lors de la déclaration de la fonction :
void bougeEtStop(int vitesseMoteurGauche, int vitesseMoteurDroite, int tempo)
Ici, vitesseMoteurGauche, vitesseMoteurDroite et tempo sont les paramètres.
Il ne s'agit que de vocabulaire, mais il permet de comprendre les documentations techniques. Ca donne parameter et argument en anglais.
07° Créer une fonction carre qui va permettre à votre robot de bouger comme s'il suivait les contours d'un carré. Votre fonction devra bien entendu utiliser les 4 fonctions précédentes.
Pour renvoyer le contenu d'une variable reponse dans la partie de code qui a fait appel à la fonction, il suffit d'utiliser le code suivant return (reponse)
si reponse = a*x+b
. Mais on pourrait même faire plus court en utilisant directement return(a*x+b)
.
A titre d'exemple, voici une fonction qui détecte la présence d'un obstacle et qui renvoit la distance de celui-ci. Par contre, s'il détecte que l'obstacle est à 400 cm, nous allons avoir un doute sur le retour du signal ultrasonore et renvoyer une distance de 0m au cas où. Dans le doute, autant préserver le robot plutôt que lui faire percuter un mur...
La fonction possède un paramètre : la variable-objet contenant la liaison avec l'émetteur-récepteur ultrasonore.
float distanceObstacle(MeUltrasonicSensor referenceUltraSensor)
{
float distance = referenceUltraSensor.distanceCm();
Serial.println(distance);
if ( distance > 399.0 ) {
distance = 0;
}
return(distance);
}
Mais pourquoi avoir donner la référence du capteur puisque c'est une variable globale ? On peut y avoir accès de toutes manières. Oui, c'est vrai. Mais peut-être que vous aurez plusieurs détecteurs de distance sur votre robot. Ils pourront ainsi tous faire référence à la même fonction. Pas la peine de créer une fonction par capteur. Malin, non ?
On voit que la fonction utilise un return : elle revoie une valeur de type float contenant la valeur contenue dans distance.
Et pourquoi renvoie-t-elle un float ? Regardez donc la première ligne de la déclaration :
float distanceObstacle(MeUltrasonicSensor referenceUltraSensor)
Voilà la différence entre procédure (qui ont un type void) et les fonctions avec retour (qui ont un autre type que void).
Voici le code dans sa globalité : on teste la distance à chaque fois qu'on lance un appel à bougeEtStop.
#include "MeOrion.h"
#include <SoftwareSerial.h>
Me7SegmentDisplay disp(PORT_4);
MeInfraredReceiver capteurIR(PORT_6);
MeUltrasonicSensor ultraSensor(PORT_7); /* Ultrasonic module can ONLY be connected to port 3, 4, 6, 7, 8 of base shield. */
MeDCMotor motor3(M1);
MeDCMotor motor4(M2);
uint8_t motorSpeed = 100;
float distanceObstacle(MeUltrasonicSensor referenceUltraSensor)
{
float distance = referenceUltraSensor.distanceCm();
if ( distance > 399.0 ) {
distance = 0;
}
return(distance);
}
void bougeEtStop(int vitesseMoteurGauche, int vitesseMoteurDroite, int tempo)
{
Serial.println(distanceObstacle(ultraSensor) );
motor3.run(vitesseMoteurGauche);
motor4.run(vitesseMoteurDroite);
delay(tempo);
motor3.stop();
motor4.stop();
}
void avance()
{
bougeEtStop(motorSpeed, motorSpeed, 500);
}
void recule()
{
bougeEtStop(-motorSpeed, -motorSpeed, 500);
}
void tourneGauche()
{
bougeEtStop(motorSpeed, 0, 500);
}
void tourneDroite()
{
bougeEtStop(0, motorSpeed, 500);
}
void setup()
{
capteurIR.begin();
Serial.begin(9600);
Serial.println("Ceci est un message automatique pour dire qu'on est dans la fonction setup.");
avance();
delay(1000);
}
void loop()
{
Serial.println("Ceci est un message automatique pour dire qu'on est dans la fonction loop.");
recule();
avance();
tourneGauche();
tourneDroite();
delay(1000);
}
08° Utiliser le programme pour vérifier qu'il fonctionne correctement.
09° Modifier maintenant le programme : on veut que le robot recule s'il détecte un obstacle devant lui à moins de 10 cm. Et ce, quelque soit l'ordre donné initialement.
10° Question finale et globale : pour finaliser votre connaissance, réalisez un robot commandable via la télécommande mais qui stoppe et recule si on lui demande d'avancer dans un obstacle. Il faudra bien entendu utiliser les connaissances de la partie précédente et celle de cette partie. Profitez en pour faire afficher la distance supposée sur l'afficheur digital plutôt que sur la console série : votre robot risque de s'éloigner un peu ...
Dans le prochain chapitre, nous allons passer à la pince robotisée.