✔零知派(零知开源)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比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。但通常推荐相反方向,因为模拟真实重力的感觉。
-
单片机
+关注
关注
6078文章
45636浏览量
675964 -
游戏
+关注
关注
2文章
798浏览量
27608 -
开源
+关注
关注
3文章
4435浏览量
46628
发布评论请先 登录
零知开源——ESP32语音交互系统(AI小智)开发教程
零知派——ESP32‑S3 基于 ESP32-CAM 驱动 OV3660 摄像头模块开发
零知派——基于ESP32的BLE Mesh蓝牙组网系统(iOS/Android APP控制)
EVADXL362Z ADXL362 分线板
零知派ESP32-ADXL362体感迷宫游戏
评论