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屏幕上显示个数
已实现的功能:
(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
如何通过网络获取时间:
程序:
#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);
}
感悟:
开发起来不复杂,对新人比较友好,就是跳绳技术方面不太灵敏,也可能是阈值设的还是有点高了,不过相差两个还是在误差的可接受范围内。