Tuto pas-à-pas C++ : utiliser les potentiomètres rotatifs

On va voir étape par étape comment utiliser les potentiomètres du kit d’accessoires sur notre Gamebuino Meta. Le but est d’obtenir un programme qui ressemblera à ceci :

Le tuto est destiné à être réalisé avec le matériel fourni dans le pack d’accessoires Gamebuino

Pour ce tutoriel, il vous faut :

  • une console Gamebuino META
  • deux potentiomètres compatibles arduino (vous pouvez aussi le faire avec un seul mais c’est moins joli)
  • un ordinateur avec l’environnement de développement l’IDE Aduino installé

Si ce n’est pas encore fait, reportez vous à la fiche
Premiers pas (C++ 1/5) : Installation rapide du logiciel - Gamebuino pour installer l’IDE Arduino.

Si vous avez déjà les bases en programmation (ou la flemme de lire tout le tuto qui suit) vous pouvez trouver le code complet du programme réalisé ici dans le Mini-code : potentiomètres

Les branchements

En parallèle de notre code il faut brancher les potentiomètres à la console, si possible avec le backpack.
On branche les alimentations (VCC) sur des pins 3v3, les masses (GND) sur des masses et les sorties SIG (signal) sur A1 pour le potentiomètre de gauche et A2 pour celui de droite.

Potentiomètre Backpack Couleur Description
GND GND BRUN Masse
VCC 3V3 ROUGE Alimentation
SIG A1/A2 JAUNE Sortie de signal



Etape 1 : Bien commencer

Cette étape est identique sur la plupart des tutos car elle constitue la base indispensable de n’ilmporte quel programme pour Gamebuino.

Pour commencer, ouvrez l’éditeur Arduino préconfiguré pour la Gamebuino

-Vous devriez avoir ceci sur votre écran :

void setup() {
  // put your setup code here, to run once:
}

void loop() {
  // put your main code here, to run repeatedly:
}

Pour l’instant, le code est un peu vide, mais on va le compléter.

  • En premier lieu on a besoin d’ajouter au tout début du fichier la ligne suivante :
#include <Gamebuino-Meta.h>

C’est ce qu’on appelle une “librairie”, elle va nous permettre d’utiliser tout un tas de fonctions sans avoir à les programmer à la main. Cette librairie contient toutes les fonctions de la Gamebuino et est très utile pour la suite.
On va ajouter deux fonctions pour que notre programme puisse fonctionner :

  • Dans la fonction setup() on ajoute un appel à la fonction gb.begin() comme suit :
void setup() {
  // put your setup code here, to run once:
  gb.begin();
}

La fonction gb.begin() permet d’initialiser la console : sans cela rien ne fonctionnera !

  • Ensuite, on ajoute la fonction waitForUpdate() dans la fonction loop :
void loop() {
  // put your main code here, to run repeatedly:
  gb.waitForUpdate();
}

La fonction waitForUpdate() permet de rafraichir le contenu de l’écran (c’est à dire dessiner dessus), gère la lecture des sons, les appuis sur les touches, bref tout ce qui donne vie à votre Gamebuino.

On a à présent une structure de programme fonctionnelle, même si elle ne fait encore rien de concret.

Etape 2 : Récupérer les mesures et les transformer

Nos potentiomètres envoient des mesures analogiques, c’est à dire physiques, vers la console. On va récupérer les valeurs de ces mesures et les transformer en quelque chose que la machine peut comprendre, à savoir une valeur numérique.
Pour cela on va utiliser la fonction analogRead() de la librairie arduino par défaut. Cette fonction permet de transformer une valeur analogique (physique) en valeur numérique grâce au module CAN du processeur de la console.

  • On commence par déclarer deux paires de variables int32 pour stocker nos valeurs et on anticipe le fait de les transformer :
int32_t i32_L_pot_value; //variables pour le potentiometre de gauche
int32_t i32_L_pot_angle;

int32_t i32_R_pot_value; //variables pour le potentiometre de droite
int32_t i32_R_pot_angle;

On place ces déclarations au début de notre code, entre l’inclusion des librairies et la fonction setup().

Ces variables déclarées en dehors d’une fonction spécifiques sont appelées des variables “globales”, elles peuvent être appelées et modifiées depuis n’importe quel endroit de notre programme. Dans un petit programme comme celui-ci ce n’est pas gênant, mais dans un gros programme de plusieurs centaines de lignes avec de nombreuses variables on considère que c’est une mauvaise pratique que d’utiliser trop de variables globales, en effet il devient plus compliqué de suivre ce qui se passe si tout se fait en global.

On va maintenant déclarer une fonction qui va nous permettre d’assigner des valeurs à nos variables et de transformer ces valeurs.

  • On va appeler cette fonction read_and_calc() car elle va servir à lire des valeurs et à effectuer des calculs :
void read_and_calc() {

}
  • On ajoute tout de suite l’appel à cette fonction dans notre boucle loop() :
void loop() {
  // put your main code here, to run repeatedly:
  gb.waitForUpdate();

  read_and_calc();
}

On veut en effet que cette fonction s’execute de manière répétée à chaque boucle de notre programme.

Maintenant il faut remplir un peu notre fonction pour qu’elle fasse son travail de lecture à l’aide la fonction analogRead().

  • La lecture se fait de la manière suivante :
void read_and_calc() {
  i32_L_pot_value = analogRead(A1);
  i32_R_pot_value = analogRead(A2);
}

On assigne ainsi à nos variables les valeurs de nos potentiomètres de manière continue.
Ces valeurs sont comprises entre 0 et 1023, c’est bon à savoir mais pas très pratique. On va donc les ramener à des valeurs d’angle comprises entre 0 et 360 pour que ce soit plus simple à comprendre.

  • Cette conversion se fait en appliquant une formule mathématique simple :
void  read_and_calc(){

  i32_L_pot_value = analogRead(A1);
  i32_R_pot_value = analogRead(A2);

  // we convert the values from a range of 0 to 1023 to angles within 360 degrees
  i32_L_pot_angle = (i32_L_pot_value * 360) / 1023;
  i32_R_pot_angle = (i32_R_pot_value * 360) / 1023;
}

Avec un simple produit en croix on ramène les valeurs de 0 à 1023 à des “angles” entre 0 et 360.

Etape 3 : Afficher les valeurs

Pour mieux représenter l’action de nos potentiomètres on va afficher les valeurs obtenues à l’étape précédente afin de suivre leurs évolutions en direct.

Pour cela on va utiliser une fonction dédiée à l’affichage que nous appellerons draw_interface()

  • Nous déclarons cette fonction de la manière suivante :
void  draw_interface(){
  // the clear function is needed to reset the screen between each frame, otherwise frames would mix together and get messy
  gb.display.clear();
}

Notez que l’on a besoin d’inclure la fonction clear() car sans elle les images successives “s’empilent” et deviennent rapidement illisibles.

  • On pense bien entendu à appeler cette nouvelle fonction dans notre boucle loop() pour qu’elle puisse être exécutée à chaque cycle :
void loop() {
  // put your main code here, to run repeatedly:
  gb.waitForUpdate();

  // the read_and_calc function will handle the data acquisition and math needed for the program
  read_and_calc();

  //the draw_interface function will handle the display of a custom interface
  draw_interface();
}

Notre fonction loop est maintenant complète, on n’y ajoutera plus aucun élément, les modifications éventuelles se feront directement dans les fonctions read_and_calc() et/ou draw_interface().

Il s’agit à présent de compléter notre fonction draw_interface pour afficher nos valeurs brutes et nos valeurs d’angles.

  • Rien de bien compliqué avec la fonction printf de notre Gamebuino :
void  draw_interface(){
  // the clear function is needed to reset the screen between each frame, otherwise frames would mix together and get messy
  gb.display.clear();

  gb.display.printf(" L_value = %d\n L_angle = %d\n R_value = %d\n R_angle = %d", i32_L_pot_value, i32_L_pot_angle, i32_R_pot_value, i32_R_pot_angle);
}

Ca semble un peu lourd mais c’est simplement la mise en forme du texte à afficher pour la fonction printf, le texte entre guillemets est affiché tel quel mais les %d correspondent en fait aux valeurs de variables spécifiées, dans l’ordre, à la suite du texte et les \n sont des retours à la ligne.
(La fonction printf est une fonction standard du language C, si vous souhaitez vous payer un bon mal de tête allez lire sa documentation complète ou son code source original).

En lançant la vérification de votre code et le téléversement vers votre console vous devriez voir apparaître quelque chose de semblable à ceci :

00000

C’est bien mais c’est quand même un peu moche. Du coup on va améliorer tout ça.

Etape 4 : Rajouter des visuels

Dans cette partie nous allons voir comment transformer les valeurs obtenue précedement en deux représentations visuelles identiques, lesquelles sont visibles sur la photo au début du tuto.

Pour cela nous allons devoir utiliser quelques calculs mathématiques relativement simples, en utilisant notamment les formules cos() et sin(). (si vous avez fait spé math au lycée ça ira, sinon accrochez-vous un peu).

Mais avant ça nous allons utiliser des define pour poser les dimensions et positions de deux cercles qui serviront de base à nos visuels.

  • Ces define se font au début du code en-dessous des inclusions de librairie :
//define of our circles' properties, here the radius is 15 units and both are centered on the screen
#define CIRCLE_R 15
#define L_CIRCLE_X gb.display.width()/4
#define R_CIRCLE_X (gb.display.width()/4)*3
#define CIRCLE_Y gb.display.height()/2

On définit ainsi un rayon CIRCLE_R de 15 unités, un positionnement horizontal à 1/4 de l’écran pour le cercle de gauche (L_CIRCLE_X) et 3/4 de l’écran pour celui de droite (R_CIRCLE_X), enfin les deux cercles auront une position verticale au centre de l’écran (CIRCLE_Y).

On peut dès à présent faire deux choses supplémentaires :

  • Afficher nos cercles à l’écran :
void  draw_interface(){
  // the clear function is needed to reset the screen between each frame, otherwise frames would mix together and get messy
  gb.display.clear();

  gb.printf(" L_value = %d\n L_angle = %d\n R_value = %d\n R_angle = %d", i32_L_pot_value, i32_L_pot_angle, i32_R_pot_value, i32_R_pot_angle);

  // we draw the circles with the properties we defined at the beginning, their centers' positions and radius
  gb.display.drawCircle(L_CIRCLE_X, CIRCLE_Y, CIRCLE_R);
  gb.display.drawCircle(R_CIRCLE_X, CIRCLE_Y, CIRCLE_R);
}

00001

  • Masquer le texte affiché précedemment en mettant notre fonction printf() en commentaire :
void  draw_interface(){
  // the clear function is needed to reset the screen between each frame, otherwise frames would mix together and get messy
  gb.display.clear();

//  gb.printf(" L_value = %d\n L_angle = %d\n R_value = %d\n R_angle = %d", i32_L_pot_value, i32_L_pot_angle, i32_R_pot_value, i32_R_pot_angle);

  // we draw the circles with the properties we defined at the beginning, their centers' positions and radius
  gb.display.drawCircle(L_CIRCLE_X, CIRCLE_Y, CIRCLE_R);
  gb.display.drawCircle(R_CIRCLE_X, CIRCLE_Y, CIRCLE_R);
}

00003

Maintenant attention, on va se lancer dans des calculs à base de sin() et de cos() pour ajouter des “aiguilles” tournant dans nos cercles en fonction du mouvement de nos potentiomètres.

Pour cela nous allons déclarer deux paires de variables globales qui nous serviront à placer un point sur le périmètre de chaque cercle, de manière à tracer ensuite une ligne entre le centre du cercle et le point extérieur afin de représenter nos aiguilles.

  • Ces déclarations se font ainsi, toujours au début de notre code :
//variable declaration for the points used to draw lines
int16_t i16_L_point_x;
int16_t i16_L_point_y;

int16_t i16_R_point_x;
int16_t i16_R_point_y;

On a bien une paire de variables pour chaque point, on s’en servira de coordonnées X et Y pour positionner ces points dans l’espace.

Il s’agit à présent d’assigner des valeurs à nos nouvelles variables pour qu’elles se rendent utiles.
Pour cela nous allons ajouter la libraire “math.h” à notre programme.

  • L’ajout se fait tout simplement en incluant la librairie en haut de notre programme, sous la librairie Gamebuino :
#include <Gamebuino-Meta.h>
//we include the math library to get access to sin() and cos() functions as well as a value of PI used in the program
#include <math.h>

Cela nous donne accès à une variable PI prédéfinie dans la librairie et aux fonctions sin() et cos(). Il est possible de ne pas inclure la librairie math.h car les fonctions cos() et sin() sont en principe inclues dans les librairies Arduino par défaut avec une macro pour donner une valeur à PI, on le fait ici pour le principe.

  • Pour assigner les valeurs de nos variables on va utiliser les formules suivantes dans notre fonction read_and_calc():
  // we calculate the coordinates of a point on a circular perimeter corresponding to the value of the angle found above
  // note the multiplication by PI/180, this is because the angle we calculated in degrees must be converted to radians
  i16_L_point_x = L_CIRCLE_X + CIRCLE_R * cos(i32_L_pot_angle * M_PI/180);
  i16_L_point_y = CIRCLE_Y + CIRCLE_R * sin(i32_L_pot_angle * M_PI/180);
  
  i16_R_point_x = R_CIRCLE_X + CIRCLE_R * cos(i32_R_pot_angle * M_PI/180);
  i16_R_point_y = CIRCLE_Y + CIRCLE_R * sin(i32_R_pot_angle * M_PI/180);

Cela détermine de manière dynamique la position des points sur le périmètre des deux cercles en fonction des valeurs lues puis transformées en angles.

Une fois ces coordonnées déterminées on peut les utiliser pour tracer nos aiguilles à l’intérieur de nos cercles.

  • Cela se fait simplement grâce à la fonction drawLine() appelée dans notre fonction draw_interface() telle que :
  // we draw the circles with the properties we defined at the beginning, their centers' positions and radius
  gb.display.drawCircle(L_CIRCLE_X, CIRCLE_Y, CIRCLE_R);
  gb.display.drawCircle(R_CIRCLE_X, CIRCLE_Y, CIRCLE_R);
  // we draw a line between the center of the circle and the point on its perimeter corresponding to the potentiometer value we read
  gb.display.setColor(RED);
  gb.display.drawLine(L_CIRCLE_X, CIRCLE_Y, i16_L_point_x, i16_L_point_y);
  gb.display.drawLine(R_CIRCLE_X, CIRCLE_Y, i16_R_point_x, i16_R_point_y);

On ajoute la fonction setColor(RED) pour que les aiguilles soient tracées en rouge, c’est plus joli. Si vous n’aimez pas le rouge vous pouvez choisir une autre couleur comme par exemple GREEN ou YELLOW mais attention à la faute de goût.

Lancer la compilation et le téléversement de votre code devrait vous donner le résultat suivant :

00004

Plus qu’une dernière étape et nous aurons le même programme que sur la photo de départ.

Etape 5 : Déplacer le texte

On va découper le texte affiché précédemment en plusieurs parties de manière à séparer les différents éléments et ensuite les répartir sur l’écran.
Pour cela on va utiliser principalement la fonction setCursor qui permet de déplacer librement le curseur de texte sur l’écran à l’aide de coordonnées X et Y. Combinée à la fonction printf() on peut ainsi disposer du texte où l’on veut sur l’écran sans devoir formater une longue chaîne de texte compliquées qui se composerait principalement d’espaces et de retours à la ligne.

Le tout se passe dans notre fonction draw_interface() bien entendu.

  • On commence par les mots “values” et “angles” qu’on place au centre de l’écran, en haut et en bas :
void  draw_interface(){
  // the clear function is needed to reset the screen between each frame, otherwise frames would mix together and get messy
  gb.display.clear();

  // we print the value read from the potentiometer and its value when converted to an angle
  gb.display.setCursor(gb.display.width()/2 - 12, 0);
  gb.display.printf("Values");
  gb.display.setCursor(gb.display.width()/2 - 12, gb.display.height() - 6);
  gb.display.printf("Angles");

//...
}

00005

  • On continue avec les valeurs brutes positionnées au-dessus de nos cadrans circulaires :
void  draw_interface(){

//...

  //raw values display
  gb.display.setCursor(gb.display.width()/4 - 5, CIRCLE_Y - CIRCLE_R - 7);
    gb.display.printf("%d", i32_L_pot_value);

  gb.display.setCursor((gb.display.width()/4)*3 - 5, CIRCLE_Y - CIRCLE_R - 7);
    gb.display.printf("%d", i32_R_pot_value);

//...
}

00006

  • On termine enfin par les valeurs d’angles calculées plus tôt qu’on positionne sous les cadrans :
void  draw_interface(){

//...

  //angles' values display
  gb.display.setCursor(gb.display.width()/4 - 5, CIRCLE_Y + CIRCLE_R + 5);
    gb.display.printf("%d", i32_L_pot_angle);

  gb.display.setCursor((gb.display.width()/4)*3 - 5, CIRCLE_Y + CIRCLE_R + 5);
    gb.display.printf("%d", i32_R_pot_angle);

//...
}

Et voilà !
00007

Vous avez survécu à ce nouveau tutoriel, félicitations ! Vous avez désormais un beau programme réalisé avec vos petites mains pour tester vos potentiomètres. A vous de jouer à présent.

:face_with_monocle: Et pour aller plus loin, pourquoi ne pas enchainer sur ce tuto de @Steph ?
Commander un servomoteur SG90 avec un potentiomètre

1 Like

Nos potentiomètres envoient des mesures analogiques, c’est à critical bodies, vers la console. On va récupérer les valeurs de ces mesures et les transformer en quelque picked que la machine peut comprendre, à savoir une valeur numérique.
Thanks.
Aakil working at Mr.furniture

1 Like

Google translate :wink: ?