基于M5StackC PLUS实现跳绳计数器
设计一个跳绳计数器,佩戴在手腕上跳绳,要求跳50个跳绳计数误差不大于3个,并在LCD屏幕上显示个数
标签
嵌入式系统
显示
M5StackC_PLUS
跳绳计数器
lxb
更新2022-09-06
哈尔滨工业大学
851

M5StickC PLUS M5StickC的大屏幕版本,主控采用ESP32-PICO-D4模组,具备蓝牙4.2与WIFI功能,小巧的机身内部集成了丰富的硬件资源,如红外、RTC、麦克风、LED、IMU、按键、蜂鸣器、PMU等,在保留原有M5StickC功能的基础上加入了无源蜂鸣器,同时屏幕尺寸升级到1.14寸、135*240分辨率的TFT屏幕,相较之前的0.96寸屏幕增加18.7%的显示面积,电池容量达到120mAh,接口同样支持HAT与Unit系列产品。

开关机操作

  • 开机:按复位按键,持续至少 2 秒

  • 关机:按复位按键,持续至少 6 秒

任务4:设计一个跳绳计数器,佩戴在手腕上跳绳,要求跳50个跳绳计数误差不大于3个,并在LCD屏幕上显示个数

Fnodd_5auishsxhrTxIWiDA7dEht

已实现的功能:

(1)联网后显示日期和时间(已调整为+8时区)

(2)显示电量

(3)通过短按button A 0.5秒切换显示模式;(为了防止误触)(切换显示模式不会改变跳绳的计数个数)

(4)通过长按button B 3秒将计数个数清零;

(5)通过长按button B 6秒切换IMU的参考轴;

 

6 轴传感器官方示例

        显示加速度和陀螺仪的示例。当然,在摆动时,数字会发生变化,但实时数字显示中的字符很小,因此,在移动时,无法跟踪哪个轴的值在变化。

示例 MPU6886.ino链接:
https://github.com/m5stack/M5StickC/blob/master/examples/Basics/MPU6886/MPU6886.ino

        参考下面这篇博客,通过将加速度和陀螺仪的输出输出到串行绘图仪,在 Arduino IDE 端以图形方式显示。 这样就不必看液晶屏。

博客链接:

https://lang-ship.com/blog/work/m5stickc-imu-mpu6886/

       通过观察我决定使用X参数,因为当我摆动连接到手臂上的 M5StickC 跳绳时,陀螺仪的 X 轴(上图中的红色图形)的变化很大,但我仍保留了已其它轴为参数的计数方法。

跳绳计数算法

      通过挥手,我们发现陀螺仪 Y 轴在 +500 和 -500 之间变化,因此我们使用以下算法测量次数:

• 超过 350 时向上,如果低于 -350,则计数一次

• 同理,超过 -350 时向下,如果高于 350,则计数一次

 

配置开发环境(Arduino):

https://docs.m5stack.com/zh_CN/quick_start/m5stickc_plus/arduino

屏幕的使用方法:

https://docs.m5stack.com/zh_CN/api/stickc/lcd_m5stickc

姿态传感器的使用方法:

https://docs.m5stack.com/zh_CN/api/stickc/imu

电源管理芯片使用方法:

https://docs.m5stack.com/zh_CN/api/stickc/axp192_m5stickc

如何通过网络获取时间:

Fp4FL1VrOBqAfzLFHzykhueS7a8c

 

程序:

#include <M5StickCPlus.h>
#include <WiFi.h>
#include "time.h"

#define UPPER_LIMIT 350.0  // jump up threshold
#define LOWER_LIMIT -400.0 // jump down threshold

#define SAMPLE_PERIOD 20   // sampling msec
#define SAMPLE_SIZE 150    // 20ms x150 = 3sec
#define X0 5               // chart horizontal start
#define MINZ -600          // chart vertical max
#define MAXZ  600          // chart vertical min

// 配置所连接wifi的名称和密码
const char* ssid     = "lxbphone";
const char* password = "123456789";

const char* ntpServer =
    "time1.aliyun.com";  // Set the connect NTP server.  设置连接的NTP服务器
const long gmtOffset_sec     = 8*3600; //+8时区
const int daylightOffset_sec = 0; //夏令时为3600

float gyroX = 0.0F;
float gyroY = 0.0F;
float gyroZ = 0.0F;
float gyro  = 0.0F;
float gX[SAMPLE_SIZE];
float gY[SAMPLE_SIZE];
float gZ[SAMPLE_SIZE];

int mode = 0;
uint16_t counter = 0;
int upcount = 0;
int gcounter = 0;
int choose = 0; 

//RTC_TimeTypeDef TimeStruct;
//RTC_DateTypeDef DateStruct;

//double vbat = 0.0;
//int bat;
int Vaps;
int AllVaps = 3554;
int NowV;

void vaps() {
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(168, 33);
  M5.Lcd.printf(" %3d%%", NowV);
}

//void week() {
//  if (DateStruct.WeekDay == 1) {
//    M5.Lcd.printf(" Mon");    
//  } else if (DateStruct.WeekDay == 2) {
//    M5.Lcd.printf("Tues");
//  } else if (DateStruct.WeekDay == 3) {
//    M5.Lcd.printf(" Wed");
//  } else if (DateStruct.WeekDay == 4) {
//    M5.Lcd.printf("Thur");
//  } else if (DateStruct.WeekDay == 5) {
//    M5.Lcd.printf(" Fri");
//  } else if (DateStruct.WeekDay == 6) {
//    M5.Lcd.printf(" Sat");
//  } else if (DateStruct.WeekDay == 7) {
//    M5.Lcd.printf(" Sun");
//  }
//}

void choosed() {
  if (choose == 0) {
    M5.Lcd.printf(" choose X\n");
  } else if (choose == 1) {
    M5.Lcd.printf(" choose Y\n");
  } else if (choose == 2) {
    M5.Lcd.printf(" choose Z\n");
  } 
}

void xyz() {
  if (choose == 0 ) {
    gyro=gyroX;
  } else if (choose == 1 ) {
    gyro=gyroY;
  } else if (choose == 2 ) {
    gyro=gyroZ;
  }
  if (gyro > UPPER_LIMIT) {
    upcount++;
  }
  // 计数
  if (gyro < LOWER_LIMIT && upcount > 0) {
    upcount = 0;
    counter++;
  }
}

void printLocalTime() {  // Output current time.  输出当前时间
    struct tm timeinfo;
    if (!getLocalTime(&timeinfo)) {  // Return 1 when the time is successfully
                                     // obtained.  成功获取到时间返回1
        M5.Lcd.println("Failed to obtain time");
        return;
    }
    
    M5.Lcd.print(&timeinfo,
                   "%A, %B %d \n%Y %H:%M:%S");  // Screen prints date and time.
                                                // 屏幕打印日期和时间
    M5.Lcd.println(" NotDST");
}

void setup() {
  M5.begin();
  Serial.begin(115200);
  M5.Axp.ScreenBreath(8);
  M5.Lcd.setRotation(3); //旋转屏幕
  M5.Lcd.setTextSize(3);
  M5.Lcd.printf("\nConnecting to %s", ssid);
    WiFi.begin(ssid, password);  // Connect wifi and return connection status.
                                 // 连接wifi并返回连接状态
    while (WiFi.status() !=
           WL_CONNECTED) {  // If the wifi connection fails.  若wifi未连接成功
        delay(500);         // delay 0.5s.  延迟0.5s
        M5.Lcd.print(".");
    }
    M5.Lcd.println("\nCONNECTED!");
    configTime(gmtOffset_sec, daylightOffset_sec,
               ntpServer);  // init and get the time.  初始化并设置NTP
  M5.Lcd.fillScreen(BLUE);
  M5.Lcd.setTextColor(BLACK, BLUE);
  M5.IMU.Init();
//  TimeStruct.Hours   = 16; //设置时间
//  TimeStruct.Minutes = 54;
//  TimeStruct.Seconds = 0;
//  M5.Rtc.SetTime(&TimeStruct);
//  DateStruct.WeekDay = 4;  //设置日期
//  DateStruct.Month = 8;
//  DateStruct.Date = 4;
//  DateStruct.Year = 2022;
//  M5.Rtc.SetData(&DateStruct);
}

void loop() {
  M5.update();
  // get gyro data
  M5.IMU.getGyroData(&gyroX, &gyroY, &gyroZ);
  //Serial.printf("%6.2f\n", gyroY);
//  M5.Rtc.GetTime(&TimeStruct); //时间
//  M5.Rtc.GetData(&DateStruct); //日期
  Vaps = M5.Axp.GetVapsData();
  NowV = (Vaps*100)/AllVaps; //剩余电量
  // 选择Y轴
  xyz();

  // Display counter
  if (mode == 0) {
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.setTextSize(2);
//    vbat = M5.Axp.GetVbatData() * 1.1 / 1000;
//    M5.Lcd.printf("vbat:%.3fV ", vbat);
//    bat = M5.Axp.GetPowerbatData()*1.1*0.5/1000;
//    M5.Lcd.printf("battery power:%dmW ", bat);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.setTextSize(2);
    //M5.Lcd.printf("%04d-%02d-%02d ",DateStruct.Year, DateStruct.Month,DateStruct.Date);
    //week();
    //M5.Lcd.printf("%02d:%02d:%02d ",TimeStruct.Hours, TimeStruct.Minutes, TimeStruct.Seconds);
    printLocalTime();
    choosed();
    M5.Lcd.setCursor(0, 70);
    M5.Lcd.setTextSize(6);
    M5.Lcd.printf(" %4d", counter);
    vaps();
  }
  // Draw glaph
  else if (mode == 1) {
    if (++gcounter > SAMPLE_SIZE) {
      gcounter = 0;
      M5.Lcd.fillScreen(GREEN);
    }
    else {
      gX[gcounter] = gyroX;
      int x0 = map((int)(gX[gcounter - 1]), MINZ, MAXZ, M5.Lcd.height(), 0);
      int x1 = map((int)(gX[gcounter]), MINZ, MAXZ, M5.Lcd.height(), 0);
      M5.Lcd.setCursor(0, 0);
      M5.Lcd.setTextSize(2);
//      M5.Lcd.printf("%04d-%02d-%02d ",DateStruct.Year, DateStruct.Month,DateStruct.Date);
//      week();
//      M5.Lcd.printf("%02d:%02d:%02d ",TimeStruct.Hours, TimeStruct.Minutes, TimeStruct.Seconds);
      printLocalTime();
      M5.Lcd.printf(" X ");
      M5.Lcd.printf("Nem:%4d\n", counter);
      vaps();
      M5.Lcd.drawLine(gcounter - 1 + X0, x0, gcounter + X0, x1, WHITE);
    }
  } 
  else if (mode == 2) {
    if (++gcounter > SAMPLE_SIZE) {
      gcounter = 0;
      M5.Lcd.fillScreen(GREEN);
    }
    else {
      gY[gcounter] = gyroY;
      int y0 = map((int)(gY[gcounter - 1]), MINZ, MAXZ, M5.Lcd.height(), 0);
      int y1 = map((int)(gY[gcounter]), MINZ, MAXZ, M5.Lcd.height(), 0);
      M5.Lcd.setCursor(0, 0);
      M5.Lcd.setTextSize(2);
//      M5.Lcd.printf("%04d-%02d-%02d ",DateStruct.Year, DateStruct.Month,DateStruct.Date);
//      week();
//      M5.Lcd.printf("%02d:%02d:%02d ",TimeStruct.Hours, TimeStruct.Minutes, TimeStruct.Seconds);
      printLocalTime();
      M5.Lcd.printf(" Y ");
      M5.Lcd.printf("Nem:%4d\n", counter);
      vaps();
      M5.Lcd.drawLine(gcounter - 1 + X0, y0, gcounter + X0, y1, RED);
    }
  }
  else if (mode == 3) {
    if (++gcounter > SAMPLE_SIZE) {
      gcounter = 0;
      M5.Lcd.fillScreen(GREEN);
    }
    else {
      gZ[gcounter] = gyroZ;
      int z0 = map((int)(gZ[gcounter - 1]), MINZ, MAXZ, M5.Lcd.height(), 0);
      int z1 = map((int)(gZ[gcounter]), MINZ, MAXZ, M5.Lcd.height(), 0);
      M5.Lcd.drawLine(gcounter - 1 + X0, z0, gcounter + X0, z1, BLUE);
      M5.Lcd.setCursor(0, 0);
      M5.Lcd.setTextSize(2);
//      M5.Lcd.printf("%04d-%02d-%02d ",DateStruct.Year, DateStruct.Month,DateStruct.Date);
//      week();
//      M5.Lcd.printf("%02d:%02d:%02d ",TimeStruct.Hours, TimeStruct.Minutes, TimeStruct.Seconds);
      printLocalTime();
      M5.Lcd.printf(" Z ");
      M5.Lcd.printf("Nem:%4d\n", counter);
      vaps();
    }
  } 

  // BtnA change display mode
  if (M5.BtnA.wasReleasefor(500)) {
    mode = (mode+1)%4;
    gcounter = 0;
    //counter = 0;
    if (mode == 0) {
      choose = 1;
      M5.Lcd.fillScreen(BLUE);
      M5.Lcd.setTextColor(BLACK, BLUE);  
    } else {
      choose = mode-1;
      M5.Lcd.fillScreen(GREEN);
      M5.Lcd.setTextColor(BLACK, GREEN);
    }
  }
  //长按B键0,切换轴的选取
  if ((M5.BtnB.wasReleasefor(3000)) and (mode == 0)) {
    choose = (choose+1)%3;
    M5.Lcd.fillScreen(BLUE);
    M5.Lcd.setTextColor(BLACK, BLUE);   
  }  
  // BtnB reset counter
  if (M5.BtnB.wasReleasefor(500)) {
    counter = 0;
    if (mode == 0) {
      M5.Lcd.fillScreen(BLUE);
      M5.Lcd.setTextColor(BLACK, BLUE);  
    } else {
      M5.Lcd.fillScreen(GREEN);
      M5.Lcd.setTextColor(BLACK, GREEN);
    }
  }
  delay(SAMPLE_PERIOD);
}

 

感悟:

        开发起来不复杂,对新人比较友好,就是跳绳技术方面不太灵敏,也可能是阈值设的还是有点高了,不过相差两个还是在误差的可接受范围内。

 

团队介绍
电子信息工程学院大四信抗
团队成员
lxb
哈尔滨工业大学电信学院信息对抗专业
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号