ARDUINO
DEBUTER

Introduction

La carte Arduino UNO R3 est une carte de développement avec tout ce qu'il faut pour sa mise en œuvre, basée sur le microcontrôleur ATMega328P d'Atmel. Elle se programme avec l'EDI (environnement de développement intégré) Arduino, dans le langage Arduino proche du C++. Selon le modèle de carte, le microcontrôleur se présente sous deux formes :

Si cette carte se prend en main assez facilement, elle reste toutefois fragile. Le court-circuit le plus bref la détruit irrémédiablement. Il convient donc de ne pas effectuer ou de modifier un câblage lorsqu'elle est sous tension.

Quelques caractéristiques

Alimentation de la carte

Premiers pas

Sélectionner le type de carte et le port USB

Relier la carte au PC via le câble USB. Dans le menu Outils :

Test de fonctionnement

La carte Arduino UNO possède une petite DEL (diode électroluminescente ou LED en anglais) associée à la variable LED_BUILTIN (de valeur 13), permettant d'effectuer des tests. Entrer le programme (on dit aussi croquis) :

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // la DEL émet de la lumière
  delay(1000);                       // on attend une seconde
  digitalWrite(LED_BUILTIN, LOW);    // la DEL cesse d'émettre de la lumière
  delay(1000);                       // on attend une seconde
}

Dans le menu de l'EDI :

Observer le Le clignotement de la DEL sur la carte et recommencer en faisant varier le délai.

Questions

A l'aide de l'icône Vérifier, répondre aux questions suivantes :

Un peu de programmation

Fonction avec un paramètre

Tester le programme suivant :

void clignote(int nombre) {
  while (nombre>0) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(200);
    digitalWrite(LED_BUILTIN, LOW);
    delay(400);
    nombre=nombre-1;
  }
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  clignote(3);
  delay(1000);
}

Variables locales, globales

Modifier le programme, avec la fonction random() et une variable locale :

void loop() {
  int cligne = random(8);
  clignote(cligne);
  delay(1000);
}

Modifier le programme, avec une variable globale cette fois :

int cligne = random(8);

...

void loop() {
  clignote(cligne);
  delay(1000);
}

Récupérer sur le PC les données provenant de la carte

Afficher du texte, des nombres

Le moniteur série permet d'afficher sur l'ordinateur du texte, des nombres. On y accède via le menu Outils/Moniteur série.

Exemple

Tester ce programme :

unsigned int compteur = 0    // unsigned signifie que l'entier est positif

void setup() {
  Serial.begin(115200);      // mise en route du port série
}

void loop() {
  Serial.print("Compteur : ");
  Serial.println(compteur);
  compteur++;
  delay(2000);
}

Ouvrir le menu Outils/Moniteur série, régler la vitesse de connexion (115200 bauds), pour obtenir :

De l'encodage ASCII à UTF-8

Les données envoyées par la carte Arduino UNO sur le port USB sont des octets. Du point de vue du moniteur série, ces huit bits représentent des caractères.

void setup() {
  Serial.begin(115200);
}

void loop() {
  // l'octet de valeur 97 donne le caractère a 
  Serial.write(97);
  Serial.print("a");
  // les octets de valeurs 195 et 169 donnent le caractère é
  Serial.write(195);
  Serial.write(169);
  Serial.println("é");
  delay(5000);
}

Ce qui est vrai pour les versions récentes de l'EDI ne l'est pas nécessairement pour une version ancienne. Le code ci-dessous permet de tester la prise en charge des caractères accentués.

void setup() {
  Serial.begin(115200);
}

void loop() {
  Serial.println("èéà");
  delay(5000);
}

Il est possible d'envoyer au moniteur série un seul octet avec le premier bit à 1. Le code ci-dessous permet de constater que cela ne présente aucun intérêt.

void setup() {
  Serial.begin(115200);
}

void loop() {
  for (int i=200; i<250; i++) {
    Serial.write(i);
    Serial.println();
  }
  delay(300);
}

Tracer une courbe

Le traceur série permet d'afficher sur l'ordinateur une série de nombres sous la forme d'un graphique. On y accède via le menu Outils/Traceur série. Dans l'exemple ci-dessous, les valeurs envoyées par la carte Arduino varient de 0 à 1023.

float v=0.0;

void setup() {
  Serial.begin(115200);
}

void loop() {
  Serial.println(round(511.5*sin(v)+511.5));
  v=v+0.1;
  delay(20);
}

Piloter la carte depuis le PC

Les octets envoyés par le PC à la carte Arduino via le câble USB sont stockés dans la mémoire tampon (buffer en anglais). Cette mémoire tampon se vide au fur et à mesure que les octets sont lus par le programme de la carte Arduino. Cette mémoire tampon peut recevoir 63 octets au maximum. Cela se vérifie avec le programme ci-dessous, en envoyant un grand nombre de caractères via le moniteur série.

void setup() {
  Serial.begin(115200);
}
void loop() {
  int nb_octets=Serial.available();
  Serial.println(nb_octets); 
  delay(1000);
}

Si vous créez votre IHM (interface homme-machine), en Python par exemple, vous pouvez envoyer à la carte Arduino les octets de votre choix. Par contre, si vous utilisez le moniteur série de l'EDI, vous devez comprendre comment il se comporte. Ce comportement, auquel il faut vous adapter, varie selon la version de l'EDI.

Qu'obtient-on en envoyant une suite de caractères via le moniteur série? Quels octets sont envoyés à la carte Arduino UNO après :

Le code ci-dessous permet de répondre à cette question.

void setup() {
  Serial.begin(115200);
}
void loop() {
  int nb_octets=Serial.available();
  Serial.println(nb_octets); 
  for (int i=0; i<nb_octets; i++) {
    int octet= Serial.read();
    Serial.println(octet);
  }
  delay(1000);
}

Piloter la DEL de la carte en envoyant un caractère

Tester le programme ci-dessous. Pour allumer ou éteindre la DEL présente sur la carte Arduino UNO, utiliser la première ligne du moniteur série, écrire les caractères a ou e. Ensuite, appuyer sur la touche Entrée du clavier ou cliquer sur bouton Envoyer.

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  if (Serial.available()>0) {       // si des caractères sont envoyés
    char caractere = Serial.read(); // lecture d'un caractère
    if (caractere == 'a') {
      digitalWrite(LED_BUILTIN, HIGH);
    }
    if (caractere == 'e') {
      digitalWrite(LED_BUILTIN, LOW);
    }
    Serial.print(caractere);
  }
}

Variante

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  while (Serial.available()==0) { } // attente d'un envoi
  int octet = Serial.read();        // lecture d'un octet
  if (octet == 97) {
    digitalWrite(LED_BUILTIN, HIGH);
  }
  if (octet == 101) {
    digitalWrite(LED_BUILTIN, LOW);
  }
  Serial.write(octet);
}

Piloter la DEL de la carte avec une chaîne de caractères

Tester ce programme, en utilisant maintenant les chaînes de caractères Allumer DEL ou Eteindre DEL :

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  while (Serial.available() > 0) {
    String commande = Serial.readString();
    if (commande == "Allumer DEL\n") {
      digitalWrite(LED_BUILTIN, HIGH);
    }
    if (commande == "Eteindre DEL\n") {
      digitalWrite(LED_BUILTIN, LOW);
    }
    Serial.print(commande);
  }
}

Il se peut que ce programme ne fonctionne pas. Selon la version de l'EDI, le moniteur série envoie ou pas un retour de chariot \r, juste avant le saut de ligne \n... La méthode trim() lève l'incertitude en supprimant les caractères non imprimables en début et fin de chaîne.

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  while (Serial.available() > 0) {
    String commande = Serial.readString();
    commande.trim();
    if (commande == "Allumer DEL") {
      digitalWrite(LED_BUILTIN, HIGH);
    }
    if (commande == "Eteindre DEL") {
      digitalWrite(LED_BUILTIN, LOW);
    }
    Serial.println(commande);
  }
}

Autre solution

Ceux qui veulent se rapprocher de la syntaxe du langage C n'emploient pas d'objets de type String. En C, il n'y a pas de chaînes de caractères. On utilise à la place un tableau de caractères avec une particularité : Le dernier caractère est suivi d'un octet de valeur 0, appelé caractère de fin de chaîne ou terminateur, noté '\0'. Les caractères sont donc lus les uns après les autres pour former le tableau de caractères.

char commande[20];
int i = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  while (Serial.available() > 0) {
    char c = Serial.read();
    if ((c == '\n') || (c == '\r')) {
        commande[i] = '\0';
        if (!strcmp(commande, "Allumer DEL")) {
          digitalWrite(LED_BUILTIN, HIGH);
        }
        if (!strcmp(commande, "Eteindre DEL")) {
          digitalWrite(LED_BUILTIN, LOW);
        }
        Serial.println(commande);
        i = 0;
    } 
    else {
      commande[i] = c;
      i++;
    }
  }
}

Question

Quels sont l'avantage et l'inconvénient du langage C par rapport au C++?

Etablir un dialogue

Exemple de calcul

Tester ce programme ci-dessous :

void setup() {
  Serial.begin(115200);
  delay(100);
  while (Serial.available()!=0) { Serial.read(); }
}

void loop() {

  Serial.println("Entrer un nombre entier : nb1 = ?");
  while (Serial.available()==0) { }
  String ch_nb1=Serial.readString();

  Serial.println("Entrer un autre nombre entier : nb2 = ?");
  while (Serial.available()==0) { }
  String ch_nb2=Serial.readString();

  int nb1 = ch_nb1.toInt();
  int nb2 = ch_nb2.toInt();
  int nb3 = 2*nb1+nb2;
 
  Serial.print("2 x nb1 + nb2 = 2 x ");
  Serial.print(nb1);
  Serial.print(" + ");
  Serial.print(nb2);
  Serial.print(" = ");
  Serial.println(nb3);
  
}

Autre écriture

Il est possible d'écrire directement :

int nombre_entier = Serial.parseInt();
float nombre_decimal = Serial.parseFloat();

Dans les deux cas, la variable prend pour valeur le nombre entré. Par contre, les caractères '\r' ou '\n' sont éventuellement laissés dans la mémoire tampon... Il faut penser à la vider avant d'entrer une nouvelle valeur!

Complément

La commande suivante convertie une chaîne de caractères en nombre décimal :

float nb1=ch_nb1.toFloat();

Mathématiques

De nombreuses fonctions mathématiques sont disponibles en important la bibliothèque math.h. Exemple :

#include <math.h>
float nb1 = M_PI/3;
float nb2 = cos(nb1);
float nb3 = sin(nb1);

Exercice d'application

L'IMC, en kg/m2 est l'indice de masse corporelle. Il se calcule en fonction de la masse m en kg et de la taille t en m, avec la formule :

IMC = mt 2

L'interprétation de l'IMC dépend de l'âge, de l'activité sportive... En première approximation :

IMC < 19Maigreur
19 ≤ IMC ≤ 25Normal
25 < IMCSurpoids

Modifier le programme proposé dans l'exemple pour calculer l'indice de masse corporelle et interpréter le résultat.

Annexes

Documents de référence

Exemple de code en C

Le code ci-dessous, en langage C, permet à la DEL associée au connecteur 13 de clignoter.

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define Port_led PORTB
#define Pin_led PB5    // PB5 = d13 pour arduino
#define seconde 0x64   // définit valeur seconde

volatile uint8_t F_bdet = 0;   // définit et RaZ flag base de temps
uint8_t cpt_cycle = seconde;   // définit et positionne compteur
uint8_t Val_BdeT = 0x64;   // 10ms quartz 16MHz

// interruption sur timer 0
ISR (TIMER0_OVF_vect) {   // interruption sur débordement timer0
  TCNT0 = Val_BdeT;
  F_bdet = 1;
}

void Init_port() {   // sous programme initialisations
  DDRB = 0x20;   // fixer la direction du port (1 = sortie) : 0010 0000	
  PORTB = 0x00;   // RaZ port b
}

void Init_timer0() {
  TCNT0 = Val_BdeT;   // positionne le compteur
  TIMSK0 |= (1 << TOIE0);   // autorise l'IRQ sur débordement
  TCCR0B |= (1 << CS02) | (1 << CS00);   // prédivision par 1024
}

int main() {
  Init_port();   // appel initialisation port
  Init_timer0();   // appel initialisation timer 0
  sei();   // autorise l'IRQ
  while(1) {   // boucle principale
    do {   // boucle 10 ms
      while (F_bdet == 0);   // attente de fin base de temps (10ms)
      F_bdet = 0;   // RaZ flags BdeT
      cpt_cycle -= 1;
    }
    while (cpt_cycle != 0);   // boucle des secondes
    cpt_cycle = seconde;   // recharge compteur
    Port_led ^= (1 << Pin_led);   // inverse sortie led
  }
  return 0;
}