一、项目概述
本项目使用STM32微控制器和OLED显示屏实现经典贪吃蛇游戏,通过按键控制蛇的移动方向,实现食物收集、碰撞检测和分数计算等功能。项目采用模块化设计,包含游戏逻辑、显示驱动和用户输入处理三大核心模块。
二、硬件配置
-
主控芯片:STM32F103C8T6(ARM Cortex-M3,72MHz)
-
显示模块:0.96寸OLED(SSD1306驱动,128×64像素,I2C接口)
-
输入设备:4个轻触按键(上、下、左、右)
-
电源:3.3V/5V DC供电
三、系统架构
c
+-------------------+ +-------------------+ +-------------------+
| 输入处理模块 | | 游戏逻辑模块 | | 显示驱动模块 |
| (按键扫描与消抖) |<--->| (蛇移动/碰撞检测) |<--->| (OLED显示控制) |
+-------------------+ +-------------------+ +-------------------+
| | | | | | | | | | | | |
| | | | | | | | | | +---->+-------------------+
| | | | | | | | | +-------->| 游戏状态管理 |
| | | | | | | | +------------>| 分数计算 |
| | | | | | | +---------------->| 游戏难度控制 |
| | | | | | +-------------------->| 音效反馈(可选) |
| | | | | +-------------------------------->| |
| | | | +------------------------------------>| |
| | | +---------------------------------------->| |
| | +-------------------------------------------->| |
| +--------------------------------------------------->| |
+--------------------------------------------------->| |
四、核心代码实现
1. 主程序框架
c
#include "stm32f1xx_hal.h"
#include "ssd1306.h"
#include "game.h"
#include "input.h"
int main(void) {
HAL_Init();
SystemClock_Config();
OLED_Init();
Input_Init();
GameState game = {0};
InitGame(&game);
while (1) {
ProcessInput(&game);
UpdateGame(&game);
RenderGame(&game);
HAL_Delay(100); // 控制游戏速度
}
}
2. 游戏逻辑实现
c
// game.h
#define GRID_SIZE 8
#define GRID_WIDTH 16
#define GRID_HEIGHT 8
typedef struct {
int x, y;
} Point;
typedef struct {
Point body[64]; // 蛇身最多64节
int length;
Point food;
int direction; // 0:上, 1:右, 2:下, 3:左
int score;
int game_over;
} GameState;
// game.c
void InitGame(GameState* game) {
// 初始化蛇身
game->body[0] = (Point){4, 4};
game->body[1] = (Point){4, 5};
game->body[2] = (Point){4, 6};
game->length = 3;
game->direction = 1; // 初始向右移动
game->score = 0;
game->game_over = 0;
GenerateFood(game);
}
void GenerateFood(GameState* game) {
do {
game->food.x = rand() % GRID_WIDTH;
game->food.y = rand() % GRID_HEIGHT;
} while (IsPointOnSnake(game, game->food));
}
int IsPointOnSnake(GameState* game, Point p) {
for (int i = 0; i < game->length; i++) {
if (game->body[i].x == p.x && game->body[i].y == p.y) {
return 1;
}
}
return 0;
}
void UpdateGame(GameState* game) {
if (game->game_over) return;
// 保存蛇尾位置(可能用于增长)
Point prev_tail = game->body[game->length - 1];
// 移动蛇身(从尾部向前更新)
for (int i = game->length - 1; i > 0; i--) {
game->body[i] = game->body[i - 1];
}
// 根据方向移动头部
switch (game->direction) {
case 0: // 上
game->body[0].y--;
break;
case 1: // 右
game->body[0].x++;
break;
case 2: // 下
game->body[0].y++;
break;
case 3: // 左
game->body[0].x--;
break;
}
// 检查是否吃到食物
if (game->body[0].x == game->food.x && game->body[0].y == game->food.y) {
// 增加长度
game->body[game->length] = prev_tail;
game->length++;
game->score += 10;
GenerateFood(game);
}
// 检查碰撞
CheckCollision(game);
}
void CheckCollision(GameState* game) {
Point head = game->body[0];
// 检查墙壁碰撞
if (head.x < 0 || head.x >= GRID_WIDTH ||
head.y < 0 || head.y >= GRID_HEIGHT) {
game->game_over = 1;
return;
}
// 检查自身碰撞
for (int i = 1; i < game->length; i++) {
if (head.x == game->body[i].x && head.y == game->body[i].y) {
game->game_over = 1;
return;
}
}
}
3. 显示驱动实现
c
// display.c
void RenderGame(GameState* game) {
OLED_Clear();
// 绘制蛇身
for (int i = 0; i < game->length; i++) {
DrawBlock(game->body[i].x, game->body[i].y, 1);
}
// 绘制食物
DrawBlock(game->food.x, game->food.y, 1);
// 显示分数
char score_str[20];
sprintf(score_str, "Score: %d", game->score);
OLED_ShowString(0, 0, score_str);
// 游戏结束提示
if (game->game_over) {
OLED_ShowString(20, 3, "GAME OVER!");
OLED_ShowString(10, 4, "Press RESET");
}
OLED_Refresh();
}
void DrawBlock(int x, int y, int fill) {
// 将网格坐标转换为像素坐标
int pixel_x = x * GRID_SIZE;
int pixel_y = y * GRID_SIZE;
for (int i = 0; i < GRID_SIZE; i++) {
for (int j = 0; j < GRID_SIZE; j++) {
OLED_DrawPixel(pixel_x + i, pixel_y + j, fill);
}
}
}
4. 输入处理实现
c
// input.c
#define UP_PIN GPIO_PIN_0
#define DOWN_PIN GPIO_PIN_1
#define LEFT_PIN GPIO_PIN_2
#define RIGHT_PIN GPIO_PIN_3
#define GPIO_PORT GPIOA
void Input_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = UP_PIN | DOWN_PIN | LEFT_PIN | RIGHT_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIO_PORT, &GPIO_InitStruct);
}
void ProcessInput(GameState* game) {
static uint32_t last_press = 0;
uint32_t now = HAL_GetTick();
if (now - last_press < 200) return; // 防抖延时
if (HAL_GPIO_ReadPin(GPIO_PORT, UP_PIN) == GPIO_PIN_RESET) {
if (game->direction != 2) game->direction = 0; // 不能向下时才能向上
last_press = now;
}
else if (HAL_GPIO_ReadPin(GPIO_PORT, DOWN_PIN) == GPIO_PIN_RESET) {
if (game->direction != 0) game->direction = 2; // 不能向上时才能向下
last_press = now;
}
else if (HAL_GPIO_ReadPin(GPIO_PORT, LEFT_PIN) == GPIO_PIN_RESET) {
if (game->direction != 1) game->direction = 3; // 不能向右时才能向左
last_press = now;
}
else if (HAL_GPIO_ReadPin(GPIO_PORT, RIGHT_PIN) == GPIO_PIN_RESET) {
if (game->direction != 3) game->direction = 1; // 不能向左时才能向右
last_press = now;
}
}
五、游戏功能扩展
1. 难度分级
c
// 在游戏状态中添加难度级别
typedef struct {
// ...其他成员
int difficulty; // 0:简单, 1:中等, 2:困难
} GameState;
// 根据难度调整游戏速度
void UpdateGameSpeed(GameState* game) {
switch (game->difficulty) {
case 0: // 简单
game->speed = 150; // ms
break;
case 1: // 中等
game->speed = 100;
break;
case 2: // 困难
game->speed = 70;
break;
}
}
2. 特殊食物
c
// 添加特殊食物类型
typedef enum {
FOOD_NORMAL,
FOOD_SPEED_UP,
FOOD_SLOW_DOWN,
FOOD_REVERSE
} FoodType;
typedef struct {
Point pos;
FoodType type;
uint32_t spawn_time;
} SpecialFood;
// 随机生成特殊食物
void SpawnSpecialFood(GameState* game) {
if (rand() % 10 == 0) { // 10%几率生成特殊食物
SpecialFood sf;
do {
sf.pos.x = rand() % GRID_WIDTH;
sf.pos.y = rand() % GRID_HEIGHT;
} while (IsPointOnSnake(game, sf.pos) ||
(sf.pos.x == game->food.x && sf.pos.y == game->food.y));
sf.type = rand() % 4;
sf.spawn_time = HAL_GetTick();
// 添加到特殊食物列表
}
}
3. 游戏存档功能
c
// 使用Flash存储游戏状态
void SaveGameState(GameState* game) {
// 擦除Flash扇区
FLASH_EraseInitTypeDef erase;
erase.TypeErase = FLASH_TYPEERASE_PAGES;
erase.PageAddress = 0x0800F000; // 选择页地址
erase.NbPages = 1;
uint32_t page_error;
HAL_FLASH_Unlock();
HAL_FLASHEx_Erase(&erase, &page_error);
// 写入数据
uint32_t data[8];
data[0] = game->score;
data[1] = game->length;
// ...其他数据
for (int i = 0; i < 8; i++) {
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 0x0800F000 + i*4, data[i]);
}
HAL_FLASH_Lock();
}
参考代码 基于stm32的贪吃蛇游戏实现 OLED屏 www.youwenfan.com/contentcss/183119.html
六、优化与改进
1. 显示优化
c
// 使用位图字体提高显示效率
const uint8_t font[][8] = {
{0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x00, 0x00}, // 0
{0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00}, // 1
// ...其他字符
};
void OLED_ShowChar(uint8_t x, uint8_t y, char c) {
if (c < ' ') return;
c -= ' ';
for (int i = 0; i < 8; i++) {
OLED_DrawBuffer(x, y+i, font[c][i]);
}
}
2. 性能优化
c
// 使用双缓冲减少闪烁
uint8_t display_buffer[128][8]; // 显存缓冲区
void OLED_Refresh(void) {
for (int page = 0; page < 8; page++) {
OLED_SetPage(page);
OLED_SetColumn(0);
for (int col = 0; col < 128; col++) {
OLED_WriteData(display_buffer[col][page]);
}
}
}
3. 增加音效
c
// 使用PWM驱动蜂鸣器
void PlaySound(int frequency, int duration) {
uint32_t period = 1000000 / frequency; // 微秒
uint32_t cycles = duration * 1000 / period;
for (uint32_t i = 0; i < cycles; i++) {
HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET);
Delay_us(period / 2);
HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_RESET);
Delay_us(period / 2);
}
}
// 在游戏中添加音效
void UpdateGame(GameState* game) {
// ...
if (ate_food) {
PlaySound(523, 50); // C5音符
}
if (game_over) {
PlaySound(262, 200); // C4音符
}
}
七、项目资源
1. 硬件连接
| OLED引脚 | STM32引脚 | 功能 |
|---|---|---|
| SDA | PB7 | I2C数据线 |
| SCL | PB6 | I2C时钟线 |
| VCC | 3.3V | 电源 |
| GND | GND | 地 |
| 按键引脚 | STM32引脚 | 功能 |
|---|---|---|
| UP | PA0 | 上方向 |
| DOWN | PA1 | 下方向 |
| LEFT | PA2 | 左方向 |
| RIGHT | PA3 | 右方向 |
2. 开发环境
-
IDE: STM32CubeIDE
-
编译器: GCC ARM Embedded
-
调试器: ST-Link V2
-
库依赖:
-
HAL库(STM32CubeMX生成)
-
SSD1306 OLED驱动库
3. 项目结构
c
├── Core/
│ ├── Inc/ // 头文件
│ ├── Src/ // 源文件
│ └── Startup/ // 启动文件
├── Drivers/
│ ├── CMSIS/ // 芯片支持包
│ └── STM32F1xx_HAL_Driver/ // HAL库
├── MDK-ARM/ // Keil工程文件
└── STM32CubeIDE/ // Eclipse工程文件
八、总结
本实现展示了如何使用STM32和OLED显示屏创建经典的贪吃蛇游戏。项目包含完整的游戏逻辑、显示驱动和用户输入处理,并提供了多种扩展功能如难度分级、特殊食物和音效反馈。通过模块化设计和优化技术,实现了流畅的游戏体验和高效的资源利用。
该项目的核心价值在于:
-
展示了嵌入式游戏开发的基本流程
-
提供了OLED显示和按键输入的完整实现
-
演示了游戏状态管理和碰撞检测算法
-
为后续开发更复杂的嵌入式游戏奠定了基础