ESP, IoT Датчик ускорений на основе ESP32 и MPU6050

kalcifer

✩✩✩✩✩✩✩
20 Мар 2024
2
0
Здравствуйте, меня зовут Дмитрий и я только начинаю познавать мир Arduino
В данном посте я хотел бы рассказать о своём небольшом проекте, который на данный момент не доделал до конца.
мой проектик - небольшая коробочка, имеющая крепление для подвешивания к различным колебательным и не только системам, например различного рода маятникам. Она должна крепится к системе и, колебаясь, измерять ускорения по 3м осям.
Зачем? Для определения по изменению данного параметра, например, периода колебаний.
Изначально данный проект является индивидуальной лабораторной работой в университете, мне стало интересно изучить работу с MPU6050, предложил данную тему и теперь работаю с ней. Изначально мне было интересно получить углы с помощью акселерометра и гироскопа (на данный момент результатом кода являются углы, поменять это на ускорения не проблема, убрав часть кода), но немного изучив и не поняв фильтр Калмана, решил пока дописать хотя бы с ускорениями. Плюс я хочу попробовать возможности передачи данных по Wi Fi в режиме AP.

Алгоритм работы: ESP32 при запуске (питание 3,7В) работает в режиме точки доступа AP, мы подключаемся к точке доступа ESP32, заходим по IP адресу и попадаем на сайт, на котором по нажатию кнопки начинаются измерения, которые передаются на этот же сайт. При повторном нажатии кнопки измерения заканчиваются.
Реализация:
1. Программная: на данный момент код работает, некоторую его часть, не буду скрывать, взял из одного из гайдов, затем просто переделал его под свою ситуацию. Но с кодом существует проблема - он выводит сообщения на сайт, но каким образом можно организовать сохранение этих данных до повторного нажатия кнопки? Ну то есть, как реализовать вывод данных последовательно, одно за другим, сохраняя предыдущее значение, чтобы потом можно было скопировать и вставить данные, например в loggerPro (да да, относительно старая штука, но мы работаем в ней, может быть есть ПО получше для таких целей?). Пробовал каким то образом выводить с помощью массива, но, очевидно, это не сработало. Множество форумов перелазил, но как-то не нашел решение именно моей проблемы каким - либо образом.
2. Материальная: Тут говорить нечего, припаял проводки быстренько, создал простую модельку коробочки с крышкой и резьбой M6 под крепление, напечатал PLA пластиком. Питание напрямую от батарейки Li-Po 3,7 вольт.
00pLPlzE_NQ.jpg
jE0lN8LATyI.jpg
YruR42alvB4.jpg
Далее представлен мой код. он устроен просто. в цикле loop находится сам цикл измерений, который происходит, если логический переменная LEDstatus имеет значение 1, тогда светодиод на 2 пине, информирующий о процессе измерения, горит. Иначе не горит и измерения не происходят. Далее управление измерение происходит в зависимости от того, по какому адресу мы перейдем, если по адресу /start, то выполняется функция handle_start, которая отправляет функции, создающей код html для сервера, SendHTML данные о состоянии LEDstatus и данные, которые измеряются датчиком MPU6050. Если перейдем по адресу /break, то измерения прекратятся. Для работы MPU6050 используется стандартная библиотека MPU6050.h от i2cdevlib. Основная проблема в том, что, данные о углах, полученные из loop, обновляются на сайте, но они не могут каким либо образом сохраниться на нем до окончания измерений. Это основная проблема, которую я не могу понять как решить в силу малого опыта (от силы 2 - 3 недели). Поэтому, прошу помощи, как новичку сделать более менее все)
ниже представлен сам код:
C:
#include <WiFi.h>
#include <WebServer.h>
#include "MPU6050.h"

/* Установите здесь свои SSID и пароль */
const char* ssid = "ESP"; 
const char* password = "01234567"; 
/* Настройки IP адреса */
IPAddress local_ip(192,168,2,1);
IPAddress gateway(192,168,2,1);
IPAddress subnet(255,255,255,0);
WebServer server(80);
uint8_t LEDpin = 2;
bool LEDstatus = LOW;

MPU6050 mpu;          //создает объект mpu
int16_t ax, ay, az;   // объявление переменных
int16_t gx, gy, gz;   // объявление переменных
int i = 1, n = 1;
unsigned long delta, integer_time, time1, serial_time;
const int intdelay = 1, serial_delay = 50;
const float g = 9.80665, pi = 3.1415926535;
float wxold, wyold, wzold, alpha2, betta2, gamma2, alpha1, betta1, gamma1, roll, pitch, yaw;
float a0,b0,g0;

void setup() {
  Serial.begin(115200);
  pinMode(LEDpin, OUTPUT);
  WiFi.softAP(ssid, password);
  WiFi.softAPConfig(local_ip, gateway, subnet);
  delay(100);
  //server.on("/", handle_OnConnect);
  server.on("/start", handle_start);
  server.on("/", handle_break);
  server.onNotFound(handle_NotFound);
  server.begin();
  Serial.println("HTTP server started");
  Serial.print("Got IP: ");  Serial.println(local_ip);

  Wire.begin();        //инициализирует библиотеку и подключает устройство к шине I2C  // выбор скорости передачи данных
  mpu.initialize();    // состояние соединения
  Serial.println(mpu.testConnection() ? "MPU6050 OK" : "MPU6050 FAIL");
  delay(1000);
  mpu.setXAccelOffset(-2437);
  mpu.setYAccelOffset(-639);
  mpu.setZAccelOffset(1522);
  mpu.setXGyroOffset(90);
  mpu.setYGyroOffset(27);
  mpu.setZGyroOffset(-83);
  mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
}

void loop() {
  server.handleClient();
  if(LEDstatus)
  {digitalWrite(LEDpin, HIGH);
  time1 = millis();
  delta = time1 - integer_time;

  if (delta > intdelay)
  {
    integer_time = time1;
      mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);  //получаем ускорения и угловую скорость
      float axx = (float)ax / 16384;
      float ayy = (float)ay / 16384;
      float azz = (float)az / 16384;
      float wx = (float)gx / 32768 * 250;
      float wy = (float)gy / 32768 * 250;
      float wz = (float)gz / 32768 * 250;
      alpha1 = ((180 * atan2(-azz,ayy))/pi);
      betta1 = ((180 * atan2(azz,axx))/pi);
      gamma1 = (180 * atan2(ayy,axx))/pi;
      
      if (i < 1){
      a0 = alpha1;
      b0 = betta1;
      g0 = gamma1;
      }
      else {
        a0 = 0;
        b0 = 0;
        g0 = 0;
      }
      alpha2 = a0 + alpha2 + (((wx+wxold)/2)*delta/1000);
      betta2 = b0 + betta2 + (((wy+wyold)/2)*delta/1000);
      gamma2 = gamma2 + (((wz+wzold)/2)*delta/1000);
      wxold = wx;
      wyold = wy;
      wzold = wz;
      time1 = millis();
       if (time1 - serial_time > serial_delay){
        serial_time = time1;
        
          roll = alpha1 * 0.96 + alpha2 * 0.04;
          pitch = betta1 * 0.96 + betta2 * 0.04;
          yaw = gamma2;
          Serial.print(n);
          Serial.print('\t');
          Serial.print(alpha1, 2);
          Serial.print('\t');
          Serial.print(betta1, 2);
          Serial.print('\t');
          Serial.print(gamma1, 2);
          Serial.print('\t');
          Serial.print('\t');
          Serial.print(alpha2);
          Serial.print('\t');
          Serial.print(betta2);
          Serial.print('\t');
          Serial.print(gamma2);
          Serial.print('\t');
          Serial.print('\t');
          Serial.print(roll);
          Serial.print('\t');
          Serial.print(pitch);
          Serial.print('\t');
          Serial.print(yaw);
          Serial.print('\n');
          n += 1;
          }
          
      i += 1;}
      
  }
  else
  {digitalWrite(LEDpin, LOW);}
}

void handle_start() {
LEDstatus = HIGH;
//Serial.println("led status: ON");
server.send(200, "text/html", SendHTML(true,roll,pitch,yaw));
}
void handle_break() {
LEDstatus = LOW;
Serial.println("Ожидание");
server.send(200, "text/html", SendHTML(false,0,0,0));
}
void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}
String SendHTML(uint8_t led1stat, float rolll, float pitchh, float yaww){
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr +="<meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr +="<meta http-equiv= \"refresh\" content=0.5 >";
  ptr +="<title>Управление гироскопом</title>\n";
  ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr +="body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr +=".button {display: block;width: 120px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr +=".button-on {background-color: #3498db;}\n";
  ptr +=".button-on:active {background-color: #2980b9;}\n";
  ptr +=".button-off {background-color: #34495e;}\n";
  ptr +=".button-off:active {background-color: #2c3e50;}\n";
  ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr +="</style>\n";

  //ptr +="<script>\n";
  //ptr +="setInterval(loadDoc,500);\n";
  //ptr +="function loadDoc() {\n";
  //ptr +="var xhttp = new XMLHttpRequest();\n";
  //ptr +="xhttp.onreadystatechange = function() {\n";
  //ptr +="if (this.readyState == 4 && this.status == 200) {\n";
  //ptr +="document.getElementById(\"webpage\").innerHTML =this.responseText}\n";
  //ptr +="};\n";
  //ptr +="xhttp.open(\"GET\", \"/\", true);\n";
  //ptr +="xhttp.send();\n";
  //ptr +="}\n";
  //ptr +="</script>\n";

  ptr +="</head>\n";
  ptr +="<body>\n";
  ptr +="<h1>Измерение углов MPU6050</h1>\n";
  ptr +="<h3>Режим точка доступа WiFi (AP)</h3>\n";
  if(led1stat) {
  ptr +="<p> Состояние: активно </p><a class=\"button button-off\" href=\"/\">Закончить измерение</a>\n";
    if(led1stat)
    {
    ptr += "</p>";
    ptr += (int)rolll;
    ptr += "&nbsp;";
    ptr += "&nbsp;";
    ptr += "&nbsp;";
    ptr += "&nbsp;";
    ptr += (int)pitchh;
    ptr += "&nbsp;";
    ptr += "&nbsp;";
    ptr += "&nbsp;";
    ptr += "&nbsp;";
    ptr += (int)yaww;
    ptr += "<br/> </p>";
    }}
  else {ptr +="<p>Состояние: не активно</p><a class=\"button button-on\" href=\"/start\">начать измерение</a>\n";}
  return ptr;
}
Также ниже можете увидеть как это выглядит на сайте:
dIhm4QvHSrk.jpgMmq3lVN-8qE.jpg


Заранее всем спасибо за мысли и помощь, если чего-то не дописал, есть какие-то нюансы с оформлением поста, пишите.
 

rkit

★★★✩✩✩✩
5 Фев 2021
484
116
Фильтр Калмана тут ни к чему. Он совершенно другие задачи решает.
Угол наклона относительно горизонта считается только по акселерометрам.
 
  • Лойс +1
Реакции: kalcifer

rkit

★★★✩✩✩✩
5 Фев 2021
484
116
Направлен после ФНЧ. И задача как раз сводится к разнице между динамическими отклонениями и горизонтом.
 
  • Лойс +1
Реакции: kalcifer

vortigont

★★★★★★✩
24 Апр 2020
937
507
Saint-Petersburg, Russia
@kalcifer, вам нужно отделить создание страницы от вывода накопленных данных.
Выводить данные на страницу смысла нет, по нажатию кнопки лучше формировать массив данных в виде csv например, браузер будет предлагать скачать и сохранить файл.
 
  • Лойс +1
Реакции: kalcifer

kalcifer

✩✩✩✩✩✩✩
20 Мар 2024
2
0
@vortigont, мне тоже пришла такая идея недавно, попробую как-нибудь сделать, только как это уже другой вопрос, нужно подумать

@Геннадий П, как я понял акселерометр получает проекции ускорения на соответствующие оси xyz, если положение датчика статично, то он показывает проекцию g на оси, в других же случаях это просто проекция ускорения.
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,871
599
44
@kalcifer, Как уже писал, в статике направление наклона считается суммированием векторов X Y Z акселерометра.
Чтобы в динамике получать угол наклона нужно уже использовать данные гироскопа, чтобы получать угловое ускорение и совмещать с изначальным статичным положением. Тут уже расчеты посложней.