简介
在完成了基于 M5 Stack Tab5 的简易电子书阅读器之后(SD卡和屏幕模块),我又把注意力移到了其他的外设资源上。
Tab5 内置了 IMU(惯性测量单元),如果只是简单地在串口里打印数据,显然有点浪费这块大尺寸高分辨率屏幕。我的目的是来做一个贪吃蛇的游戏。但是前置条件要求我必须要先读取IMU的数据。
于是我决定在 Arduino 环境下,基于 M5Unified + M5GFX,做一个实时 IMU 数据可视化界面(实现和官方出厂固件中一样的效果)
1、数据实时、稳定刷新
2、UI 清晰、结构直观
3、尽量减少屏幕闪烁(刷新的时候)
最终实现的是一个IMU 实时监控面板:左侧显示加速度计,右侧显示陀螺仪,中间用一个圆形指示器直观展示设备当前的“倾斜方向”。
主要效果如下所示

平放的状态。传感器漂移很小

向右倾斜

向左倾斜

向上倾斜

向下倾斜
整体界面设计思路
整个界面被拆分为三块:
1- 顶部标题区
显示当前功能:IMU Realtime Data
2- 左右数据区
左侧:Accelerometer(X / Y / Z) 加速度传感器
右侧:Gyroscope(X / Y / Z)(陀螺仪)
中部方向指示器
一个固定圆环
圆内红点,表示当前设备的倾斜方向(基于加速度)
由于M5 stack 已经将这个板子上所有的外设的操作都已经封装到 M5Unified 库中了,所以只需要使用库中提供的API函数即可。官网文档也给出了详细的资料。

基于上述的API,进行修改便得到了下面的代码 (代码可以直接拷贝使用)
#include <M5Unified.h>
#include <M5GFX.h>
m5::imu_data_t imuData;
bool firstDraw = true;
void setup()
{
M5.begin();
M5.Display.setRotation(2);
M5.Display.clear(TFT_BLACK);
// 绘制静态内容
drawStaticContent();
}
void drawStaticContent()
{
// 设置标题
M5.Display.setFont(&fonts::FreeMonoBold18pt7b);
M5.Display.setTextColor(TFT_WHITE);
M5.Display.setTextDatum(top_center);
M5.Display.drawString("IMU Realtime Data", M5.Display.width() / 2, 5);
// 设置数据字体
M5.Display.setFont(&fonts::FreeMonoBold12pt7b);
M5.Display.setTextDatum(top_left);
int lineHeight = 22;
int startY = 80;
int col1X = 80;
int col2X = M5.Display.width() / 2 + 80;
// 第一列 - 加速度计标签
M5.Display.setCursor(col1X, startY);
M5.Display.setTextColor(TFT_GREEN);
M5.Display.println("ACCELEROMETER");
M5.Display.setTextColor(TFT_WHITE);
M5.Display.setCursor(col1X, startY + lineHeight);
M5.Display.println("X: ");
M5.Display.setCursor(col1X, startY + lineHeight * 2);
M5.Display.println("Y: ");
M5.Display.setCursor(col1X, startY + lineHeight * 3);
M5.Display.println("Z: ");
// 第二列 - 陀螺仪标签
M5.Display.setCursor(col2X, startY);
M5.Display.setTextColor(TFT_CYAN);
M5.Display.println("GYROSCOPE");
M5.Display.setTextColor(TFT_WHITE);
M5.Display.setCursor(col2X, startY + lineHeight);
M5.Display.println("X: ");
M5.Display.setCursor(col2X, startY + lineHeight * 2);
M5.Display.println("Y: ");
M5.Display.setCursor(col2X, startY + lineHeight * 3);
M5.Display.println("Z: ");
// 绘制方向指示器圆圈(静态)
drawOrientationCircle();
}
void drawOrientationCircle()
{
int centerX = M5.Display.width() / 2;
int centerY = M5.Display.height() / 2;
int radius = 80;
// 绘制圆形指示器边框
M5.Display.drawCircle(centerX, centerY, radius, TFT_WHITE);
// 添加方向标签
M5.Display.setFont(&fonts::FreeMonoBold12pt7b);
M5.Display.setTextColor(TFT_DARKGREY);
M5.Display.setCursor(centerX - 35, centerY - radius - 22);
M5.Display.println("Orientation");
}
void loop()
{
M5.update();
M5.Imu.update();
imuData = M5.Imu.getImuData();
// 设置数据字体
M5.Display.setFont(&fonts::FreeMonoBold12pt7b);
M5.Display.setTextColor(TFT_WHITE);
M5.Display.setTextDatum(top_left);
int lineHeight = 22;
int startY = 80;
int col1X = 80;
int col2X = M5.Display.width() / 2 + 80;
// 清除数值区域(只清除数值部分,不清除标签)
// 加速度计数值区域 - 分别清除每一行
M5.Display.fillRect(col1X + 25, startY + lineHeight, 110, lineHeight + 2, TFT_BLACK);
M5.Display.fillRect(col1X + 25, startY + lineHeight * 2, 110, lineHeight + 2, TFT_BLACK);
M5.Display.fillRect(col1X + 25, startY + lineHeight * 3, 110, lineHeight + 2, TFT_BLACK);
// 陀螺仪数值区域 - 分别清除每一行
M5.Display.fillRect(col2X + 25, startY + lineHeight, 110, lineHeight + 2, TFT_BLACK);
M5.Display.fillRect(col2X + 25, startY + lineHeight * 2, 110, lineHeight + 2, TFT_BLACK);
M5.Display.fillRect(col2X + 25, startY + lineHeight * 3, 110, lineHeight + 2, TFT_BLACK);
// 更新加速度计数值
M5.Display.setCursor(col1X + 25, startY + lineHeight);
M5.Display.printf("%+7.3f", imuData.accel.x);
M5.Display.setCursor(col1X + 25, startY + lineHeight * 2);
M5.Display.printf("%+7.3f", imuData.accel.y);
M5.Display.setCursor(col1X + 25, startY + lineHeight * 3);
M5.Display.printf("%+7.3f", imuData.accel.z);
// 更新陀螺仪数值
M5.Display.setCursor(col2X + 25, startY + lineHeight);
M5.Display.printf("%+7.3f", imuData.gyro.x);
M5.Display.setCursor(col2X + 25, startY + lineHeight * 2);
M5.Display.printf("%+7.3f", imuData.gyro.y);
M5.Display.setCursor(col2X + 25, startY + lineHeight * 3);
M5.Display.printf("%+7.3f", imuData.gyro.z);
// 更新方向指示器
updateOrientationIndicator();
delay(50); // 稍微快一点的更新
}
void updateOrientationIndicator()
{
int centerX = M5.Display.width() / 2;
int centerY = M5.Display.height() / 2;
int radius = 80;
// 清除之前的红点
static int lastDotX = centerX;
static int lastDotY = centerY;
M5.Display.fillCircle(lastDotX, lastDotY, 4, TFT_BLACK);
// 根据加速度计数据绘制方向点
int dotX = centerX + (imuData.accel.x / 2.0) * radius;
int dotY = centerY + (imuData.accel.y / 2.0) * radius;
// 限制在圆内
float dist = sqrt((dotX - centerX) * (dotX - centerX) + (dotY - centerY) * (dotY - centerY));
if (dist > radius)
{
dotX = centerX + (dotX - centerX) * radius / dist;
dotY = centerY + (dotY - centerY) * radius / dist;
}
M5.Display.fillCircle(dotX, dotY, 3, TFT_RED);
// 保存当前位置用于下次清除
lastDotX = dotX;
lastDotY = dotY;
}其主要的核心代码就是下述,更具当前传感器的值来重新更新红点在圆内的位置。然后进行重新绘图。
void updateOrientationIndicator()
{
int centerX = M5.Display.width() / 2;
int centerY = M5.Display.height() / 2;
int radius = 80;
// 清除之前的红点
static int lastDotX = centerX;
static int lastDotY = centerY;
M5.Display.fillCircle(lastDotX, lastDotY, 4, TFT_BLACK);
// 根据加速度计数据绘制方向点
int dotX = centerX + (imuData.accel.x / 2.0) * radius;
int dotY = centerY + (imuData.accel.y / 2.0) * radius;
// 限制在圆内
float dist = sqrt((dotX - centerX) * (dotX - centerX) + (dotY - centerY) * (dotY - centerY));
if (dist > radius)
{
dotX = centerX + (dotX - centerX) * radius / dist;
dotY = centerY + (dotY - centerY) * radius / dist;
}
M5.Display.fillCircle(dotX, dotY, 3, TFT_RED);
// 保存当前位置用于下次清除
lastDotX = dotX;
lastDotY = dotY;
}视频效果如下
我要赚赏金
