ESP32 Web Server với gia tốc kế và con quay hồi chuyển MPU 6050 (hình vẽ 3D)

Trong dự án này, chúng ta sẽ cùng làm 1 ứng dụng về Web Server với ESP32, cụ thể sẽ hiển thị thông tin đọc được từ MPU 6050 (gia tốc kế và con quay hồi chuyển). Chúng ta cũng sẽ tạo ra 1 hình vẽ 3D minh họa trên Web.

Các kết quả đọc từ MPU6050 sẽ được cập nhật tự động bằng Server-Sent Events và hình vẽ 3D được xây dựng trên thư viện JavaScript có tên là three.js.

Để xây dựng ESP32 Web Server, chúng ta sẽ sử dụng thư viện ESPAsyncWebServer để tạo ra các máy chủ Web không đồng bộ, và xử lý các sự kiện do Server gửi.

Tổng quan dự án với MPU 6050

Đầu tiên, mình sẽ giải thích về cách dự án hoạt động của máy chủ web, để bạn dễ hiểu hơn:

Tổng quan dự án ESP32 Web Server với MPU 6050
  • Máy chủ web hiển thị các giá trị con quay hồi chuyển (theo 3 trục là trục X, Y và Z)
  • Sau mỗi 10 mili giây thì các giá trị này được cập nhật 1 lần trên Web Server (1 giây = 1000 mili giây)
  • Web Server cũng hiển thị các giá trị gia tốc kế (X, Y, Z). Các giá trị này được cập nhật liên tục sau mỗi 200 mili giây;
  • MPU 6050 cũng hỗ trợ đo nhiệt độ, nên chúng ta cũng sẽ hiển thị giá trị nhiệt độ trên ESP32 Web Server. Nhiệt độ được cập nhật mỗi giây (1000 mili giây);
  • Tất cả các giá trị đọc được này đều được cập nhật bằng Server-Sent Events;
  • Chúng ta cũng sẽ vẽ một hình ảnh 3D của cảm biến. Hướng của vật thể 3D sẽ thay đổi theo hướng cảm biến. Vị trí hiện tại của cảm biến được tính toán dựa trên các giá trị của con quay hồi chuyển MPU 6050.
  • Hình vẽ 3D trên được tạo bằng thư viện JavaScript có tên là three.js ;
  • Trên Web Server sẽ có 4 nút để điều chỉnh vị trí của đối tượng 3D:
    • ĐẶT LẠI VỊ TRÍ: đặt vị trí góc về 0 trên tất cả các trục;
    • X: đặt vị trí góc X về 0;
    • Y: đặt vị trí góc Y về 0;
    • Z: đặt vị trí góc Z về 0;
Cách hoạt động của dự án ESP32 Web Server với MPU6050

Giới thiệu ESP32 Filesystem

Để giúp dự án được cấu trúc theo 1 cách dễ hiểu hơn, chúng ta sẽ tạo 4 file khác nhau để phục vụ máy chủ Web như hình:

Các tệp html, css và js sẽ được tải lên ESP32 LittleFS filesystem. Để tải lên, chúng ta sẽ sử dụng plugin LittleFS Uploader. Bạn cần phải cài đặt plugin này trong Arduino IDE, trước khi thực hiện theo hướng dẫn này nhé!

Giới thiệu gia tốc kế và con quay hồi chuyển MPU 6050

MPU 6050 là 1 module có gia tốc kế 3 trục và con quay hồi chuyển 3 trục.

Giới thiệu MPU6050

>> Hướng dẫn liên quan: Ví dụ cách dùng ESP32 MPU6050 trên Arduino IDE

Bây giờ, chúng ta sẽ cùng tìm hiểu cách tạo từng file trên, và cuối cùng là xây dựng máy chủ ESP32 Web Server như mô tả nhé!

Xây dựng file HTML

Tạo 1 file có tên là index.html như sau:

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

Tạo tệp CSS

Tạo 1 file có tên style.css như sau:

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

Tạo file Javascript

Tạo 1 file đặt tên là script.js:

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

Code trên Arduino

Cuối cùng, bạn hãy lập trình trên Arduino IDE, để hoàn thiện dự án ESP32 Web Server với MPU6050 này nhé! Bạn có thể copy đoạn code sau (lưu ý bạn cần thay đổi thông tin wifi để code hoạt động):

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

Để ESP32 Web Server của bạn hoạt động với MPU 6050 của chính bạn, bạn cần thay đổi tên và mật khẩu wifi ở các biến sau:

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Bây giờ, bạn hãy upload code vào và kiểm tra dự án của mình nhé! Chúc bạn thành công!

IoTZone – Chuyên cung cấp thiết bị điện tử & tài liệu cho Makers

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *