零知派ESP32-ADXL362体感迷宫游戏

电子说

1.4w人已加入

描述

✔零知派(零知开源)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 “配置环境” 转移到 “创意实现”,极大降低了技术门槛。零知开源编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知派(零知开源)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!

✔访问零知实验室,获取更多实战项目和教程资源吧!

www.lingzhilab.com

项目概述

        你是否想过用自己的身体倾斜来控制一个小球,在一个迷宫里探索并抵达终点?本项目利用零知派ESP32开发板、ADXL362三轴加速度计ST7789彩色显示屏,制作了一个体感控制的迷宫游戏。当你倾斜开发板时,小球会向相反方向滚动,你需要灵活控制倾斜角度,避开深灰色的墙壁,最终抵达绿色的终点。游戏采用局部刷新机制,运行流畅无拖尾,非常适合学习嵌入式图形界面、传感器数据处理和物理模拟。

项目亮点

体感控制:使用ADXL362加速度计检测倾斜方向,小球运动方向与倾斜方向相反,操控直观有趣。

平滑物理引擎:加入速度、阻尼、加速度和最大速度限制,小球滚动真实自然。

局部刷新:仅更新小球经过的格子,大幅减少屏幕绘制开销,无闪烁拖尾。

精准碰撞检测:基于圆形边界和网格遍历,确保小球不会穿墙。

死区校准:通过偏移量和死区阈值消除静止时的抖动,操作稳定。

项目难点及解决方案

难点 解决方案
加速度计数据抖动导致小球乱动 加入软件校准偏移量和死区阈值(DEADZONE),静止时忽略微小加速度
快速倾斜时小球穿墙 分轴移动(先X后Y),碰撞时回退并置零速度;限制最大速度MAX_SPEED
屏幕刷新拖尾现象 局部擦除:小球移动后,仅重绘旧位置覆盖的格子,再在新位置绘制小球
方向反直觉(倾斜左边小球却向右) 在代码中对加速度值取反:accX = -rawX * SENSITIVITY,让小球与倾斜方向相反
SPI总线冲突(加速度计与屏幕共用) 每次读取加速度计时使用SPI事务(beginTransaction/endTransaction),确保时序正确

一、硬件系统部分

1.1 硬件清单

组件 型号/规格 数量
主控板 零知派ESP32 1
扩展板 零知派ESP32扩展板  
加速度计 ADXL362(三轴数字输出) 1
显示屏 ST7789(240×240分辨率,SPI接口) 1
连接线 杜邦线(母对母) 若干
面包板 通用型 1

1.2 接线方案

        本项目使用两个SPI设备:ADXL362加速度计和ST7789显示屏。为了不冲突,将它们连接到不同的CS片选引脚,而共用SCK、MOSI、MISO(ADXL362需要MISO,ST7789通常不需要MISO)。参考接线如下:

ESP32引脚 连接ADXL362 说明
3.3V VCC 供电
GND GND 共地
GPIO18 SCK SPI时钟线
GPIO23 MOSI SPI数据线(主机输出)
GPIO19 MISO 仅ADXL362需要读取数据
GPIO5 CS ADXL362片选(可自定义)

注意:因为有零知派扩展板,ST7789不需要接线,只需要把屏幕插到零知派扩展板上就可以了。

1.3 硬件连接图

游戏

1.4 实物连接图

游戏

二、软件架构设计

2.1 系统初始化

setup() 函数中按顺序执行:

串口初始化(用于调试)。

自动寻找终点(最后一个路径格子作为绿色终点)。

初始化TFT屏幕,设置旋转方向,清屏为黑色。

初始化ADXL362加速度计,设置为测量模式。

绘制完整的静态迷宫(墙壁、路径、终点)。

绘制初始红色小球。

2.2 主循环逻辑

loop() 中无限循环执行以下步骤(仅在非胜利状态):

读取加速度计数据:通过SPI事务获取原始X、Y轴值。

校准与死区处理:加上偏移量,小于阈值则置零。

方向取反并计算加速度:accX = -rawX * SENSITIVITY,accY = rawY * SENSITIVITY。

更新速度:velX = velX * DAMPING + accX,并限制最大速度。

移动并碰撞检测:先尝试X方向移动,若碰撞则回退并清零速度;再尝试Y方向。

胜利检测:若小球所在格子等于终点坐标,显示胜利文字,停止更新。

局部刷新:若整数位置改变,则擦除旧小球背景并绘制新小球。

延时:delay(50) 控制帧率约20FPS。

2.3 运动检测与碰撞算法

运动检测:通过对加速度值的积分(累加到速度)模拟惯性,阻尼项模拟空气阻力。

碰撞检测函数 collidesWithWall():

根据小球圆心坐标和半径,计算出小球外接正方形的四个边界。

求出正方形覆盖的网格行列范围。

遍历这些格子,若任一格子为墙壁(值1)或超出迷宫边界,则判定碰撞。

分轴移动:先移动X轴,若碰撞则回退;再移动Y轴。这样可以避免同时移动时卡入墙壁角落。

2.4 系统校准

ADXL362平放时理论输出为(0,0,1g)。但实际电路存在零偏,因此需要软校准:

偏移量:CALIB_X_OFFSET = 85,CALIB_Y_OFFSET = 55。可以通过读取平放时的数值,取平均值后填入,根据自己的实际情况来调整。

死区阈值:DEADZONE = 40,当校准后的绝对值小于40时视为0,消除微小抖动引起的误触发,根据自己的实际情况来调整。

调试技巧:在串口监视器中打印 rawX, rawY,将开发板水平静止,记录读数,将其相反数填入偏移量。例如静止时X输出为-85,则偏移量设为+85。

2.5 加速度数据采集

ADXL362通过SPI读取三轴数据(16位有符号整数)。代码中调用 accel.readXYZTData(rawX, rawY, rawZ, rawTemp) 一次性读取。注意每次读取前使用 SPI.beginTransaction 确保SPI总线不被其他设备(如屏幕)干扰,读取后立即 endTransaction。

三、代码拆分讲解

3.1 头文件与全局定义

 

#include < SPI.h >
#include < ADXL362.h >
#include < TFT_eSPI.h >
游戏

 

TFT_eSPI 库需要提前配置 User_Setup.h 指定屏幕引脚和驱动。

迷宫尺寸为12×12,每个格子20×20像素。小球半径8像素,略小于半格,以便在通道中顺畅移动。

3.2 迷宫地图

使用二维数组 maze[12][12] 定义墙壁(1)和路径(0)。起点固定在(1,1)格子,终点自动搜索最后一个0格子。你可以自由修改迷宫地图,只要保持四周为墙壁即可。

3.3 碰撞检测函数

 

bool collidesWithWall() {
  int left   = ballX - BALL_RADIUS;
  int right  = ballX + BALL_RADIUS;
  int top    = ballY - BALL_RADIUS;
  int bottom = ballY + BALL_RADIUS;
  int startX = left / CELL_SIZE;
  int endX   = right / CELL_SIZE;
  // ... 遍历这些格子
  if (maze[y][x] == 1) return true;
}
游戏

 

使用整数除法获取格子索引,避免了复杂的浮点运算。

对于圆形小球,使用矩形边界会略微严格,但在格子大小为20、半径为8的情况下,效果良好且简单。

3.4 局部刷新机制

传统做法是每帧清屏重绘,效率低且有闪烁。本代码采用:

drawBall() 绘制圆形红色小球。

restoreBallBackground() 恢复旧位置被覆盖的格子:根据旧坐标范围,调用 drawCell() 重绘每个格子(墙壁、终点或黑色路径)。

只有当小球整像素位置改变时才执行擦除和重绘,减少了不必要的绘制。

3.5 速度阻尼与最大速度限制

 

velX = velX * DAMPING + accX;
velX = constrain(velX, -MAX_SPEED, MAX_SPEED);
游戏

 

DAMPING = 0.97 使得速度每帧衰减3%,没有加速度时小球会逐渐停下。

MAX_SPEED = 5.0 限制单帧最大移动5像素,避免速度过快一次穿透多个格子。

3.6 方向取反的用意

为了让操作更符合直觉:向右倾斜开发板,小球应该向左滚动(模拟重力作用)。因此代码中 accX = -rawX * SENSITIVITY,而Y轴未取反是因为屏幕坐标系Y轴向下为正,倾斜向前(Y负方向)让小球向下滚动,符合直觉。

四、操作过程及数据展示

4.1 操作步骤

烧录程序:将零知派ESP32通过USB连接电脑,在Arduino IDE中选择正确的开发板和端口,点击上传。

放置水平:将开发板平放在桌面上,此时小球应静止在起点位置。

倾斜控制

向前(远离你)倾斜 → 小球向下(屏幕下方)滚动

向后(靠近你)倾斜 → 小球向上滚动

向左倾斜 → 小球向右滚动

向右倾斜 → 小球向左滚动

目标:在不触碰灰色墙壁的情况下,将红色小球滚到绿色终点区域。抵达后屏幕显示“YOU WIN!”并停止。

重置游戏:按开发板上的RST按钮重新开始。

4.2 数据展示(调试信息)

在 setup() 中开启 Serial.begin(115200),上传后打开串口监视器可以看到加速度计原始值(需取消注释相应 Serial.print)。示例输出(平放时):

 

rawX = 85, rawY = 55
游戏

 

若倾斜30度左右,rawX可能变化到300~500。通过调整 SENSITIVITY 可以改变灵敏度,数值越大小球对倾斜越敏感。

4.3 演示视频

五、ADXL362 加速度计技术原理

5.1 工作原理

        ADXL362是一款三轴MEMS加速度计,它内部有一个微小的质量块,当发生加速度时,质量块会位移,导致电容变化,通过电路转换为数字量输出。与传统加速度计不同,ADXL362具有超低功耗(测量模式下仅1.8μA),非常适合电池供电的可穿戴或手持设备。它通过SPI接口通信,输出16位二进制补码值,量程可配置为±2g/±4g/±8g,本项目使用默认±2g。

5.2 工作模式配置

代码中通过以下步骤配置ADXL362:

accel.begin(CS_PIN):初始化SPI,设置片选引脚。

accel.beginMeasure():将芯片从待机模式切换到测量模式,开始连续采集数据。

读取数据时调用 accel.readXYZTData(rawX, rawY, rawZ, rawTemp),它会自动发送SPI命令读取四个寄存器(X、Y、Z、温度)。

常用寄存器配置

滤波器控制寄存器(0x2C):默认配置即可,输出数据速率100Hz。

活动/非活动阈值寄存器:本项目未使用,可忽略。

如果你想改变量程,可以调用 accel.setRange(ADXL362_RANGE_4G) 等。

六、常见问题指引

Q1: 编译报错 “TFT_eSPI.h: No such file or directory”

解决:需要先安装TFT_eSPI库。在Arduino IDE中打开“项目” → “加载库” → “管理库”,搜索“TFT_eSPI”并安装。然后需要配置 User_Setup.h 文件,根据你屏幕的实际引脚修改(例如设置 TFT_CS、TFT_DC、TFT_RST 等)。

Q2: 小球完全不动或乱动

检查ADXL362的接线,尤其CS引脚是否正确。

打开串口监视器,查看 rawX, rawY 数值是否随倾斜变化。若无变化,检查SPI接线或尝试更换ADXL362库。

调整 CALIB_X_OFFSET 和 CALIB_Y_OFFSET,使静止时输出接近0。

Q3: 小球穿墙或卡在墙壁里

减小 MAX_SPEED 值(例如改为3.0),使每帧移动距离更小。

检查碰撞检测中 BALL_RADIUS 是否过大(应小于半格10像素)。

确保分轴移动逻辑正确,不要同时移动XY。

Q4: 屏幕显示残影/拖尾

检查 restoreBallBackground() 是否被正确调用。可以在移动条件中加入串口打印,确认每次位置改变时确实执行了擦除。

确认 drawCell() 中路径填充的是 TFT_BLACK,终点填充绿色,墙壁填充深灰色。

如果仍出现问题,可以尝试在 restoreBallBackground() 中扩大擦除范围(例如边界多扩展一个像素)。

Q5: 游戏胜利后不显示“YOU WIN!”

检查终点坐标是否正确被自动找到。可在 setup() 结束后串口打印 goalX, goalY。

确认胜利判断条件 cellX == goalX && cellY == goalY 中,cellX 和 cellY 是通过整数除法获得,由于小球半径存在,可能圆心落在终点格子内但边缘超出?一般不会,因为终点格子也是20×20,圆心只要在格子内即可。

可尝试将胜利条件改为:if (maze[cellY][cellX] == 0 && cellX == goalX && cellY == goalY)。

Q6: 如何修改迷宫地图?

直接修改 maze[12][12] 数组中的数字:0为路,1为墙。注意保持起点(1,1)和终点(自动找最后一个0)是通路。也可以手动指定终点坐标。

Q7: 方向感相反(倾斜左边小球向左)怎么办?

如果希望倾斜方向与小球运动方向相同,去掉 accX 前面的负号:accX = rawX * SENSITIVITY。但通常推荐相反方向,因为模拟真实重力的感觉。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分