Bonjour,
Si vous vous intéressez à la télémetrie laser, vous avez sûrement déjà vu ce télémètre, bien moins cher que ses concurrents, mais comment l'utiliser? Voici un petit tuto sympa (du moins j'espère) pour apprendre à utiliser le capteur laser TW10S-UART.
1. Le module
C'est sûrement le plus accessible pour quelqu'un qui, comme moi, veut utiliser un télémètre laser sans se ruiner!
En effet on le trouve facilement entre 40 et 45€ sur AliExpress.
Malheureusement ce laser est fourni sans aucune documentation précise, et la seule que j'ai pu trouver est très insuffisante et ne correspond pas complètement au produit .
D'expérience, ce module n'est cependant pas très approprié pour la réalisation d'un LiDAR du fait de sa faible vitesse d'acquisition.
2. Le câblage
Heureusement, nous pouvons trouver sur AliExpress le nom des broches! C'est une connexion de type UART, avec 5 pin de type MX au pas de 1.25mm:
EN_PWR => 3.3V
Rx => Sur une broche digital de la carte (PIN 3 pour l'exemple)
Tx => Sur une autre broche digital (PIN 4 pour l'exemple)
GND => GND
3.3V => 3.3V
La broche Rx du laser doit recevoir du 3.3V mais les sorties de la carte Arduino envoient du 5V. On utilise donc un pont divideur de tension pour regler ce problème avec un couple de résistance 10kΩ et 20kΩ (voir schéma).
Si vous utilisez des valeur de resistance plus forte, ou un régulateur en un peu moins de 3V, il est probable que votre carte ne reçoive pas les donées à cause d'une tension trop faible.
Pour obtenir du 3.3V pour l'alimentation du laser, je recomande d'utiliser un régulateur de tension en 3.3V afin de ne pas surcharger la sortie 3.3V de la carte (on risquerait de la griller car elle ne delivre que 50mA pour une Arduino UNO). Un régulateur de 800mA comme celui ci conviens parfaitement, mais en fonction de la charge (si vous utilisez plusieurs éléments fonctionnant en 3.3V), il peut etre utile d'en acheter un plus puissant.
N.B. Le Pin EN_PWR peut aussi être branché sur un pin de la carte et être mise à HIGH pour pouvoir utiliser le laser et LOW pour le mettre en mode économie d'énergie.
N.B. Je ne sais pas si c'est une généralité, mais dans mon cas, la partie plastique fixée sur le PCB n'est pas paralèle avec ce dernier. Faites y atention si vous réalisez un boitier pour installer le laser...
3. Le code
Malheureusement, il n'y a pas (enfin pas à ma connaissance) de librairie automatisant la tâche🥲. On va donc devoir se débrouiller tout seuls!
Tout d'abord il faut initialser la communication UART grâce à la libraire SoftwareSerial (Documentation en anglais) . L'avantage de cette librairie est qu'elle s'utilise comme vous le faites déjà sûrement avec le fameux Serial.println
.
Pour commencer voici comment procéder:
#include <SoftwareSerial.h> //on importe la librairie
#define TX_laser 4 //la broche reliée au TX du laser
#define RX_laser 3 //la broche reliée au RX du laser
SoftwareSerial laser(TX_laser, RX_laser); //on initialise la communication série qui sera appelée laser
void setup() {
Serial.begin(9600); // pour afficher les valeurs retournées par le laser on démarre Serial pour pouvoir utiliser le moniteur série
laser.begin(9600); // on démarre la communication série nomée laser avec une vitesse de 9600 bauds
delay(200);
Serial.print("start");
}
Une fois la connexion établie on va pouvoir l'utiliser pour demander au laser d'abord de s'allumer puis de nous retourner la distance mesurée.
Pour envoyer les commandes, il ne suffit pas de lui dire "allume toi!". Il faut lui envoyer une serie de Bytes (Octets) en Hexadécimal selon le protocole MODBUS:
Format de requête MODBUS:
(1Byte- code d'adresse) (1Byte- code de fonction) (2Bytes - adresse initiale ) (2Bytes- Register number (N) ) (2Bytes- CRC)
Format de réponse MODBUS:
Réponse normale:
(1Byte- code d'adresse) (1Byte- code de fonction) (1Bytes - nombre de Byte ) (2 * N Bytes - valeurs ) (2Bytes- CRC)
Réponse anormale:
(1Byte- code d'adresse ) (1Byte- code d'erreur) (1Bytes - code d'Exception ) (2Bytes- CRC).
Définition des codes d'Exception:
0x01: Erreur dans le code de fonction
0x02: Erreur dans l'adresse initiale
0x03: Erreur dans le Register Number
0x04: Erreur dans la valeur
0x05: Erreur dans le CRC
0x06: Equipement Occupé (Busy)
Nous verrons plus tard à quoi sert le CRC
Pour envoyer la commande "allume toi" par exemple il faudra donc envoyer les Bytes suivants :
{0x01, 0x10, 0x00, 0x03, 0x00, 0x01, 0x02, 0x00, 0x01, 0x67, 0xA3}
où 0x
signifie que c'est une valeur en Hexadécimal. C'est la même commande pour ensuite l'éteindre.
La commande pour demander une mesure unique et la commande pour une mesure continue sont les suivantes:
uint8_t commandeContinue[8] = {0x01, 0x03, 0x00, 0x01, 0x00, 0x02, 0x95, 0xCB};
uint8_t commandeUnique[8] = {0x01, 0x03, 0x00, 0x0F, 0x00, 0x02, 0xF4, 0x08};
uint8_t marche[11] = {0x01, 0x10, 0x00, 0x03, 0x00, 0x01, 0x02, 0x00, 0x01, 0x67, 0xA3}; //commande on/off
uint8_t arretContinue[8] = {0x01, 0x03, 0x00, 0x0A, 0x00, 0x02, 0xE4, 0x09} //arrête la mesure continue (non testée pour l'instant)
Ce site (avec une partie en japonais et l'autre en anglais) récapitule les bytes à envoyer pour plusieurs autres commandes. Attention! Lorsqu'on passe par le langague arduino il faut penser à lui préciser que les valeurs sont en hexadécimal en rajoutant 0x
devant la valeur comme on le fait dans le code précédent.
Pour envoyer les commande il suffit d'écrire sur le port série qu'on vient d'initialiser:
void setup() {
Serial.begin(9600); // pour afficher les valeurs retournées par le laser on démarre Serial pour pouvoir utiliser le moniteur série
laser.begin(9600); // on démarre la communication série nomée laser avec une vitesse de 9600 bauds
delay(200);
Serial.print("start");
laser.write(marche, 11); //envoie la commande "allume toi"
}
void loop() {
laser.write(commandUnique, 8); //on lui demande une valeur de mesure de distance
}
Si tout se passe bien on devrait recevoir de la part du capteur une suite de Bytes de type: {0x01, 0x03, 0x04, 0xXX, 0xXX, 0xXX, 0xXX, 0xYY, 0xYY}
où XX sont les Bytes de valeur de la distance et YY le code CRC
Pour lire la réponse on va utiliser laser.readBytes()
(Voir documentation de la librairie)
laser.write(commandUnique, 8); //requête
delay(200);
if( laser.available() >0){ //on verifie que des données sont disponibles à la lecture
laser.readBytes(data, 9); //on lit les 9 premiers Bytes et on les stocke dans data qui est de type uint_8[9]
//laser.overflow();
}
for(int i = 0; i < sizeof(data); i++) { //on parcourt toutes les valeurs de data
Serial.print(data[i], HEX); //on affiche le Byte au format Hexadécimal dans le moniteur série
Serial.print(" "); //sépare les valeurs
}
Bon c'est bien beau tout ça mais on en fait quoi de cette liste de caractères incomprehensible ?!
On va pouvoir décoder les caractères reçus tout simplement en convertissant l'hexadécimal en décimal. Pour cela j'utilise cette fonction:
byte3_1 = data[3] / 16; //un byte en hexadecimal a 2 lettres/chiffres donc il faut prendre chacun des deux séparément: ici on récupère la première partie (le poids le plus fort)
byte3_2 = data[3] - byte3_1 * 16; //ici on récupère la deuxième partie (poids faible)
byte4_1 = data[4] / 16;
byte4_2 = data[4] - byte4_1 * 16;
byte5_1 = data[5] / 16;
byte5_2 = data[5] - byte5_1 * 16;
byte6_1 = data[6] / 16;
byte6_2 = data[6] - byte6_1 * 16;
uint8_t bytes[8] = {byte6_1,byte6_2,byte5_1,byte5_2,byte4_1,byte4_2,byte3_1,byte3_2}; //bit de poids faible en premier
float dist = 0;
for(int i =0; i< 8;i++) { //parcourt toutes les valeurs de bytes[]
dist+= bytes[i]*pow(16,i); //pour chaque bit en commençant par le bit de poids faible, on le multiplie par 16 élevé à la puissance de i et on l'ajoute à la valeur de dist
}
En savoir plus sur la conversion Hex->Dec
Pour rendre le traitement des données plus facile, je concentre toute la gestion de la reception des informations transmises par le laser dans une seule fonction. Ici je crée la fonction getDist()
qui effectue tous les calculs décrits précédemment et retourne la distance mesurée.
long getDist() {
if( laser.available() >0){ //lecture des données
laser.readBytes(data, 9);
laser.overflow();
}
for(int i = 0; i < sizeof(data); i++) //affichage des données reçues
{
Serial.print(data[i], HEX);
Serial.print(" ");
}
//traitement des données
delay(200);
byte3_1 = data[3] / 16;
byte3_2 = data[3] - byte3_1 * 16;
byte4_1 = data[4] / 16;
byte4_2 = data[4] - byte4_1 * 16;
byte5_1 = data[5] / 16;
byte5_2 = data[5] - byte5_1 * 16;
byte6_1 = data[6] / 16;
byte6_2 = data[6] - byte6_1 * 16;
uint8_t bytes[8] = {byte6_2,byte6_1,byte5_2,byte5_1,byte4_2,byte4_1,byte3_2,byte3_1}; //bit de poids faible en premier
float dist = 0;
for(int i =0; i< 8;i++) {
dist+= bytes[i]*pow(16,i);
}
return dist; //retourne la distance mesurée
}
4. Mais ducoup... c'est quoi le CRC
Le CRC c'est tout simplement un code de vérification qui permet d'être sûr que les données ne sont pas corrumpues (qu'on en a pas perdu en route en gros). Pour faire simple, les caractères de la chaîne de données transmise sont ajoutés d'une certaine mainière qui fait que la probabilité de trouver la même somme avec des caractères différents est extrêmement faible. On transmet à la fin du "message" la somme ainsi calculée c'est à dire le code CRC (Cyclic Redundancy Check). La personne (ou ordinateur) qui reçoit le message va faire le même calcul et comparer le résultat au code CRC reçu. Si les deux sont identiques, les données ont bien été reçues correctement. Sinon il faut demander à réenvoyer les données.
Le calcul du CRC est le résultats de calculs mathématiques complexes incluant des divisions polynomiales et tout un tas de choses avec lesquelles on n'a pas envie de s'embêter. C'est pour ça qu'on est content quand on voit qu'une librairie s'en charge à notre place!
Comment l'utiliser?
Tout d'abord il faut la télécharger via le gestionnaire de librairies (son petit nom c'est tout simplement CRC) et l'importer au début de notre sketch:
#include <CRC16.h>
//puis on l'initialise avec des constantes toutes prêtes
CRC16 crc(CRC16_MODBUS_POLYNOME,
CRC16_MODBUS_INITIAL,
CRC16_MODBUS_XOR_OUT,
CRC16_MODBUS_REV_IN,
CRC16_MODBUS_REV_OUT);
Ensuite on remet le "compteur" à zéro à chaque fois qu'on veut l'utiliser en utilisant crc.restart();
( il ne faut pas oublier cette étape sinon tous les calculs effectués par la librairie seront faussés )
Pour calculer le CRC, il faut ajouter un par un les bytes reçus avec crc.add(byte);
puis le calculer avec crc.calc();
. Au final ça donne ça:
crc.restart();
laser.write(commandUnique, 8);
delay(200);
if( laser.available() >0){
laser.readBytes(data, 9);
//laser.overflow();
}
for(int i = 0; i < sizeof(data); i++)
{
crc.add(data[i]);
}
uint8_t CRC = crc.calc();
if (CRC==0) { //le code est bon si il vaut 0 (la différence entre le code calculé et celui envoyé avec les données est égale à 0 si les 2 codes sont les mêmes)
Serial.println("Les données ne sont pas corrompues");
}
5. Récap
Au final après avoir fait tout ça on peut résumer l'utilisation du laser à:
- un câblage:
- un code:
/**
* Code pour une première utilisation simple du télémètre laser TW10S-UART
* https://fr.aliexpress.com/item/33035807395.html
*
* Par guillaume_lrt
* Le 24/06/2024
*/
#include <CRC16.h> //librairie pour le CRC
#include <SoftwareSerial.h> //librairie pour la communication série
#define TX_laser 4 //la broche reliée au TX du laser
#define RX_laser 3 //la broche reliée au RX du laser
SoftwareSerial laser(TX_laser, RX_laser); //on initialise la communication série qui sera appelée laser
CRC16 crc(CRC16_MODBUS_POLYNOME,
CRC16_MODBUS_INITIAL,
CRC16_MODBUS_XOR_OUT,
CRC16_MODBUS_REV_IN,
CRC16_MODBUS_REV_OUT); //on initialise la librairie qui calculera le CRC
//les codes à envoyer au laser
uint8_t commandContinue[8] = {0x01, 0x03, 0x00, 0x01, 0x00, 0x02, 0x95, 0xCB}; //mesure continue
uint8_t arretContinue[8] = {0x01, 0x03, 0x00, 0x0A, 0x00, 0x02, 0xE4, 0x09} //arrête la mesure continue (non testée pour l'instant)
uint8_t commandUnique[8] = {0x01, 0x03, 0x00, 0x0F, 0x00, 0x02, 0xF4, 0x08}; //mesure unique
uint8_t marche[11] = {0x01, 0x10, 0x00, 0x03, 0x00, 0x01, 0x02, 0x00, 0x01, 0x67, 0xA3}; //commande on/off
uint8_t data[9];
//*****************************************************************************
void setup() {
Serial.begin(9600); // pour afficher les valeurs retournées par le laser on démarre Serial pour pouvoir utiliser le moniteur série
laser.begin(9600); // on démarre la communication série nomée laser avec une vitesse de 9600 bauds
delay(200);
Serial.print("start");
laser.write(marche, 11); //démarre le laser
}
//*****************************************************************************
void loop() {
Serial.println(getDist()); //appelle la fonction getDist() et affiche le résultat
}
/**********************Fonction Distance**************************/
long getDist() {
crc.restart();
laser.write(commandUnique, 8);
delay(200);
if( laser.available() >0){
laser.readBytes(data, 9);
//laser.overflow();
}
for(int i = 0; i < sizeof(data); i++)
{
//Serial.print(data[i], HEX);
//Serial.print(" ");
crc.add(data[i]);
}
uint8_t CRC = crc.calc();
//Serial.println(CRC, HEX);
uint8_t byte6_1, byte5_1, byte6_2, byte5_2, byte4_1, byte4_2, byte3_1, byte3_2;
delay(200);
byte3_1 = data[3] / 16; //premier caractère du byte en hex
byte3_2 = data[3] - byte3_1 * 16; //Deuxième caractère
byte4_1 = data[4] / 16;
byte4_2 = data[4] - byte4_1 * 16;
byte5_1 = data[5] / 16;
byte5_2 = data[5] - byte5_1 * 16;
byte6_1 = data[6] / 16;
byte6_2 = data[6] - byte6_1 * 16;
uint8_t bytes[8] = {byte6_2,byte6_1,byte5_2,byte5_1,byte4_2,byte4_1,byte3_2,byte3_1}; //bit de poids faible en premier
float dist = 0;
for(int i =0; i< 8;i++) {
dist+= bytes[i]*pow(16,i);
}
if (CRC == 0) {
Serial.println("Les données ne sont pas corrompues");
return dist;
} else return -1; //retourne -1 si la mesure n'est pas recevable (CRC pas bon)
}
En éspérant que ce tuto vous ait plu et soit utile au plus grand nombre,
bon courage dans vos projets et aventures avec Arduino!