ex0ns

about
A Computer Science Student's blog, about programming, algorithms, hack and a lot more.

Débuts avec Arduino : Partie 3

07 Jul 2013

Voici le troisième article consacré à Arduino, le thème est : "SD, LCD", avec, comme fil conducteur, la réalisation d'un lecteur de carte SD sur un écran LCD (accompagné de quelques boutons afin de se diriger). En tant que tel, le projet n'a pas tant d'utilité (les autres projets non plus...) mais on peut imaginer l'intégrer dans un montage plus important, et autonome (j'ai quelques idées que je développerai à la fin de cet article), néamoins, il présente des notions de programmation et d'éléctronique interessantes. Dans cet article, j'utilise:

  • Un module SD, j'ai trouvé celui ci sur pas mal de site, pour un prix abordable
  • Un écran LCD avec connexion I2C
  • Quelques resistances d'une valeur de 10K Ohms (4)
  • Des boutons (4)

Pour ce qui est de l'écran, j'ai décidé ce prendre un modèle avec une connexion i2c afin de limiter le nombre de pin's nécessaire pour faire fonctionner l'écran, le fonctionnement de ce système est largement detaillé et documenté sur internet, nous en resterons à son utilisation (cependant comprendre le phénomène est également interessant). Comme d'habitude, on va séparer le projet en plusieurs parties, dans notre cas, trois, une pour le lecteur de carte, une pour l'écran LCD et une pour les controles et les boutons. Le projet est bien plus gros que les petits tests que je faisait avant, le code final tourne plus autour des 150lignes.

Lecture de la carte

Le lecteur SD comporte six connexions, dont deux pour une entrée sur 3.3V ou 5V et pour le ground. Les autres, sont : CLK (Clock), que l'on connecte au PIN 13, DO sur le PIN 12 et DI sur le PIN 11, CS quand à lui peut être branché à n'importe quel PIN de l'arduino. Le choix des PIN's n'est pas arbitraire, en effet, pour fonctionner, le lecteur utilise une connexion SPI (Serial Peripherical Interface) et on voit un peu plus bas sur cette page, que les connexions SPI ont besoin d'être connectée sur des PIN précis en fonction de l'Arduino que l'on utilise. DO doit être connecté sur MISO, DI sur MOSI et CLK sur le pin n°13.

Dans la suite de cet article, nous utiliserons la bibliothèque SD déjà présente sur l'IDE par defaut. On remarque d'ailleurs sur cette page que la carte doit être formatée en FAT16 et, par conséquent, avoir une taille inférieure à 2Go, à priori le FAT32 fonctionne également, mais j'ai eu quelques problèmes à l'utilisation. Voici la commande pour effectuer ce formattage:

mkdosfs -F 16 /dev/sdc1

Afin de vérifier que tout fonctionne, pré-chargez l'exemple SD/CardInfo et modifiez le numéro du PIN utilisé pour CS (à priori le n°10), puis tester le sur la carte, si cette dernière est bien detectée, alors nous allons pouvoir commencer à coder notre lecteur de carte. La première étape est de construire une liste de tous les fichiers et dossiers contenus dans le repertoire, ce qui simplifiera grandement la partie pour la gestion de l'écran (afficher uniquement deux entrées à partir d'un index i du tableau). Néanmoins, il nous faut un moyen de nous repérer par rapport à la racine de la carte SD (afin d'ouvrir les sous dossiers, tout en pouvant revenir à celui précedent). Pour la liste des fichiers, il faut également pouvoir distinguer un fichier d'un dossier, on ne peut donc pas se contenter d'un

char **

Nous allons créer une structure basique nommée directory :

struct directory{
  char *name;
  boolean isDir;
};

Petit détail (qui m'a demandé pas mal d'effort pour trouver la solution), une structure (pour pouvoir être utilisées dans un retour de fonction par exemple), doit être déclarée dans un .h séparé ! (sur l'IDE arduino, il faut créer un nouvelle onglet à l'aide de la flèche à droite du nom de votre fichier). source. Ajoutons une variable globale afin de sauvegarder le contenu du répertoire ouvert:

directory *openDir = NULL;

Pour ce qui est du setup, on a :

void setup(){
    Serial.begin(9600);
    pinMode(10, OUTPUT);
    if(!SD.begin(10)){
        Serial.println("Error opening SD");
        return;
    }
}

Pour se souvenir du dossier actuel, on va simplement déclarer une nouvelle variable globale

char **path = NULL;

Ainsi que d'ajouter la ligne suivante dans le setup():

path = (char**)malloc(sizeof(path[0]));

Il faut maintenant coder une petite fonction qui nous permettra de construire le vrai chemin à partir de cette variable (concatener les différentes parties de ce dernier), en n'oubliant pas la variable globale int pathLength=0 qui nous permet de connaitre le nombre de dossier parcouru jusque là.

char *concatPath(){
    int sizePath = 1;
    int slashes = 0;
    for(int i=0; i < pathLength; i++)
        sizePath+=strlen(path[i]);
    if(pathLength>1)
        sizePath+=pathLength-1;
    char *tmp = (char*)malloc(sizePath*sizeof(char));
    strcpy(tmp,path[0]);
    for(int i=1;i<pathLength;i++){
        if(i>=1)
            strcat(tmp,"/");
        strcat(tmp, path[i]); 
    } 
    return tmp;
}

Créons maintenant une fonction qui récupère le nombre de fichiers/dossiers contenus dans un dossier, cela facilitera la création du tableau de structure, ajoutons également deux variables globales filesInDirectory :

int filesInDirectory = 0;
File dir;
void nbFile(){
    File entry;
    filesInDirectory = 0;
    dir.rewindDirectory();
    if(!dir.isDirectory()){
     Serial.println("Not a directory");
    return;
  }
  while((entry = dir.openNextFile())){
    filesInDirectory++;
    entry.close();
  }
  dir.rewindDirectory();
}

File dir représente le dossier courant, que nous parcourons afin de compter l'éléments qu'il contient, rewindDirectory permet de remettre à 'le curseur' au début du dossier, pour pouvoir le parcourir à nouveau. Nous avons maintenant toutes les fonctions auxiliaires necéssaires au stockage des informations du dossier dans lequel nous nous situons, codons maintenant la gestion de l'ouverture d'un chemin quelconque. La variable globale permettant de sauvegarder l'index du fichier/dossier actuellement selectionné:

int cSelect=0

void initDir(char *dirName){
    File entry;
    cleanOpenDir();
    dir = SD.open(dirName);
    nbFile();
    openDir = (directory *)malloc(filesInDirectory*sizeof(struct directory));
    for(int i=0;i<filesInDirectory;i++){
      entry = dir.openNextFile();
      openDir[i].name = strdup(entry.name());
      openDir[i].isDir = entry.isDirectory();
      entry.close();
    }
    cSelected = 0;
    dir.rewindDirectory();
    dir.close();
    displayDir(0);
}

On compte donc le nombre d'éléments, on nettoie la variable globale openDir() (nous verrons cette fonction par la suite), car allouée dynamiquement par strdup() ! La fonction displayDir elle, est liée à l'écran LCD, et fera donc l'objet de la seconde partie de cet article, que nous allons d'ailleurs attaquer de suite, car la gestion de la lecture de la carte SD est terminée ! Un dernier élement avant de passer à la partie suivante : fonction cleanOpenDir, afin de nettoyer proprement la variable globale openDir.

void cleanOpenDir(){
   if(openDir != NULL){
      for(int i=0;i<filesInDirectory;i++)
        free(openDir[i].name);
   }
  free(openDir);
}

Gestion de l'écran

Comme énoncé dans l'introduction, je n'utilise pas une connexion "normale" pour l'écran LCD, afin d'économiser les PINs, mais une connexion I2C, hors, ce mode de communication n'est pas géré par default avec les bibliothèques Arduino, et la plupart de celle qu'on trouve sur internet commencent à vieillir et à ne pas être supportées par les cartes récentes (ou du moins, je n'ai pas réussi à les faires fonctionner avec). Cependant, AdaFruit a réaliser une bibliothèque fonctionelle sur mon modèle d'écran, qui est disponible sur GitHub, pour ceux qui n'ont jamais installé de bibliothèques sur Arduino, voici la procédure sur le site officiel.

La connexion I2C comporte (en plus du courant et du ground), deux connexions : SDA et SCL, une fois de plus, d'après le site officiel, il faut connecter SDA à l'analogique 4 (A4) et SCL à l'analogique 5 (A5), et fournir à l'écran une tension de 5V (probablement fonctionel avec 3.3 mais étant donné que le reste du montage tourne sur du 5V autant ne pas se compliquer la vie). Modifions maintenant le setup afin d'initier l'écran LCD :

lcd.init();
lcd.backlight();
lcd.blink();

Pour ce qui est de l'affichage, nous allons afficher le contenu, deux éléments à chaque fois (un seul pour le dernier si le nombre total est un nombre impaire), voici donc la fonction displayDir.

void displayDir(int index){
  lcd.clear();
  if(filesInDirectory != 0){
    for(int i=0;i<min(2,filesInDirectory-index);i++){
      lcd.setCursor(0,i);
      lcd.print(openDir[index+i].name);
      if(openDir[index+i].isDir)
        lcd.print("/");
    }
  }else{
    lcd.print("No file found"); 
  }
  lcd.setCursor(0,0);
}

Cette fonction prend un argument : l'index du fichier à afficher, et determine par la suite s'il faut afficher 1 ou 2 documents, puis replace le curseur à sa position originale (en haut à gauche). Afin de distinguer les dossiers des fichiers, on ajoute un "/" à la fin de ces premiers. Vous n'allez peut être pas me croire, mais la partie sur la gestion de l'écran LCD est terminée, l'utilisation des bilbiothèque a rendu le processus bien plus aisé ! Nous attaquons maintenant la partie qui demande le plus de branchement (d'un point de vue éléctronique) et qui constitue la majeur partie du loop() (vous aviez remarqué qu'il était manquant n'est ce pas !) : les boutons !

Controles

Pour cette partie, il nous faut brancher quatres boutons, un pour monter, un pour descendre, un pour valider (changer de dossier) et un pour revenir en arrière, chacun a besoin d'une résistance, afin de faire un montage de résistance en pull down pour eviter le court circuit et les interferences sur le montage, tout ceci est detaillé sur cet article, comme ca, quand le bouton est pressé, sa valeur est égale à HIGH, pour nos quatres boutons, le montage ressemble donc à quelque chose tel que :

buttons_control
Nous utilisons donc les PINs 4 à 7 dans cet exemple, mais de toute facon, nous les définirons dans une variable globale représentant, dans l'ordre : bas, validation, haut, retour

const int buttons[] = {5,6,7,2};

Ainsi qu'une second variable pour leur état respéctif:

int buttonsStates[  = {0,0,0,0};

Attaquons nous maintenant à la gestion de ces boutons, à l'intérieur du loop().

void loop(){
  for(int i = 0;i < 4; i++){
   int cState = digitalRead(buttons[i]);
   if(cState != buttonsStates[i]){
       buttonsStates[i] = cState;
       if(cState == HIGH){
          switch(i){

          }
       }
   } 
  }
}

Voici pour la base de la lecture sur chaque bouton, on compare donc l'état actuel avec le précedent, pour chaque bouton, et le switch va nous servir à determiner quel est le bouton qui est préssé et donc, quel action doit être demarrée, nous aurions très bien pu utiliser une succession de test de i, mais je dois avouer apprécier le switch :). Dans notre tableau, comme indiqué precedement, le premier index est le bouton du bas, le second, la validation, le troisème le bouton du haut, et le quatrième le retour en arrière. Voici le code pour le bouton du bas :

case 0:
  if(cSelected < filesInDirectory-1){
    if(cSelected == 1 || cSelected%2){
      cSelected++;
      displayDir(cSelected);
    }else{
      cSelected++;
      lcd.setCursor(0,1);
    }        
  }
break;

Donc concrètement, on regade s'il reste des fichiers/dossiers à afficher, et la position actuelle du curseur (impaire en bas, pair en haut, étant donné que nous n'avons que deux lignes), et si nous sommes en bas, il faut charger les nouveaux fichiers à afficher, et mettre à jour l'affichage, dans le cas ou le cuseur est en haut, nous avons juste à changer sa position ainsi que l'index de l'élément selectionné. Pour ce qui est du bouton de validation, il doit nous servir à changer de dossier (et eventuellement à charger un fichier, dans une application plus complète), le code ressemble donc a quelque chose comme:

case 1:
  if(openDir[cSelected].isDir){
    path = (char**)realloc(path, (pathLength+1)*sizeof(char *));
    path[pathLength] = strdup(openDir[cSelected].name);
    pathLength++;
    initDir(concatPath());
  }
break;

On verifie donc que la selection est un dossier, et si c'est le cas, on ajouter un élément à notre path (vous l'aviez oublié n'est ce pas ?) et puis on initialise ce nouveau dossier, afin de charger son contenu l'afficher à l'écran. Le bouton haut fait également l'inverse du premier, si ce n'est qu'il faut s'assurer que l'index de le selection ne soit pas inférieur à 0.

case 2:
  if(cSelected > 0){
    if(cSelected%2){
      lcd.setCursor(0,0);
    }else{
      displayDir(cSelected-2);
      lcd.setCursor(0,1);
    }
    cSelected--;
  }
break;

Enfin, pour le bouton de retour, il faut vérifier que nous ne sommes pas déjà à la racine de la carte, et puis mettre à jour la variable path.

case 3:
  if(pathLength){
    pathLength--;
    free(path[pathLength]);
    if(pathLength == 0)
      initDir("/");
    else
      initDir(concatPath());
  }
break;

Et voilà, en mettant le tout ensemble (disponible ici, vous devriez pouvoir explorer le contenu de votre carte SD sur l'écran LCD, et naviguer dedans à l'aide des boutons de controle ! Génial n'est ce pas ? Seul, ce projet n'est pas très utile, mais on pourrait imaginer un embarqué indépendant, ou alors (sur un robot), un moyen de charger des parcours pre-définis dans des fichiers, ou alors des configurations à adopter. On peut également imaginer créer des fichiers ayant une certaines structure, que l'on pourrait charger directement et qui joueraient un morceau directement sur un haut parleur branché à l'Arduino, enfin je pense que les applications sont multiples, et puis permettent de manipuler une grande diversité de bibliothèque sous Arduino, ce qui est toujours interessant (et amusant). J'espère que cet article n'est pas trop long (je n'aime pas trop écrire des articles aussi long, mais ce projet me tenait à coeur), et, qu'une fois de plus, j'ai réussi à éveiller votre curiosité.

Bonne vacances à tous !

Débuts avec Arduino : Partie 2

24 Feb 2013

Après de longues semaines (mois ? ) d'absence, me voilà de retour, et motivé ! Voici donc le second numéro de la suite d'articles basés sur Arduino, je tiens à préciser que j'en ai profité pour rajouter des vidéos sur le premier article.

Au programme de ce deuxième article, il y a les ponts diviseurs de tension (et leur utilité), les photorésistances (résistance variable en fonction de l'intensité lumineuse), mais également les buzzers (pour faire du bruit !), en outre, encore des applications relativement simples, mais qui n'en restent pas moins ludiques et intéressantes.

Liste du matériel nécessaire:

  • Des photorésistances (au moins 2)
  • Des résistances (500 et 10k par exemple)
  • Un bouton poussoir
  • Des fils

Le projet final de cet article est de créer un "clavier tactile" (photosensible), permettant de libérer différents sons (comme à chaque fois, les parties sont étudiées séparément, une à une, avant d'être rassemblées).

0x01] Le Matériel

Les photorésistances

Une photorésistance est une résistance variable (résistance photodépendante) qui varie en fonction de l'intensité de la lumière captée. La résistance de cette dernière diminue lorsque l'intensité lumineuse augmente. Ainsi, nous pouvons utiliser ces photorésistances comme des capteurs lumineux, définir un seuil de luminosité et effectuer des actions en conséquence (en domotique, la gestion de l'intensité de l'éclairage dans une pièce par exemple). Dans cet article, nous utiliserons ces photorésistances pour créer des touches "tactiles". En plaçant le doigt entre la source lumineuse et la résistance, il est possible de modifier brutalement l'intensité captée, et donc de déclencher une action en avale.

Les buzzers

Ce sont grossièrement des "haut-parleurs" qui vont donc nous permettre d'émettre des sons. Pour cela, nous utiliserons la fonction tone(pin, frequency, duration) dont un exemple est disponible sur le site officiel. Les buzzers, tout comme les LEDs possèdent une grande patte (+) et une petite (-). L'utilisation d'un buzzer est donc ce qu'il y a de plus basique, un branchement (avec une petite résistance, disons 100ohm (comme sur le site Arduino)), une fonction Tone(), et le tour est joué !

Dans notre montage, nous aurons donc deux circuits, le premier avec une photorésistance, dont nous pourrons lire la valeur à l'aide de la fonction analogRead (vous vous en souvenez ? J'en avais parlé dans mon premier article :)). Dans notre cas, anaogRead permet de lire une tension aux bornes d'un pin analogique (A0-A5), pour autant, nous ne pouvons pas lire directement la valeur à la sortie de notre photorésistance, pour cela, nous allons utiliser un pont diviseur de tension. L'autre circuit est celui qui contrôlera le buzzer (ou les buzzers, nous verrons après pourquoi).

0x02] Ponts diviseurs de tension

Définition

Un pont diviseur de tension est un montage en série qui comme son nom l'indique, permet d'obtenir une tension plus faible, à partir d'une tension entrée.

Dans cet article, il sera constitué de deux résistances, R1 et R2, la formule liée à une pont diviseur de tension est : ${Vout} = {Vin} * {R2}/(R1+R2)$, avec Vout et Vin en volt; R1 et R2 en Ohm. Voici un schema du pont diviseur de tension que nous allons créer : voltage divisor

Utilité (dans le cadre de cet article)

Comme stipulé précédemment, nous aurons besoin de lire la valeur de la tension aux bornes de la photorésistance, grâce à la formule énoncée ci-dessus, en connaissant Vout (mesuré par AnalogRead), Vin (3.3V ou 5V sur l'Arduino) et la valeur de R1 (ou de R2), c'est-à-dire une résistance connue, nous pourrons déterminer la valeur de la résistance variable (photorésistante). Deux choix s'offrent à nous, dans le premier, nous envoyons directement la valeur de cette résistance, à travers la fonction map(value, fromLow, fromHigh, toLow, toHigh) au buzzer ce qui, en soit, risque de produire des sons relativement atroces et dont je voudrais nous protéger, ou alors, nous pouvons donner fréquences au buzzer, et activer ce dernier lorsque le seuil d'intensité lumineuse minimal (fixé) est dépassé (dans le sens inférieur), dans un soucis pédagogique, j'effectuerai les deux montages et les deux codes seront disponibles.

Lecture analogique sur diviseur

Connectons une première résistance, de 470ohm à la source 5V disponible sur l'Arduino, branchons également notre photorésistance de façon à créer ce pont diviseur de tension (ne pas oublier de relier la photorésistance au sol (GND)), branchons un fil en direction de l'entrée analogique n°0 au milieu du pont, ce qui devrait donner plus ou moins :

diviseur

La valeur de la résistance de la photorésistance varie en fonction de la luminosité, il va de soit (d'après la formule énoncée plus haut) que la tension de sortie varie en même temps, c'est la valeur que nous devons lire sur l'entrée analogique n°0.

Exercice n°1

Histoire de s'habituer une nouvelle fois avec le transfert de données par la fonction Serial, nous allons afficher en temps réel la valeur de la photorésistance (on va faire un peu de mathématiques par la même occasion). Il s'agit premièrement d'isoler R2 dans la relation précédente, pour cela, je vous conseille de procéder à un double passage à l'inverse, on retrouve donc : $${Vin}/{Vout} = {R1+R2}/{R2}$$ $${Vout}/{Vin-Vout} = {R2}/{R1}$$ $${R2} = (Vout*R1)/(Vin-Vout)$$ Les valeurs respectives de chaque variable sont :

  • 5v pour Vin (ou 3.3 en fonction de l'alimentation que vous choisissez)
  • R1, 470ohm, choisi arbitrairement
  • AnalogRead() sur A0 pour connaitre la valeur de Vout

Note : AnalogRead() fournit une valeur située entre 0 (0v) et 1024 (5v), il faut donc convertir Vout avant de l'utiliser dans la formule (sinon les résultats risquent d'être quelques peu étranges). Pour faire cette transformation on utilise : $${Vout} = (5*{Vout})/1024$$ Vous l'aurez compris, le premier exercice consiste à écrire un code qui affiche sur la connexion serial la valeur de la photorésistance (en utilisant le montage précédent)

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

void loop(){
        float vout = analogRead(A0), vin = 5, r1 = 470, r2 = 0;
    vout = (5*vout)/1024;
    r2 = (vout*r1)/(vin-vout);
    Serial.println(r2);
    delay(250);
}

Ici, on utilise des variables de type float pour éviter les imprecisions liées aux arrondis des rapports (imaginez les tronquages qui sont effectués lors de la division par 1024...). Rien de bien sorcier, juste un petit exercice histoire de reprendre les bases. Attention, la valeur affichée peut différer de la réalité (remplacez R2 par une résistance fixe connues) et être liée à de nombreuses incertitudes (celle de R1, de R2, de la lecture analogique, des arrondis et autres), mais elle permet cependant d'avoir une idée approximative de la valeur. Pour autant, en remplacant la photorésistance par une résistance de 470ohm elle aussi, la valeur affichée est tout à fait correct.

Maintenant que nous savons comment réaliser le premier circuit et récupéré la valeur de la résistance, il faut s'attaquer au second circuit, celui du buzzer, de même, ce montage reste tout ce qu'il y a de plus basique :).

Circuit du buzzer

Une résistance de 100ohm, une buzzer, un pin rien de plus pour notre circuit, dont voici le schema : Buzzer.

Avant de finaliser le montage, voici un petit exercice mélodique histoire de découvrir un peu le fonctionnement du buzzer, le but est de jouer la gamme tempérée (do, ré, mi, fa, sol, la, si, do), vous pourrez trouver les fréquences de chaque note facilement sur wikipedia.

int buzzer = 7;
void setup(){
    pinMode(buzzer, OUTPUT);
}

void loop(){
    float frequences[] = {261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25};
    for(int i=0; i<8; i++){
        tone(buzzer, frequences[i]);
        delay(1000);
    }
}

Les plus curieux (ou instruits) auront deviné que la résistance ne modifie que l'intensité du son, plus la résistance sera élevée, plus le son produit sera faible, modifiez la résistance par un photorésistance, et plus l'intensité lumineuse sera élevée, plus l'intensité sonore sera elle aussi importante (cela pourrait être un début de système permettant de jouer une musique lorsque vous allumez la lumière de la pièce par exemple). Maintenant que nous avons nos deux circuits, nous allons fusionner les deux afin de n'en faire plus qu'un ! Mais avant cela, il nous manque un dernier ingrédient avant d'éviter que le son se repete en boucle. Voici une illustration de cette merveilleuse gamme jouée par un buzzer de piètre qualité:

Seuil d'intensité

Il s'agit ici de définir un seuil de luminosité (grâce à la tension lue sur l'entrée analogique) en dessous du quel le buzzer devra être activé (sinon la photorésistance n'est d'aucune utilité), dans un premier temps, je pense que le plus simple et de re-faire le premier montage (pont diviseur de tension, avec une résistance et une photorésistance), de regarder les valeurs de la tension (pas besoin de s'embeter avec la formule) puis de masquer la lumière en placant votre doigt au dessus de la photorésistance, et de re-noter la valeur affichée, puis de définir une variable qui représentera le seuil à dépasser pour activer le son.

Cette méthode est un peu crade et pas portable (l'intensité dépendra du milieu et il faudra le changer à chaque modification de la luminosité (extérieur, nouvelle lampe)). Cependant, on pourrait considérer une autre méthode qui consiste à faire un circuit avec un bouton, et de déclancher la determination du seuil lorsque ce dernier est pressé. Pour autant, je n'ai toujours pas detaillé le fonctionnement des boutons, et je souhaite garder ce sujet pour un prochain article, sans le bacler, mais ne vous inquietez pas, lorsqu'il sera temps, je reviendrai sur cette article afin de présenter le montage complet !

Fusion !

Pour ce qui est du montage, c'est un assemblage des deux précédents, ce qui nous donne : Montage Final Je serais prêt à parier que vous aviez trouvé !

Comme ce que j'avais annoncé au début de l'article, nous allons utiliser ce montage de deux facons, la première avec la fonction map(value, fromLow, fromHigh, toLow, toHigh) afin de transformer les valeurs de la résistances (ou de la tension, ce qui nous évite les calculs) en fréquence audibles (mais loin d'être charmantes :) ), voici donc le code, suivi d'une vidéo

int buzzer = 7, res = A5, seuil = 800, duree = 500; 
int res    = A0;
void setup(){
    pinMode(buzzer, OUTPUT);
}
void loop(){
    int tension = analogRead(res), frequence;
    if(tension > seuil){
        frequence = map(tension, 0, 1024, 260, 525);
        tone(buzzer, frequence);
        delay(duree);
        noTone(buzzer);
    }
}

Tout d'abord, il faut déterminer si la valeur lue est supérieur au seuil arbitraire puis comme nous savons que les valeurs lues par l'entrée analogique varient entre 0 et 1024 et que nous voulons obtenir des fréquences situées entre 260Hz et 525 Hz, on passe ces paramètres à la fonction map. Le reste du code ne change pas, si ce n'est la fonction noTone() qui permet d'arreter le buzzer après qu'il ai sonné (sans quoi le buzzer continuera de sonner sans s'arreter avec un fréquence égale à la dernière fréquence connue). L'autre montage que je vous avez proposé de réaliser est un montage attribuant une fréquence précise à une photorésistance, et de ne jouer cette dernière que si le seuil est depassé. Afin que ce projet soit plus interessant, j'utiliserai 2 photorésistances, une sur le pin analogique A0 et l'autre sur le A2 (les deux autres résistances ne changent pas : 470ohm) voici le code :

int buzzer = 7, seuil = 800, duree = 1000; 
int photores[] = {A0, A2};
float frequences[] = {261.63, 293.66}; // Fréquence attribué à chaque capteur
void setup(){
    pinMode(buzzer, OUTPUT);
}

void loop(){
    for(int i = 0; i< 2; i++){ // On parcours nos différentes photorésistances
        int tension = analogRead(photores[i]), frequence;
        if(tension > seuil){ 
            tone(buzzer, frequences[i]);
            delay(duree);
            noTone(buzzer); // pareil que tout à l'heure, il faut penser à arreter le buzzer.
        }     
    }
    delay(100);
}

Etant donnée que je suis quelqu'un de sympathique, voici une vidéo illustrant le montage finale de cet article :

Débuts avec Arduino : Partie 1

02 Dec 2012

C'est bon, c'est fait, j'ai craqué... Et oui, il y a un mois de ça, j'ai recu mon Arduino, mon premier pas dans le domaine : l'électronique !!

Ce vaste domaine qui me fascine depuis ma plus tendre enfance, époque à laquelle je démontais tout objet électronique dès lors qu'il m'intriguai de trop (et beaucoup y ont perdu la vie : ventilateur, vieux débris informatique, voitures télécommandés, et autres jouets en tout genre). Cependant n'ayant jamais étudié le domaine jusqu'à aujourd'hui, je dois avouer être complètement perdu, sans aucune notion viable en électricité.

Me plaindre ne fait pas parti de mon caractère et je me retrouve donc parcourant la toile à la recherce de documents (et google sait combien de documents il y a sur le sujet...), intéressants qui traitent des principales lois et propriétées à connaitre lorsque l'on commencer à jouer avec de l'électricité.

Une fois cette étape "achevée" (ou plutôt survolée par impatience) nous voici dans le le vif du sujet : Arduino ! Quelques tutoriaux basiques disponibles sur le site officiel ainsi que sur youtube permettent de prendre en main le sujet très rapidement, tutoriaux qui reposent pour le moment sur l'utilisation et la communication avec des LEDs, en intégrant des résistances, un potentiomètre afin de faire connaissance avec tout ce monde là et de se familiariser avec l'électronique.

Une chose me frappe cependant, dans les nombreux tutoriaux que j'ai suivi, une chose me frappe, il semblerait que les programmeurs de LEDs (débutants, ou instructeurs pour débutants sur Arduino) soient allergiques au boucles dans leur programmes et il m'est arrivé de trouver des codes faisant intervenir presque 10 LEDs, UNE à UNE ! Allumer, éteindre, allumer, éteindre, ce qui m'a quelque peu énervé lorsque l'on sait que nous pouvons effectuer les mêmes actions grâce à une instruction on ne peut plus basique telle que le "for" ou le "while" que n'importe quel débutant PEUT comprendre, mais il semblerait que le but soit de faire intégrer aux novices des mauvaises habitudes, dès le début, les rendant dépendants, preuve d'intelligence pour les auteurs qui forcent ainsi les utilisateurs à consulter la ressource plus fréquemment !

Il est vrai que je suis mauvaise langue et pousse probablement l'analyse trop loin sur ce dernier point, mais l'utilisation des boucles est tout de même quelque chose dinévitable lorsque l'on souhaite éxecuter une suite d'instructions répétitives.

Dans la suite de cet article le materiel que nous utiliserons est le suivant :

  • Un potentiomètre (résistance variable)
  • Quelques LED (au minimum 3-4)
  • Des résistances, environ 500ohm (plus ou moins)
  • Une Platine Labdec (plus communément appelé BreadBoard ou table de prototypage)
  • Des fils

La transmission d'informations

Premièrement, il faut savoir que vous pouvez faire transiter des informations entre Arduino et l'IDE grâce au "Serial Monitor" pour cela :

void setup(){
    Serial.begin(9600);
    Serial.println("Hello World"); 
    /*
     println pour un retour à la ligne automatique
     print   simplement pour afficher une information
    */
}

void loop(){ // On n'a pas besoin de la boucle pour le moment
}

Ici, on initialise la communication à 9600 baud ( bits par seconde ), arbitrairement, puis on écrit envoi la chaine "Hello World". Sur l'IDE dans l'onglet "Tools" activez le "Serial Monitor", vous devriez, devant vos yeux ébahis, voir apparaitre "Hello World".

Attaquons nous maintenant au potentiomètre

Fonctionnement d'un potentiomètre et lecture analogique

Avant de pouvoir faire quoi que ce soit, il faut comprendre le fonctionnement du potentiomètre et ce qu'il nous retourne, vous l'aurez deviné, on va utiliser la fonction Serial pour analyser tout ça. Un potentiomètre a 3 branches, une pour l'alimentation, une autre pour la valeur de sortie (celle du milieu) et enfin, la troisième que l'on branche à la sortie (ground), ce qui nous donne le schéma suivant : schema potentiomètre Remarque importante : Pour lire une valeur sur un PIN, il faut utiliser la fonction analogRead(int pint) c'est pour cela que la branche du milieu est connectée à un PIN de type "Analog In" (qui commence avec avec la lettre A).

Le code :

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

void loop(){
    Serial.println(analogRead(0)); // On lit la valeur sur le pin analogique 0 (A0) puis on l'affiche
    delay(1000); // On attend une seconde
}

En jouant un peu avec le potentiomètre, on se rend compte que la valeur lue sur A0 varie entre 1023 et 0.

Make the LEDs rock

En utilisant ce que nous avons vu au dessus, nous allons nous amuser à allumer 4 LEDs dans un ordre... incertain... En effet, le but est de lire sur une entrée analogique la valeur envoyée par le potentiomètre puis d'affiche les LEDs branchées sur les PINs 6 à 10 de l'Arduino, un bout de code valant mieux que 10 phrases (surtout les mienne), voici le code :

int ancien = 0;
void setup(){
    for(int i = 6; i < 10; i++) // Pour les LEDs branchées sur les PIN 6 à 10 de l'arduino (6 inclu 10 exclu)
    pinMode(i, OUTPUT);
}

void loop(){
    int value = analogRead(1);
    value %= 4;
    digitalWrite(ancien+6, LOW);
    digitalWrite(value+6, HIGH);
    ancien  = value;
    delay(500);
}

Voici quelques explications: value %= 4 : permet d'obtenir le reste de la division par 4, cette astuce est fort utile pour ramener de très grandes valeurs à l'intérieur d'un interval plus petit,en effet, le reste de la division par un nombre est toujours inférieur à ce nombre, dans notre cas, value prend donc une valeurs située entre 0 et 4 (la borne inférieure est toujours inclue, l'extérieur exclue). value+6 : Nos LEDs sont branchées sur les PIN 6 à 10, il suffit donc d'ajouter 6 à la valeur pour allumer une de ces quatre leds. ancien = value : cette ligne permet de se souvenir quelle LED a été allumé à la boucle précédente, pour ainsi la laisser allumé jusqu'à ce que l'on modifie le potentiomètre afin d'allumer une autre LED. digitalWrite(ancien+6, LOW): Sans cette ligne, les LEDS s'allumerai une part une sans jamais s'éteindre.

En somme, rien de très complexe, l'ordre dépend du reste de la division et est donc "complètement" aléatoire. Mais nous allons maintenant nous intéresser à la manipulation de LED les unes à la suite des autres dans un ordre choisi, afin de manipuler un peu tout ce monde à l'aide des fameuses BOUCLES dont je vous parle depuis le début. Vidéo :

Chenillards à gogo !

Premier projet : allumer 4-5 LEDs les une la suite des autres, en faisant varier la vitesse. Le materiel necessaire est le même que la liste énoncée ci dessus, voici le schema du montage : AP1_2 4 résistances, 4 leds, un potentiomètre, nous voilà pret pour la grande aventure, enfin il manque tout de même un élément relativement important : le code que voici :

void setup(){
 for(int i = 6; i < 10 ; i++)
  pinMode(i, OUTPUT); 
}

void loop(){
 int wait = analogRead(0);
 for(int i = 6; i < 10 ; i++){
  digitalWrite(i, HIGH);
  delay(wait);
  digitalWrite(i, LOW);
 } 
}

On récupère la valeur sur l'entrée Analogique 0 (celle branchée au potentiomètre) cette valeur sera utilisée dans le delay, plus la résistance est grande, plus la valeur est petite, plus le delai sera long et donc plus l'animation semblera lente. On allume, on attend puis on éteint sans attendre de nouveau, et cela car nous voulons que l'enchainement du chenillard soit fluide en rajoutant un delai, il y aurait une période pendant laquelle aucune LED n'est allumée : MOCHE !

Après plusieurs tests vous vous êtes peu-être rendu compte que la variation de la vitesse est loin d'être précise, cela s'explique par le fait que nous lisons la valeur du courant transmis à travers le potentiomètre une seule fois pour les quatres LEDs, si vous modifiez cette valeur entre la 2ième et 3ième LED, elle ne sera prise en compte qu'au tour suivant, pour combler cela transformez votre loop() comme suit :

void loop(){
 for(int i = 6; i < 10 ; i++){
  int wait = analogRead(0);
  digitalWrite(i, HIGH);
  delay(wait);
  digitalWrite(i, LOW);
 } 
}

Vous devriez sentir une nette différence entre les deux codes, comme quoi, parfois cela ne tient qu'a une ligne :)

La fonction map

Avec le code précendent, la valeur du delay peut varier entre 0 et 1023 c'est à dire en 0ms et 1,23s ce que certains peuvent trouver très restrictif, pour contourner cette contrainte, il faut utiliser la fonction Map qui s'utilise comme suit si nous voulons convertir une valeur située entre 0 et 1023 en une valeur dans l'interval [0, 200]

wait = map(wait, 0, 1023, 0, 200);

Mais, grâce à cette fonction nous pouvons également attendre des delais largements supérieur à 1,23 secondes, par exemple :

wait = map(wait, 0, 1023, 1500, 2500);

Dans ce cas, le delai minimal sera de 1,5 seconde et le maximal de 2,5 secondes, ce qui peut s'avérer utile dans certains cas voici le code complet dans lequel le delai minimal est de 0,5 seconde et le maximal de 3 secondes!

void loop(){
    for(int i = 6; i < 10 ; i++){
        int wait = analogRead(0);
        wait = map(wait, 0, 1023, 500, 3000);
        digitalWrite(i, HIGH);
        delay(wait);
        digitalWrite(i, LOW);
   } 
}

A partir de là, nous avons tout les outils nécessaire pour nous amuser simplement avec des LEDs, et faire toute sorte de chenillard, par exemple (je n'écrirai maintenant que le void loop() le void setup() ne change pas !

void loop(){
    for(int i = 6; i < 10 ; i++){
        int wait = analogRead(0);
        digitalWrite(i, HIGH);
        delay(wait);

   } 
   for(int i = 6; i < 10; i++)
             digitalWrite(i, LOW);

   delay(200);
}

Dans ce cas, les LED vont s'allumer les unes après les autres, puis s'éteindront simultanément pour recommencer (une sorte "barre de progression")

Autres "astuces"

Imaginons que les LEDs ne soient pas situées sur des PINs qui se suivent, mais sur les PIN 3, 8 et 10, dans ce cas, la boucle précédente ne fonctionnerai pas, pour pallier à ce problème il faut utiliser un tableau:

int led = [3, 8, 10];

Dans ce code, on déclare un tableau avec trois valeurs : 3;8 et 10 qui correspondent au PIN des LEDs, maintenant, il faut modifier la boucle :

void loop(){
    for(int i = 0; i < 3 ; i++){ // Nous avons trois LED
        int wait = analogRead(0);
        wait = map(wait, 0, 1023, 500, 3000);
        digitalWrite(led[i], HIGH); // Le PIN situé à la case "i" du tableau
        delay(wait);
        digitalWrite(led[i], LOW);
   } 
}

Attention : comme dans beaucoup de langage, les tableaux sont numérotés à partir de 0, donc si votre tableau contient 3 éléments, le troisième élément se trouve à l'indice 2.