零知开源——基于STM32F103RBT6与ADXL362三轴加速度计的体感迷宫游戏设计与实现

 ✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!

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

www.lingzhilab.com

目录

一、硬件系统设计

1.1 硬件清单

1.2 接线方案

1.3 硬件连接图

1.4 接线实物图 

二、软件系统设计

2.1 头文件与引脚定义

2.2 对象初始化与全局变量

2.3 核心函数详解

2.4 完整代码

三、操作结果展示

3.1 操作流程

3.2 显示屏界面分布

3.3 视频演示

四、ADXL362的SPI接口技术

4.1 SPI通信方式

4.2 SPI数据传输和接收机制

4.3 加速度数据格式和解析

五、常见问题解答 (FAQ)

Q1: 为什么我的串口打印是乱码?

Q2: 小球控制不灵敏或反向?

Q3: 小球穿墙bug如何处理?


(1)项目概述

        本项目是一个有趣的嵌入式体感交互游戏。核心功能是通过一个三维加速度传感器ADXL362来检测开发板的倾斜姿态,从而控制屏幕上的小球在迷宫内移动,躲避墙壁,最终抵达随机生成的目标点。项目综合了传感器数据采集、数据处理、图形显示、碰撞检测等多个嵌入式开发的关键知识点 

(2)项目亮点

        >通过倾斜ADXL362传感器进行体感交互操作游戏
        >实时绘制迷宫、小球和目标点,视觉效果清晰
        >系统启动时自动校准加速度计,消除静态误差
        >采用局部刷新策略,避免整屏刷新带来的闪烁

(3)项目难点及解决方案

        问题1描述:简单的中心点检测会导致小球“嵌”入墙壁。

解决方案:

         采用多点检测法,同时检测小球的上、下、左、右四个边缘点以及四个角点,确保任何部位触墙都能被准确识别。

        问题2描述:传感器原始数据存在噪声和偏移,导致小球无故移动。

解决方案:

        软件死区: 设置一个阈值 (deadZone),忽略微小的加速度值。
        开机校准: 在 setup() 阶段读取多次数据求平均值,将后续读数减去此平均值以消除零偏。

一、硬件系统设计

1.1 硬件清单

组件数量说明
零知标准板1主控制器,基于STM32F103RBT6。
ST7789 TFT屏11.3英寸,240x240分辨率,SPI接口。
ADXL362加速度计1超低功耗,SPI/I2C接口,本项目使用SPI。
杜邦线若干用于连接各模块。
Micro USB线1为开发板供电和程序下载。

1.2 接线方案

请严格按照代码中的引脚定义进行连接:

(1)ADXL362 传感器接线

ADXL362引脚零知标准板引脚功能
VCC3.3V电源
GNDGND
CS10片选(SPI)
MOSI11 (硬件SPI)SPI主出从入
MISO12 (硬件SPI)SPI主入从出
SCLK13 (硬件SPI)SPI时钟

(2)ST7789显示屏接线

ST7789引脚零知标准板引脚功能
VCC3.3V 或 5V*电源 (*视屏幕型号而定)
GNDGND
CS6片选(软件SPI)
DC2数据/命令控制
RST4复位
MOSI8软件SPI数据线
SCK7软件SPI时钟线
BL3.3V背光(可选)

        PS:代码中TFT屏使用了软件SPI(引脚7, 8),而ADXL362使用了硬件SPI(引脚10,11,12,13)

1.3 硬件连接图

1.4 接线实物图 

  

二、软件系统设计

2.1 头文件与引脚定义

        BALL_RADIUS: 决定了小球的大小和碰撞检测范围。
        MAZE_CELL_SIZE: 决定了迷宫的精细度。增大它会使迷宫更简单,小球移动空间更大。
        MAZE_WIDTH & MAZE_HEIGHT: 由屏幕分辨率和单元格大小自动计算得出。

#include <SPI.h>
#include <Wire.h> // 本项目未使用I2C,可移除
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <ADXL362.h>// ST7789 显示屏引脚定义 (使用软件SPI)
#define TFT_CS   6
#define TFT_RST  4
#define TFT_DC   2
#define TFT_MOSI 8
#define TFT_SCLK 7// 定义显示屏参数
#define SCREEN_WIDTH  240
#define SCREEN_HEIGHT 240// 颜色定义 (ST77XX_ 是Adafruit库预定义的颜色)
#define BACKGROUND  ST77XX_BLACK
#define TEXT_COLOR  ST77XX_WHITE
#define WALL_COLOR  ST77XX_BLUE
#define BALL_COLOR  ST77XX_RED
#define TARGET_COLOR ST77XX_GREEN
#define PATH_COLOR ST77XX_WHITE // 路径颜色,通常为背景色或浅色// 游戏参数
#define BALL_RADIUS 8 // 小球像素半径
#define MAZE_CELL_SIZE 20 // 每个迷宫单元格的像素大小
#define MAZE_WIDTH (SCREEN_WIDTH / MAZE_CELL_SIZE) // 迷宫宽度(单元格数)
#define MAZE_HEIGHT (SCREEN_HEIGHT / MAZE_CELL_SIZE) // 迷宫高度(单元格数)

2.2 对象初始化与全局变量

        maze数组: 这是一个二维数组,完全定义了迷宫的布局。修改这个数组就可以创造全新的关卡。1代表墙,0代表可通行的路径。

Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
ADXL362 xl;// 小球和目标的初始位置(位于迷宫左上角和右下角路径的中央)
int ballX = MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2;
int ballY = MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2;
int targetX = (MAZE_WIDTH - 2) * MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2;
int targetY = (MAZE_HEIGHT - 2) * MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2;// 迷宫地图 (0=路径, 1=墙)
byte maze[MAZE_HEIGHT][MAZE_WIDTH] = { ... }; // 此处省略数组内容// 加速度校准值
int16_t calibX = 0;
int16_t calibY = 0;
int16_t calibZ = 0;

2.3 核心函数详解

(1)calibrateAccelerometer() - 传感器校准

void calibrateAccelerometer() {Serial.println("Calibrating accelerometer...");// 读取多次样本并取平均值long sumX = 0, sumY = 0, sumZ = 0;int samples = 20;for (int i = 0; i < samples; i++) {xl.readXYZTData(accelX, accelY, accelZ, temp);sumX += accelX;sumY += accelY;sumZ += accelZ;delay(10);}calibX = sumX / samples;calibY = sumY / samples;calibZ = sumZ / samples;
}

(2)clearBall() - 优化的小球清除

        避免了重绘整个屏幕或整个单元格,极大减少了刷新时间,消除了画面闪烁,精确地恢复小球所占区域的原始迷宫图案。

void clearBall(int x, int y) {// 只清除小球区域,不重新绘制整个迷宫// 创建一个临时缓冲区来存储小球区域的背景uint16_t bgBuffer[(BALL_RADIUS*2+2) * (BALL_RADIUS*2+2)];// 确定要清除的区域int clearX1 = max(0, x - BALL_RADIUS - 1);int clearY1 = max(0, y - BALL_RADIUS - 1);int clearX2 = min(SCREEN_WIDTH - 1, x + BALL_RADIUS + 1);int clearY2 = min(SCREEN_HEIGHT - 1, y + BALL_RADIUS + 1);int width = clearX2 - clearX1 + 1;int height = clearY2 - clearY1 + 1;// 绘制正确的背景for (int py = clearY1; py <= clearY2; py++) {for (int px = clearX1; px <= clearX2; px++) {// 计算像素到小球中心的距离int dx = px - x;int dy = py - y;int distance = dx*dx + dy*dy;// 如果在小球半径范围内,则绘制正确的背景if (distance <= (BALL_RADIUS+1)*(BALL_RADIUS+1)) {// 获取像素所在的迷宫单元格int cellX = px / MAZE_CELL_SIZE;int cellY = py / MAZE_CELL_SIZE;// 根据单元格类型选择颜色uint16_t color = (maze[cellY][cellX] == 1) ? WALL_COLOR : PATH_COLOR;// 绘制像素tft.drawPixel(px, py, color);}}}
}

(3)updateBallPosition() - 控制逻辑

        deadZone: 死区范围。绝对值小于50的加速度读数将被视为噪声并忽略,防止小球轻微抖动。

void updateBallPosition() {const int deadZone = 50; // 死区阈值,过滤微小抖动xl.readXYZTData(accelX, accelY, accelZ, temp); // 读取数据accelX -= calibX; // 应用校准accelY -= calibY;int moveX = 0;int moveY = 0;// 映射加速度值到移动速度if (abs(accelX) > deadZone) {moveX = map(accelX, -200, 200, -5, 5); // 映射范围可调整}if (abs(accelY) > deadZone) {moveY = map(accelY, -200, 200, -5, 5);}ballX = constrain(ballX + moveX, BALL_RADIUS, SCREEN_WIDTH - BALL_RADIUS);ballY = constrain(ballY - moveY, BALL_RADIUS, SCREEN_HEIGHT - BALL_RADIUS); // 注意Y轴是减
}

(4)checkCollision() 与 checkPointCollision() - 碰撞检测

        综合检查四边中点四个角点共8个点是否进入墙壁单元格。这种多点检测法比只检查中心点要准确得多,能有效防止卡墙和穿墙的Bug

bool checkCollision() {// 检查小球边缘的四个中点int top = ballY - BALL_RADIUS;int cellYTop = top / MAZE_CELL_SIZE;if (maze[cellYTop][ballX/MAZE_CELL_SIZE] == 1) return true;// ... 检查bottom, left, right ...// 检查四个角点if (checkPointCollision(ballX - BALL_RADIUS, ballY - BALL_RADIUS)) return true; // 左上角// ... 检查其他三个角 ...return false;
}bool checkPointCollision(int x, int y) {int cellX = x / MAZE_CELL_SIZE;int cellY = y / MAZE_CELL_SIZE;// 检查是否撞墙return (maze[cellY][cellX] == 1);
}

2.4 完整代码

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <ADXL362.h>// ST7789 显示屏引脚定义
#define TFT_CS   6   // 设置软件SPI的片选引脚
#define TFT_RST  4   // 显示屏复位引脚
#define TFT_DC   2   // 显示屏数据/控制命令引脚
#define TFT_MOSI 8   // 软件SPI的MOSI引脚
#define TFT_SCLK 7   // 软件SPI的SCK引脚Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);// 定义显示屏参数
#define SCREEN_WIDTH  240
#define SCREEN_HEIGHT 240// 颜色定义
#define BACKGROUND  ST77XX_BLACK
#define TEXT_COLOR  ST77XX_WHITE
#define WALL_COLOR  ST77XX_BLUE
#define BALL_COLOR  ST77XX_RED
#define TARGET_COLOR ST77XX_GREEN
#define PATH_COLOR ST77XX_WHITE// ADXL362对象
ADXL362 xl;// 游戏参数
#define BALL_RADIUS 8
#define MAZE_CELL_SIZE 20
#define MAZE_WIDTH (SCREEN_WIDTH / MAZE_CELL_SIZE)
#define MAZE_HEIGHT (SCREEN_HEIGHT / MAZE_CELL_SIZE)// 小球位置
int ballX = MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2;
int ballY = MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2;// 目标位置
int targetX = (MAZE_WIDTH - 2) * MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2;
int targetY = (MAZE_HEIGHT - 2) * MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2;// 加速度数据
int16_t accelX, accelY, accelZ, temp;// 迷宫地图 (0=路径, 1=墙)
byte maze[MAZE_HEIGHT][MAZE_WIDTH] = {{1,1,1,1,1,1,1,1,1,1,1,1},{1,0,0,0,1,0,0,0,0,0,0,1},{1,0,1,0,1,0,1,1,1,1,0,1},{1,0,1,0,0,0,0,0,0,1,0,1},{1,0,1,1,1,1,1,1,0,1,0,1},{1,0,0,0,0,0,0,1,0,1,0,1},{1,0,1,1,1,1,0,1,0,1,0,1},{1,0,1,0,0,1,0,1,0,0,0,1},{1,0,1,0,1,1,0,1,1,1,0,1},{1,0,1,0,0,0,0,0,0,1,0,1},{1,0,0,0,1,1,1,1,0,0,0,1},{1,1,1,1,1,1,1,1,1,1,1,1}
};// 游戏状态
bool gameActive = true;
int score = 0;// 加速度校准值
int16_t calibX = 0;
int16_t calibY = 0;
int16_t calibZ = 0;// 计时器变量
unsigned long lastDebugUpdate = 0;
const unsigned long DEBUG_UPDATE_INTERVAL = 10; // 每500ms更新一次调试信息void setup() {Serial.begin(9600);// 初始化显示屏tft.init(SCREEN_WIDTH, SCREEN_HEIGHT);tft.setRotation(1);tft.fillScreen(BACKGROUND);tft.setTextColor(TEXT_COLOR);tft.setTextSize(1);// 显示初始化信息tft.setCursor(50, 100);tft.print("Initializing...");// 初始化加速度计xl.begin(10);        // CS引脚连接D10xl.beginMeasure();   // 进入测量模式// 校准加速度计 - 读取初始值作为偏移calibrateAccelerometer();// 绘制迷宫drawMaze();// 绘制目标drawTarget();// 绘制初始小球drawBall();// 显示游戏信息displayGameInfo();delay(1000);
}void loop() {if (!gameActive) {delay(100);return;}// 读取加速度数据xl.readXYZTData(accelX, accelY, accelZ, temp);// 应用校准accelX -= calibX;accelY -= calibY;accelZ -= calibZ;// 保存旧位置int oldBallX = ballX;int oldBallY = ballY;// 根据加速度更新小球位置updateBallPosition();// 检查碰撞if (checkCollision()) {// 如果碰撞,恢复旧位置ballX = oldBallX;ballY = oldBallY;} else {// 清除旧位置的小球clearBall(oldBallX, oldBallY);// 绘制新位置的小球drawBall();}// 检查是否到达目标if (checkTargetReached()) {score++;displayGameInfo();// 生成新目标generateNewTarget();drawTarget();// 短暂暂停delay(500);}// 控制调试信息刷新率unsigned long currentTime = millis();if (currentTime - lastDebugUpdate >= DEBUG_UPDATE_INTERVAL) {displayDebugInfo();lastDebugUpdate = currentTime;}// 控制游戏速度delay(50);
}// 校准加速度计
void calibrateAccelerometer() {Serial.println("Calibrating accelerometer...");// 读取多次样本并取平均值long sumX = 0, sumY = 0, sumZ = 0;int samples = 20;for (int i = 0; i < samples; i++) {xl.readXYZTData(accelX, accelY, accelZ, temp);sumX += accelX;sumY += accelY;sumZ += accelZ;delay(10);}calibX = sumX / samples;calibY = sumY / samples;calibZ = sumZ / samples;Serial.print("Calibration values - X: ");Serial.print(calibX);Serial.print(" Y: ");Serial.print(calibY);Serial.print(" Z: ");Serial.println(calibZ);
}// 绘制迷宫
void drawMaze() {for (int y = 0; y < MAZE_HEIGHT; y++) {for (int x = 0; x < MAZE_WIDTH; x++) {if (maze[y][x] == 1) {tft.fillRect(x * MAZE_CELL_SIZE, y * MAZE_CELL_SIZE, MAZE_CELL_SIZE, MAZE_CELL_SIZE, WALL_COLOR);} else {tft.fillRect(x * MAZE_CELL_SIZE, y * MAZE_CELL_SIZE, MAZE_CELL_SIZE, MAZE_CELL_SIZE, PATH_COLOR);}}}
}// 绘制小球
void drawBall() {tft.fillCircle(ballX, ballY, BALL_RADIUS, BALL_COLOR);// 绘制小球轮廓tft.drawCircle(ballX, ballY, BALL_RADIUS, BACKGROUND);
}// 清除小球
void clearBall(int x, int y) {// 只清除小球区域,不重新绘制整个迷宫// 创建一个临时缓冲区来存储小球区域的背景uint16_t bgBuffer[(BALL_RADIUS*2+2) * (BALL_RADIUS*2+2)];// 确定要清除的区域int clearX1 = max(0, x - BALL_RADIUS - 1);int clearY1 = max(0, y - BALL_RADIUS - 1);int clearX2 = min(SCREEN_WIDTH - 1, x + BALL_RADIUS + 1);int clearY2 = min(SCREEN_HEIGHT - 1, y + BALL_RADIUS + 1);int width = clearX2 - clearX1 + 1;int height = clearY2 - clearY1 + 1;// 绘制正确的背景for (int py = clearY1; py <= clearY2; py++) {for (int px = clearX1; px <= clearX2; px++) {// 计算像素到小球中心的距离int dx = px - x;int dy = py - y;int distance = dx*dx + dy*dy;// 如果在小球半径范围内,则绘制正确的背景if (distance <= (BALL_RADIUS+1)*(BALL_RADIUS+1)) {// 获取像素所在的迷宫单元格int cellX = px / MAZE_CELL_SIZE;int cellY = py / MAZE_CELL_SIZE;// 根据单元格类型选择颜色uint16_t color = (maze[cellY][cellX] == 1) ? WALL_COLOR : PATH_COLOR;// 绘制像素tft.drawPixel(px, py, color);}}}
}// 绘制目标
void drawTarget() {tft.fillCircle(targetX, targetY, BALL_RADIUS, TARGET_COLOR);// 绘制目标轮廓tft.drawCircle(targetX, targetY, BALL_RADIUS, BACKGROUND);
}// 更新小球位置
void updateBallPosition() {// 应用死区过滤微小移动const int deadZone = 50;// 映射加速度到移动速度int moveX = 0;int moveY = 0;// 根据您的传感器数据调整映射范围if (abs(accelX) > deadZone) {moveX = map(accelX, -200, 200, -5, 5);}if (abs(accelY) > deadZone) {moveY = map(accelY, -200, 200, -5, 5);}// 更新位置ballX = constrain(ballX + moveX, BALL_RADIUS, SCREEN_WIDTH - BALL_RADIUS);ballY = constrain(ballY - moveY, BALL_RADIUS, SCREEN_HEIGHT - BALL_RADIUS); // 注意Y轴方向// 调试输出Serial.print("Accel - X: ");Serial.print(accelX);Serial.print(" Y: ");Serial.print(accelY);Serial.print(" Move - X: ");Serial.print(moveX);Serial.print(" Y: ");Serial.print(moveY);Serial.print(" Ball - X: ");Serial.print(ballX);Serial.print(" Y: ");Serial.println(ballY);
}// 检查碰撞 - 使用小球边缘进行检测
bool checkCollision() {// 检查小球边缘的四个点int top = ballY - BALL_RADIUS;int bottom = ballY + BALL_RADIUS;int left = ballX - BALL_RADIUS;int right = ballX + BALL_RADIUS;// 检查上边缘int cellXTop = ballX / MAZE_CELL_SIZE;int cellYTop = top / MAZE_CELL_SIZE;if (cellYTop >= 0 && cellYTop < MAZE_HEIGHT && cellXTop >= 0 && cellXTop < MAZE_WIDTH &&maze[cellYTop][cellXTop] == 1) {return true;}// 检查下边缘int cellXBottom = ballX / MAZE_CELL_SIZE;int cellYBottom = bottom / MAZE_CELL_SIZE;if (cellYBottom >= 0 && cellYBottom < MAZE_HEIGHT && cellXBottom >= 0 && cellXBottom < MAZE_WIDTH &&maze[cellYBottom][cellXBottom] == 1) {return true;}// 检查左边缘int cellXLeft = left / MAZE_CELL_SIZE;int cellYLeft = ballY / MAZE_CELL_SIZE;if (cellYLeft >= 0 && cellYLeft < MAZE_HEIGHT && cellXLeft >= 0 && cellXLeft < MAZE_WIDTH &&maze[cellYLeft][cellXLeft] == 1) {return true;}// 检查右边缘int cellXRight = right / MAZE_CELL_SIZE;int cellYRight = ballY / MAZE_CELL_SIZE;if (cellYRight >= 0 && cellYRight < MAZE_HEIGHT && cellXRight >= 0 && cellXRight < MAZE_WIDTH &&maze[cellYRight][cellXRight] == 1) {return true;}// 检查四个角点if (checkPointCollision(left, top) || checkPointCollision(right, top) ||checkPointCollision(left, bottom) || checkPointCollision(right, bottom)) {return true;}return false;
}// 检查单个点是否碰撞
bool checkPointCollision(int x, int y) {int cellX = x / MAZE_CELL_SIZE;int cellY = y / MAZE_CELL_SIZE;// 检查是否超出边界if (cellX < 0 || cellX >= MAZE_WIDTH || cellY < 0 || cellY >= MAZE_HEIGHT) {return true;}// 检查是否撞墙if (maze[cellY][cellX] == 1) {return true;}return false;
}// 检查是否到达目标
bool checkTargetReached() {// 计算小球和目标之间的距离int dx = ballX - targetX;int dy = ballY - targetY;int distance = dx*dx + dy*dy;// 如果距离小于两者半径之和,则认为到达目标return distance < (BALL_RADIUS * 2);
}// 生成新目标
void generateNewTarget() {int newX, newY;bool validPosition = false;int attempts = 0;// 尝试找到有效位置while (!validPosition && attempts < 50) {newX = random(1, MAZE_WIDTH - 1) * MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2;newY = random(1, MAZE_HEIGHT - 1) * MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2;// 检查是否在墙上int cellX = newX / MAZE_CELL_SIZE;int cellY = newY / MAZE_CELL_SIZE;if (maze[cellY][cellX] == 0) {validPosition = true;}attempts++;}// 清除旧目标clearBall(targetX, targetY);// 设置新目标targetX = newX;targetY = newY;
}// 显示游戏信息
void displayGameInfo() {// 在屏幕顶部显示分数tft.fillRect(0, 0, SCREEN_WIDTH, 20, BACKGROUND);tft.setCursor(10, 5);tft.setTextColor(TEXT_COLOR);tft.setTextSize(2);tft.print("Score: ");tft.print(score);
}// 显示调试信息
void displayDebugInfo() {// 在屏幕底部显示加速度数据tft.fillRect(0, SCREEN_HEIGHT - 20, SCREEN_WIDTH, 20, BACKGROUND);tft.setCursor(10, SCREEN_HEIGHT - 15);tft.setTextColor(TEXT_COLOR);tft.setTextSize(1);tft.print("X:");tft.print(accelX);tft.print(" Y:");tft.print(accelY);tft.print(" Z:");tft.print(accelZ);
}

三、操作结果展示

3.1 操作流程

  1. 按照接线图连接硬件。
  2. 用零知IDE打开并上传代码至零知标准板。
  3. 将开发板平放在桌面上,系统自动进行加速度计校准。
  4. 游戏开始后,倾斜传感器。向前倾斜小球向下移动,向左倾斜小球向左移动,以此类推。倾斜角度越大,小球移动速度越快。

3.2 显示屏界面分布

        顶部区域(约20像素高): 显示当前得分(Score)。
        主体区域: 显示迷宫、红色小球和绿色目标点。
        底部区域(约20像素高): 调试信息区,实时滚动显示原始的加速度值(X, Y, Z)。

3.3 视频演示

ADXL362三轴加速度计的体感迷宫游戏设计

实际游戏运行效果,包括倾斜控制、碰撞、得分

四、ADXL362的SPI接口技术

4.1 SPI通信方式

        ADXL362支持SPI和I2C两种通信协议。本项目采用SPI协议,因其速度更快,时序更稳

4.2 SPI数据传输和接收机制

        在传输数据前,通过设置GPIO引脚的电平来激活CS信号,开始通信。
        通过SPI的数据寄存器发送数据到SPI总线上。
        发送数据的同时,通过读取SPI的数据寄存器来接收数据。

4.3 加速度数据格式和解析

        加速度数据以数字值的形式存储在特定的寄存器中,每个轴的数据通常由两个连续的寄存器组成,一个用于高字节,一个用于低字节。代码中使用 xl.readXYZTData(...) 函数一次性读取X, Y, Z三轴的12位数字输出和温度值

        XDATA_L包含8个最低有效位(LSBs), XDATA_H包含4个最高有效位(MSBs)

五、常见问题解答 (FAQ)

Q1: 为什么我的串口打印是乱码?

A:请排查:

        确保零知IDE的串口监视器的波特率设置为9600,与代码中的 Serial.begin(9600) 一致。

Q2: 小球控制不灵敏或反向?

A:请根据以下提示操作:

        调整 updateBallPosition() 函数中的 map 函数的参数,例如将 map(accelY, -200, 200, -5, 5) 改为 map(accelY, -200, 200, 5, -5) 可以反转Y轴方向。
        增大 deadZone 值可以减少抖动,增大 map 的输出范围(如-8~8)可以增加灵敏度。

Q3: 小球穿墙bug如何处理?

A:可能的原因:

        小球移动速度(map输出的值)设置过快,单帧移动距离超过了墙壁的厚度。尝试减小 map 的输出范围(如改为-2~2),让小球每帧移动慢一些。


项目资源:

        零知IDE:零知实验室官网下载

        ADXL362加速度计库:ADXL362-master

        ADXL362数据手册:ADXL362 (Rev.G)

        通过实践,我们掌握了如何驱动SPI显示屏、读取和处理加速度传感器数据、设计高效的图形刷新算法以及实现复杂的游戏碰撞逻辑

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/919795.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/919795.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《Linux 网络编程一:网络编程导论及UDP 服务器的创建与数据接收》

Linux下的网络编程1. 目的实现不同主机之间进程的通信。2. 问题主机之间在物理层面必须互联互通。进程之间在软件层面必须互联互通。IP地址&#xff1a;计算机的软件地址&#xff0c;用于标识计算机设备。MAC地址&#xff1a;计算机的硬件地址&#xff08;固定&#xff09;。网…

排序(数据结构)

比较排序 插入排序&#xff08;斗地主摸牌就是一个插入排序&#xff09; 单纯的插入排序也叫直接插入排序 时间复杂度&#xff1a; 最好O(n)最坏O(n^2) 过程 先写单趟&#xff0c;再写整体 依次比较&#xff0c;如果大于就往后挪动&#xff0c;否则就退出循环&#xff0c;插入数…

【C++组件】Elasticsearch 安装及使用

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;C框架/库 目录&#x1f525; 介绍 &#x1f525; ES 安装 &#x1f98b; 安装 kibana&#x1f98b; ES 客户端的安装&#x1f525; ES 核心概念 &#x1f98b; 索引&#xff08;Index&#xff09;&…

项目:电动车报警器

1.项目需求 点击遥控器A按键&#xff0c;系统进入警戒模式&#xff0c;一旦检测到震动(小偷偷车)&#xff0c;则喇叭发出声响报警&#xff0c;吓退小偷。 点击遥控器B按键&#xff0c;系统退出警戒模式&#xff0c;再怎么摇晃系统都不会报警&#xff0c;否则系统一直发出尖叫&a…

GDSFactory环境配置(PyCharm+Git+KLayout)

1、安装 PyCharm 和 KLayout 安装 PyCharm&#xff08;官网社区版即可&#xff09;和 KLayout&#xff08;官网最新版&#xff09;&#xff0c;这两款软件均开源&#xff0c;安装操作简单&#xff0c;这里不再赘述。&#xff08;注意&#xff1a;PyCharm软件是否安装成功以能否…

STM32 定时器(输出模式)

⚙️ ​一、输出模式总览​STM32定时器的输出比较模式通过比较计数器&#xff08;CNT&#xff09;与捕获/比较寄存器&#xff08;CCRx&#xff09;的值&#xff0c;控制输出引脚&#xff08;OCx&#xff09;的电平状态。六种模式定义如下&#xff1a;​模式宏​​触发动作​&am…

嵌入式硬件篇---手柄

手柄原理&#xff1a;手柄遥控的原理其实可以简单理解为 “信号的发送与接收”&#xff0c;就像两个人用对讲机聊天&#xff0c;一方说话&#xff08;发送信号&#xff09;&#xff0c;另一方听话&#xff08;接收信号&#xff09;&#xff0c;然后根据内容行动。下面用通俗的方…

数据库架构开发知识库体系

摘要面向初创与企业团队&#xff0c;系统梳理数据库与数据平台从采集、传输、存储、处理、服务化到治理与安全的全链路。覆盖 OLTP/OLAP/HTAP、湖仓一体与实时数据栈&#xff0c;结合国内外工具与方法论&#xff0c;给出架构选型、性能优化、可靠性与合规要点&#xff0c;以及可…

在Excel和WPS表格中合并多个单元格这样最快

如果要把WPS表格和Excel中多个单元格的数据合成到一个单元格中&#xff0c;不用函数&#xff0c;只需要先写输入公式&#xff0c;然后在各个单元格之间输入&符号即可。&#xff08;当然&#xff0c;&符号不只是连接单元格的数据&#xff0c;也可以直接输入内容连接&…

在嵌入式上用 C++14实现简单HSM状态机

文章目录概述为什么要迁移到 C&#xff0c;以及 C 的陷阱目标与挑战为什么不能直接使用 std::function&#xff1f;解决方案&#xff1a;POD 回调与模板 Trampoline核心设计模板 trampoline 实现两种成员函数绑定策略1. **Per-Transition Context&#xff08;每个状态转移绑定一…

【unity】Obfuz加固混淆日志还原解析方案ObfuzResolver

Github | Gitee ObfuzResolver是基于obfuz-tools针对Obfuz的一项辅助工具&#xff0c;方便开发者在unity编辑器中或者运行时快捷将使用Obfuz混淆加固后的日志信息还原为原始信息&#xff0c;以辅助开发者快速定位Bug。 特性 支持unity编辑器模式下还原被加固混淆的日志信息&a…

2025DevOps平台趋势解读:哪些DevOps工具正在引领行业变革?

DevOps平台已成为企业提升研发效能、实现敏捷交付的核心支柱。2025年DevOps领域正经历深刻变革&#xff0c;平台能力正从“工具链整合”向“价值流智能中枢”跃升。01. 2025Devops平台趋势解读“全栈式”与“模块化/可组合”的平衡&#xff1a;企业既需要能覆盖开发、测试、部署…

第二阶段Winform-4:MDI窗口,布局控件,分页

1_MDI窗口 &#xff08;1&#xff09;MDI是指将多控件窗体在同一窗体中打开,可以设置重叠打开&#xff0c;平捕打开等&#xff0c;MDI窗体&#xff08;Multiple-Document Interface&#xff0c;多文档界面&#xff09;用于同时显示多个文档。在项目中使用MDI窗体时&#xff0c…

实用R语言机器学习指南:从数据预处理到模型实战(附配套学习资源)

一、为什么需要掌握机器学习建模&#xff1f;在科研与项目实践中&#xff0c;机器学习已成为数据挖掘的核心工具。本文手把手带你在R语言中实现7大常用模型&#xff1a;逻辑回归/正则化回归决策树/随机森林SVM支持向量机XGBoost梯度提升神经网络全程包含数据标准化→模型训练→…

go.uber.org/zap 日志库高性能写入

使用 go.uber.org/zap 实现日志分割功能 实现按照单个文件最大MB自动分割,最多保留多少天的文件,是否启用压缩,按天自动分割日志 核心依赖 go.uber.org/zap:核心日志库 lumberjack.v2:日志轮转工具(实现按大小/时间分割) 时间处理依赖标准库 time 实现步骤 1. 初始化…

电脑端完全免费的动态壁纸和屏保软件(真正免费、无广告、无会员)

✅ 1. Lively Wallpaper&#xff08;强烈推荐&#xff09; 特点&#xff1a;完全免费、开源、无广告&#xff0c;支持本地视频/GIF/网页作为动态壁纸内置资源&#xff1a;12个高质量动态壁纸&#xff08;可自定义&#xff09;屏保功能&#xff1a;支持将动态壁纸一键设为屏保系…

C#_组合优于继承的实际应用

2.2 Composition over Inheritance&#xff1a;组合优于继承的实际应用 继承&#xff08;Inheritance&#xff09;是面向对象编程中最容易被过度使用和误用的特性之一。传统的教学往往让人们优先选择继承来实现代码复用和建立“是一个&#xff08;is-a&#xff09;”的关系。然…

Kafka消息丢失的场景有哪些

生产者在生产过程中的消息丢失 broker在故障后的消息丢失 消费者在消费过程中的消息丢失ACK机制 ack有3个可选值&#xff0c;分别是1&#xff0c;0&#xff0c;-1。 ack0&#xff1a;生产者在生产过程中的消息丢失 简单来说就是&#xff0c;producer发送一次就不再发送了&#…

盼之代售 231滑块 csessionid 分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向分析 部分python代码 url "…

STL关联式容器解析:map与set详解

目录 1. 关联式容器 2. 键值对 3. 树形结构的关联式容器 3.1 set 3.1.2 set的使用 3.2 map 3.2.1 map的介绍 3.2.2 map的使用 3.3 multiset 3.3.1 multiset的介绍 3.3.2 multiset的使用 3.4 multimap 3.4.1 multimap的介绍 3.4.2 multimap的使用 4.红黑树模拟实现…