Dans ce projet, nous allons construire un serveur Web avec l’ESP32 pour afficher les lectures de l’accéléromètre et du capteur gyroscope MPU-6050. Nous allons également créer une représentation 3D de l’orientation du capteur sur le navigateur Web. Les lectures sont mises à jour automatiquement à l’aide des événements envoyés par le serveur et la représentation 3D est gérée à l’aide d’une bibliothèque JavaScript appelée three.js. La carte ESP32 sera programmée à l’aide du noyau Arduino.

Pour créer le serveur Web, nous utiliserons la bibliothèque ESPAsyncWebServer qui fournit un moyen simple de créer un serveur Web asynchrone et de gérer les événements envoyés par le serveur.
Pour en savoir plus sur les événements envoyés par le serveur, lisez : Serveur Web ESP32 utilisant les événements envoyés par le serveur (Mettre à jour automatiquement les lectures des capteurs).
Regardez la démonstration vidéo
Regardez la vidéo suivante pour un aperçu du projet que nous allons construire.

Aperçu du projet
Avant de passer directement au projet, il est important de décrire ce que fera notre serveur Web, afin qu’il soit plus facile à comprendre.

- Le serveur web affiche les valeurs du gyroscope des axes X, Y et Z ;
- Les valeurs du gyroscope sont mises à jour sur le serveur Web toutes les 10 millisecondes ;
- Il affiche les valeurs de l’accéléromètre (X, Y, Z). Ces valeurs sont mises à jour toutes les 200 millisecondes ;
- Le module de capteur MPU-6050 mesure également la température, nous afficherons donc également la valeur de la température. La température est mise à jour toutes les secondes (1000 millisecondes) ;
- Toutes les lectures sont mises à jour à l’aide des événements envoyés par le serveur ;
- Il y a une représentation 3D du capteur. L’orientation de l’objet 3D change en fonction de l’orientation du capteur. La position actuelle du capteur est calculée à l’aide des valeurs du gyroscope ;
- L’objet 3D est créé à l’aide d’une bibliothèque JavaScript appelée trois.js;
- Il y a quatre boutons pour ajuster la position de l’objet 3D :
- RESET POSITION : définit la position angulaire sur zéro sur tous les axes ;
- X : définit la position angulaire X sur zéro ;
- Y : définit la position angulaire Y sur zéro ;
- Z : met la position angulaire Z à zéro ;

Système de fichiers ESP32
Pour garder notre projet organisé et le rendre plus facile à comprendre, nous allons créer quatre fichiers différents pour construire le serveur Web :

- le code Arduino qui gère le serveur Web ;
- Fichier HTML : pour définir le contenu de la page Web ;
- Fichier CSS : pour styliser la page Web ;
- Fichier JavaScript : pour programmer le comportement de la page Web (gestion des réponses du serveur Web, des événements et création de l’objet 3D).
Les fichiers HTML, CSS et JavaScript seront téléchargés sur le SPIFFS ESP32 (système de fichiers). Pour télécharger des fichiers sur le système de fichiers ESP32, nous utiliserons le plugin SPIFFS Uploader.
Si vous utilisez PlatformIO + VS Code, lisez cet article pour savoir comment télécharger des fichiers sur le système de fichiers ESP32 :
Gyroscope et accéléromètre MPU-6050
Le MPU-6050 est un module avec un accéléromètre 3 axes et un gyroscope 3 axes.

Le gyroscope mesure la vitesse de rotation (rad/s) – c’est le changement de la position angulaire dans le temps le long des axes X, Y et Z (roulis, tangage et lacet). Cela nous permet de déterminer l’orientation d’un objet.

L’accéléromètre mesure l’accélération (taux de variation de la vitesse d’un objet). Il détecte les forces statiques comme la gravité (9,8 m/s2) ou des forces dynamiques comme les vibrations ou le mouvement. Le MPU-6050 mesure l’accélération sur les axes X, Y et Z. Idéalement, dans un objet statique, l’accélération sur l’axe Z est égale à la force gravitationnelle, et elle doit être nulle sur les axes X et Y.
A partir des valeurs de l’accéléromètre, il est possible de calculer les angles de roulis et de tangage par trigonométrie, mais il n’est pas possible de calculer le lacet.
Nous pouvons combiner les informations des deux capteurs pour obtenir des informations précises sur l’orientation du capteur.
En savoir plus sur le capteur MPU-6050 : ESP32 avec accéléromètre, gyroscope et capteur de température MPU-6050.
Schéma de principe – ESP32 avec MPU-6050
Pour ce projet, vous avez besoin des pièces suivantes :
Vous pouvez utiliser les liens précédents ou accéder directement à MakerAdvisor.com/tools pour trouver toutes les pièces pour vos projets au meilleur prix !
Câblez l’ESP32 au capteur MPU-6050 comme indiqué dans le schéma suivant : connectez la broche SCL au GPIO 22 et la broche SDA au GPIO 21.

Préparation de l’IDE Arduino
Nous allons programmer la carte ESP32 à l’aide de l’IDE Arduino. Assurez-vous donc que le module complémentaire ESP32 est installé. Suivez le tutoriel suivant :
Si vous préférez utiliser VSCode + PlatformIO, suivez plutôt le tutoriel suivant :
Installation des bibliothèques MPU-6050
Il existe différentes façons d’obtenir des lectures du capteur. Dans ce tutoriel, nous utiliserons le Bibliothèque Adafruit MPU6050. Pour utiliser cette bibliothèque, vous devez également installer le Bibliothèque de capteurs unifiés Adafruit et le Bibliothèque d’E/S de bus Adafruit.
Ouvrez votre IDE Arduino et accédez à Sketch> Inclure la bibliothèque> Gérer les bibliothèques. Le gestionnaire de bibliothèque devrait s’ouvrir.
Tapez « adafruit mpu6050 » dans le champ de recherche et installez la bibliothèque.

Ensuite, recherchez « Adafruit Unified Sensor ». Faites défiler vers le bas pour trouver la bibliothèque et installez-la.

Enfin, recherchez « Adafruit Bus IO » et installez-le.

Installation des bibliothèques de serveur Web asynchrone
Pour construire le serveur Web, nous utiliserons le ESPAsyncWebServer bibliothèque. Cette bibliothèque a besoin de AsyncTCP bibliothèque pour fonctionner correctement. Cliquez sur les liens ci-dessous pour télécharger les bibliothèques.
Ces bibliothèques ne peuvent pas être installées via le gestionnaire de bibliothèque Arduino, vous devez donc copier les fichiers de bibliothèque dans le dossier Bibliothèques d’installation Arduino. Alternativement, dans votre IDE Arduino, vous pouvez aller à Sketch> Inclure la bibliothèque> Ajouter une bibliothèque .zip et sélectionner les bibliothèques que vous venez de télécharger.
Installation de la bibliothèque Arduino_JSON
Dans cet exemple, nous enverrons les lectures du capteur au navigateur au format JSON. Pour faciliter la gestion des variables JSON, nous utiliserons le Bibliothèque Arduino_JSON.
Vous pouvez installer cette bibliothèque dans le gestionnaire de bibliothèque Arduino IDE. Allez simplement dans Sketch > Inclure la bibliothèque > Gérer les bibliothèques et recherchez le nom de la bibliothèque comme suit : Arduino_JSON.

Si vous utilisez VS Code avec PlatformIO, copiez les lignes suivantes dans le fichier platformio.ini pour inclure toutes les bibliothèques nécessaires.
lib_deps = adafruit/Adafruit MPU6050 @ ^2.0.3
adafruit/Adafruit Unified Sensor @ ^1.1.4
me-no-dev/ESP Async WebServer @ ^1.2.3
arduino-libraries/Arduino_JSON @ 0.1.0
Plug-in de téléchargement de système de fichiers
Pour suivre ce tutoriel, vous devez avoir installé le plugin ESP32 Filesystem Uploader dans votre IDE Arduino. Si ce n’est pas le cas, suivez le tutoriel suivant pour l’installer :
Si vous utilisez VS Code + PlatformIO, suivez le didacticiel suivant pour apprendre à télécharger des fichiers sur le système de fichiers ESP32 :
Organisation de vos fichiers
Pour créer le serveur Web, vous avez besoin de quatre fichiers différents. Le sketch Arduino, le fichier HTML, le fichier CSS et le fichier JavaScript. Les fichiers HTML, CSS et JavaScript doivent être enregistrés dans un dossier appelé data dans le dossier d’esquisse Arduino, comme indiqué ci-dessous :

Vous pouvez télécharger tous les fichiers du projet :
Création du fichier HTML
Créez un fichier index.html avec le contenu suivant ou télécharger tous les fichiers du projet.
<!--
Rui Santos
Complete project details at https://Raspberryme.com/esp32-mpu-6050-web-server/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-->
<!DOCTYPE HTML><html>
<head>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<link rel="stylesheet" type="text/css" href="https://www.raspberryme.com/esp32-mpu-6050-web-server/style.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/107/three.min.js"></script>
</head>
<body>
<div class="topnav">
<h1><i class="far fa-compass"></i> MPU6050 <i class="far fa-compass"></i></h1>
</div>
<div class="content">
<div class="cards">
<div class="card">
<p class="card-title">GYROSCOPE</p>
<p><span class="reading">X: <span id="gyroX"></span> rad</span></p>
<p><span class="reading">Y: <span id="gyroY"></span> rad</span></p>
<p><span class="reading">Z: <span id="gyroZ"></span> rad</span></p>
</div>
<div class="card">
<p class="card-title">ACCELEROMETER</p>
<p><span class="reading">X: <span id="accX"></span> ms<sup>2</sup></span></p>
<p><span class="reading">Y: <span id="accY"></span> ms<sup>2</sup></span></p>
<p><span class="reading">Z: <span id="accZ"></span> ms<sup>2</sup></span></p>
</div>
<div class="card">
<p class="card-title">TEMPERATURE</p>
<p><span class="reading"><span id="temp"></span> °C</span></p>
<p class="card-title">3D ANIMATION</p>
<button id="reset" onclick="resetPosition(this)">RESET POSITION</button>
<button id="resetX" onclick="resetPosition(this)">X</button>
<button id="resetY" onclick="resetPosition(this)">Y</button>
<button id="resetZ" onclick="resetPosition(this)">Z</button>
</div>
</div>
<div class="cube-content">
<div id="3Dcube"></div>
</div>
</div>
<script src="https://www.raspberryme.com/esp32-mpu-6050-web-server/script.js"></script>
</body>
</html>
Diriger
Les balises
et marquent le début et la fin de la tête. L’en-tête est l’endroit où vous insérez des données sur le document HTML qui ne sont pas directement visibles pour l’utilisateur final, mais qui ajoutent des fonctionnalités à la page Web – c’est ce qu’on appelle les métadonnées.La ligne suivante donne un titre à la page Web. Dans ce cas, il est défini sur ESP Web Server. Tu peux le changer si tu veux. Le titre est exactement ce à quoi il ressemble : le titre de votre document, qui s’affiche dans la barre de titre de votre navigateur Web.
<title>ESP Web Server</title>
La balise méta suivante rend votre page Web réactive. Une conception Web réactive s’ajustera automatiquement aux différentes tailles d’écran et fenêtres d’affichage.
<meta name="viewport" content="width=device-width, initial-scale=1">
Nous utilisons la balise Meta suivante car nous ne diffuserons pas de favicon pour notre page Web dans ce projet.
<link rel="icon" href="data:,">
Les styles pour styliser la page Web se trouvent dans un fichier séparé appelé fichier style.css. Donc, nous devons référencer le fichier CSS sur le fichier HTML comme suit.
<link rel="stylesheet" type="text/css" href="https://www.raspberryme.com/esp32-mpu-6050-web-server/style.css">
Incluez les styles de site Web Font Awesome pour inclure des icônes dans la page Web, telles que l’icône du gyroscope.
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
Enfin, nous devons inclure la bibliothèque three.js pour créer la représentation 3D du capteur.
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/107/three.min.js"></script>
Corps
Les balises
et marquent le début et la fin du corps. Tout ce qui se trouve à l’intérieur de ces balises est le contenu visible de la page.Barre du haut
Il y a une barre supérieure avec un titre dans la page Web. C’est un titre 1 et il est placé à l’intérieur d’une balise
<div class="topnav">
<h1><i class="far fa-compass"></i> MPU6050 <i class="far fa-compass"></i></h1>
</div>
Grille de contenu
Tous les autres contenus sont placés dans une balise
<div class="content">
Nous utilisons la disposition de la grille CSS pour afficher les lectures sur différentes cases alignées (carte). Chaque case correspond à une cellule de la grille. Les cellules de grille doivent être à l’intérieur d’un conteneur de grille, donc les boîtes doivent être placées à l’intérieur d’une autre balise
<div class="cards">
Pour en savoir plus sur la disposition des grilles CSS, nous vous recommandons cet article : Un guide complet de la grille. Voici la carte pour les lectures du gyroscope :
<div class="card">
<p class="card-title">GYROSCOPE</p>
<p><span class="reading">X: <span id="gyroX"></span> rad/s</span></p>
<p><span class="reading">Y: <span id="gyroY"></span> rad/s</span></p>
<p><span class="reading">Z: <span id="gyroZ"></span> rad/s</span></p>
</div>
La carte a un titre avec le nom de la carte :
<p class="card-title">GYROSCOPE</p>
Et trois paragraphes pour afficher les valeurs du gyroscope sur les axes X, Y et Z.
<p><span class="reading">X: <span id="gyroX"></span> rad/s</span></p>
<p><span class="reading">Y: <span id="gyroY"></span> rad/s</span></p>
<p><span class="reading">Z: <span id="gyroZ"></span> rad/s</span></p>
Dans chaque paragraphe, il y a une balise avec un identifiant unique. Ceci est nécessaire pour que nous puissions insérer les lectures au bon endroit plus tard en utilisant JavaScript. Voici les identifiants utilisés :
- gyroX pour la lecture du gyroscope X ;
- gyroY pour la lecture du gyroscope Y ;
- gyroZ pour la lecture du gyroscope Z.
La carte pour afficher les lectures de l’accéléromètre est similaire, mais avec des identifiants uniques différents pour chaque lecture :
<div class="card">
<p class="card-title">ACCELEROMETER</p>
<p><span class="reading">X: <span id="accX"></span> ms<sup>2</sup></span></p>
<p><span class="reading">Y: <span id="accY"></span> ms<sup>2</sup></span></p>
<p><span class="reading">Z: <span id="accZ"></span> ms<sup>2</sup></span></p>
</div>
Voici les identifiants pour les lectures de l’accéléromètre :
- accX pour la lecture de l’accéléromètre X ;
- accY pour la lecture de l’accéléromètre Y ;
- accZ pour la lecture de l’accéléromètre Z.
Enfin, les lignes suivantes affichent la carte de la température et les boutons de réinitialisation.
<div class="card">
<p class="card-title">TEMPERATURE</p>
<p><span class="reading"><span id="temp"></span> °C</span></p>
<p class="card-title">3D ANIMATION</p>
<button id="reset" onclick="resetPosition(this)">RESET POSITION</button>
<button id="resetX" onclick="resetPosition(this)">X</button>
<button id="resetY" onclick="resetPosition(this)">Y</button>
<button id="resetZ" onclick="resetPosition(this)">Z</button>
</div>
L’identifiant unique pour la lecture de la température est temp.
Ensuite, il y a quatre boutons différents qui, une fois cliqués, appelleront la fonction JavaScript resetPosition() plus tard. Cette fonction se chargera d’envoyer une requête à l’ESP32 l’informant que l’on souhaite réinitialiser la position, que ce soit sur tous les axes ou sur un axe individuel. Chaque bouton a un identifiant unique, afin que nous sachions sur quel bouton a été cliqué :
- réinitialiser : pour réinitialiser la position dans tous les axes ;
- resetX : pour réinitialiser la position sur l’axe X ;
- resetY : pour réinitialiser la position sur l’axe Y ;
- resetZ : pour réinitialiser la position sur l’axe Z.
Représentation 3D
Nous devons créer une section pour afficher la représentation 3D.
<div class="cube-content">
<div id="3Dcube"></div>
</div>
L’objet 3D sera rendu sur le
Référencez le fichier JavaScript
Enfin, comme nous allons utiliser un fichier JavaScript externe avec toutes les fonctions pour gérer les éléments HTML et créer l’animation 3D, nous devons référencer ce fichier (script.js) comme suit :
<script src="https://www.raspberryme.com/esp32-mpu-6050-web-server/script.js"></script>
Création du fichier CSS
Créez un fichier appelé style.css avec le contenu suivant ou télécharger tous les fichiers du projet.
Ce fichier est responsable du style de la page Web.
/*
Rui Santos
Complete project details at https://Raspberryme.com/esp32-mpu-6050-web-server/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
html {
font-family: Arial;
display: inline-block;
text-align: center;
}
p {
font-size: 1.2rem;
}
body {
margin: 0;
}
.topnav {
overflow: hidden;
background-color: #003366;
color: #FFD43B;
font-size: 1rem;
}
.content {
padding: 20px;
}
.card {
background-color: white;
box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
}
.card-title {
color:#003366;
font-weight: bold;
}
.cards {
max-width: 800px;
margin: 0 auto;
display: grid; grid-gap: 2rem;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.reading {
font-size: 1.2rem;
}
.cube-content{
width: 100%;
background-color: white;
height: 300px; margin: auto;
padding-top:2%;
}
#reset{
border: none;
color: #FEFCFB;
background-color: #003366;
padding: 10px;
text-align: center;
display: inline-block;
font-size: 14px; width: 150px;
border-radius: 4px;
}
#resetX, #resetY, #resetZ{
border: none;
color: #FEFCFB;
background-color: #003366;
padding-top: 10px;
padding-bottom: 10px;
text-align: center;
display: inline-block;
font-size: 14px;
width: 20px;
border-radius: 4px;
}
Nous n’expliquerons pas comment fonctionne le CSS pour ce projet car il n’est pas pertinent pour l’objectif de ce projet.
Création du fichier JavaScript
Créez un fichier appelé script.js avec le contenu suivant ou télécharger tous les fichiers du projet.
/*
Rui Santos
Complete project details at https://Raspberryme.com/esp32-mpu-6050-web-server/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
let scene, camera, rendered, cube;
function parentWidth(elem) {
return elem.parentElement.clientWidth;
}
function parentHeight(elem) {
return elem.parentElement.clientHeight;
}
function init3D(){
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
camera = new THREE.PerspectiveCamera(75, parentWidth(document.getElementById("3Dcube")) / parentHeight(document.getElementById("3Dcube")), 0.1, 1000);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(parentWidth(document.getElementById("3Dcube")), parentHeight(document.getElementById("3Dcube")));
document.getElementById('3Dcube').appendChild(renderer.domElement);
// Create a geometry
const geometry = new THREE.BoxGeometry(5, 1, 4);
// Materials of each face
var cubeMaterials = [
new THREE.MeshBasicMaterial({color:0x03045e}),
new THREE.MeshBasicMaterial({color:0x023e8a}),
new THREE.MeshBasicMaterial({color:0x0077b6}),
new THREE.MeshBasicMaterial({color:0x03045e}),
new THREE.MeshBasicMaterial({color:0x023e8a}),
new THREE.MeshBasicMaterial({color:0x0077b6}),
];
const material = new THREE.MeshFaceMaterial(cubeMaterials);
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
renderer.render(scene, camera);
}
// Resize the 3D object when the browser window changes size
function onWindowResize(){
camera.aspect = parentWidth(document.getElementById("3Dcube")) / parentHeight(document.getElementById("3Dcube"));
//camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
//renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setSize(parentWidth(document.getElementById("3Dcube")), parentHeight(document.getElementById("3Dcube")));
}
window.addEventListener('resize', onWindowResize, false);
// Create the 3D representation
init3D();
// Create events for the sensor readings
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('gyro_readings', function(e) {
//console.log("gyro_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("gyroX").innerHTML = obj.gyroX;
document.getElementById("gyroY").innerHTML = obj.gyroY;
document.getElementById("gyroZ").innerHTML = obj.gyroZ;
// Change cube rotation after receiving the readinds
cube.rotation.x = obj.gyroY;
cube.rotation.z = obj.gyroX;
cube.rotation.y = obj.gyroZ;
renderer.render(scene, camera);
}, false);
source.addEventListener('temperature_reading', function(e) {
console.log("temperature_reading", e.data);
document.getElementById("temp").innerHTML = e.data;
}, false);
source.addEventListener('accelerometer_readings', function(e) {
console.log("accelerometer_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("accX").innerHTML = obj.accX;
document.getElementById("accY").innerHTML = obj.accY;
document.getElementById("accZ").innerHTML = obj.accZ;
}, false);
}
function resetPosition(element){
var xhr = new XMLHttpRequest();
xhr.open("GET", "/"+element.id, true);
console.log(element.id);
xhr.send();
}
Création d’un objet 3D
La fonction init3D() crée l’objet 3D. Pour pouvoir afficher quoi que ce soit avec three.js, nous avons besoin de trois choses : la scène, la caméra et le moteur de rendu, afin de pouvoir rendre la scène avec la caméra.
function init3D(){
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
camera = new THREE.PerspectiveCamera(75, parentWidth(document.getElementById("3Dcube")) / parentHeight(document.getElementById("3Dcube")), 0.1, 1000);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(parentWidth(document.getElementById("3Dcube")), parentHeight(document.getElementById("3Dcube")));
document.getElementById('3Dcube').appendChild(renderer.domElement);
Pour créer l’objet 3D, nous avons besoin d’un BoxGeometry. Dans la géométrie de la boîte, vous pouvez définir les dimensions de votre objet. Nous avons créé l’objet avec les bonnes proportions pour ressembler à la forme du MPU-6050.
const geometry = new THREE.BoxGeometry(5, 1, 4);
Outre la géométrie, nous avons également besoin d’un matériau pour colorer l’objet. Il existe différentes façons de colorer l’objet. Nous avons choisi trois couleurs différentes pour les visages.
// Materials of each face
var cubeMaterials = [
new THREE.MeshBasicMaterial({color:0x03045e}),
new THREE.MeshBasicMaterial({color:0x023e8a}),
new THREE.MeshBasicMaterial({color:0x0077b6}),
new THREE.MeshBasicMaterial({color:0x03045e}),
new THREE.MeshBasicMaterial({color:0x023e8a}),
new THREE.MeshBasicMaterial({color:0x0077b6}),
];
const material = new THREE.MeshFaceMaterial(cubeMaterials);
Enfin, créez l’objet 3D, ajoutez-le à la scène et ajustez la caméra.
cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
renderer.render(scene, camera);
Nous vous recommandons de jeter un œil à ce rapide tutoriel three.js pour mieux comprendre comment cela fonctionne : Premiers pas avec three.js – Création d’une scène.
Pour pouvoir redimensionner l’objet lorsque la fenêtre du navigateur Web change de taille, nous devons appeler la fonction onWindowResize() lorsque l’événement resize se produit.
// Resize the 3D object when the browser window changes size
function onWindowResize(){
camera.aspect = parentWidth(document.getElementById("3Dcube")) / parentHeight(document.getElementById("3Dcube"));
//camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
//renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setSize(parentWidth(document.getElementById("3Dcube")), parentHeight(document.getElementById("3Dcube")));
}
window.addEventListener('resize', onWindowResize, false);
Appelez la fonction init3D() pour créer réellement la représentation 3D.
init3D();
Événements (ESS)
L’ESP32 envoie périodiquement de nouvelles lectures de capteur sous forme d’événements au client (navigateur). Nous devons gérer ce qui se passe lorsque le client reçoit ces événements.
Dans cet exemple, nous souhaitons placer les lectures sur les éléments HTML correspondants et modifier l’orientation de l’objet 3D en conséquence.
Créez un nouvel objet EventSource et spécifiez l’URL de la page qui envoie les mises à jour. Dans notre cas, il /events.
if (!!window.EventSource) {
var source = new EventSource('/events');
Une fois que vous avez instancié une source d’événement, vous pouvez commencer à écouter les messages du serveur avec addEventListener().
Ce sont les écouteurs d’événements par défaut, comme indiqué ici dans AsyncWebServer Documentation.
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
Lorsque de nouvelles lectures de gyroscope sont disponibles, l’ESP32 envoie un événement gyro_readings au client. Nous devons ajouter un écouteur d’événement pour cet événement spécifique.
source.addEventListener('gyro_readings', function(e) {
Les lectures du gyroscope sont une chaîne au format JSON. Par exemple:
{
"gyroX" : "0.09",
"gyroY" : "0.05",
"gyroZ": "0.04"
}
JavaScript a une fonction intégrée pour convertir une chaîne, écrite au format JSON, en objets JavaScript natifs : JSON.parse().
var obj = JSON.parse(e.data);
La variable obj contient les lectures du capteur au format JavaScript natif. Ensuite, nous pouvons accéder aux lectures comme suit :
- lecture gyroscope X : obj.gyroX ;
- lecture du gyroscope Y : obj.gyroY ;
- lecture Z du gyroscope : obj.gyroZ ;
Les lignes suivantes placent les données reçues dans les éléments HTML correspondants sur la page Web.
document.getElementById("gyroX").innerHTML = obj.gyroX;
document.getElementById("gyroY").innerHTML = obj.gyroY;
document.getElementById("gyroZ").innerHTML = obj.gyroZ;
Enfin, nous devons modifier la rotation du cube en fonction des lectures reçues, comme suit :
cube.rotation.x = obj.gyroY;
cube.rotation.z = obj.gyroX;
cube.rotation.y = obj.gyroZ;
renderer.render(scene, camera);
Remarque : dans notre cas, les axes sont inversés comme indiqué précédemment (rotation X –> gyroY, rotation Z –> gyroX, rotation Y –> gyroZ). Vous devrez peut-être modifier cela en fonction de l’orientation de votre capteur.
Pour les accéléromètres_lectures et les événements de température, nous affichons simplement les données sur la page HTML.
source.addEventListener('temperature_reading', function(e) {
console.log("temperature_reading", e.data);
document.getElementById("temp").innerHTML = e.data;
}, false);
source.addEventListener('accelerometer_readings', function(e) {
console.log("accelerometer_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("accX").innerHTML = obj.accX;
document.getElementById("accY").innerHTML = obj.accY;
document.getElementById("accZ").innerHTML = obj.accZ;
}, false);
Enfin, nous devons créer la fonction resetPosition(). Cette fonction sera appelée par les boutons de réinitialisation.
function resetPosition(element){
var xhr = new XMLHttpRequest();
xhr.open("GET", "/"+element.id, true);
console.log(element.id);
xhr.send();
}
Cette fonction envoie simplement une requête HTTP au serveur sur une URL différente selon le bouton qui a été pressé (element.id).
xhr.open("GET", "/"+element.id, true);
- Bouton RESET POSITION -> demande : /reset
- Bouton X -> demande : /resetX
- Bouton Y -> demande : /resetY
- Bouton Z -> demande : /resetZ
Esquisse Arduino
Enfin, configurons le serveur (ESP32). Copiez le code suivant dans l’IDE Arduino ou télécharger tous les fichiers du projet.
/*********
Rui Santos
Complete project details at https://Raspberryme.com/esp32-mpu-6050-web-server/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*********/
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Arduino_JSON.h>
#include "SPIFFS.h"
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
// Create an Event Source on /events
AsyncEventSource events("/events");
// Json Variable to Hold Sensor Readings
JSONVar readings;
// Timer variables
unsigned long lastTime = 0;
unsigned long lastTimeTemperature = 0;
unsigned long lastTimeAcc = 0;
unsigned long gyroDelay = 10;
unsigned long temperatureDelay = 1000;
unsigned long accelerometerDelay = 200;
// Create a sensor object
Adafruit_MPU6050 mpu;
sensors_event_t a, g, temp;
float gyroX, gyroY, gyroZ;
float accX, accY, accZ;
float temperature;
//Gyroscope sensor deviation
float gyroXerror = 0.07;
float gyroYerror = 0.03;
float gyroZerror = 0.01;
// Init MPU6050
void initMPU(){
if (!mpu.begin()) {
Serial.println("Failed to find MPU6050 chip");
while (1) {
delay(10);
}
}
Serial.println("MPU6050 Found!");
}
void initSPIFFS() {
if (!SPIFFS.begin()) {
Serial.println("An error has occurred while mounting SPIFFS");
}
Serial.println("SPIFFS mounted successfully");
}
// Initialize WiFi
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
Serial.print("Connecting to WiFi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.println("");
Serial.println(WiFi.localIP());
}
String getGyroReadings(){
mpu.getEvent(&a, &g, &temp);
float gyroX_temp = g.gyro.x;
if(abs(gyroX_temp) > gyroXerror) {
gyroX += gyroX_temp/50.00;
}
float gyroY_temp = g.gyro.y;
if(abs(gyroY_temp) > gyroYerror) {
gyroY += gyroY_temp/70.00;
}
float gyroZ_temp = g.gyro.z;
if(abs(gyroZ_temp) > gyroZerror) {
gyroZ += gyroZ_temp/90.00;
}
readings["gyroX"] = String(gyroX);
readings["gyroY"] = String(gyroY);
readings["gyroZ"] = String(gyroZ);
String jsonString = JSON.stringify(readings);
return jsonString;
}
String getAccReadings() {
mpu.getEvent(&a, &g, &temp);
// Get current acceleration values
accX = a.acceleration.x;
accY = a.acceleration.y;
accZ = a.acceleration.z;
readings["accX"] = String(accX);
readings["accY"] = String(accY);
readings["accZ"] = String(accZ);
String accString = JSON.stringify (readings);
return accString;
}
String getTemperature(){
mpu.getEvent(&a, &g, &temp);
temperature = temp.temperature;
return String(temperature);
}
void setup() {
Serial.begin(115200);
initWiFi();
initSPIFFS();
initMPU();
// Handle Web Server
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html", "text/html");
});
server.serveStatic("/", SPIFFS, "/");
server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request){
gyroX=0;
gyroY=0;
gyroZ=0;
request->send(200, "text/plain", "OK");
});
server.on("/resetX", HTTP_GET, [](AsyncWebServerRequest *request){
gyroX=0;
request->send(200, "text/plain", "OK");
});
server.on("/resetY", HTTP_GET, [](AsyncWebServerRequest *request){
gyroY=0;
request->send(200, "text/plain", "OK");
});
server.on("/resetZ", HTTP_GET, [](AsyncWebServerRequest *request){
gyroZ=0;
request->send(200, "text/plain", "OK");
});
// Handle Web Server Events
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);
server.begin();
}
void loop() {
if ((millis() - lastTime) > gyroDelay) {
// Send Events to the Web Server with the Sensor Readings
events.send(getGyroReadings().c_str(),"gyro_readings",millis());
lastTime = millis();
}
if ((millis() - lastTimeAcc) > accelerometerDelay) {
// Send Events to the Web Server with the Sensor Readings
events.send(getAccReadings().c_str(),"accelerometer_readings",millis());
lastTimeAcc = millis();
}
if ((millis() - lastTimeTemperature) > temperatureDelay) {
// Send Events to the Web Server with the Sensor Readings
events.send(getTemperature().c_str(),"temperature_reading",millis());
lastTimeTemperature = millis();
}
}
Avant de télécharger le code, assurez-vous d’insérer vos informations d’identification réseau sur les variables suivantes :
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Comment fonctionne le code
Continuez à lire pour savoir comment fonctionne le code ou passez à la section suivante.
Bibliothèques
Tout d’abord, importez toutes les bibliothèques requises pour ce projet :
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Arduino_JSON.h>
#include "SPIFFS.h"
Informations d’identification réseau
Insérez vos identifiants réseau dans les variables suivantes :
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
AsyncWebServer et AsyncEventSource
Créez un objet AsyncWebServer sur le port 80.
AsyncWebServer server(80);
La ligne suivante crée une nouvelle source d’événement sur /events.
AsyncEventSource events("/events");
Déclaration de variables
La variable lectures est une variable JSON pour conserver les lectures du capteur au format JSON.
JSONVar readings;
Dans ce projet, nous enverrons les relevés du gyroscope toutes les 10 millisecondes, les relevés de l’accéléromètre toutes les 200 millisecondes et les relevés de température toutes les secondes. Nous devons donc créer des variables de minuterie auxiliaires pour chaque lecture. Vous pouvez modifier les délais si vous le souhaitez.
// Timer variables
unsigned long lastTime = 0;
unsigned long lastTimeTemperature = 0;
unsigned long lastTimeAcc = 0;
unsigned long gyroDelay = 10;
unsigned long temperatureDelay = 1000;
unsigned long accelerometerDelay = 200;
MPU-6050
Créez un objet Adafruit_MPU5060 appelé mpu, créez des événements pour les lectures de capteur et des variables pour conserver les lectures.
// Create a sensor object
Adafruit_MPU6050 mpu;
sensors_event_t a, g, temp;
float gyroX, gyroY, gyroZ;
float accX, accY, accZ;
float temperature;
Décalage du gyroscope
Ajustez le décalage du capteur du gyroscope sur tous les axes.
//Gyroscope sensor deviation
float gyroXerror = 0.07;
float gyroYerror = 0.03;
float gyroZerror = 0.01;
Pour obtenir le décalage du capteur, accédez à Fichier > Exemples > Adafruit MPU6050 > basic_readings. Avec le capteur en position statique, vérifiez les valeurs X, Y et Z du gyroscope. Ensuite, ajoutez ces valeurs aux variables gyroXerror, gyroYerror et gyroZerror.
Initialiser MPU-6050
La fonction initMPU() initialise le capteur MPU-6050.
// Init MPU6050
void initMPU(){
if (!mpu.begin()) {
Serial.println("Failed to find MPU6050 chip");
while (1) {
delay(10);
}
}
Serial.println("MPU6050 Found!");
}
Initialiser les SPIFF
La fonction initSPIFFS() initialise le système de fichiers ESP32 afin que nous puissions accéder aux fichiers enregistrés sur SPIFFS (index.html, style.css et script.js).
void initSPIFFS() {
if (!SPIFFS.begin()) {
Serial.println("An error has occurred while mounting SPIFFS");
}
Serial.println("SPIFFS mounted successfully");
}
Initialiser le Wi-Fi
La fonction initWiFi() connecte l’ESP32 à votre réseau local.
// Initialize WiFi
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi ..");
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
Serial.println(WiFi.localIP());
}
Obtenez des lectures de gyroscope
La fonction getGyroReadings() obtient de nouvelles lectures de gyroscope et renvoie l’orientation angulaire actuelle sur les axes X, Y et Z sous forme de chaîne JSON.
Le gyroscope renvoie la vitesse angulaire actuelle. La vitesse angulaire est mesurée en rad/s. Pour déterminer la position actuelle d’un objet, nous devons multiplier la vitesse angulaire par le temps écoulé (10 millisecondes) et l’ajouter à la position précédente.
current angle (rad) = last angle (rad) + angular velocity (rad/s) * time(s)
La variable gyroX_temp contient temporairement la valeur X actuelle du gyroscope.
float gyroX_temp = g.gyro.x;
Pour éviter de petites oscillations du capteur (voir Décalage du gyroscope), nous vérifions d’abord si les valeurs du capteur sont supérieures à l’offset.
if(abs(gyroX_temp) > gyroXerror) {
Si la valeur actuelle est supérieure à la valeur de décalage, nous considérons que nous avons une lecture valide. Ainsi, nous pouvons appliquer la formule précédente pour obtenir la position angulaire du capteur actuel (gyroX).
gyroX += gyroX_temp / 50.0;
Remarque : théoriquement, nous devrions multiplier la vitesse angulaire actuelle par le temps écoulé (10 millisecondes = 0,01 seconde (gyroDelay)) – ou diviser par 100. Cependant, après quelques expériences, nous avons découvert que le capteur répond mieux si nous divisons par 50,0 plutôt. Votre capteur peut être différent et vous devrez peut-être ajuster la valeur.
Nous suivons une procédure similaire pour obtenir les valeurs Y et Z.
float gyroX_temp = g.gyro.x;
if(abs(gyroX_temp) > gyroXerror) {
gyroX += gyroX_temp/50.00;
}
float gyroY_temp = g.gyro.y;
if(abs(gyroY_temp) > gyroYerror) {
gyroY += gyroY_temp/70.00;
}
float gyroZ_temp = g.gyro.z;
if(abs(gyroZ_temp) > gyroZerror) {
gyroZ += gyroZ_temp/90.00;
}
Enfin, nous concaténons les lectures dans une variable JSON (lectures) et renvoyons une chaîne JSON (jsonString).
readings["gyroX"] = String(gyroX);
readings["gyroY"] = String(gyroY);
readings["gyroZ"] = String(gyroZ);
String jsonString = JSON.stringify(readings);
return jsonString;
Obtenir des lectures d’accéléromètre
La fonction getAccReadings() renvoie les lectures de l’accéléromètre.
String getAccReadings(){
mpu.getEvent(&a, &g, &temp);
// Get current acceleration values
accX = a.acceleration.x;
accY = a.acceleration.y;
accZ = a.acceleration.z;
readings["accX"] = String(accX);
readings["accY"] = String(accY);
readings["accZ"] = String(accZ);
String accString = JSON.stringify (readings);
return accString;
}
Obtenir des relevés de température
La fonction getTemperature() renvoie la lecture de température actuelle.
String getTemperature(){
mpu.getEvent(&a, &g, &temp);
temperature = temp.temperature;
return String(temperature);
}
installation()
Dans le setup(), initialisez le Serial Monitor, le Wi-Fi, le SPIFFS et le capteur MPU.
void setup() {
Serial.begin(115200);
initWiFi();
initSPIFFS();
initMPU();
Traiter les demandes
Lorsque l’ESP32 reçoit une requête sur l’URL root, nous souhaitons envoyer une réponse avec le contenu du fichier HTML (index.html) stocké dans SPIFFS.
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html", "text/html");
});
Le premier argument de la fonction send () est le système de fichiers où les fichiers sont enregistrés, dans ce cas, il est enregistré dans SPIFFS. Le deuxième argument est le chemin où se trouve le fichier. Le troisième argument fait référence au type de contenu (texte HTML).
Dans votre fichier HTML, vous référencez les fichiers style.css et script.js. Ainsi, lorsque le fichier HTML se charge sur votre navigateur, il fera une demande pour ces fichiers CSS et JavaScript. Ce sont des fichiers statiques enregistrés dans le même répertoire (SPIFFS). Ainsi, nous pouvons simplement ajouter la ligne suivante pour servir des fichiers statiques dans un répertoire à la demande de l’URL root. Il sert automatiquement les fichiers CSS et JavaScript.
server.serveStatic("/", SPIFFS, "/");
Nous devons également gérer ce qui se passe lorsque les boutons de réinitialisation sont enfoncés. Lorsque vous appuyez sur le bouton RESET POSITION, l’ESP32 reçoit une requête sur le chemin /reset. Lorsque cela se produit, nous mettons simplement les variables gyroX, gyroY et gyroZ à zéro pour restaurer la position initiale du capteur.
server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request){
gyroX=0;
gyroY=0;
gyroZ=0;
request->send(200, "text/plain", "OK");
});
Nous envoyons une réponse « OK » pour indiquer que la demande a réussi.
Nous suivons une procédure similaire pour les autres requêtes (boutons X, Y et Z).
server.on("/resetX", HTTP_GET, [](AsyncWebServerRequest *request){
gyroX=0;
request->send(200, "text/plain", "OK");
});
server.on("/resetY", HTTP_GET, [](AsyncWebServerRequest *request){
gyroY=0;
request->send(200, "text/plain", "OK");
});
server.on("/resetZ", HTTP_GET, [](AsyncWebServerRequest *request){
gyroZ=0;
request->send(200, "text/plain", "OK");
});
Les lignes suivantes configurent la source d’événement sur le serveur.
// Handle Web Server Events
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);
Enfin, démarrez le serveur.
server.begin();
loop() – Envoyer des événements
Dans la boucle (), nous enverrons des événements au client avec les nouvelles lectures de capteur.
Les lignes suivantes envoient les lectures du gyroscope sur l’événement gyro_readings toutes les 10 millisecondes (gyroDelay).
if ((millis() - lastTime) > gyroDelay) {
// Send Events to the Web Server with the Sensor Readings
events.send(getGyroReadings().c_str(),"gyro_readings",millis());
lastTime = millis();
}
Utilisez la méthode send() sur l’objet events et passez en argument le contenu que vous souhaitez envoyer et le nom de l’événement. Dans ce cas, nous souhaitons envoyer la chaîne JSON renvoyée par la fonction getGyroReadings(). La méthode send() accepte une variable de type char, nous devons donc utiliser la méthode c_str() pour convertir la variable. Le nom des événements est gyro_readings.
Nous suivons une procédure similaire pour les lectures de l’accéléromètre, mais nous utilisons un événement différent (accelerometer_readings) et un temps de retard différent (accelerometerDelay) :
if ((millis() - lastTimeAcc) > accelerometerDelay) {
// Send Events to the Web Server with the Sensor Readings
events.send(getAccReadings().c_str(),"accelerometer_readings",millis());
lastTimeAcc = millis();
}
Et enfin, pour les relevés de température :
if ((millis() - lastTimeTemperature) > temperatureDelay) {
// Send Events to the Web Server with the Sensor Readings
events.send(getTemperature().c_str(),"temperature_reading",millis());
lastTimeTemperature = millis();
}
Téléchargement de code et de fichiers
Après avoir inséré vos informations d’identification réseau, enregistrez le code. Accédez à Sketch > Show Sketch Folder et créez un dossier appelé data.

Dans ce dossier, vous devez enregistrer les fichiers HTML, CSS et JavaScript.
Ensuite, téléchargez le code sur votre carte ESP32. Assurez-vous d’avoir sélectionné la bonne carte et le bon port COM. Assurez-vous également que vous avez ajouté vos informations d’identification réseau au code.

Après avoir téléchargé le code, vous devez télécharger les fichiers. Accédez à Outils> ESP32 Data Sketch Upload et attendez que les fichiers soient téléchargés.

Lorsque tout est téléchargé avec succès, ouvrez le moniteur série à un débit en bauds de 115200. Appuyez sur le bouton ESP32 EN/RST, et il devrait imprimer l’adresse IP ESP32.

Manifestation
Ouvrez votre navigateur et tapez l’adresse IP ESP32. Vous devriez avoir accès à la page Web qui affiche les lectures du capteur.
Déplacez le capteur et voyez les lectures changer ainsi que l’objet 3D sur le navigateur.

Remarque : le capteur dérive un peu sur l’axe X, malgré quelques ajustements dans le code. Beaucoup de nos lecteurs ont déclaré que c’était normal pour ce type de MCU. Pour réduire la dérive, certains lecteurs ont suggéré d’utiliser un filtre complémentaire ou un filtre de kalman.
Conclusion
Le MPU-6050 est un accéléromètre, un gyroscope et un capteur de température sur un seul module. Dans ce didacticiel, vous avez appris à créer un serveur Web avec l’ESP32 pour afficher les lectures du capteur du capteur MPU-6050. Nous avons utilisé des événements envoyés par le serveur pour envoyer les lectures au client.
En utilisant le trois.js Bibliothèque JavaScript, nous avons construit une représentation 3D du capteur pour montrer sa position angulaire à partir des lectures du gyroscope. Le système n’est pas parfait, mais il donne une idée de l’orientation du capteur. Si quelqu’un de plus compétent sur ce sujet peut partager quelques conseils pour l’étalonnage du capteur, ce serait grandement apprécié.
Nous espérons que vous avez trouvé ce tutoriel utile.
En savoir plus sur l’ESP32 avec nos ressources :
Cette vidéo vous emmène dans l’histoire de Raspberry Pi :

-
DollaTek GY-521, MPU-6050 Module, Module de mpu 6050, 3 Axes capteur gyroscopique analogique 3 Axes Accéléromètre Module pour Arduino BricolageNom du produit: Axis Gyroscope + Module Accéléromètre; Puce: MPU-6050 Fit for: MCU; Nombre Pins: 5 Pin: 2.54mm / 0.1 "; Tension de travail: 3-5V PCB Board Taille: 20 x 15mm Net Weight : 4g