基于STM32的贪吃蛇游戏实现(OLED屏)

一、项目概述

本项目使用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显示屏创建经典的贪吃蛇游戏。项目包含完整的游戏逻辑、显示驱动和用户输入处理,并提供了多种扩展功能如难度分级、特殊食物和音效反馈。通过模块化设计和优化技术,实现了流畅的游戏体验和高效的资源利用。

该项目的核心价值在于:

  1. 展示了嵌入式游戏开发的基本流程

  2. 提供了OLED显示和按键输入的完整实现

  3. 演示了游戏状态管理和碰撞检测算法

  4. 为后续开发更复杂的嵌入式游戏奠定了基础

相关推荐
Zevalin爱灰灰4 小时前
能源与电源管理电路——防倒灌电路
单片机·嵌入式硬件·电源管理
Awna5 小时前
Skynet / KBEngine / BigWorld 完整介绍与横向对比
游戏
jianqiang.xue7 小时前
ESP-IDF保姆级入门02|Ubuntu24.04搭建EIM-GUI开发环境,打通Windows_Linux双平台统一开发
stm32·单片机·物联网
无垠的广袤7 小时前
【工业树莓派 CM0 Dev Board】扩展板设计
linux·python·嵌入式硬件·pcb设计·模块化·传感器
嵌入式-老费8 小时前
esp32开发与应用(添加数据分区)
单片机·嵌入式硬件
嵌入式-老费9 小时前
esp32开发与应用(OTA升级)
嵌入式硬件
CYLAM202511 小时前
STC32G 单片机0.96“OLED 西历农历可调万年历 (1901-2100)
单片机·嵌入式硬件·stc32g·万年历·西历农历·0.96”oled 12864·4x4 矩阵键盘
Discipline~Hai12 小时前
ARM03-蜂鸣器和中断
c语言·arm开发·单片机·嵌入式硬件·嵌入式
KaifuZeng13 小时前
电源面试问题汇总一
单片机·嵌入式硬件·面试·电路
笨鸟先飞的橘猫14 小时前
对于不同类型角色类游戏的战斗系统思考
游戏