课程设计项目——基于ESP32的智能跳绳监测系统

课程设计项目——基于ESP32的智能跳绳监测系统


💁‍♂️前言介绍: 又到期末周了,期末的要求是交一份基于ESP32的智能化项目。所谓的智能化,就是在ESP32上跑模型,也很有意思,在这么小的一块板子上能跑些什么模型呢。所以我搞了一个简易的跳绳监测系统,和以往一样,相关的代码都附在文末,大家自取!

在这里插入图片描述



👨‍🏫内容1:前言


👨‍🏫 前言
👉先简单介绍一下这个项目大致的内容
👨‍💻我们将通过ESP32和陀螺仪,采集7000条跳绳的运动数据。这些数据将保存在csv文件中,再经过模型训练后,烧录到ESP32开发板中。之后,当我们再进行跳绳时,所获取的数据将在ESP32上进行运行,来检测你是否有效跳绳。最终的结果将在OLED和前端大屏上实时显示。
👋那我们依次来看看这个项目是如何实现的吧!

在这里插入图片描述

🌸🌸🌸🌷🌷🌷💐💐💐🌷🌷🌷🌸🌸🌸

👨‍⚖️内容2:组件介绍


1️⃣ESP32开发板:
👉ESP32是一款由乐鑫(Espressif Systems)生产的低功耗微控制器,具有集成的Wi-Fi和蓝牙功能。它被广泛用于物联网(IoT)项目,因为它提供了一种成本效益高、易于编程的方式来连接设备到互联网。ESP32具有以下特点:

  • 1.双核心处理器:ESP32具有两个Xtensa® 32位LX6 CPU核心,可以运行高达240MHz。
  • 2.Wi-Fi和蓝牙:它支持802.11 b/g/n Wi-Fi和双模蓝牙(BR/EDR和BLE)。
  • 3.多种外设接口:包括SPI、I2C、UART、ADC、DAC等。
  • 4.丰富的GPIO:提供多个通用输入/输出(GPIO)引脚,支持各种传感器和外设。
    在这里插入图片描述

2️⃣OLED显示屏:
👉位于下方的是一个小型OLED显示屏,能够显示128x64像素的图形,支持多种颜色模式,支持I2C和SPI两种通信接口,便于与不同的微控制器连接。在本项目中用于实时显示信息,如跳绳次数(Jump: 63)和心率(BPM: 76)
在这里插入图片描述

3️⃣陀螺仪模块:
👉GY-25Z是一种姿态传感模块,它通过UART接口与上位机或微控制器进行通信。以下是GY-25Z模块的一些关键特性和功能:

  • 1.功耗小:功耗较低,适合长时间运行的便携式设备。
  • 2.体积小:模块体积小巧,便于集成到各种设备中。
  • 3.工作原理:GY-25Z通过内置的陀螺仪和加速度传感器,并结合数据融合算法来获取角度数据。
    在这里插入图片描述

4️⃣心率传感器:
👉MAX30102是一款集成的脉搏血氧仪和心率监测模块,它集成了多个LED、光电检测器、光学元件和具有环境光抑制功能的低噪声电子电路,能够提供心率监测和血氧饱和度测量的功能。MAX30102通过标准的I2C兼容接口与主机进行通信,它使用光电容积法(PPG)来测量心率,通过LED灯照射人体组织,然后由光电传感器检测反射光并转换为电信号,再经过处理得到心率和血氧数据。
在这里插入图片描述

🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡

👨‍🎓内容3:数据采集


首先我们先采集一些跳绳的数据,用于模型的训练
接下来我将介绍数据采集部分的代码【代码解析看我的注释】🙈

库文件:

/*
    功能:  数据采集
    描述:  采集100次动作,每个动作70个元组;
            当三个轴向的加速度之和大于3.5倍的重力时,认为动作发生;
            当三轴加速度之和小于1.9个重力加速度时,认为是静止状态;
            当三轴加速度之和连续10次低于1.9,认为动作结束;
    输出:  串口输出7000条记录,每条记录为6个维度(aX, aY, aZ, gX, gY, gZ)
*/
#include <Hardwareserial.h>
#define TEST_PRINT                      1

#define ACTION_COUNT_MAX                10
#define ONCE_ACTION_RECORD_NUM_MAX      70
#define ONCE_ACTION_RECORD_NUM_MIN      0
#define ONCE_ACTION_STOP_NUM_MAX        10
#define ONCE_ACTION_NOT_STOP            0
#define ONCE_ACTION_COLLECT_FINISH      1
#define ONCE_ACTION_COLLECT_NOT_FINISH  0

变量定义:

HardwareSerial mySerial(1);  //声明串口1
const float accelerationThreshold_HIGH  = 3.5;  // 触发阈值(动作发生) 为3.5倍重力
const float accelerationThreshold_LOW   = 1.9;  // 静止状态(无动作)   为1.9倍重力
int stop_record_count = 0; //记录在一次动作采集种,静止节点的连续次数,连续记录低于1.9g的次数,10次认定动作结束(0-10)
int once_action_record_count = 0; //记录在一次动作种,采集节点的数量(0-70)
int action_count = 0;//记录动作的个数(0-10)

float record_aX[ONCE_ACTION_RECORD_NUM_MAX];
float record_aY[ONCE_ACTION_RECORD_NUM_MAX];
float record_aZ[ONCE_ACTION_RECORD_NUM_MAX];

float record_gX[ONCE_ACTION_RECORD_NUM_MAX];
float record_gY[ONCE_ACTION_RECORD_NUM_MAX];
float record_gZ[ONCE_ACTION_RECORD_NUM_MAX];
//捕获数据参数
int conut = 0, dirty = 0;
unsigned char sign = 0;
unsigned char Re_buf[25], counter = 0;
int16_t acc[3] = { 0 };   //加速器计
int16_t gyro[3] = { 0 };  //陀螺仪
int YPR[3];               //欧拉角
float aX, aY, aZ, gX, gY, gZ, yX, yY, yZ;

unsigned char once_action_over_sign = 0;//采集完成一次动作的信号

初始化设置:

void setup(){
    //ESP32与电脑通信波特率(串口0)
    Serial.begin(115200);
    //ESP32与GY-25Z通信波特率(串口1)
    mySerial.begin(115200, SERIAL_8N1, 32, 33);   //rxPin = 32,txPin = 33
    //init GY-25Z
    delay(500);
    //输出数据设置指令------- 0xA5+0x55+0xXX+sum
    mySerial.write(0xA5);
    mySerial.write(0x55);
    mySerial.write(0x53);
    mySerial.write(0x4D);
    delay(100);
    //自动输出数据指令-------0xA5+0x56+0x02+0xFD
    mySerial.write(0xA5);
    mySerial.write(0x56);
    mySerial.write(0x02);
    mySerial.write(0xFD);
    delay(100);
}

相关函数:

//打印一组动作的数据,即70个节点
void Print_once_action_data(){
    for(int k = ONCE_ACTION_RECORD_NUM_MIN ;k < ONCE_ACTION_RECORD_NUM_MAX ;k++){
        Serial.print(record_aX[k]);
        Serial.print(",");
        Serial.print(record_aY[k]);
        Serial.print(",");
        Serial.print(record_aZ[k]);
        Serial.print(",");
        Serial.print(record_gX[k]);
        Serial.print(",");
        Serial.print(record_gY[k]);
        Serial.print(",");
        Serial.print(record_gZ[k]);
        Serial.println("");
    }
}
//记录一组动作数据
void Record_data(){
    float aSum = fabs(aX) + fabs(aY) + fabs(aZ); 
    if (aSum >= accelerationThreshold_HIGH && once_action_record_count == 0){//动作开始
#if TEST_PRINT == 1
        Serial.println("Action detected.");//检测到动作。
#endif
        //记录开始动作
        record_aX[once_action_record_count] = aX;
        record_aY[once_action_record_count] = aY;
        record_aZ[once_action_record_count] = aZ;
        record_gX[once_action_record_count] = gX;
        record_gY[once_action_record_count] = gY;
        record_gZ[once_action_record_count] = gZ;
        once_action_record_count = 1;
    }
    else if(once_action_record_count > ONCE_ACTION_RECORD_NUM_MIN && once_action_record_count < ONCE_ACTION_RECORD_NUM_MAX){//一个动作记录中
        record_aX[once_action_record_count] = aX;
        record_aY[once_action_record_count] = aY;
        record_aZ[once_action_record_count] = aZ;
        record_gX[once_action_record_count] = gX;
        record_gY[once_action_record_count] = gY;
        record_gZ[once_action_record_count] = gZ;
        once_action_record_count++;
        if(aSum >= accelerationThreshold_LOW)//非静止
            stop_record_count = ONCE_ACTION_NOT_STOP;//清空标记
        else//静止
            stop_record_count++;

        if(stop_record_count == ONCE_ACTION_STOP_NUM_MAX || once_action_record_count == ONCE_ACTION_RECORD_NUM_MAX){//动作完成
            once_action_over_sign = ONCE_ACTION_COLLECT_FINISH;
            once_action_record_count = 0;
#if TEST_PRINT == 1
            Serial.println("One action collection is completed.");//一次动作采集完成。
#endif
        }
    }
    else{//未检测到动作
#if TEST_PRINT == 1
        Serial.println("No action detected !!!");
#endif
    }
}
//陀螺仪采集一次数据
void Collect_data(){
    dirty = 0;
    while (dirty != 1) {
        measure();
        analysis();
    }
    //Print_zbw_lzh_yjy();
    delay(100);
}

//分析数据
void analysis() {
  if (sign) {
    sign = 0;
    if (Re_buf[0] == 0X5A && Re_buf[1] == 0X5A) {
      dirty = 1;
      acc[0] = (Re_buf[4] << 8 | Re_buf[5]);
      acc[1] = (Re_buf[6] << 8 | Re_buf[7]);
      acc[2] = (Re_buf[8] << 8 | Re_buf[9]);
      gyro[0] = (Re_buf[10] << 8 | Re_buf[11]);
      gyro[1] = (Re_buf[12] << 8 | Re_buf[13]);
      gyro[2] = (Re_buf[14] << 8 | Re_buf[15]);
      YPR[0] = (Re_buf[16] << 8 | Re_buf[17]);
      YPR[1] = (Re_buf[18] << 8 | Re_buf[19]);
      YPR[2] = (Re_buf[20] << 8 | Re_buf[21]);
      for (int j = 0; j < 3; j++) {
        if (YPR[j] > 46000) {
          YPR[j] = YPR[j] - 29535;
        } 
        else {
          YPR[j] = YPR[j];
        }
      }
      aX = acc[0] / 16383.5;
      aY = acc[1] / 16383.5;
      aZ = acc[2] / 16383.5;
      gX = gyro[0] / 16.3835;
      gY = gyro[1] / 16.3835;
      gZ = gyro[2] / 16.3835;
      yX = YPR[0];
      yY = YPR[1];
      yZ = YPR[2];
    }
  }
}
//从GY-25Z读取数据
void measure() {
    while (mySerial.available()) {
        Re_buf[counter] = (unsigned char)mySerial.read();
        if (counter == 0 && Re_buf[0] != 0x5A) {
            return;
        }
        counter++;
        if (counter == 25) {
            counter = 0;
            sign = 1;
        }
    }
}
//串口打印
void Print_zbw_lzh_yjy() {
  Serial.println("********************************************************");
  Serial.print("加速度计:");
  Serial.print("\taX:");
  Serial.print(aX);
  Serial.print("\taY:");
  Serial.print(aY);
  Serial.print("   \taZ:");
  Serial.println(aZ);
  Serial.print("陀螺仪:");
  Serial.print("\tgX:");
  Serial.print(gX);
  Serial.print(" \tgY:");
  Serial.print(gY);
  Serial.print(" \tgZ:");
  Serial.println(gZ);
  Serial.print("欧拉角:");
  Serial.print("\tyX:");
  Serial.print(yX);
  Serial.print("\tyY:");
  Serial.print(yY);
  Serial.print("\tyZ:");
  Serial.println(yZ);
  Serial.println("********************************************************");
}

循环语句:

void loop(){
    //Collect_data();//收集数据

    if(action_count == ACTION_COUNT_MAX){//收集完毕,采集完10个动作
        Serial.println("All actions are collected.");//所有动作采集完毕
        while(1);//loop()停止
    }
    else{//继续采集
        while(once_action_over_sign == ONCE_ACTION_COLLECT_NOT_FINISH){//读完一组动作时,70个节点采集完(0-69)
            Collect_data();//收集完一个节点的数据
            Record_data();//记录完一个节点的数据
        }
        once_action_over_sign = ONCE_ACTION_COLLECT_NOT_FINISH;
        Print_once_action_data();//打印数据
        action_count++;
#if TEST_PRINT == 1
        Serial.print("action_count == ");
        Serial.println(action_count);
#endif
    }
}

每次采集后的数据,我们都保存在csv文件中【共有7000条数据】
在这里插入图片描述

🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈

👨‍🎨内容4:模型训练


数据采集完成之后,我们将对其进行模型训练
这里我们就用最简单的序列模型来实现本次项目

1️⃣数据预处理:
我们先把数据文件导入
这里为了便利,我仅导入了1800条数据

import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import numpy as np
import pandas as pd
from tqdm import tqdm

flex = pd.read_csv('D:\新型桌面\嵌入式系统\期末大作业\\final.csv',header=None)
flex

在这里插入图片描述

接着我们将数据存储为一个二维矩阵
然后进行归一化处理
并给数据进行标签处理

SAMPLES_PER_GESTURE = 30   #每个手势样本包含的数据点数

def processData(d,v):   #d:数据  v:标签
    dataX = np.empty([0,SAMPLES_PER_GESTURE*6])   #存储特征数据
    dataY = np.empty([0])  #存储标签数据

    data = d.values
    dataNum = 60

    for i in tqdm(range(dataNum)):    #提供一个循环进度条
        tmp = []   #存储当前样本的所有处理后的数据点
        for j in range(SAMPLES_PER_GESTURE):   #遍历每个样本的70个数据点,进行归一化处理
            tmp += [(data[i * SAMPLES_PER_GESTURE + j][0] + 4.0) / 8.0]
            tmp += [(data[i * SAMPLES_PER_GESTURE + j][1] + 4.0) / 8.0]
            tmp += [(data[i * SAMPLES_PER_GESTURE + j][2] + 4.0) / 8.0]
            tmp += [(data[i * SAMPLES_PER_GESTURE + j][3] + 2000.0) / 4000.0]
            tmp += [(data[i * SAMPLES_PER_GESTURE + j][4] + 2000.0) / 4000.0]
            tmp += [(data[i * SAMPLES_PER_GESTURE + j][5] + 2000.0) / 4000.0]

        tmp = np.array(tmp)
        tmp = np.expand_dims(tmp, axis=0)  #提高维度  [420] => [1,420]
        dataX = np.concatenate((dataX, tmp), axis=0) #将tmp添加到dataX的末尾,这样dataX就包含了所有样本的特征
        dataY = np.append(dataY, v)  #将当前样本的标签v[i]添加到dataY数组的末尾

    return dataX, dataY
flexX, flexY = processData(flex,0)

dataX = flexX
dataY = flexY

dataY

在这里插入图片描述


2️⃣准备数据集:
我们将80%的数据作为训练集,20%的数据作为测试集

premutationTrain = np.random.permutation(dataX.shape[0])

dataX = dataX[premutationTrain]
dataY = dataY[premutationTrain]

idx = int(dataX.shape[0] *0.8)    #80%训练集  20%测试集
x_train = dataX[0:idx]
y_train = dataY[0:idx]
x_test = dataX[idx:dataX.shape[0]]
y_test = dataY[idx:dataY.shape[0]]

3️⃣训练模型:
所有准备工作完成后
我们开始进行模型训练
我们创建一个序列模型
并将层按顺序堆叠起来

model = keras.Sequential()   #使用Sequential顺序模型
model.add(keras.layers.Dense(32, input_shape=(6 * SAMPLES_PER_GESTURE,), activation='relu'))
model.add(keras.layers.Dense(16, activation='relu'))
model.add(keras.layers.Dense(2, activation='softmax'))

接着我们初始化了一个Adam优化器,并进行模型编译
Adam是一种自适应学习率优化算法,常用于训练深度学习模型

adam = keras.optimizers.Adam()

model.compile(loss='sparse_categorical_crossentropy',
              optimizer=adam,
              metrics=['sparse_categorical_crossentropy'])

model.summary()   #查看模型各层的参数状况

在这里插入图片描述

最后我们进行模型训练

history = model.fit(x_train,y_train,batch_size=1, validation_data=(x_test,y_test), epochs=200, verbose=1)  #模型训练

在这里插入图片描述

4️⃣模型转换保存:
由于我们要把模型烧录到ESP32开发板中
因此我们这里将训练好的模型进行一个转换保存
我们使用linux命令xxd –i 将model二进制文件内容存储在C代码静态数组

converter = tf.lite.TFLiteConverter.from_keras_model(model)   #模型转换保存
tflite_model = converter.convert()

open("Final_model","wb").write(tflite_model)

在这里插入图片描述

在这里插入图片描述

🌻🌻🌻🌼🌼🌼🌺🌺🌺🌼🌼🌼🌻🌻🌻

🙇内容5:效果展示


为了使我们的项目更加模块化
我们绘制了PCB板来做成一个简易的跳绳装置
在这里插入图片描述
在这里插入图片描述

接着,我们在Arduino中,将上述的模型导入
在这里插入图片描述

我们将实时采取运动数据
并将数据传入到模型中进行计算
最终得出是否实现跳绳动作

在这里插入图片描述

为了更好的展示跳绳的效果
我们将最终的数据同步到前端页面中
具体的前端代码和ESP32的代码
这边就不做介绍

大家可以直接在文末获取整个项目的代码!
所有的代码都做了相应的注释

那我们来看一下最终的实现效果吧!

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

文档成果物:
内容包括【完整的项目报告+效果演示视频】(满分项目报告!!!)
点击下载文档成果物

系统工程文件
资源包括【ESP32采集代码、ESP32跳绳识别代码、模型训练代码、前端展示代码、PCB板工程文件】
点击下载系统工程文件

在这里插入图片描述

评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失散多年的哥哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值