Bases de programmation sur Arduino : variables, fonctions et structures

Publié le par arduino-from-scratch

source-code_blog.jpg

 

 

 

La programmation sur Arduino se fait dans un langage qui s'inspire à la fois du C et du C++. Le C++ intervient surtout pour la création de librairies, ce que nous verrons plus tard.

 

Pour l'instant, nous resterons essentiellement dans le domaine de la programmation en C.

 

Sachons déjà pour commencer que dans tout code, il est fondamental de glisser des commentaires qui peuvent être utiles soit lorsqu'on le reprend après un long moment, soit lorsque l'on diffuse notre code.

 

Les commentaires ne sont pas évalués par le compilateur, ils ne s'adressent qu'au développeur.

 

En C et en C++, les commentaires sur une seule lignes commencent par // et ceux placés sur plusieurs lignes sont encadrés par les signes /* et */.

 

I - Les types

 

Pour programmer, nous allons utiliser plusieurs sortes de données. Par exemple, si nous voulons incrémenter un compteur, nous utiliserons une variable de type entier. Si nous voulons calculer la moyenne d'une série de données fournies par un capteur, nous aurons besoin d'un nombre réel. S'il s'agit de tester une condition, nous pourrions avoir besoin d'un simple booléen. Voici un rappel des principaux types :

 

  • void : c'est un mot clef utilisé pour spécifier qu'il n'y a pas de variable, on le retrouve généralement dans le prototype d'une fonction pour indiquer que celle-ci ne renvoie pas de paramètre (ou qu'elle n'en prend pas)
  • boolean : c'est une variable logique qui ne peut prendre que deux états : false, true. Elle sert essentiellement à tester des conditions, des états binaires.
  • char : traditionnellement, le char est utilisé pour les caractères. En fait, il s'agit d'une variable codée sur un octet, qui peut donc prendre 256 valeurs (2^8) différentes. Les caractères ayant longtemps été indexés sur un table à 256 entrées, on a tendance à ne réserver l'usage du type char qu'aux seuls caractères. Mais il peut de fait être utilisé pour toute variable qui n'a pas besoin d'excéder l'intervalle [-128 ; 127].
  • unsigned char : le mot clef unsigned signifie que l'on considère des valeurs positives uniquement. Ainsi, si un char permet de représenter des valeurs allant de -128 à +127, le type unsigned char utilise quant à lui l'intervalle [0, 255].
  • byte : le byte est un synonyme du unsigned char, c'est-à-dire qu'il se code sur un seul octet et prend des valeurs pouvant aller de 0 à 255. Il est particulièrement adapté pour les sorties PWM.
  • int : un int (pour integer) est un entier relatif codé sur deux octets, ce qui signifie qu'il possède 2^16 valeurs différentes. Il peut prendre des valeurs comprises dans l'intervalle [-32,768 ; 32,767].
  • unsigned int : c'est un entier positif codé sur deux octets, prenant ses valeurs dans l'intervalle [0 ; 65,535].
  • word : synonyme de unsigned int.
  • long : entier relatif codé sur 4 octets, pouvant donc prendre des valeurs allant de -2,147,483,648 à 2,147,483,647.
  • unsigned long : entier positif codé sur 4 octets, prenant dès lors ses valeurs dans l'intervalle [0 ; 4,294,967,295].
  • float : il s'agit d'une variable de type réel, codée sur 4 octets. Sa précision n'excède pas 6 ou 7 décimales.
  • double : traditionnellement, le type double offre une précision deux fois supérieure à celle du float. Dans le cas du microcontrôleur Arduino, les deux types sont synonymes : double = float.
  • String : le type String est le premier type complexe que nous rencontrons. Ce n'est d'ailleurs plus vraiment un type, mais un Objet, au sens de la POO (programmation object oriented). Pour faire simple dans un premier temps, considérons qu'il s'agit d'une chaîne de caractères, que l'on peut donc y stocker des mots, des phrases, ... Le type String vient avec plusieurs méthodes qui permettent de traiter, comparer, manipuler, ... les chaînes de caractères.
  • array : ce n'est pas un type à proprement parler. Il est possible de déclarer pour chaque type un tableau de données de ce type, un tableau d'entiers par exemple pour stocker des états, ou un tableau de booléens, pour caractériser une série de données. On utilisera pour cela les crochets [ ] placés après le nom de la variable.

 

Ainsi, lorsque l'on souhaitera créer une variable, on commencera par spécifier son type, puis son nom, et éventuellement on lui assignera une valeur qu'il sera ensuite possible de modifier.

 

Exemples :

 

int age = 13 ; // on créé un entier contenant la valeur 13

 

String message = "hello world" ; // on créé une chaîne de caractères contenant la valeur "hello world"

 

char key = 'c' ; // on créé une variable de type char contenant le caractère 'c', ou plus exactement la position du caractère 'c' dans le tableau indexant les principaux caractères

 

int tabValeurs [1024] ;  // on créé un tableau pouvant contenir 1024 entiers. Les valeurs ne sont pas initialisées, elles peuvent donc contenir absolument n'importe quoi dans un premier temps.

 

Pour accéder à une valeur stockée dans un tableau, on utilisera l'index de sa position que l'on indiquera entre les crochets, tout en faisant attention au fait que l'indexation commence à 0. Ainsi, pour un tableau de 1024 éléments, le premier éléments sera accessible en appelant tabValeurs[0] et le dernier en invoquant tabValeurs[1023].

 

 

Pourquoi tous ces types ? N'est-il pas plus simple de ne déclarer que des long, voire uniquement des floats ? L'intérêt réside dans l'utilisation de la mémoire. La mémoire d'un microcontrôleur est fortement limitée (32 ko pour l'Arduino). Créer un tableau de 1024 entiers prend 2048 octets, soit 2 ko, soit 1/16 de la mémoire disponible. Si l'on sait par avance que les valeurs ne seront jamais supérieures à 255 (et toujours positives), par exemple dans le stockage de sorties analogiques, on pourrait utiliser à la place des bytes et gagner ainsi la moitié de l'espace.

 

Supposons par exemple que l'on veuille stocker une image couleur de 80x60 pixels. On aura un tableau contenant 200x200x3 = 14400 entrées. Si on les déclare comme des int, l'espace mémoire utilisé est 28800 ko, soit la quasi-totalité des ressources du micro-contrôleur. Autant dire que l'on ne pourra pas faire de traitement vraiment intéressant de cette image. Maintenant, comme on sait que les codes couleurs sont codés sur un seul octet, on peut décider d'utiliser des bytes plutôt que des int. On n'utlise alors que 14 ko, soit moins de la moitié de la mémoire totale, ce qui laisse de la place pour un traitement de l'image.

 

 

 

II - Les fonctions

 

Une fonction possède un nom, des paramètres d'entrée et des paramètres de sortie. On appelle prototype d'une fonction la spécification de ces trois données. Par exemple, on peut vouloir créer une fonction qui prend deux entiers en paramètre, puis renvoie leur produit. Son prototype sera :

 

int multiplication(int, int)

 

Si la fonction ne renvoie aucune donnée, son type de retour sera void. Si elle ne prend aucun paramètre, on pourra soit ne rien mettre entre les parenthèses, soit écrire également void.

 

Si la fonction renvoie une donnée, elle devra le faire avec le mot-clef return.

 

Enfin, tout ce que fait la fonction doit être placé entre deux accolades.

 

Exemples :

 

int multiplication (int a, int b)

{

      int c ;

      c = a * b ;

      return c ;

}

 

 

que l'on pourra écrire plus succinctement :

 

int multiplication (int a, int b)

{

      return a * b ;

}

 

 

Ainsi, à chaque fois que nous aurons à exécuter une multiplication, il suffira d'appeler la fonction que nous  venons de définir :

 

void quelques_calculs()

{

      int a = 7 ;

      int b = 8 ;

      int c = muliplication(a, b) ;

}

 

III - Les structures de condition et d'itération

 

Les structures principales sont grosse merdo de deux sortes : structure conditionnelles et structures itératives. Les principales sont les suivantes :

 

1 - Structure conditionnelle IF ... ELSE

La structure conditionnelle IF ... ELSE permet de vérifier une condition logique. Elle repose donc sur l'évaluation d'une expression simple ou complexe, à laquelle elle donne la valeur Vrai (TRUE ou 1) ou la valeur Faux (FALSE, ou 0).

 

Par exemple, on veut vérifier sur la valeur donnée par un capteur de distance est supérieure à un certain seuil. On écrira :

 

 

int seuil = 80 ;

int valeur = analogRead(capteur_distance) ;


if (valeur > seuil)

{

// traitement si la valeur est strictement supérieure    

}

else

{

// traitement si la valeur est inférieure ou égale    

}

 


Ici, si la valeur lue est supérieure au seuil, le code situé entre les deux premières accolades sera exécuté, mais pas celui entre les deux accolades suivantes.

Si la valeur est inférieure - ou égale -, alors c'est l'inverse. Le code situé entre les deux premières accolades ne sera pas exécuté, mais celui placé entre les deux suivantes le sera.

 

Il est possible d'utiliser les opérateurs logiques pour évaluer plusieurs conditions en même temps. On utilisera l'opérateur de négation ! pour avoir l'inverse d'une valeur (si P est Vrai, !P est faux), l'opérateur de conjonction && qui renverra Vrai uniquement si les deux conditions qu'il rassemble sont vraies ensembles, l'opérateur de disjonction || qui renverra Vrai si et seulement si une des valeurs est Vraie, et enfin l'opérateur de disjonction exclusive ^ qui renverra Vrai si une seule des conditions est vrai et l'autre fausse.

 

Exemple :

 

int a = 80 ;

int b = 30 ;

int c = 2 ;

( ( (b == c) || ! (b > a) ) ^ (a < c) )

 

renverra Vrai.

 

En effet ;

  • l'expression (b == c) est fausse, puisque b est différent de c
  • l'expression ( b > a ) est fausse
  • donc l'expression !(b > a) est vraie
  • par conséquent, ( (b == c) || ! (b > a) ) est vraie, puisque l'une des deux expressions est vraie
  • Ensuite, (a < c) est fausse.
  • En conclusion, comme ( (b == c) || ! (b > a) ) est vraie et (a < c) est fausse, leur disjonction exclusive est vraie. L'expression est donc vraie.

 

 

On peut bien-sûr enchaîner séquentiellement les évaluations conditionnelles :

 

 

int a = 80 ;

int b = 30 ;

int c = 2 ;

if ( a > b )

{

// traitement

}

else

if ( a > c)

{

// traitement

}

else

{

// traitement

}

 

 

Notons que le ELSE est facultatif.

 

Enfin, la condition du IF ... ELSE peut-être un simple booléen, comme dans l'exemple suivant :

 

boolean estActif = true ;

 

... // traitements qui peuvent modifier la valeur de estActif

 

if ( estActif )

{

// traitement

}

 

 

 

 

2 - Structure de branchement SWITCH ... CASE ... DEFAULT

 

Le SWITCH peut être imaginé comme un carrefour. On arrive à un moment du code où l'on peut prendre plusieurs directions. On suppose par exemple que l'on cherche à savoir parmi quatre boutons-poussoirs lequel a été actionné. On a pour cela une variable qui contient le numéro du bouton-poussoir que l'on recherche, valeur que l'on examine et en fonction de laquelle on exécute différents traitements.

 

Exemple :

 

 

byte boutonActif = 0 ;

 

... // traitement qui permet de savoir sur quel bouton on a appuyé, contient 0 si aucun bouton n'a été utilisé

 

switch ( boutonActif )

{

case 1 :

// traitement si c'est le bouton 1

break ;

 

case 2 :

// traitement si c'est le bouton 2

break ;

 

 

 

case 3 :

// traitement si c'est le bouton 3

break ;

 

 

 

case 4 :

// traitement si c'est le bouton 4

break ;

 

 

 

default :

// traitement si aucun bouton n'a été utilisé

break ;

   

}

 

 

La limitation principale du SWITCH ... CASE ... DEFAULT est qu'il opère de façon discrète (par opposition à continue) et sur des expressions simples. On ne peut par exemple pas avoir un case a < b, ou un case a && b.

 

Le mot-clef BREAK signifie que l'on sort du SWITCH à la fin du traitement. Il faut en effet savoir que chaque case correspond à une entrée, mais qu'une fois dans le SWITCH, on est censé parcourir toutes les valeurs qui suivent jusqu'à la dernière. On peut le voir comme un toboggan à plusieurs entrées. On choisit à quelle hauteur on arrive, mais une fois dans le toboggan, on doit parcourir toute la distance, toutes les hauteurs, et cela jusqu'en bas. Le BREAK permet d'avoir des points de sortie. Traditionnellement, il vient conclure chaque CASE, mais on peut imaginer évidemment des situations plus compliquées. Par exemple, on veut savoir quel actionneur d'un robot doit être activé. On veut que si les valeurs des actionneurs 1, 3, ou 5 ont a être modifiées, les actionneurs qui leur succèdent doivent s'adapter également (respectivement (2, 4 et 6). En revanche, si on décide de modifier les valeurs de 2, 4 ou 6 (pour une correction suite à une information donnée par le retour d'un capteur), on ne veut pas modifier les actionneurs 1, 3 et 5. On aura alors :

 

switch ( valeur_indiquant_l_actionneur_a_modifier )

{

case 1 :

// traitement si c'est l'actionneur 1

 

case 2 :

// traitement si c'est l'actionneur 2

break ;

 

case 3 :

// traitement si c'est l'actionneur 3

 

case 4 :

// traitement si c'est l'actionneur 4

break ;

 

case 5 :

// traitement si c'est l'actionneur 5

 

case 6 :

// traitement si c'est l'actionneur 6

break ;

default :

break ;

}

 

 

Si on modifie l'actionneur 1, on passe ensuite au second qui est lui aussi modifié, puis on sort du SWITCH. Si par contre on commence par modifier l'actionneur 2, on sort immédiatement après et rien d'autre n'est modifié.

 

 

Notons enfin que le mot-clef DEFAULT qui vient conclure un SWITCH est utilisé comme traitement par défaut. Dans le cas par exemple où la valeur considérée correspondrait à une touche de clavier, dont seules les pressions sur les flèches nous intéresseraient, toute autre pression de touche sera renvoyée sur DEFAULT. On peut imaginer que ce n'est pas obligatoire, mais certains compilateurs vous hurleront aux oreilles si vous n'envisagez pas tous les cas. Il est donc toujours préférable de le mettre, même s'il reste vide. Le BREAK qui le suit est évidemment inutile, mais on peut le laisser par souci d'homogénéité du code (et rien n'oblige à finir par le traitement par default. Il peut très bien figurer en début de branchement).

 

3 - Structure d'itération FOR ( initialisation ; condition de traitement ; incrémentation )

Il est parfois utile d'exécuter un traitement un grand nombre de fois. On veut par exemple parcourir un tableau - pour en faire la moyenne -, ou faire clignoter un nombre défini de fois une LED, ou encore faire avancer tourner une tourelle sur laquelle est fixé un capteur. On utilisera pour cela une boucle FOR. Dans sa forme la plus simple, un FOR utilise un compteur que l'on incrémente.

 

Exemple :

 

int tab[128] ; // définition d'un tableau d'entiers contenant 128 cases, indexées de 0 à 127

 

... // traitement pour remplir le tableau avec des valeurs provenant d'un capteur

 

 

int somme = 0 ;

 

for (int i = 0 ; i < 128 ; i++)

{

somme += tab[i] ;

}

 

float moyenne = somme / 128.0 ;

 

 

On commence donc par définir un tableau de 128 cases. On remplit ensuite ce tableau avec des valeurs issues par exemple d'un capteur. On initialise ensuite une variable entière à 0, la variable somme.

 

La boucle FOR nous permet ensuite de parcourir le tableau. A chaque tour de boucle, la valeur contenue dans la case du tableau correspond à l'index donné par la variable i est ajoutée à la variable somme.

 

Une fois le tableau parcouru, on créé une variable de type DOUBLE, et l'on divise la somme par le nombre d'éléments pour obtenir la moyenne.

 

 

Plusieurs remarques déjà d'ordre syntaxique :

  • l'écriture i++ est un raccourci pour i = i +1. C'est-à-dire qu'à chaque tour de boucle, on augmente la valeur de i d'une unité
  • l'écriture somme += tab[i] est un raccourci pour somme = somme + tab[i]. A chaque tour de boucle, somme ajoute à sa valeur précédente la valeur contenue dans la case du tableau sur laquelle on pointe.
  • enfin, on écrit 128.0 pour en faire un float et non un entier. En effet, si la division est effectuée entre deux entiers, elle agira comme une division entière. Ainsi, 5/2 = 2, 3/8 = 0, et 1000/128 = 7. En forçant l'une des deux valeurs à être de type float, on a alors une division au sens usuel, et son résultat sera un nombre réel, de type float.

 

Au sein de la boucle FOR, on commence donc par initialiser un compteur à 0. On spécifie que l'on continue à rentrer dans la boucle tant que sa valeur est inférieure à 128 (on va donc jusqu'à la case 127). Puis, à chaque tour, on incrémente la valeur de 1.

 

 

Supposons que l'on souhaite faire exécuter à un servo-moteur une rotation de 180 degrés, avec un relevé d'un capteur tous les 5 degrés. On écrira alors :

 

 

Servo s ; // on créé une variable de type Servo (ce que l'on verra plus tard)

s.attach(9) ; // on spécifie que la communication avec le Servo se fair sur le connecteur 9 de l'Arduino

 

... // quelques traitements

 

for (byte degre = 0 ; degre <= 180 ; degre += 5)

{

s.write(degre) ; // on indique en degre la position que doit atteindre le Servo

lireCapteur() ; // on exécute une fonction qui lit un capteur

 

... // traitement, par exemple un envoi de données en série si la valeur du capteur dépasse un seuil

}

 

 

Ici, dans la boucle, on initialise la valeur de degre à 0. Dans le premier tour de boucle, le Servo se dirige vers la position 0 qui se trouve être le point le plus à gauche. A la fin de la boucle, on augmente la valeur de degre de 5. Puis on augmente à nouveau de 5 au tour suivant, etc etc etc ... A chaque tour, le Servo se déplace donc de 5 degrés, jusqu'à ce qu'il atteigne 180 degrés. A la fin de ce dernier tour, la variable est incrémentée de 5, elle vaut donc 185, et la condition de traitement n'est plus respectée. On sort donc de la boucle.


 

4 - Structure d'itération WHILE

 

Si le WHILE et le FOR sont interchangeables, ils sont utilisés dans des cadres différents. Lorsqu'on utilise un FOR, on suppose que l'on connaît à l'avance le nombre d'itérations. Celui-ci est fixé en dehors de la boucle FOR. Pour un WHILE, la condition d'arrêt dépend d'un événement qui inervient durant le processus de bouclage. On attend par exemple une entrée au clavier, ou un signal Bluetooth, ou par exemple une valeur située dans un intervalle. Quand un WHILE démarre, on ne sait pas à l'avance quand - et si - il va s'arrêter.

 

Pour rappel, on a dans la structure du FOR une initialisation, une condition de déroulement, et une incrémentation. Dans le cas du WHILE, l'initialisation se fait avant la boucle. La condition de déroulement se fait à l'appel du WHILE, et la modification des variables impliquées dans la condition se fait au sein de la boucle. Elle dépend de l'environnement (environnement physique, utilisateur, ...), et n'est pas fixée par le programme. On peut dire que le FOR est une boucle aux conditions immanentes, tandis que le WHILE est une boucle aux conditions transcendantes.

 

D'un point de vue syntaxique, la WHILE est défini ainsi :

 

 

... // initialisation des variables de condition de traitement 

while ( condition )

{

// différents traitements

// réactualisation de la condition

// différents traitements

}

 

 

 

On a donc pour le FOR :

 

FOR (initialisation, condition, actualisation)

{

// traitement

}

 

et pour le WHILE

 

// initialisation

WHILE ( condition )

{

// traitement

// actualisation

// traitement

}

 

Quelques exemples.

 

Dans le premier cas, on lit les valeurs d'un capteur de distance. Quand celui-ci détecte un objet situé à une proximité inférieure à une valeur seuil donnée, on sort de la boucle. Tant qu'on est dans la boucle, on fait par exemple avancer un robot en ligne droite.

 

 

int distance = 800 ; // initialisation de la distance à 800 mm

int seuil = 40 ; // fixation du seuil à 40 mm

while (distance > seuil)

{

avanceRobot(2) ; // fonction faisant avancer le robot

distance = analogRead(capteur_distance) ; // actualisation de la variable de distance par lecture du capteur

delay(100) ; // attente de 100 millisecondes entre chaque lecture

}

tourneGaucheRobot(90) ; // fonction faisant tourner le robot

 

On commence donc par initialiser les variables de contrôle. A chaque tour de boucle, on vérifie que l'on est pas trop proche d'un obstacle. Si la condition est vérifiée, on avance de 2 millimètres, et on lit la nouvelle valeur.

Si la valeur est inférieure à 40 mm - parce que le robot avance par exemple vers les jambes de quelqu'un, alors la condition n'est plus vérifiée, et on sort de la boucle. On tourne alors vers la gauche de 90 degrés.

 

 

 

Deuxième exemple : on attend l'activation d'un bouton poussoir. Tant que celui-ci n'est pas activé, on fait clignoter une LED signalant un mauvais fonctionnement. Une fois la pression effectuée, la LED est en position éteinte, et on allume une LED signalant le rétablissement des fonctions du système.

 

int state = LOW ; // on suppose le bouton inactivé

while (state == LOW)

{

digitalWrite(LED1, HIGH) ; // on allume la LED d'alarme

delay(500) ; // on attend une demi-seconde

digitalWrite(LED1, LOW) ; // on éteint la LED d'alarme

delay(500) ; // on attend une demi-seconde


state = digitalRead(button) ;

}

digitalWrite(LED2, HIGH) ; // on allume la LED indiquant un bon fonctionnement

 

 

En résumé, s'il est possible de transformer un FOR en WHILE et inversement, on préfèrera un FOR pour une série d'instruction dont on maîtrise les conditions de sortie, et un WHILE lorsqu'il s'agit d'une intervention contingente au programme.

 

 

 

IV - Les directives de préprocesseur

 

Les directives de préprocesseur sont généralement placées en début de programme. Les plus utilisées sont de deux sortes : les inclusions et les définitions.

 

Une inclusion sert à utiliser des bouts de programmes contenus dans un autre fichier. C'est le cas par exemple lorsque l'on appelle une bibliothèque (ou librairie). Lorsque l'on a utilisé plus haut une variable de type Servo, c'est parce que nous avons inclus une bibliothèque qui définit ce type d'objets. Si l'on veut utiliser des fonctions de mathématiques avancées, on utilisera une bibliothèque dédiée.

 

L'inclusion se fait à l'aide de la directive #include < nom_du_fichier > ou #include " nom_du_fichier ". Dans le premier cas (chevrons), la bibliothèque se situe à un endroit connu par le programme, défini pour contenir l'ensemble des bibliothèques. Dans le second cas (guillements), le fichier importé se situe dans le même répertoire que le fichier qui l'appelle, ou à une position définie à partir de lui.

 

Ainsi, si l'on écrit :

#include < mes_librairies/ma_librairie.h>

on ira chercher les données contenues dans un fichier ma_librairie.h se situant dans le répertoire mes_librairies qui lui-même se trouve dans le répertoire dans lequel sont usuellement placées les librairies.

 

En écrivant :

#include "mes_librairies/ma_librairie.h"

on se place au niveau du répertoire contenant le fichier dans lequel on écrit, on va dans un de ses sous-répertoires nommé mes_librairies, et on importe le fichier ma_librairie.h qui s'y trouve.

 

 

La seconde directive la plus utilisée est #define. Elle sert principalement à remplacer une valeur par un mot. Lors de la compilation, partout où ce mot aura été utilisé, le compilateur placera la valeur. C'est utile par exemple pour définir la hauteur et la largeur d'une image. On écrira :

#define HAUTEUR 600

#define LARGEUR 800

 

Dans le code, on pourra utiliser HAUTEUR et LARGEUR, ce qui permettra de le rendre plus clair. A la compilation, ces valeurs seront remplacées par 600 et 800 respectivement. Elles ne peuvent être modifiées durant l'écriture ou l'exécution du programme.

 

 

 

V - Les variables globales

 

Les variables globales sont placées également en début de programme, et sont connues par toutes les fonctions. De manière générale, une variable définie dans une fonction n'existe qu'au sein de celle-ci. Aucune autre fonction ne la connaît, à moins qu'elle lui soit passée en paramètre.

 

Avec les variables globales, on peut partager des données. Ainsi, si plusieurs fonctions lisent un capteur, ou l'actualisent, on définira ce capteur en dehors des fonctions, ce qui évitera d'avoir à en recréer une instance à chaque nouvelle fonction appelée, et de le transmettre à chaque fois en paramètre.

 

Un bon code trouve l'équilibre entre variables globales et variables locales. Les variables globales seront généralement des variables du système physique (la valeur d'un potentiomètre par exemple), tandis que les variables locales pourront être des compteurs de parcours, des valeurs de stockage pour des calculs intermédiaires, ...

 

 

 

 

SO, LET'S BEGIN !!

 

En route pour notre premier programme, et notre premier montage Arduino.

 

 

 


Pour être informé des derniers articles, inscrivez vous :
Commenter cet article
M
merci
Répondre
U
utilise un while<br /> tellque: if(cm<50){<br /> while(cm<50){<br /> digitalWrite(pinled, HIGH);}}<br /> <br /> digitalWrite(pinled, LOW);
Répondre
J
bonjour, avant de décrire mon problème, je vais décrire le projet que j'ai réalisé, ce projet consiste a utiliser un capteur a ultrason et une LED, je code pour faire en sorte que quand je passe ma main (ou n'importe quoi) à 50 cm ou moins du capteur, alors la LED s'allume mais s'éteins juste après car j'ai aussi coder qu'a plus de 50cm, la LED s'eteint. Et c'est la ou réside mon problème, je n'arrive pas a ce que la LED reste allumé lorsque je passe ma main et qu'elle reste éteinte lorsque je repasse ma main (le système d'un interrupteur enfete). Si quelqu'un pourrait m'aider ce serait génial. Voici le code en quéstion: <br /> /* /* Utilisation du capteur Ultrason HC-SR04 */<br /> <br /> // définition des broches utilisées<br /> int trig = 12;<br /> int echo = 11;<br /> long lecture_echo;<br /> long cm;<br /> boolean baui;<br /> <br /> int pinled;<br /> <br /> void setup()<br /> { pinled = 9;<br /> pinMode(trig, OUTPUT);<br /> digitalWrite(trig, LOW);<br /> pinMode(echo, INPUT);<br /> Serial.begin(9600);<br /> pinMode(pinled, OUTPUT);<br /> digitalWrite(pinled, LOW);<br /> baui = true;<br /> <br /> }<br /> <br /> void loop()<br /> {<br /> digitalWrite(trig, HIGH);<br /> delayMicroseconds(10);<br /> digitalWrite(trig, LOW);<br /> lecture_echo = pulseIn(echo, HIGH);<br /> cm = lecture_echo / 58;<br /> Serial.print("cm : ");<br /> Serial.println(cm);<br /> <br /> if (baui)<br /> {<br /> digitalWrite(pinled, HIGH);<br /> }<br /> <br /> if (cm<=50)<br /> {<br /> pinled,LOW;<br /> }<br /> <br /> if (cm >= 50)<br /> { <br /> baui = true;<br /> }}<br /> <br /> ne faite pas attention a la variable boolean, je testait juste une façon de regler le problème.
Répondre
S
bonjour (ou bonsoir)! Pour un projet j'utilise un servo moteur avec une carte arduino pour le faire tourner de 0 à 180 degrés puis l'inverse. Mais ce petit morceau de programme je le répète plus d'une dizaine de fois! Mon programme n'en fini pas. Et malgré toutes vos explications je n'ai pas saisi comment mettre en fonction un programme de moteur sans paramètre... J'ai essayé avec un void, je n'ai pas réussi. Voila le programme que je veux mettre en fonction:<br /> #include //on importe la bibliothèque Servo<br /> int pinMonServo=9; // variable pour stocker le pin pour la commande<br /> Servo monServo; // on définit un objet Servo nommé monServo<br /> int positionMonServo = 0;<br /> <br /> <br /> void setup() {<br /> monServo.attach(pinMonServo); // on relie l'objet au pin de commande<br /> monServo.write(positionMonServo);<br /> <br /> }<br /> <br /> void loop() {<br /> for(positionMonServo =0; positionMonServo <= 179; positionMonServo++)<br /> {<br /> monServo.write(positionMonServo);<br /> delay(5);<br /> }<br /> for(positionMonServo =179; positionMonServo >=0; positionMonServo--)<br /> {<br /> monServo.write(positionMonServo);<br /> delay(5);<br /> }<br /> }<br /> <br /> }<br /> Pourriez vous s'il vous plaît me conseiller ?
Répondre
V
venousto prezydent de la terre<br /> on vote toute les decision des dirigeant par le peuple<br /> le peuple vote chac decision des dirigeant<br /> le pouvoir entre les main des gens du peuple<br /> votons chac loi de nivo vitale<br /> <br /> phi-phi=phj-phj=phk-phk=phiphjphk=a+bphi+cphj+dphk<br /> <br /> 2phi-1phi=1phi+1(0phi=1)<br /> <br /> ii=jj=kk=ijk=a+bi+cj+dk<br /> <br /> nu=iphi
Répondre