这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【M5STACKTAB5W/OBATTERY】【四】IMU位置实时绘图

共1条 1/1 1 跳转至

【M5STACKTAB5W/OBATTERY】【四】IMU位置实时绘图

工程师
2026-01-22 21:05:44     打赏

简介

在完成了基于 M5 Stack Tab5 的简易电子书阅读器之后(SD卡和屏幕模块),我又把注意力移到了其他的外设资源上。
Tab5 内置了 IMU(惯性测量单元),如果只是简单地在串口里打印数据,显然有点浪费这块大尺寸高分辨率屏幕。我的目的是来做一个贪吃蛇的游戏。但是前置条件要求我必须要先读取IMU的数据。

于是我决定在 Arduino 环境下,基于 M5Unified + M5GFX,做一个实时 IMU 数据可视化界面(实现和官方出厂固件中一样的效果)

1、数据实时、稳定刷新

2、UI 清晰、结构直观

3、尽量减少屏幕闪烁(刷新的时候)

最终实现的是一个IMU 实时监控面板:左侧显示加速度计,右侧显示陀螺仪,中间用一个圆形指示器直观展示设备当前的“倾斜方向”。


主要效果如下所示

aef114bd848f1b173e300d9b503c9bc8.jpg

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

a6535b7d08ec34f5fe2b45f2082db045.jpg

向右倾斜

d2ad026903b8b079243a81348fa4d1ed.jpg

向左倾斜

23da5a8f45a7f97eddd53788bae6a456.jpg

向上倾斜

9aaf274cfce7f3313765cfcb836b0bd3.jpg

向下倾斜


整体界面设计思路

整个界面被拆分为三块:

1- 顶部标题区
        显示当前功能:IMU Realtime Data

2- 左右数据区

        左侧:Accelerometer(X / Y / Z) 加速度传感器

        右侧:Gyroscope(X / Y / Z)(陀螺仪)

中部方向指示器

        一个固定圆环

        圆内红点,表示当前设备的倾斜方向(基于加速度)

由于M5 stack 已经将这个板子上所有的外设的操作都已经封装到 M5Unified 库中了,所以只需要使用库中提供的API函数即可。官网文档也给出了详细的资料。


image.png

基于上述的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;
}


视频效果如下




关键词: M5STACKTAB5W     IMU     绘图    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]