M5Stack-智慧养老综合服务系统-Arduino中文社区 - Powered by Discuz! Archiver

krwlng 发表于 2019-9-18 15:03

M5Stack-智慧养老综合服务系统

本帖最后由 krwlng 于 2019-9-23 18:44 编辑

随着时代的发展,我国的老龄化人口日益增加,我国也渐入到人口老龄化的情况当中。因此我借用M5Stack制作了一套智慧养老服务系统。M5stack中有一块ESP32的WiFi模块,同时还有一块电子的显示屏幕与喇叭。十分适合用于老人身体的姿态数据的采集的工作。



M5Stack主要是用于了基础数据的采集工作,M5Stack Gray和Fire配备9轴加速/陀螺仪/磁传感器(IMU)通过我们将M5Stack装在测试者的腰部偏上的位置,用于基础数据的采集工作。利用Processing制作出了一款简单的3D模型,通过局域网将M5Stack采集到的实时的数据发送到了PC端,用来实时的处理数据。

利用MATLAB和现有的数据集(UCI)做了简单的数据分析。


利用MATLAB的加持便可以更加准确的计算出走路的步数情况。
在通过Python和Mysql对于采集到的基本数据进行分类和处理工作,数据存入到数据库中,用来方便日后医生和数据管理人员的使用。

数据处理保存姿态数据之外,还保存了当前佩戴者的GPS以及时间等。

利用ThingSpeak做了临时的数据存储网站,通过将数据上传到网站当中,就可以将数据分享给你需要分享的人。例如;老人的家属、医生等。
同时,还通过M5Stack上的按键制作了“一键报警”功能。将报警信息通过邮件的方式发送到预先设定好的账户当中。通过这样的可视化的网站,可以很方便的监测到老人的基本身体健康数据。但是 这些都是要用“”
国内 透传云 也是一个不错的选择了。接下来简单介绍下如何使用M5stack在Processing上制作3D模型并移动它。
1.在个人计算机上创建M5Stack的3D模型
2.使用IMU计算终端姿态
3.将姿态数据发送到个人计算机并控制3D模型
这一些都是得益于M5Stack所采集到的基本的姿态数据所实现的。在计算机上制作M5Stack的3D模型
有各种各样的工具可以创建3D模型,但是这次我们将使用Processing,因为它是一种易于使用的工具,可以使用传感器数据旋转模型。
处理是一种编程语言和集成开发环境,可以轻松处理线条,图形,图像等。

安装处理
处理可在Windows,Mac OS X和Linux上运行。从Processing官方网站上的Download Processing页面下载适用于您的OS的软件ZIP文件。对于Mac OS,将下载的ZIP文件解压缩,然后将出现在应用程序文件夹中的应用程序文件放置完成安装。

启动应用程序时,将显示以下屏幕。


好像您在某处见过。屏幕被上下拆分,顶部就像一个运行按钮,选项卡上显示“ sketch_190821a”。是的,非常类似于Arduino IDE。根据Wikipedia的说法,处理始于2001年,对Arduino产生了重大影响。
该程序的结构与Arduino非常相似,当有一个名为setup的函数时,它只会执行一次,然后会重复执行一个名为draw的Arduino循环函数。执行初始设置,例如使用设置功能设置要绘制的屏幕尺寸,然后使用绘制功能实际绘制线条和图形。由于绘制功能是重复执行的,因此可以通过在每次执行时更改线条和图形的位置和大小来显示电影。

建立3D模型
首先,创建M5Stack的3D模型。在“处理”中,使用称为Shapes 3D的库,可以将纹理粘贴到各种形状的3D对象中。但是,由于Shapes 3D的规格尚不清楚,因此我们将在M5Stack的之前,之后,左侧,右侧,顶部,底部和顶部粘贴六个图像到一个三维空间中。

首先,从M5Stack的正面,背面,左侧,右侧,顶部,底部和6面拍照。检查所拍摄的照片,正面和背面的照片大约为700 x 700像素,左上和右下的照片分别为700 x 225像素。接下来,使用图像软件(例如在Mac上预览)调整图像尺寸,以使前后照片为700 x 700,左上和右上照片为700 x 225像素。

接下来,在Processing中创建一个M5Stack模型。考虑一个代表M5Stack的长方体。长方体的大小与M5Stack的高度和宽度相同,但根据照片的像素数为700 x 700 x 225,以便于理解。

如果将长方体放置在3D空间的原点,则M5Stack的前表面的坐标按从左上角U形的顺序如下。 V1:(-350,-112,-350) V2:(350,-112,-350) V3:(350,-112、350) V4:(-350,-112、350)将M5Stack正面的图像粘贴到此坐标,然后尝试用鼠标移动它。用鼠标移动3D模型启动Processing,然后输入以下程序,并用适当的名称保存它,例如M5Stack

%% Refine peak finding by adding more specific requirements
= pwelch(abw,[],[],[],fs);

fmindist = 0.25;                  % Minimum distance in Hz
N = 2*(length(f)-1);                % Number of FFT points
minpkdist = floor(fmindist/(fs/N)); % Minimum number of frequency bins

= findpeaks(p,'npeaks',8,'minpeakdistance',minpkdist,...
    'minpeakprominence', 0.15);

plot(f,db(p),'.-')
grid on
hold on
plot(f(locs),db(pks),'rs')
hold off
addActivityLegend(1)
title('Power Spectral Density with Peaks Estimates')
xlabel('Frequency (Hz)')
ylabel('Power/Frequency (dB/Hz)')
#include <M5Stack.h>
#include "utility/MPU9250.h"
#include "utility/quaternionFilters.h"

#define processing_out false
#define AHRS true         // Set to false for basic data read
#define SerialDebug true// Set to true to get Serial output for debugging
#define LCD

MPU9250 IMU;
// Kalman kalmanX, kalmanY, kalmanZ; // Create the Kalman instances

void setup()
{
M5.begin();
Wire.begin();

IMU.MPU9250SelfTest(IMU.SelfTest);

// キャリブレーション
IMU.calibrateMPU9250(IMU.gyroBias, IMU.accelBias);
IMU.initMPU9250();

delay(500);

IMU.initAK8963(IMU.magCalibration);


M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(GREEN ,BLACK);
M5.Lcd.fillScreen(BLACK);
}

void loop()
{
// If intPin goes high, all data registers have new data
// On interrupt, check if data ready interrupt
if (IMU.readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01)
{
    IMU.readAccelData(IMU.accelCount);// Read the x/y/z adc values
    IMU.getAres();

    // 加速度计算
    IMU.ax = (float)IMU.accelCount*IMU.aRes; // - accelBias;
    IMU.ay = (float)IMU.accelCount*IMU.aRes; // - accelBias;
    IMU.az = (float)IMU.accelCount*IMU.aRes; // - accelBias;

    IMU.readGyroData(IMU.gyroCount);// Read the x/y/z adc values
    IMU.getGres();

   
    IMU.gx = (float)IMU.gyroCount*IMU.gRes;
    IMU.gy = (float)IMU.gyroCount*IMU.gRes;
    IMU.gz = (float)IMU.gyroCount*IMU.gRes;

    IMU.readMagData(IMU.magCount);// Read the x/y/z adc values
    IMU.getMres();
    IMU.magbias = +470.;
    IMU.magbias = +120.;
    IMU.magbias = +125.;

   
    IMU.mx = (float)IMU.magCount*IMU.mRes*IMU.magCalibration - IMU.magbias;
    IMU.my = (float)IMU.magCount*IMU.mRes*IMU.magCalibration - IMU.magbias;
    IMU.mz = (float)IMU.magCount*IMU.mRes*IMU.magCalibration - IMU.magbias;
}

IMU.updateTime();

MahonyQuaternionUpdate(IMU.ax, IMU.ay, IMU.az, IMU.gx*DEG_TO_RAD,
                         IMU.gy*DEG_TO_RAD, IMU.gz*DEG_TO_RAD, IMU.my,
                         IMU.mx, IMU.mz, IMU.deltat);

IMU.delt_t = millis() - IMU.count;

if (IMU.delt_t > 100)
{
      IMU.yaw   = atan2(2.0f * (*(getQ()+1) * *(getQ()+2) + *getQ() *
                  *(getQ()+3)), *getQ() * *getQ() + *(getQ()+1) * *(getQ()+1)
                  - *(getQ()+2) * *(getQ()+2) - *(getQ()+3) * *(getQ()+3));
      IMU.pitch = -asin(2.0f * (*(getQ()+1) * *(getQ()+3) - *getQ() *
                  *(getQ()+2)));
      IMU.roll= atan2(2.0f * (*getQ() * *(getQ()+1) + *(getQ()+2) *
                  *(getQ()+3)), *getQ() * *getQ() - *(getQ()+1) * *(getQ()+1)
                  - *(getQ()+2) * *(getQ()+2) + *(getQ()+3) * *(getQ()+3));
      IMU.pitch *= RAD_TO_DEG;
      IMU.yaw   *= RAD_TO_DEG;
      IMU.yaw   -= 8.5;
      IMU.roll*= RAD_TO_DEG;

      M5.Lcd.setCursor(0, 0); M5.Lcd.print("    x      y      z ");
      M5.Lcd.setCursor(0,24);
      M5.Lcd.printf("% 5d% 5d% 5dmg\n",(int)(1000*IMU.ax), (int)(1000*IMU.ay), (int)(1000*IMU.az));
      M5.Lcd.setCursor(0,44);
      M5.Lcd.printf("% 5d% 5d% 5ddeg/s \n", (int)(IMU.gx), (int)(IMU.gy), (int)(IMU.gz));
      M5.Lcd.setCursor(0,64);
      M5.Lcd.printf("% 5d% 5d% 5dmG   \n",(int)(IMU.mx), (int)(IMU.my), (int)(IMU.mz));

      M5.Lcd.setCursor(0,100);
      M5.Lcd.printf("yaw : % 5.2f\r\n",(IMU.yaw));
      M5.Lcd.printf("pitch : % 5.2f\r\n",(IMU.pitch));
      M5.Lcd.printf("roll : % 5.2f\r\n",(IMU.roll));
      IMU.count = millis();
}
}

PImage front;

void setup() {
size(800, 600, P3D);
front = loadImage("front.jpg");
textureMode(IMAGE);
}

void draw() {
background(0);
translate(width / 2, height / 2);
scale(0.4);

pushMatrix();
float rotationX = map(mouseY, 0, height, PI, -PI);
float rotationY = map(mouseX, 0, width, -PI, PI);
rotateX(rotationX);
rotateY(rotationY);
drawM5StickC();
popMatrix();
}

void drawM5StickC() {
beginShape();
texture(front);
vertex(-350, -112, -350,   0,   0); //V1
vertex( 350, -112, -350, 700,   0); //V2
vertex( 350, -112,350, 700, 700); //V3
vertex(-350, -112,350,   0, 700); //V4
endShape();
}在“处理首选项”中检查程序被保存的文件夹的位置。对于Mac,默认值为“ /用户/用户名/文档/处理/草图名称”。在此文件夹中创建一个名为data的文件夹,并将图像文件放置在M5Stack的正面,并将其命名为front.jpg。
单击处理的“执行”按钮时,将出现一个图像窗口,并显示M5Stack前面的图像。当您移动鼠标时,您可以看到M5Stack前面的图像相应地移动。

由于仅存在正面图像,因此存在一些奇怪的事情,例如根据角度可见背面,但是图像是通过鼠标在3D空间中旋转的。通过将其他5个表面也粘贴到3D空间中,可以创建M5Stack的3D模型并用鼠标移动它。

有关创建3D模型并用鼠标移动它的程序,请参见下文。
https://gist.github.com/TakehikoShimojima/f5b58a5bf7f5545fa27ae32d0d09e1a0
在保存程序的文件夹中创建一个名为data的文件夹,这一次放置六个图像文件,名称分别为front.jpg,back.jpg,right.jpg,left.jpg,top.jpg,bottom.jpg。
移动程序时,将显示以下图像,您可以确认它根据鼠标的移动而移动。

使用IMU计算终端的姿态

现在我们已经在处理中创建了M5Stack的3D模型,下一步是使用M5Stack的IMU计算M5Stack本身的姿态。
使用IMU数据进行姿势
为了表示对象的姿势,如下图所示,使用绕x轴旋转的旋转角度(roll)绕y轴旋转的倾斜角度(pitch)和绕z轴的倾斜角度。

有一个名为MadgwickAHRS的库,可根据9轴加速度/陀螺仪/磁传感器和6轴加速度/陀螺仪传感器的值计算侧倾,俯仰和偏航。AHRS是“态度和航向参考系统”的缩写。它使用由名叫Madgwick的人开发的算法,该算法根据IMU值计算姿态和方向。由于我们在不使用磁传感器值的情况下获得了足够准确的姿态数据,因此我们将仅使用加速度和陀螺仪传感器值。

使用M5Stack从MPU9250获取加速度和陀螺仪值的Arduino程序如下。


#include“ utility / MPU9250.h”

MPU9250 IMU;

void setup(){
    IMU.initMPU9250(); //初始化MPU9250
    IMU.calibrateMPU9250(IMU.gyroBias,IMU.accelBias); //校准
}

无效循环(){
    同时(!(IMU.readByte(MPU9250_ADDRESS,INT_STATUS)&0x01));
    IMU.readAccelData(IMU.accelCount); //获取原始加速度数据
    IMU.getAres(); //获取比例值
    //计算x / y / z轴加速度
    IMU.ax =(float)IMU.accelCount * IMU.aRes-IMU.accelBias ;
    IMU.ay =(float)IMU.accelCount * IMU.aRes-IMU.accelBias ;
    IMU.az =(浮动)IMU.accelCount * IMU.aRes-IMU.accelBias ;

    IMU.readGyroData(IMU.gyroCount); //获取陀螺仪原始数据
    IMU.getGres(); //获取比例值
    //计算x / y / z轴的陀螺值
    IMU.gx =(浮动)IMU.gyroCount * IMU.gRes;
    IMU.gy =(浮动)IMU.gyroCount * IMU.gRes;
    IMU.gz =(浮动)IMU.gyroCount * IMU.gRes;
}
创建一个MPU9250对象(IMU),并在设置功能中执行初始化和校准。要获取加速度数据,请使用readAccelData获取原始数据,使用getAres获取标度值,然后将原始数据乘以标度值以计算加速度值。陀螺仪值也是如此。

如下根据加速度值和陀螺仪值计算姿势。
#include <MadgwickAHRS.h>

Madgwick过滤器; //创建一个计算姿态的对象

void setup(){
    filter.begin(10); //初始化10Hz滤波器
}

无效循环(){
    filter.updateIMU(IMU.gx,IMU.gy,IMU.gz,IMU.ax,IMU.ay,IMU.az);

    浮动卷= filter.getRoll();
    浮点距= filter.getPitch();
    浮动偏航= filter.getYaw();
}

计算姿态的Madgwick过滤器创建一个对象(过滤器),然后使用设置功能对其进行初始化。在参数中,以Hz为单位通过过滤器更新周期。

通过使用循环函数传递加速度值和陀螺仪值来更新滤波器函数(updateIMU)时,可以使用getRoll,getPitch和getYaw函数获取侧倾,俯仰和偏航值。调用updateIMU的频率设置为初始设置中指定的周期。

从MPU9250获取加速度和陀螺仪数据,计算姿态并串行输出数据的程序(mpu9250_serial.ino)如下。
#include <M5Stack.h>
#include "utility/MPU9250.h"
#include <MadgwickAHRS.h>

MPU9250 IMU;

Madgwick filter;
unsigned long microsPerReading, microsPrevious;

void setup() {
    M5.begin();
    M5.Lcd.setCursor(0, 0, 2);
    M5.Lcd.fillScreen(BLACK);
    Serial.begin(115200);
    Wire.begin();
    if (IMU.readByte(MPU9250_ADDRESS, WHO_AM_I_MPU9250) != 0x71) {
      M5.Lcd.print("cannnot find MPU9250");
      while (true) ;
    }
    IMU.initMPU9250();// MPU9250
    IMU.calibrateMPU9250(IMU.gyroBias, IMU.accelBias);

    filter.begin(10);// 10Hzfilter

    microsPerReading = 1000000 / 10;
    microsPrevious = micros();
}

#define INTERVAL 5
float rolls, pitchs, yaws;
int maidx = 0; // moving average index
bool once = true;

void loop() {
    if (micros() - microsPrevious >= microsPerReading) {
      while (!(IMU.readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01)) ;
      IMU.readAccelData(IMU.accelCount);
      IMU.getAres();
      
      IMU.ax = (float)IMU.accelCount * IMU.aRes - IMU.accelBias;
      IMU.ay = (float)IMU.accelCount * IMU.aRes - IMU.accelBias;
      IMU.az = (float)IMU.accelCount * IMU.aRes - IMU.accelBias;

      IMU.readGyroData(IMU.gyroCount);
      IMU.getGres();
      
      IMU.gx = (float)IMU.gyroCount * IMU.gRes;
      IMU.gy = (float)IMU.gyroCount * IMU.gRes;
      IMU.gz = (float)IMU.gyroCount * IMU.gRes;

      M5.Lcd.fillScreen(BLACK);
      M5.Lcd.setCursor(0, 5, 2);
      M5.Lcd.printf("%6.2f %6.2f %6.2f", IMU.ax, IMU.ay, IMU.az);

      M5.Lcd.setCursor(0, 40);
      M5.Lcd.printf("%6.2f %6.2f %6.2f", IMU.gx, IMU.gy, IMU.gz);
   
      filter.updateIMU(IMU.gx, IMU.gy, IMU.gz, IMU.ax, IMU.ay, IMU.az);

      rolls = filter.getRoll();
      pitchs = filter.getPitch();
      yaws = filter.getYaw();
      maidx++;

      float roll = 0.0;
      float pitch = 0.0;
      float yaw = 0.0;
      for (int i = 0; i < INTERVAL; i++) {
            roll += rolls;
            pitch += pitchs;
            yaw += yaws;
      }
      roll /= INTERVAL;
      pitch /= INTERVAL;
      yaw /= INTERVAL;

      M5.Lcd.setCursor(0, 75);
      M5.Lcd.printf("%6.2f %6.2f %6.2f", roll, pitch, yaw);
      Serial.printf("%6.2f, %6.2f, %6.2f\r\n", roll, pitch, yaw);

      microsPrevious = microsPrevious + microsPerReading;
    }
}
实际移动时,侧倾,俯仰和偏航值会波动,因此将获取间隔5的移动平均值,并将其平均。

将姿态数据发送到PC以控制3D模型
由于姿态可以由M5Stack计算并串行发送,因此数据在个人计算机上由Processing处理,然后移动首先制成的M5Stack的3D模型。
通过串行电缆发送姿势数据以控制3D模型
通过Processing从串行获取数据如下。
导入处理序列号*;

串行端口;

void setup(){
String []端口= Serial.list();

for(int i = 0; i <ports.length; i ++){
    println(i +“:” + ports );
}
port = new Serial(this,ports ,115200);
}

无效draw(){
if(port.available()== 0)return; //如果行中没有数据,则返回

字符串str = port.readStringUntil('\ n'); //读一行
if(str == null)return; //如果数据为空则返回

字符串toks [] = split(trim(str),“,”); //获取以逗号(,)分隔的令牌
if(toks.length!= 3)return; //如果token不是3则返回

float roll = float(toks ); //将令牌0转换为float
float pitch = -float(toks ); //将令牌1转换为浮点数
float yaw = 180-float(toks ); //将令牌2转换为float
}
导入串行模块,并在设置功能中输出可用串行线的列表。以下结果显示在Processing IDE的消息区域中。
0: /dev/cu.Bluetooth-Incoming-Port
1: /dev/cu.SLAB_USBtoUART
2: /dev/tty.Bluetooth-Incoming-Port
3: /dev/tty.SLAB_USBtoUART
由于1号(/dev/cu.SLAB_USBtoUART)是连接M5Stack和个人计算机的USB串行线,因此请打开该设备。可以使用的串行线因计算机而异,请根据环境进行更改。

要读取字符数据,请使用可用函数检查是否有数据,如果有数据,请使用readStringUntil('\ n')函数读取一行换行符之前的数据。

使用trim函数删除空格字符,并使用split函数提取以逗号分隔的标记。

使用float函数将字符串转换为float数据。为了使M5Stack上的x轴,y轴和z轴方向与Processing方向匹配,pitch和yaw的值以符号或180度反转。

基于滚动,俯仰,偏航数据旋转处理数据的逻辑是Arduino的教程(https://www.arduino.cc/zh/Tutorial/Genuino101CurieIMUOrientationVisualiser)这很有帮助。

整个处理草图如下所示:
PImage front, back, right, left, top, bottom;
import processing.serial.*;

Serial port;

void setup() {
size(1200, 800, P3D);
front = loadImage("front.jpg");
back = loadImage("back.jpg");
right = loadImage("right.jpg");
left = loadImage("left.jpg");
top = loadImage("top.jpg");
bottom = loadImage("bottom.jpg");
textureMode(IMAGE);

String[] ports = Serial.list();

for (int i = 0; i < ports.length; i++) {
    println(i + ": " + ports);
}
port = new Serial(this, ports, 115200);
}

void draw() {
if (port.available() == 0) return;

String str = port.readStringUntil('\n');
if (str == null) return;

String toks[] = split(trim(str), ",");
if (toks.length != 3) return;

float roll = float(toks);
float pitch = -float(toks);
float yaw = 180 - float(toks);
print(yaw); print(", ");
print(pitch); print(", ");
println(roll);

background(0);
translate(width / 2, height / 2);
scale(0.4);

pushMatrix();
float c1 = cos(radians(roll));
float s1 = sin(radians(roll));
float c2 = cos(radians(pitch));
float s2 = sin(radians(pitch));
float c3 = cos(radians(yaw));
float s3 = sin(radians(yaw));
applyMatrix(c2*c3, s1*s3+c1*c3*s2, c3*s1*s2-c1*s3, 0,
            -s2, c1*c2, c2*s1, 0,
            c2*s3, c1*s2*s3-c3*s1, c1*c3+s1*s2*s3, 0,
            0, 0, 0, 1);

drawM5StickC();
popMatrix();
}

void drawM5StickC() {
beginShape();
texture(front);
vertex(-350, -112, -350,   0,   0); //V1
vertex( 350, -112, -350, 700,   0); //V2
vertex( 350, -112,350, 700, 700); //V3
vertex(-350, -112,350,   0, 700); //V4
endShape();

beginShape();
texture(back);
vertex( 350,113, -350,   0,   0); //V1
vertex(-350,113, -350, 700,   0); //V2
vertex(-350,113,350, 700, 700); //V3
vertex( 350,113,350,   0, 700); //V4
endShape();

beginShape();
texture(right);
vertex( 350,-112, -350,   0,   0); //V1
vertex( 350,   113, -350, 225,   0); //V2
vertex( 350,   113,350, 225, 700); //V3
vertex( 350,-112,350,   0, 700); //V4
endShape();

beginShape();
texture(left);
vertex(-350,   113, -350,   0,   0); //V1
vertex(-350,-112, -350, 225,   0); //V2
vertex(-350,-112,350, 225, 700); //V3
vertex(-350,   113,350,   0, 700); //V4
endShape();

beginShape();
texture(top);
vertex( 350,-112, -350,   0,   0); //V1
vertex(-350,-112, -350, 700,   0); //V2
vertex(-350,   113, -350, 700, 225); //V3
vertex( 350,   113, -350,   0, 225); //V4
endShape();

beginShape();
texture(bottom);
vertex(-350,-112,350,   0,   0); //V1
vertex( 350,-112,350, 700,   0); //V2
vertex( 350,   113,350, 700, 225); //V3
vertex(-350,   113,350,   0, 225); //V4
endShape();
}
如果在水平位置启动Mpu9250_serial.ino并移动“处理”侧,则M5Stack的3D模型将出现在计算机屏幕上,并且您可以看到3D模型根据M5Stack的实际移动而移动关于''MATLAB''数据处理的可以去这个网站看下:https://ww2.mathworks.cn/matlabcentral/fileexchange/64268-neural-network-for-sensor-data-analysis
页: [1]
查看完整版本: M5Stack-智慧养老综合服务系统