What I Used
- 6-axis IMU (ICM-42688-P) for acceleration/gyro data (core for tilt control)
- 2.0" 320×240 IPS LCD (game graphics & UI)
- Built-in speaker (sound effects for collecting stars/crashing)
- USB-C for power & programming
Dev Env: Arduino IDE + M5Unified Library (super handy for accessing all the board’s features in one place)
How the Game Works
Key Code Logic
- Hardware Init: Fire up the M5CoreS3, IMU, and speaker with the M5Unified Library—super straightforward, minimal setup needed.
- IMU Data Smoothing: Read acceleration data from the IMU and apply a low-pass filter to reduce jitter, making the ball movement smooth instead of jumpy.
- Ball Physics: Convert filtered IMU X/Y acceleration values to ball speed/direction, update the ball position, and add screen boundary limits so it doesn’t fly off-screen.
- Collision Detection: Simple checks for the ball hitting obstacles (game over) or collecting stars (score + sound).
- Graphics & UI: Redraw the screen at ~50FPS with obstacles, stars, the ball, and a score counter; a red game over screen with restart prompt for when you crash.
Full Code
============================Split line================================
/
* 使用 M5CoreS3 的 6 轴 IMU 控制小球收集星星,避开障碍物
*/
#include <M5Unified.h>
// 小球参数
int ballX = 160, ballY = 120;
float ballVX = 0, ballVY = 0;
// 星星(目标)
struct Star {
int x, y;
bool collected;
} stars[5];
// 障碍物
struct Obstacle {
int x, y;
int size = 20;
} obstacles[3];
// 游戏状态
int score = 0;
bool gameRunning = true;
unsigned long lastStarTime = 0;
const unsigned long starInterval = 8000; // 每8秒生成新星星(实际是重置未收集的)
// 滤波系数(用于平滑 IMU 数据)
const float alpha = 0.2;
float filteredAx = 0, filteredAy = 0;
// 初始化星星和障碍物
void initGame() {
score = 0;
ballX = 160; ballY = 120;
ballVX = ballVY = 0;
// 初始化星星(随机位置)
for (int i = 0; i < 5; i++) {
stars[i].x = random(20, 300);
stars[i].y = random(20, 220);
stars[i].collected = false;
}
// 初始化障碍物(固定位置,避免太难)
obstacles[0] = {80, 60};
obstacles[1] = {240, 180};
obstacles[2] = {160, 120};
gameRunning = true;
lastStarTime = millis();
}
// 检测点是否在矩形内
bool pointInRect(int px, int py, int rx, int ry, int size) {
return (px > rx && px < rx + size && py > ry && py < ry + size);
}
// 检测点是否在圆内(小球半径=8)
bool pointInBall(int px, int py, int bx, int by) {
int dx = px - bx;
int dy = py - by;
return (dx * dx + dy * dy) < (8 * 8);
}
void setup() {
auto cfg = M5.config();
cfg.clear_display = true;
M5.begin(cfg);
// 初始化 IMU
if (!M5.IMU.Init()) {
M5.Lcd.println("IMU Init Failed!");
while (1) delay(100);
}
M5.Speaker.setVolume(3);
randomSeed(analogRead(0));
initGame();
}
void loop() {
M5.update();
if (!gameRunning) {
// Game Over 画面
M5.Lcd.fillScreen(TFT_RED);
M5.Lcd.setTextColor(TFT_WHITE);
M5.Lcd.setTextSize(3);
M5.Lcd.setCursor(40, 80);
M5.Lcd.println("GAME OVER");
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(90, 130);
M5.Lcd.printf("Score: %d", score);
M5.Lcd.setTextSize(1);
M5.Lcd.setCursor(60, 180);
M5.Lcd.println("Press Btn A to Restart");
if (M5.BtnA.wasPressed()) {
initGame();
}
delay(50);
return;
}
// === 读取并滤波 IMU 数据 ===
float ax, ay, az;
M5.IMU.getAccelData(&ax, &ay, &az);
// 低通滤波(减少抖动)
filteredAx = alpha * ax + (1 - alpha) * filteredAx;
filteredAy = alpha * ay + (1 - alpha) * filteredAy;
// === 更新小球速度与位置 ===
ballVX = -filteredAx * 80; // X 轴反向:右倾 → 小球左移
ballVY = filteredAy * 80; // Y 轴:前倾 → 小球下移(符合直觉)
ballX += ballVX * 0.02;
ballY += ballVY * 0.02;
// 边界限制(留出小球半径)
ballX = constrain(ballX, 8, 312);
ballY = constrain(ballY, 8, 232);
// === 碰撞检测:障碍物 ===
for (int i = 0; i < 3; i++) {
if (pointInRect(ballX, ballY, obstacles[i].x, obstacles[i].y, obstacles[i].size)) {
// 播放爆炸音效(简单 beep)
M5.Speaker.tone(200, 200);
gameRunning = false;
break;
}
}
// === 收集星星 ===
for (int i = 0; i < 5; i++) {
if (!stars[i].collected && pointInBall(stars[i].x, stars[i].y, ballX, ballY)) {
stars[i].collected = true;
score += 10;
M5.Speaker.tone(1000, 100); // 收集音效
}
}
// === 自动刷新未收集的星星(每8秒)===
if (millis() - lastStarTime > starInterval) {
for (int i = 0; i < 5; i++) {
if (!stars[i].collected) {
stars[i].x = random(20, 300);
stars[i].y = random(20, 220);
}
}
lastStarTime = millis();
}
// === 绘制画面 ===
M5.Lcd.fillScreen(TFT_BLACK);
// 绘制障碍物(红色方块)
for (int i = 0; i < 3; i++) {
M5.Lcd.fillRect(obstacles[i].x, obstacles[i].y, obstacles[i].size, obstacles[i].size, TFT_RED);
}
// 绘制星星(黄色 ★ 符号,用小三角模拟)
for (int i = 0; i < 5; i++) {
if (!stars[i].collected) {
int x = stars[i].x, y = stars[i].y;
M5.Lcd.fillCircle(x, y, 3, TFT_YELLOW);
// 简单星形(可选)
M5.Lcd.drawPixel(x, y - 4, TFT_YELLOW);
M5.Lcd.drawPixel(x, y + 4, TFT_YELLOW);
M5.Lcd.drawPixel(x - 4, y, TFT_YELLOW);
M5.Lcd.drawPixel(x + 4, y, TFT_YELLOW);
}
}
// 绘制小球(白色)
M5.Lcd.fillCircle(ballX, ballY, 8, TFT_WHITE);
// 显示分数
M5.Lcd.setTextColor(TFT_GREEN);
M5.Lcd.setTextSize(1);
M5.Lcd.setCursor(10, 10);
M5.Lcd.printf("Score: %d", score);
delay(20); // ~50 FPS
}
============================Split line================================
How to Upload & Run
- Open Arduino IDE, install the M5Unified Library (Library Manager → search "M5Unified").
- Create a new sketch, paste the full code above.
- Select the board: Tools → Board → M5Stack → M5CoreS3.
- Connect the M5CoreS3 to your PC via USB-C—IDE will auto-detect the port.
- Click Upload; the board will restart automatically, and the game starts right away!
Quick Tweaks for Fun
- Adjust the IMU filter alpha (0.1-0.3) for more/less ball smoothness.
- Change the ball speed multiplier (80) to make the ball faster/slower.
- Randomize obstacle positions (instead of fixed) to up the difficulty.
- Add more stars/obstacles, or a timer for a time challenge mode.
- The onboard mic is unused here—easily add voice control (e.g., voice restart) with a little extra code!
- I used a basic low-pass filter for IMU data smoothing. What’s your preferred method to balance control responsiveness vs. jitter in real-time motion projects?
- For lightweight 2D collision detection on ESP32, are there better CPU-efficient alternatives to basic circle/rectangle checks?
- If I later want to send IMU data over WiFi to a drone or robot, what’s the best way to reduce transmission lag while keeping movement smooth?
Yassin | Building Compact, High-Current Connections for Drones & Robots



