Yahoo refuse tous les emails du site. Si vous avez une adresse chez un autre prestataire, c'est le moment de l'utiliser ;)

En cas de soucis, n'hésitez pas à aller faire un tour sur la page de contact en bas de page.

Quelques fonctions bien pratiques du framework Arduino

A garder sous le bras pour plus tard

Image d'entête

par skywodd | | Licence (voir pied de page)

Catégories : Tutoriels Arduino | Mots clefs : Arduino Genuino


Dans ce mini tutoriel (plus un simple rappel qu'un vrai tutoriel), je vous propose de découvrir ou de redécouvrir plusieurs fonctions bien pratiques du framework Arduino. Manipulation de bits, d'octets, de caractères ou de valeurs, il y en a pour tous les goûts.

Sommaire

Bonjour à toutes et à tous !

Aujourd'hui, comme souvent, j'étais en train de faire des recherches pour un prochain article, et voilà qu'entre deux clics de souris, je tombe sur un morceau de code qui réinvente la roue. Un de plus me direz vous, pas de quoi casser trois pattes à un canard … Certes.

À force de répéter encore et encore "langage Arduino" dans la presse web, les utilisateurs finissent par oublier que le framework Arduino n'est pas un langage, mais simplement une bibliothèque de code C/C++, avec en bonus, un compilateur C/C++ et un éditeur de code tout-en-un. Les programmes Arduino héritent par conséquent de toutes les fonctions disponibles en programmation C et C++.

Bref, durant mes recherches, je suis tombé sur cette perle :

1
2
3
int round_upper (float value) {
  return (int) (value + 0.5);
}

Pour ceux qui ne maîtrisent pas bien la langue anglaise ou qui n'auraient pas encore déchiffré la ligne de code ci-dessus, cette fonction ne fait qu'une seule et unique chose : arrondir un nombre à virgule à l'entier supérieur.

Damned, pourquoi faire une fonction d'arrondie quand il existe déjà cette même fonction (nommée ceil()) dans le fichier <math.h>. Fichier qui est inclus de base lors de la compilation d'un code Arduino. P-o-u-r-q-u-o-i ?

Bref, dans cet article, je vous propose de découvrir ou de redécouvrir quelques fonctions bien pratiques du framework Arduino, histoire de ne pas réinventer la roue dans vos prochains programmes ;)

Avant propos

Avant de commencer, petit rappel de courtoisie.

Vous pouvez utiliser toutes les fonctions de base fournies avec le langage C et C++ (version de base, pas C++11 ou C++17, sauf si vous avez un compilateur récent, ce qui n'est pas le cas de celui fourni de base avec le logiciel Arduino).

Cela signifie que vous pouvez allez sur cppreference, faire une rapide recherche et profiter des innombrables fonctions fournies par la bibliothèque standard C/C++.

Attention quand même, certaines fonctions ne sont pas disponibles ou ne sont adaptées pour de la programmation Arduino / embarquée. Une petite recherche sur votre moteur de recherche préféré est donc conseillée pour ne pas tomber dans un piège ;)

Pour en revenir à ma perle ci-dessus, il aurait suffi d'utiliser la bibliothèque math.h, pour avoir accès à une quantité astronomique de fonctions mathématiques diverses, dont floor() pour faire un arrondi à l'entier inférieur, round() pour faire un arrondi à l'entier le plus proche ou ceil() pour faire un arrondi à l'entier supérieur.

Inutile de réinventer la roue, il existe plein d'autres bibliothèques, standard ou non, pour faire plein de choses.

Les grands classiques min / max / abs

Commençons par les grands classique, min(), max() et abs(). Ironiquement, l'équipe Arduino a réinventé la roue avec ces trois fonctions qui existe déjà dans la bibliothèque standard C/C++.

1
2
3
min(a, b);
max(a, b);
abs(x);

La fonction min() prend en argument deux valeurs (nombre entier ou à virgule) et retourne la plus petite valeur des deux.

Exemple : min(1, 2) retourne 1.

La fonction max() prend en argument deux valeurs (nombre entier ou à virgule) et retourne la plus grande valeur des deux.

Exemple : max(1, 2) retourne 2.

La fonction abs() prend en argument une unique valeur (nombre entier ou à virgule) et retourne la valeur absolue (toujours positive). Autrement dit, si la valeur est positive, la valeur est retournée telle quelle. Si la valeur est négative, l'inverse de la valeur est retourné.

Exemple : abs(10) retourne 10 et abs(-8) retourne 8.

Macro fonctions

Si je parle de ces trois fonctions, ce n'est pas uniquement parce que l'équipe Arduino a réinventé la roue, mais parce qu'elle a sans le vouloir réinventé la roue carrée.

Ces trois fonctions sont des macro-fonctions. Cela signifie qu'il s'agit de fonctions qui génèrent du code lors de la compilation. Ce ne sont pas des fonctions classiques, mais des "super" fonctions qui peuvent prendre n'importe quel type de valeur en arguments (int, long, float, char, etc.). Vous pouvez considérer les macro-fonctions comme des remplacements de texte automatique.

Par exemple, la fonction abs() est définie comme suit dans le fichier Arduino.h :

1
#define abs(x) ((x) > 0 ? (x) : -(x))

En français : si x est supérieur à 0, utilisez la valeur x, sinon, utiliser la valeur -x. Les parenthèses sont là uniquement pour éviter les erreurs de compilations.

Si dans votre code, vous faites :

1
2
int valeur = 0;
int resultat = abs(valeur++);

Au lieu d'avoir resultat = 1, vous allez obtenir resultat = 2, car le code vu par le compilateur va ressembler à ceci :

1
2
int valeur = 0;
int resultat = ((valeur++) > 0 ? (valeur++) : -(valeur++));

Et paf, un joli bug invisible, car dissimulé par la macro fonction. C'est une erreur classique et très énervante à débug.

Pour éviter ce genre de problème, vous ne devez JAMAIS passer une opération (calcul ou appel de fonction) en paramètre d'une macro-fonction. Utilisez une variable temporaire ou faite le calcul avant d'appeler la macro :

1
2
3
int valeur = 0;
valeur++; // Là c'est bon
int resultat = abs(valeur);

La manipulation de fourchettes de valeurs

Un truc extrêmement classique que je rencontre tout le temps dans mes programmes, c'est les produits en croix.

Vous avez une valeur entre 0 et 1023, mais vous voulez cette même valeur transposée entre 0 et 99 par exemple. La solution : un produit en croix.

1
long map(valeur, min_in, max_in, min_out, max_out);

En programmation Arduino, vous pouvez faire très facilement un produit en croix avec la fonction map().

Cette fonction prend en argument une valeur, une fourchette de valeurs sources (min et max) et une fourchette de valeurs de sortie (min et max). En retour, la fonction retourne la valeur fournie, mais transposée de la fourchette source à la fourchette de sortie.

Dans l'exemple 0 ~ 1023 vers 0 ~ 99, il suffit de faire :

1
long pourcentage = map(valeur, 0, 1023, 0, 99);

Si valeur = 0 on obtient 0, si valeur = 1023 on obtient 99. Pour les valeurs intermédiaires, on obtient le résultat du produit en croix.

Simple, rapide, efficace.

N.B. Ça ne marche qu'avec les nombres entiers, mais pour les amoureux de nombres à virgules voici une version modifiée :

1
2
3
float map(float x, float in_min, float in_max, float out_min, float out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

N.B. La fonction map() ne fait aucune validation de la valeur d'entrée. Donc si dans mon exemple valeur = 2000, vous obtiendrez une valeur de sortie supérieure à 99.

PS Vous pouvez très bien avoir une fourchette de sortie inférieure ou supérieure à la fourchette d'entrée. Exemple : map(x, 1, 50, 50, 1) ou map(x, 1, 50, 50, -100). C'est vraiment un bête produit en croix entre deux fourchettes de valeurs.

Une autre opération très classique consiste à forcer une valeur dans une fourchette de valeurs bien définie.

1
constrain(valeur, a, b);

Imaginons que vous avez une valeur quelconque et que vous voulez que cette valeur soit comprise entre 0 et 99, quoi qu'il arrive. Pour cela, vous pouvez utiliser la fonction constrain().

La fonction constrain() prend en arguments une valeur (quelconque) et deux paramètres a et b. Si la valeur est entre a et b, la valeur est retournée telle quelle. Si la valeur est supérieure à b, la fonction retourne b. Si la valeur est inférieure à a, la fonction retourne a.

Exemple :

1
2
3
constrain(-1, 0, 99); // = 0
constrain(10, 0, 99); // = 10
constrain(100, 0, 99); // = 99

N.B. Constrain() est une macro-fonction, voir avertissement dans le chapitre précédent.

La manipulation de mots

En programmation Arduino, un "mot" ("word" en anglais) est un nombre entier sur 16 bits.

Parfois, on a besoin de manipuler un mot pour extraire un octet (8 bits) ou concaténer deux octets pour former un mot. C'est particulièrement vrai quand on utilise des capteurs, qui génèrent souvent des résultats de mesure sur deux octets.

1
2
lowByte(x);
highByte(x);

La fonction lowByte() permet d'extraire l'octet de poids faible (LSB, bits 0 à 7) d'un mot.

La fonction highByte() permet d'extraire l'octet de poids fort (MSB, bits 8 à 15) d'un mot.

1
word(msb, lsb);

La fonction word() permet de concaténer deux octets pour former un mot.

Avec ces fonctions, il est possible de faire pas mal de choses très intéressantes. Exemple :

1
2
3
4
5
6
int valeur = analogRead(A0); // Retourne une valeur sur 10 bits

byte lsb = lowByte(valeur); // Contient uniquement les bits 0-7
byte msb = highByte(valeur); // Contient uniquement les bits 8-9

int valeur_2 = word(msb, lsb); // Reconstruit le mot d'origine

N.B. Ce sont des macro-fonctions, voir avertissement dans le chapitre précédent.

La manipulation de bits

Quand on communique avec des capteurs, on se retrouve généralement à devoir modifier un registre pour configurer le capteur. On manipule donc des bits (rappel : 8 bits = un octet), chaque bit ayant une signification bien précise, définie par le fabricant du capteur.

Le framework Arduino intègre toute une série de macro-fonctions pour manipuler des bits.

1
2
3
4
5
BitRead(x, n);
bitWrite(x, n, b);
bitSet(x, n);
bitClear(x, n);
bit(n);

La fonction bitRead() permet de lire l'état d'un bit dans un nombre entier.

La fonction prend en argument la valeur binaire et le numéro de bit à lire (débute à 0). En retour, la fonction retourne 0 sir le bit est à 0 et 1 si le bit est à 1.

Exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 4 = 0000 0100 (binaire)

BitRead(4, 0); // = 0
BitRead(4, 1); // = 0
BitRead(4, 2); // = 1
BitRead(4, 3); // = 0
BitRead(4, 4); // = 0
BitRead(4, 5); // = 0
BitRead(4, 6); // = 0
BitRead(4, 7); // = 0

La fonction bitWrite() permet d'écrire l'état d'un bit dans un nombre entier.

La fonction prend en argument la valeur binaire, le numéro de bit à écrire (débute à 0) et l'état du bit (0 ou 1).

Exemple :

1
2
3
4
5
int valeur = 4; // 0000 0100 (binaire)
bitWrite(valeur, 2, 0);
// valeur = 0 = 0000 0000 (binaire)
bitWrite(valeur, 0, 1);
// valeur = 1 = 0000 0001 (binaire)

La fonction bitSet() permet de mettre un bit à "1" dans un nombre entier.

La fonction prend en argument la valeur binaire et le numéro de bit à écrire (débute à 0).

Exemple :

1
2
3
int valeur = 4; // 0000 0100 (binaire)
bitSet(valeur, 0);
// valeur = 5 = 0000 0101 (binaire)

La fonction bitClear() permet de mettre un bit à "0" dans un nombre entier.

La fonction prend en argument la valeur binaire et le numéro de bit à écrire (débute à 0).

Exemple :

1
2
3
int valeur = 4; // 0000 0100 (binaire)
bitClear(valeur, 2, 0);
// valeur = 0 = 0000 0000 (binaire)

La fonction bit() permet de retourner la valeur numérique correspondant au poids d'un bit :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
bit(0) = 1
bit(1) = 2
bit(2) = 4
bit(3) = 8
bit(4) = 16
bit(5) = 32
bit(6) = 64
bit(7) = 128
bit(8) = 256
// ...

Bonus : la manipulation de caractères

En plus de fournir tout un tas de fonctions pour manipuler des mots et des bits, le framework Arduino fournit des fonctions pour manipuler des caractères.

Ces fonctions sont particulièrement intéressantes si vous faites un traitement caractère par caractère dans votre programme.

Toutes les fonctions ci-dessous retournent un booléen vrai (true) si la condition est respectée, et un booléen faux (false) si la condition n'est pas respectée. Toutes ces fonctions prennent en argument un unique caractère.

La fonction isAlphaNumeric() permet de savoir si le caractère est une lettre (minuscule ou majuscule) ou un chiffre.

La fonction isAlpha() permet de savoir si le caractère est une lettre minuscule ou majuscule.

La fonction isWhitespace() permet de savoir si le caractère est une tabulation.

La fonction isControl() permet de savoir si le caractère est un caractère spécial.

La fonction isDigit() permet de savoir si le caractère est un chiffre,

La fonction isHexadecimalDigit() permet de savoir si le caractère est chiffre hexadécimal (0123456789, ABCDEF, abcdef).

La fonction isUpperCase() permet de savoir si le caractère est une lettre majuscule.

La fonction isLowerCase() permet de savoir si le caractère est une lettre minuscule.

La fonction isPrintable() permet de savoir si le caractère est un caractère imprimable.

La fonction isGraph() permet de savoir si le caractère est un caractère (= tout caractère imprimable sauf l'espace).

La fonction isPunct() permet de savoir si le caractère est une ponctuation (point, virgule, etc.)

La fonction isSpace() permet de savoir si le caractère est un espace ou une tabulation.

En bonus, la fonction toLowerCase() permet de convertir un caractère en minuscule et la fonction toUpperCase() permet de convertir un caractère en majuscule.

PS Ai-je précisé que l'équipe Arduino adore réinventer la roue ?

Conclusion

Ce mini tutoriel est désormais terminé.

Si ce mini tutoriel vous a plu, n'hésitez pas à le commenter sur le forum, à le partager sur les réseaux sociaux et à soutenir le site si cela vous fait plaisir.