零知開源——基于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
英文地址,請注明出處:http://en.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.紅黑樹模擬實現…