ESP32 Web Server with MPU-6050 Accelerometer and Gyroscope (3D object representation)

In this project we’ll build a web server with the ESP32 to display readings from the MPU-6050 accelerometer and gyroscope sensor. We’ll also create a 3D representation of the sensor orientation on the web browser. The readings are updated automatically using Server-Sent Events and the 3D representation is handled using a JavaScript library called three.js. The ESP32 board will be programmed using the Arduino core.

ESP32 Web Server MPU-6050 Accelerometer Gyroscope 3D object representation Arduino

To build the web server we’ll use the ESPAsyncWebServer library that provides an easy way to build an asynchronous web server and handle Server-Sent Events.

To learn more about Server-Sent Events, read: ESP32 Web Server using Server-Sent Events (Update Sensor Readings Automatically).

Watch the Video Demonstration

Watch the following video for a preview of the project we’ll build.

Project Overview

Before going straight to the project, it’s important to outline what our web server will do, so that it is easier to understand.

  • The web server displays the gyroscope values of the X, Y an Z axis;
  • The gyroscope values are updated on the web server every 10 milliseconds;
  • It displays the accelerometer values (X, Y, Z). These values are updated every 200 milliseconds;
  • The MPU-6050 sensor module also measures temperature, so we’ll also display the temperature value. The temperature is updated every second (1000 milliseconds);
  • All the readings are updated using Server-Sent Events;
  • There is a 3D representation of the sensor. The orientation of the 3D object changes accordingly to the sensor orientation. The current position of the sensor is calculated using the gyroscope values;
  • The 3D object is created using a JavaScript library called three.js;
  • There are four buttons to adjust the position of the 3D object:
    • RESET POSITION: sets angular position to zero on all axis;
    • X: sets the X angular position to zero;
    • Y: sets the Y angular position to zero;
    • Z: sets the Z angular position to zero;
MPU-6050 Accelerometer Gyroscope Web Server ESP32 How it Works

ESP32 Filesystem

To keep our project organized and make it easier to understand, we’ll create four different files to build the web server:

  • the Arduino code that handles the web server;
  • HTML file: to define the content of the web page;
  • CSS file: to style the web page;
  • JavaScript file: to program the behavior of the web page (handle web server responses, events and creating the 3D object).

The HTML, CSS and JavaScript files will be uploaded to the ESP32 LittleFS filesystem. To upload files to the ESP32 filesystem, we’ll use the LittleFS Uploader Plugin. Make sure you install it on your Arduino IDE:

If you’re using PlatformIO + VS Code, read this article to learn how to upload files to the ESP32 filesystem:

MPU-6050 Gyroscope and Accelerometer

The MPU-6050 is a module with a 3-axis accelerometer and a 3-axis gyroscope.

MPU6050 Module Accelerometer Gyroscope Temperature Sensor

The gyroscope measures rotational velocity (rad/s) – this is the change of the angular position over time along the X, Y and Z axis (roll, pitch and yaw). This allows us to determine the orientation of an object.

Roll Pitch Yaw Angles gyroscope

The accelerometer measures acceleration (rate of change of the velocity of an object). It senses static foces like gravity (9.8m/s2) or dynamic forces like vibrations or movement. The MPU-6050 measures acceleration over the X, Y an Z axis. Ideally, in a static object the acceleration over the Z axis is equal to the gravitational force, and it should be zero on the X and Y axis.

Using the values from the accelerometer, it is possible to calculate the roll and pitch angles using trigonometry, but it is not possible to calculate the yaw.

We can combine the information from both sensors to get accurate information about the sensor orientation.

Learn more about the MPU-6050 sensor: ESP32 with MPU-6050 Accelerometer, Gyroscope and Temperature Sensor.

Schematic Diagram – ESP32 with MPU-6050

For this project you need the following parts:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Wire the ESP32 to the MPU-6050 sensor as shown in the following schematic diagram: connect the SCL pin to GPIO 22 and the SDA pin to GPIO 21.

MPU6050 Accelerometer Gyroscope Wiring to ESP32 Schematic Diagram Circuit

Preparing Arduino IDE

We’ll program the ESP32 board using Arduino IDE. So, make sure you have the ESP32 add-on installed. Follow the next tutorial:

If you prefer using VSCode + PlatformIO, follow the next tutorial instead:

Installing MPU-6050 Libraries

There are different ways to get readings from the sensor. In this tutorial, we’ll use the Adafruit MPU6050 library. To use this library you also need to install the Adafruit Unified Sensor library and the Adafruit Bus IO Library.

Open your Arduino IDE and go to Sketch > Include Library > Manage Libraries. The Library Manager should open.

Type “adafruit mpu6050” on the search box and install the library.

Install Adafruit MPU6050 Library Arduino IDE

Then, search for “Adafruit Unified Sensor”. Scroll all the way down to find the library and install it.

Install Adafruit Unified Library Arduino IDE

Finally, search for “Adafruit Bus IO” and install it.

Install Adafruit Bus IO Library Arduino IDE

Installing Async Web Server Libraries

To build the web server we’ll use the ESPAsyncWebServer library. This library needs the AsyncTCP library to work properly. Click the links below to download the libraries.

These libraries aren’t available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation Libraries folder. Alternatively, in your Arduino IDE, you can go to Sketch Include Library > Add .zip Library and select the libraries you’ve just downloaded.

Installing Arduino_JSON library

In this example, we’ll send the sensor readings to the browser in JSON format. To make it easier to handle JSON variables, we’ll use the Arduino_JSON library.

You can install this library in the Arduino IDE Library Manager. Just go to Sketch Include Library > Manage Libraries and search for the library name as follows: Arduino_JSON.

Install Arduino_JSON Library Arduino IDE

If you’re using VS Code with PlatformIO, copy the following lines to the platformio.ini file to include all the necessary libraries, set the serial monitor baud rate to 115200 and set the default filesystem to LittleFS.

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
monitor_speed = 115200
board_build.filesystem = littlefs

Filesystem Uploader Plugin

To follow this tutorial you should have the ESP32 Filesystem Uploader plugin installed in your Arduino IDE. If you don’t, follow the next tutorial to install it:

If you’re using VS Code + PlatformIO, follow the next tutorial to learn how to upload files to the ESP32 filesystem:

Organizing Your Files

To build the web server you need four different files. The Arduino sketch, the HTML file, the CSS file and the JavaScript file. The HTML, CSS and JavaScript files should be saved inside a folder called data inside the Arduino sketch folder, as shown below:

Organizing Your ESP Project Files HTML CSS JavaScript Arduino files

You can download all project files:

Creating the HTML File

Create an index.html file with the following content or download all the project files.

<!--
  Rui Santos
  Complete project details at https://RandomNerdTutorials.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="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> &deg;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="script.js"></script>
</body>
</html>

View raw code

Head

The <head> and </head> tags mark the start and end of the head. The head is where you insert data about the HTML document that is not directly visible to the end user, but adds functionalities to the web page – this is called metadata.

The next line gives a title to the web page. In this case, it is set to ESP Web Server. You can change it if you want. The title is exactly what it sounds like: the title of your document, which shows up in your web browser’s title bar.

<title>ESP Web Server</title>

The following meta tag makes your web page responsive. A responsive web design will automatically adjust for different screen sizes and viewports.

<meta name="viewport" content="width=device-width, initial-scale=1">

We use the following meta tag because we won’t serve a favicon for our web page in this project.

<link rel="icon" href="data:,">

The styles to style the web page are on a separated file called style.css file. So, we must reference the CSS file on the HTML file as follows.

<link rel="stylesheet" type="text/css" href="style.css">

Include the Font Awesome website styles to include icons in the web page like the gyroscope icon.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">

Finally, we need to include the three.js library to create the 3D representation of the sensor.

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/107/three.min.js"></script>

Body

The <body> and </body> tags mark the start and end of the body. Everything that goes inside those tags is the visible page content.

Top Bar

There’s a top bar with a heading in the web page. It is a heading 1 and it is placed inside a <div> tag with the class name topnav. Placing your HTML elements between <div> tags, makes them easy to style using CSS.

<div class="topnav">
  <h1><i class="far fa-compass"></i> MPU6050 <i class="far fa-compass"></i></h1>
</div>

Content Grid

All the other content is placed inside a <div> tag called content.

<div class="content">

We use CSS grid layout to display the readings on different aligned boxes (card). Each box corresponds to a grid cell. Grid cells need to be inside a grid container, so the boxes need to be placed inside another <div> tag. This new tag has the classname cards.

<div class="cards">

To learn more about CSS grid layout, we recommend this article: A Complete Guide to Grid. Here’s the card for the gyroscope readings:

<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>

The card has a title with the name of the card:

<p class="card-title">GYROSCOPE</p>

And three paragraphs to display the gyroscope values on the X, Y and Z axis.

<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>

In each paragraph there’s a <span> tag with a unique id. This is needed so that we can insert the readings on the right place later using JavaScript. Here’s the ids used:

  • gyroX for the gyroscope X reading;
  • gyroY for the gyroscope Y reading;
  • gyroZ for the gyroscope Z reading.

The card to display the accelerometer readings is similar, but with different unique ids for each reading:

<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>

Here’s the ids for the accelerometer readings:

  • accX for the accelerometer X reading;
  • accY for the accelerometer Y reading;
  • accZ for the accelerometer Z reading.

Finally, the following lines display the card for the temperature and the reset buttons.

<div class="card">
  <p class="card-title">TEMPERATURE</p>
  <p><span class="reading"><span id="temp"></span> &deg;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>

The unique id for the temperature reading is temp.

Then there are four different buttons that when clicked will call the resetPosition() JavaScript function later on. This function will be responsible for sending a request to the ESP32 informing that we want to reset the position, whether its on all the axis or on an individual axis. Each button has a unique id, so that we know which button was clicked:

  • reset: to reset the position in all axis;
  • resetX: to reset the position on the X axis;
  • resetY: to reset the position on the Y axis;
  • resetZ: to reset the position on the Z axis.

3D Representation

We need to create a section to display the 3D representation.

<div class="cube-content">
  <div id="3Dcube"></div>
</div>

The 3D object will be rendered on the <div> with the 3Dcube id.

Reference the JavaScript File

Finally, because we’ll use an external JavaScript file with all the functions to handle the HTML elements and create the 3D animation, we need to reference that file (script.js) as follows:

<script src="script.js"></script>

Creating the CSS File

Create a file called style.css with the following content or download all the project files.

This file is responsible for styling the web page.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.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;
}

View raw code

We won’t explain how the CSS for this project works because it is not relevant for the goal of this project.

Creating the JavaScript File

Create a file called script.js with the following content or download all the project files.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.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();
}

View raw code

Creating a 3D Object

The init3D() function creates the 3D object. To actually be able to display anything with three.js, we need three things: scene, camera and renderer, so that we can render the scene with camera.

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);

To create the 3D object, we need a BoxGeometry. In the box geometry you can set the dimensions of your object. We created the object with the right proportions to resemble the MPU-6050 shape.

const geometry = new THREE.BoxGeometry(5, 1, 4);

Besides the geometry, we also need a material to color the object. There are different ways to color the object. We’ve chosen three different colors for the faces.

// 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);

Finally, create the 3D object, add it to the scene and adjust the camera.

cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
renderer.render(scene, camera);

We recommend taking a look at this quick three.js tutorial to better understand how it works: Getting Started with three.js – Creating a Scene.

To be able to resize the object when the web browser window changes size, we need to call the onWindowResize() function when the event resize occurs.

// 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);

Call the init3D() function to actual create the 3D representation.

init3D();

Events (SSE)

The ESP32 sends new sensor readings periodically as events to the client (browser). We need to handle what happens when the client receives those events.

In this example, we want to place the readings on the corresponding HTML elements and change the 3D object orientation accordingly.

Create a new EventSource object and specify the URL of the page sending the updates. In our case, it /events.

if (!!window.EventSource) {
  var source = new EventSource('/events');

Once you’ve instantiated an event source, you can start listening for messages from the server with addEventListener().

These are the default event listeners, as shown here in the 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);

When new gyroscope readings are available, the ESP32 sends an event gyro_readings to the client. We need to add an event listener for that specific event.

source.addEventListener('gyro_readings', function(e) {

The gyroscope readings are a String in JSON format. For example:

{
  "gyroX" : "0.09",
  "gyroY" : "0.05",
  "gyroZ": "0.04"
}

JavaScript has a built-in function to convert a string, written in JSON format, into native JavaScript objects: JSON.parse().

var obj = JSON.parse(e.data);

The obj variable contains the sensor readings in native JavaScript format. Then, we can access the readings as follows:

  • gyroscope X reading: obj.gyroX;
  • gyroscope Y reading: obj.gyroY;
  • gyroscope Z reading: obj.gyroZ;

The following lines put the received data into the corresponding HTML elements on the web page.

document.getElementById("gyroX").innerHTML = obj.gyroX;
document.getElementById("gyroY").innerHTML = obj.gyroY;
document.getElementById("gyroZ").innerHTML = obj.gyroZ;

Finally, we need to change the cube rotation according to the received readings, as follows:

cube.rotation.x = obj.gyroY;
cube.rotation.z = obj.gyroX;
cube.rotation.y = obj.gyroZ;
renderer.render(scene, camera);

Note: in our case, the axis are switched as shown previously (rotation X –> gyroY, rotation Z –> gyroX, rotation Y –> gyroZ). You might need to change this depending on your sensor orientation.

For the accelerometer_readings and temperature events, we simply display the data on the HTML page.

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);

Finally, we need to create the resetPosition() function. This function will be called by the reset buttons.

function resetPosition(element){
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/"+element.id, true);
  console.log(element.id);
  xhr.send();
}

This function simply sends an HTTP request to the server on a different URL depending on the button that was pressed (element.id).

xhr.open("GET", "/"+element.id, true);
  • RESET POSITION button –> request: /reset
  • X button –> request: /resetX
  • Y button –> request: /resetY
  • Z button –> request: /resetZ

Arduino Sketch

Finally, let’s configure the server (ESP32). Copy the following code to the Arduino IDE or download all the project files.

/*********
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.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 "LittleFS.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 initLittleFS() {
  if (!LittleFS.begin()) {
    Serial.println("An error has occurred while mounting LittleFS");
  }
  Serial.println("LittleFS 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();
  initLittleFS();
  initMPU();

  // Handle Web Server
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(LittleFS, "/index.html", "text/html");
  });

  server.serveStatic("/", LittleFS, "/");

  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();
  }
}

View raw code

Before uploading the code, make sure you insert your network credentials on the following variables:

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

How the Code Works

Continue reading to learn how the code works or proceed to the next section.

Libraries

First, import all the required libraries for this project:

#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 "LittleFS.h"

Network Credentials

Insert your network credentials in the following variables:

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

AsyncWebServer and AsyncEventSource

Create an AsyncWebServer object on port 80.

AsyncWebServer server(80);

The following line creates a new event source on /events.

AsyncEventSource events("/events");

Declaring Variables

The readings variable is a JSON variable to hold the sensor readings in JSON format.

JSONVar readings;

In this project, we’ll send the gyroscope readings every 10 milliseconds, the accelerometer readings every 200 milliseconds, and the temperature readings every second. So, we need to create auxiliary timer variables for each reading. You can change the delay times if you want.

// 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

Create an Adafruit_MPU5060 object called mpu, create events for the sensor readings and variables to hold the readings.

// Create a sensor object
Adafruit_MPU6050 mpu;

sensors_event_t a, g, temp;

float gyroX, gyroY, gyroZ;
float accX, accY, accZ;
float temperature;

Gyroscope Offset

Adjust he gyroscope sensor offset on all axis.

//Gyroscope sensor deviation
float gyroXerror = 0.07;
float gyroYerror = 0.03;
float gyroZerror = 0.01;

To get the sensor offset, go to File > Examples > Adafruit MPU6050 > basic_readings. With the sensor in a static position, check the gyroscope X, Y, and Z values. Then, add those values to the gyroXerror, gyroYerror and gyroZerror variables.

Initialize MPU-6050

The initMPU() function initializes te MPU-6050 sensor.

// Init MPU6050
void initMPU(){
  if (!mpu.begin()) {
    Serial.println("Failed to find MPU6050 chip");
    while (1) {
      delay(10);
    }
  }
  Serial.println("MPU6050 Found!");
}

Initialize LittleFS

The initLittleFS() function initializes the ESP32 filesystem so that we’re able to get access to the files saved on LittleFS (index.html, style.css and script.js).

void initLittleFS() {
  if (!LittleFS.begin()) {
    Serial.println("An error has occurred while mounting LittleFS");
  }
  Serial.println("LittleFSmounted successfully");
}

Initialize Wi-Fi

The initWiFi() function connects the ESP32 to your local network.

// 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());
}

Get Gyroscope Readings

The getGyroReadings() function gets new gyroscope readings and returns the current angular orientation on the X, Y an Z axis as a JSON string.

The gyroscope returns the current angular velocity. The angular velocity is measured in rad/s. To determine the current position of an object, we need to multiply the angular velocity by the elapsed time (10 milliseconds) and add it to the previous position.

current angle (rad) = last angle (rad) + angular velocity (rad/s) * time(s)

The gyroX_temp variable temporarily holds the current gyroscope X value.

float gyroX_temp = g.gyro.x;

To prevent small oscillations of the sensor (see Gyroscope Offset), we first check if the values from the sensor are greater than the offset.

if(abs(gyroX_temp) > gyroXerror) {

If the current value is greater than the offset value, we consider that we have a valid reading. So, we can apply the previous formula to get the current sensor’s angular position (gyroX).

gyroX += gyroX_temp / 50.0;

Note: theoretically, we should multiply the current angular velocity by the elapsed time (10 milliseconds = 0.01 seconds (gyroDelay)) – or divide by 100. However, after some experiments, we found out that the sensor responds better if we divide by 50.0 instead. Your sensor may be different and you may need to adjust the value.

We follow a similar procedure to get the Y and Z values.

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;
}

Finally, we concatenate the readings in a JSON variable (readings) and return a JSON string (jsonString).

readings["gyroX"] = String(gyroX);
readings["gyroY"] = String(gyroY);
readings["gyroZ"] = String(gyroZ);

String jsonString = JSON.stringify(readings);
return jsonString;

Get Accelerometer Readings

The getAccReadings() function returns the accelerometer readings.

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;
}

Get Temperature Readings

The getTemperature() function returns the current temperature reading.

String getTemperature(){
  mpu.getEvent(&a, &g, &temp);
  temperature = temp.temperature;
  return String(temperature);
}

setup()

In the setup(), initialize the Serial Monitor, Wi-Fi, LittleFS and the MPU sensor.

void setup() {
  Serial.begin(115200);
  initWiFi();
  initLittleFS();
  initMPU();

Handle Requests

When the ESP32 receives a request on the root URL, we want to send a response with the HTML file (index.html) content that is stored in LittleFS.

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(LittleFS, "/index.html", "text/html");
});

The first argument of the send() function is the filesystem where the files are saved, in this case it is saved in LittleFS. The second argument is the path where the file is located. The third argument refers to the content type (HTML text).

In your HTML file, you reference the style.css and script.js files. So, when the HTML file loads on your browser, it will make a request for those CSS and JavaScript files. These are static files saved on the same directory (LittleFS). So, we can simply add the following line to serve static files in a directory when requested by the root URL. It serves the CSS and JavaScript files automatically.

server.serveStatic("/", LittleFS, "/");

We also need to handle what happens when the reset buttons are pressed. When you press the RESET POSITION button, the ESP32 receives a request on the /reset path. When that happens, we simply set the gyroX, gyroY and gyroZ variables to zero to restore the sensor initial position.

server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request){
  gyroX=0;
  gyroY=0;
  gyroZ=0;
  request->send(200, "text/plain", "OK");
});

We send an “OK” response to indicate the request succeeded.

We follow a similar procedure for the other requests (X, Y and Z buttons).

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");
});

The following lines setup the event source on the server.

// 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);

Finally, start the server.

server.begin();

loop() – Send Events

In the loop(), we’ll send events to the client with the new sensor readings.

The following lines send the gyroscope readings on the gyro_readings event every 10 milliseconds (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();
}

Use the send() method on the events object and pass as argument the content you want to send and the name of the event. In this case, we want to send the JSON string returned by the getGyroReadings() function. The send() method accepts a variable of type char, so we need to use the c_str() method to convert the variable. The name of the events is gyro_readings.

We follow a similar procedure for the accelerometer readings, but we use a different event (accelerometer_readings) and a different delay time (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();
}

And finally, for the temperature readings:

if ((millis() - lastTimeTemperature) > temperatureDelay) {
  // Send Events to the Web Server with the Sensor Readings
  events.send(getTemperature().c_str(),"temperature_reading",millis());
  lastTimeTemperature = millis();
}

Uploading Code and Files

After inserting your network credentials, save the code. Go to Sketch > Show Sketch Folder, and create a folder called data.

Arduino IDE Open Sketch Folder to create data folder

Inside that folder you should save the HTML, CSS and JavaScript files.

Then, upload the code to your ESP32 board. Make sure you have the right board and COM port selected. Also, make sure you’ve added your networks credentials to the code.

Arduino IDE 2 Upload Button

After uploading the code, you need to upload the files. In the Arduino IDE, press [Ctrl] + [Shift] + [P] on Windows or [] + [Shift] + [P] on MacOS to open the command palette. Search for the Upload LittleFS to Pico/ESP8266/ESP32 command and click on it.

Upload LittleFS to Pico ESP8266 ESP32 Arduino IDE

If you don’t have this option is because you didn’t install the filesystem uploader plugin. Check this tutorial.

Important: make sure the Serial Monitor is closed before uploading to the filesystem. Otherwise, the upload will fail.

When everything is successfully uploaded, open the Serial Monitor at a baud rate of 115200. Press the ESP32 EN/RST button, and it should print the ESP32 IP address.

ESP32 IP Address printed in the Arduino IDE Serial Monitor

Demonstration

Open your browser and type the ESP32 IP address. You should get access to the web page that shows the sensor readings.

Move the sensor and see the readings changing as well as the 3D object on the browser.

ESP32 ESP8266 NodeMCU Web Server Demonstration

Note: the sensor drifts a bit on the X axis, despite some adjustments in the code. Many of our readers commented that that’s normal for this kind of MCUs. To reduce the drifting, some readers suggested using a complementary filter or a kalman filter.

Wrapping Up

The MPU-6050 is an accelerometer, gyroscope and temperature sensor on a single module. In this tutorial you’ve learned how to build a web server with the ESP32 to display sensor readings from the MPU-6050 sensor. We used server-sent events to send the readings to the client.

Using the three.js JavaScript library we’ve built a 3D representation of the sensor to show its angular position from the gyroscope readings. The system is not perfect, but it gives an idea of the sensor orientation. If someone more knowledgeable about this topic can share some tips for the sensor calibration it would be greatly appreciated.

We hope you’ve found this tutorial useful.

Learn more about the ESP32 with our resources:



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Recommended Resources

Build a Home Automation System from Scratch » With Raspberry Pi, ESP8266, Arduino, and Node-RED.

Home Automation using ESP8266 eBook and video course » Build IoT and home automation projects.

Arduino Step-by-Step Projects » Build 25 Arduino projects with our course, even with no prior experience!

What to Read Next…


Enjoyed this project? Stay updated by subscribing our newsletter!

135 thoughts on “ESP32 Web Server with MPU-6050 Accelerometer and Gyroscope (3D object representation)”

  1. Hi.
    Very interesting project,thank You again. How could You know
    this is just what I was growing interested in 😉
    For me the Z–axis drifts about one rad/sec in negative direction.
    Can it be some timing problem,considering there is a lot of processing going on?

    -theFinn

    Reply
        • Hi.
          You may need to adjust the offset values, as well as the values on these lines:
          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;
          }
          Regards,
          Sara

          Reply
  2. Hi All,
    this tuto is, as always, great, very clear.
    I was linking this with VR sensors like Oculus.
    If we know accelerations, we are able to calculate x,y,z positions.
    Have you already tried ? how accurate it may be ?
    Probably more accurate with esp32 than Arduino.
    It may lead to interesting stuff 🙂
    Regards,
    Bruno

    Reply
  3. Hi,

    This is indeed a real meaty project with a number of very intersting aspects.

    However I am into MicroPython and not Arduino.

    As with your other projects are you doing a MicroPython version of this?

    Regards, Sandy

    Reply
  4. Great article but I solved the drift problem by using the acceleration results to position the box on the screen,

    document.getElementById(“accX”).innerHTML = obj.accX;
    document.getElementById(“accY”).innerHTML = obj.accY;
    document.getElementById(“accZ”).innerHTML = obj.accZ;

    // Change cube rotation after receiving the readings
    yy = obj.accY;
    xx = obj.accX;
    var zz = obj.accZ;
    var xxx = (xx - xxs) * 1.57 / 10;
    var yyy = (yy - yys) * 1.57 / 10;
    zz = 0;

    console.log("xxx:" + xxx);
    console.log("yyy:" + yyy);

    cube.rotation.x = yyy;
    cube.rotation.z = xxx;
    cube.rotation.y = zz;
    renderer.render(scene, camera);

    and adding a couple of store variables in Reset

    function resetPosition(element) {

    //Store current position
    xxs = xx;
    yys = yy;

    Reply
    • Hi Alistair, I attempted your suggestions to correct drift, however unsuccessful. If I could get past this glitch, it’d be near perfect for a remote level project that I’m attempting. I wonder if you could post the entire sketch (or we could communicate directly).

      Much appreciated
      Mike
      Ont, Canada

      Reply
  5. You should find gyrox/y/z biases and subtract them directly after reading to minimize gyro drift. They can be + or –

    Your best project of many great ones, with many things i had wanted to know about presentation.

    Reply
  6. Hi!

    Nice project but… No way to make it run under Platformio with ESP8266 Nodemcu V3.

    There’s no way for the libraries to load nor get the dependencies.
    Any idea?

    Reply
    • Hi.
      This project is not compatible with the ESP8266. It lags a lot! I think the ESP8266 doesn’t have enough processing power for this project as it sends events every 10 milliseconds.
      Regards,
      Sara

      Reply
  7. I’m very satisfied , good result and test of different technolgy , SPIFF, Gyro z,y,z , json, html . Many compilments to the Random Nerd Tutorial group. Gianni

    Reply
  8. Hi i check my device with other sketch and work well but when i load this sketch this error appears in the serial monitor “Failed to find MPU6050 chip”.i use i2c-scaner program and showed me it is well .i have this problem with basic_reading program also. i think this problem occurred from i2c address in the program . please help me for solving this problem.

    Reply
  9. Hi Sara!
    when i use sketch program with below i2c address i do’nt have any problem and all is ok
    “const int MPU_addr=0x68; // I2C address of the MPU-6050″
    but when i upload program with ” #include <Adafruit_MPU6050.h>” and dont define i2c directly in program lines this error appears in the serial monitor “Failed to find MPU6050 chip” if it is possible please give exact cod for solving this problem. thanks in advance for your help .

    Reply
  10. Hello,

    This is a very great and interesting project. I just wanted to know how could I get Gyroscope readings in Degrees and not degrees per second?

    Thank you.

    Reply
    • You need to take at least two measurements in deg/s and then multiply by the elapsed time. you’ll have the measurement in degrees.
      Regards,
      Sara

      Reply
  11. Hi Sara, awesome project!.. I cant make it fully work, got this in the arduino serial:

    Connecting to WiFi….
    192.168.100.38
    E (1219) SPIFFS: mount failed, -10025
    An error has occurred while mounting SPIFFS
    SPIFFS mounted successfully
    MPU6050 Found!

    But when I paste the ip address(192.168.100.38) on the chrome web browser, I got : HTTP ERROR 404
    Where may I start to troubleshoot this fail?. Thanks!

    Reply
  12. Hi! Good project. I’m trying to transmit the data via Wifi, but unlikely the web page doesn’t appear over the browser. However the serial monitor gives messages that wifi and Spiffs are working. Indipendently the library for MPU6050 works too, having loaded the code on a ESP32. The Ip address 192.168.1.174 however seems wrong. What error I did?
    Thank you very much for the assistance.

    Reply
    • Hi.
      What do you see on the web browser?
      Make sure that you uploaded the files to the filesystem—that’s probably your issue.
      Regards,
      Sara

      Reply
  13. Hello,

    Awesome project! I just wanted to know how could I get the tilt angle in (degrees) and not the rotational velocity (radians/sec).

    Thank you.

    Reply
  14. I like this project and have had other web based ESP32 ones of yours working. However, this one runs ok on the ESP32 and connects to the Net, but I get no web page when I use the IP address it returns. I was wondering if this is due to the fact that our network is only within the Uni for devices only and has no external link to the WWW? I noticed it has a URL within the code.
    https://cdnjs.cloudflare.com/ajax/libs/three.js/107/three.min.js
    Perhaps it cannot be run locally.

    Reply
    • Hi James.
      Double-check you uploaded the needed HTML, CSS and JavaScript files using the filesystem uploader plugin.
      Regards,
      Sara

      Reply
  15. Hello,

    Great project, but the x and y axis are drifting. Is there any solution for this? I adjusted the offset values, as well as the values on these lines but the axis are still drifting:

    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;

    Reply
  16. Hey guys is there any way to make this work for an sparkfun MPU-9250 (and if yes which parts of the code do i have to replace)?

    Reply
    • Hi Chris.
      First, you need to know how to get measurements with your sensor.
      If it is a Sparkfun product, usually they have a tutorial for their parts on their website.
      Then, you just need to change the sensor initialization and the getGyroReadings() function.
      Everything else should work as it is.
      Regards,
      Sara

      Reply
  17. Hello, I have an ESP32 lite device. I couldn’t find the GPIO22 and GPIO 21 pin connections. I’ve been looking for the I2C pins but couldn’t find it. Failed to find MPU6050 is displayed on the serial port screen. I would be very happy if you could help me with this subject.

    Reply
  18. Hi
    A great project I have been experimenting with the ESP32 and GY521. I have noticed that with enabled has an effect on the Z axis readings. Others have noted that with a 90 degree rotation the reading is only around the 75 degrees. If I run with out the wifi enabled then I can calibrate the offsets so that the sensor will read 90 degrees, however with wfi enable the it returns 75 degrees with the same calibration settings. Calibrating with wifi enabled makes no difference.
    I could try moving the sensor some distance away from the ESP32 to see if this makes a difference. Have you or anyone else seen this. My short term solution is to multiply by 1.2

    Reply
  19. Hi,

    I am using this board LOLIN32 w/ battery holder (link attached)
    When I flash your code: the compilation passes but I get an error when the code is flashing to the board.

    it says: A fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header

    amazon.com/Wal-front-Bluetooth-Transceiver-Development/dp/B07HGS5JBP?SubscriptionId=AKIAJQUMBT74S4ZKCBTA&tag=makeradvisor-20&linkCode=xm2&camp=2025&creative=165953&creativeASIN=B07HGS5JBP

    Reply
    • Hi
      After uploading the code, when you start seeing a lot of dots on the debugging window, press and hold for about a second the BOOT button.
      Regards,
      Sara

      Reply
    • Hi.
      Make sure that you’ve uploaded all the provided files to the board filesystem.
      See the section “Uploading Code and Files”.
      Regards,
      Sara

      Reply
  20. Hi everyone,

    I’m really glad, that this project is very complete and explained in detail!

    Thank you so much!

    Reply
  21. Dear Sara, Thank you for the good post.
    The Z-axis is moving in the direction of “-0.01rad”, is there any solution yet?

    Reply
  22. Hi thanks for this wonderful project, i have a question,
    is there a any possibilty to add accelometer reading saving function like a button after we click this button automaticly saving 3 second reading to txt file?

    thanks for advance

    Reply
  23. Hi sara , is it possible to change from radian to degree? and how? i really need it for my project assignment, it would be nice if you can help me

    Reply
  24. hola, me ayudan?
    el progranma se me queda dando puntos uno tras otro cundo trata de conectarse a la wifi?como lo soluciono?
    estoy tratando de conectarme a un punto de acceso de un movil.Puede ser?
    Ayuda!

    Reply
  25. Hi, very interesting and nice project. Can I ask what should I do if my Web Page isn’t loading? i get the error “IP ADRESS refused to connect.”

    Reply
  26. Hola; una pregunta! Por qué para acceder a la servidor web necesito estar conectado a internet?? No se supone q es un servidor local. Alguien que me pueda responder esa pregunta!

    Reply
      • Hi
        But is it theoretically possible to build this example as in the tutorial “ESP32 Access Point”? So you could use the ESP32 as an Accespoint instead of the web server?

        Reply
  27. Hi
    I tried first time with a set of esp32 and mpu6050, the project work nicely done.
    But after that i tried with another set of esp32 and mpu6050(different esp32 and mpu), but when i run the code it said mpu6050 chip not found. How can i solve it? Tks.

    Reply
  28. I have implemented this project with PlatformIO in VScode and it is working fine, except for this message “ERROR: Too many messages queued”. After some minutes, it reboots.

    I changed gyroDelay to 50ms, the messages continue, but it stopped rebooting.

    Is there any advice to return gyroDelay to 10ms and keep it from rebooting?

    Moreover, what about some advice to get rid of these messages?

    Reply
  29. hello, I want to use esp32 for controlling antenna rotator using webpage; by calculating the position of a satellite and give the position in terms of steps for the stepper motors on the antenna rotator,,,,,, I want to use sgp4 to calculate to position of the satellite that I want to track , can you help me on this?

    Reply
    • Hi.
      Maybe your network credentials are not correct.
      Or the board can’t get access to Wi-Fi signal.
      Regards,
      Sara

      Reply
  30. I have an ESP32 WROOM 32 and MPU6050.
    I’ve succesfully compiled and uploaded the sketch and webserver files.
    Everything works BUT…weird movements 🙁
    Every time I reset, the object’s position continues to tilt and rotate without even moving the circuit. I tried to calibrate using the adafruit example too, but it keeps spinning every time. What can I do?
    Thanks
    P.S. I’ve also changed the mpu6050 (i own 3 of them) but with no luck

    Reply
    • Solved with this:
      float gyroXerror = 1.0;
      float gyroYerror = 0.02;
      float gyroZerror = 1.0;
      It’s a bit glitchy but it works

      Reply
  31. Dear Sara and Rui,
    Thank you for this nice program, this is my experience. The sketch works great My esp 32 connects to wifi, I receive the address, 6050 is found and SPIFFS mounted (as my serial wrote).
    But once I open the web address nothing happens. My Arduino IDE is the 2.0.3 and while I have found the “show sketch folder” under “sketch menu”, with the data folder and the 3 other files (index script and style), I couldn’ t find the “Esp32 sketch data upload” under the tools menu, it doens’t exist! Where I was wrong?

    Reply
  32. Just back here for tell you that i have obtained much better results with a BNO055. It cost a little more than mpu but has a builtin algorithm called Fusion that gives you directly stable pitch/roll/yaw values and prevent headaches caused by drift/derive errors and complex filters integrations.
    Thanks for the project. It helped me a lot during tests.

    Reply
    • Hi, Monisha Gowri S
      I am designing a circuit that combines esp32 with mpu6050 using battery 5V to feed the whole circuit. Can I refer to the project you did? I am a newbie, I have many problems with the stability of battery power and connection.
      I’m looking forward hearing from you.
      Regards./

      Reply
  33. Hello Sara,
    I tried your code with Arduino IDE and Platformio.

    Here is the result after uploading:

    rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
    configsip: 0, SPIWP:0xee
    clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
    mode:DIO, clock div:2
    load:0x3fff0030,len:1184
    load:0x40078000,len:13192
    load:0x40080400,len:3028
    entry 0x400805e4

    Connecting to WiFi….
    192.168.1.18
    SPIFFS mounted successfully

    MPU6050 Found!

    I tried to connect to 192.168.1.18 with different browsers.
    The result was :the site is forbidden or nothing.
    Do you have idea what to try?

    Regards

    Reply
  34. Good afternoon.
    First of all, thank you very much for this great article as always.
    I have loaded it into an ESP32 wroom and it works perfectly with the drift in my case of the z axis but the rest very well.
    I have tried to load the same sw on Xiao ESP32C3 and at the compilation moment it gives me the following error
    Error compiling for the XIAO_ESP32C3 card.
    Does anyone know the cause?
    Thank you very much

    Reply
  35. Hello, good, because when you change all the configuration on the website in the gyroscope and accelerometer values, it gives me a value of 2 in all the fields, do you know how I can solve it?

    Reply
  36. Hey great tutorial! Love all your guys work, and thinks it great how informative it is for beginners. Learned from this a few months back, but needed to get the reference for accreditation, and in the middle of googling it, found the tutorial taken word for word on another site – just with slightly different colors and pictures – but no credit or anything given. Here’s the link to the guy’s site if you guys haven’t been made aware of this yet.
    (REMOVED URL)
    Thanks again for all you guys do for the community!!

    Reply
    • Hi.
      Yes. I know.
      Not only this post, but almost all of our posts are copied into their website.
      Unfortunately, there is nothing we can do about it.
      It’s very frustrating and so unfair because they get half of the traffic by just copying all our articles and just changing a few words.
      Regards,
      Sara

      R

      Reply
  37. Hi. great work on this project. How can I edit the web page so it just plots a graph of the x, y, z, acceleration values and one of the temperature? thanks.

    Reply
  38. Thank you for all the hard work! I have learned a ton here!
    Not sure if this has been covered.
    Does anyone know how I could Freeze the Web Page and throw an alert up if the Gyro was moved by some many degrees? Kind of like a Movement monitor for security. I would assume it could be accomplished in the JS? Any Ideas or help would be very much appreciated.

    Regards
    Brian

    Reply
  39. have found multiple error trying to build it on arduino ide 2.3.2

    /Arduino/libraries/ESPAsyncWebServer/src/WebAuthentication.cpp: In function ‘bool getMD5(uint8_t*, uint16_t, char*)’:
    /Arduino/libraries/ESPAsyncWebServer/src/WebAuthentication.cpp:80:3: error: ‘mbedtls_md5_starts_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_starts’?
    80 | mbedtls_md5_starts_ret(&_ctx);

    have tried downloading the .zip for ESPAsyncWebServer or using the ESPAsyncWebServer that appear on library manager

    Reply
  40. hello there first of all great program but I have a question is there a way I could change the box into another shape as I’m currently working on a project which requires me to simulate the motion of a rocket in a similar way as done by you

    Reply
    • Hi.
      Close all serial monitor connections before uploading the SPIFFS image.
      Additionally, you may need to press the ESP32 boot button when you start seeing a lof of dots on the debuggin window.
      Regards,
      Sara

      Reply
  41. hola, me sale un problema con el SPIFSS, y cuando coloco la dirección ip en mi navegador, no me aparece nada, estoy usando la version 2.2.3.
    Saludos

    Reply
    • Suitable for spifts above arduino 2.0:https://github.com/espx-cz/arduino-spiffs-upload
      Remember to click “upload” after completing “spifts upload”

      Reply
  42. Hello, I have the following problem, I get the IP adress from the serial monitor, but when I go to that IP adress in my browser I get a “You can’t access this web site” message. What should I do? or What could be the problem?

    Reply
    • Hi.
      Did you upload the files to the filesystem?
      If you open the javascript console, which errors do you get?
      Regards,
      Sara

      Reply
      • Thanks, I already solved the problem, my pc was’t conected to the same router that provided the wifi network for the esp32

        Reply
  43. hello ,
    i want convert this project into Access point method ,im try to do but isnt work it,can you share me code for this

    Reply
  44. “Connecting to WiFi……192……..LittleFS mounted successfully…MPU6050 Found!”

    then when I type the ip address, “This site can’t be reached”
    hellpppppppp

    Reply
    • Make sure you are on the same network that the ESP32.
      Also make sure that you actually uploaded the codes to the board, which is probably your problem.
      Regards.
      Sara

      Reply
  45. Hey,

    Really cool tutorial!

    I was wondering if you knew how I could change the 3D object to display a CAD file that I have created to move with the gyroscope readings for a different application?

    Reply

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.