STM32F4贪吃蛇小游戏开发:从CubeMX配置到状态机实现

发布时间:2026/9/10 15:03:29

STM32F4贪吃蛇小游戏开发:从CubeMX配置到状态机实现 简介基于STM32F4开发板的贪吃蛇小游戏完整工程包面向嵌入式初学者与趣味项目开发者采用C语言编写整合触摸屏、LCD显示、四向按键与LED状态指示解决综合外设应用与游戏逻辑设计的练习需求。工程在经典玩法之上加入EASY/HARD难度选择、STOP/START触摸按钮、积分/等级/蛇身长度实时显示及失败边框闪烁提示蛇吃果后速度逐渐加快撞墙或咬尾即结束并具备LED0/LED1在运行与暂停时的不同闪烁逻辑完整呈现状态机与中断配合的交互思路。资源为RAR压缩包共246个文件以60个C源文件、60个H头文件为主体辅以编译生成的O目标文件、CRF交叉引用、HEX烧录文件及D调试信息总体积约6.96MB结构清晰适合导入Keil工程直接查看或二次开发。已有3500余人学习下载适合希望通过实际项目综合训练STM32F4 GPIO、定时器、LCD驱动、触摸屏检测和软件状态管理的读者从中获取完整的工程组织方式与外设初始化、事件处理等可复用经验。1. 基于STM32F4开发板的贪吃蛇小游戏先当外设体检再当程序练手“基于STM32F4开发板的贪吃蛇小游戏”在嵌入式社区里出镜率极高但多数人只把它当入门作业忽略了题目本身的含金量。贪吃蛇规则再简单落到开发板上也要同时处理四条链路按键输入要消抖定时器要提供稳定的游戏节拍OLED或TFT上要绘制蛇身、食物和分数逻辑层还要管理转向缓存与碰撞判定。任何一环出问题都会表现出按键失灵、蛇自己消失、跑几局就卡死这类让新手抓狂的典型现象。对刚接触F4的人这是第一个能看到完整交互闭环的程序对做过多年嵌入式的人一块陌生开发板到手用它验证GPIO、定时器、显示总线和中断优先级远比翻手册快。下面直接从CubeMX配置讲到可玩版本不依赖RTOS。2. STM32F4贪吃蛇的硬件选型与CubeMX初始化显示、按键与定时器心跳2.1 显示方案怎么选0.96寸I2C OLED、SPI TFT与开发板的接口现状贪吃蛇要求画面有格子、蛇身、食物、分数其实只需要一块能寻址像素的屏。常见STM32F4开发板上能见到的显示外设大致有三类I2C接口的0.96寸OLEDSSD1306、SPI接口的1.8寸或2.4寸TFTST7735 / ILI9341、以及走FSMC总线的3.5寸大屏。对贪吃蛇这种逻辑帧率只有10Hz的游戏I2C OLED的带宽完全够128x64分辨率、1KB显存、400kHz I2C下全屏刷新约20ms剩下的时间足够留给按键扫描和逻辑计算。SPI TFT的刷新速度快一个量级代价是多占SCK、MOSI、DC、CS、RST五个引脚FSMC大屏接线和配置复杂属于杀鸡用牛刀。显示方案接口占用引脚全屏刷新耗时估适用场景0.96寸 OLED SSD1306I2CSCL、SDA共2根约20ms 400kHz贪吃蛇默认选择1.8寸 TFT ST7735SPI5根约1ms 18MHz彩色版小游戏2.8寸 TFT ILI9341SPI/FSMC6根以上更快高分辨率界面我一般把OLED的SCL、SDA接到PB8、PB9F4的I2C1这两个引脚在大多数开发板和核心板上都引到了排针对线方便。决定用哪组引脚前先翻一下自己开发板的原理图确认没有和其他外设冲突F4上有I2C1、I2C2、I2C3三组可选引脚冲突时换一组就行驱动代码不需要改只换HAL里的句柄初始化。2.2 CubeMX配置四个方向键GPIO输入模式与内部上拉方向控制不需要摇杆或编码器四个轻触按键是最稳的方案。按键一端接GPIO另一端接GNDCubeMX里把对应引脚设为GPIO输入并开启内部上拉这样松开时读到高电平按下时读到低电平不需要外接上拉电阻。以PA0到PA3为例初始化代码长这样GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOA, GPIO_InitStruct);GPIO_MODE_INPUT表示纯输入GPIO_PULLUP表示内部上拉。这里刻意不用GPIO_MODE_IT_RISING_FALLING双边沿中断原因在第4章的按键扫描里讲清楚动作游戏的转向需要周期性确认按键电平边沿中断只在按下瞬间触发一次不能满足连续转向需求。2.3 用TIM2更新中断做游戏心跳为什么不用HAL_Delay贪吃蛇需要一个稳定的逻辑节拍。常见误用是在main循环里HAL_Delay(100)这会让按键扫描和显示刷新全部阻塞在延时里一旦在中断里改速度或暂停延时误差会越积越大。正确做法是让TIM2产生周期更新中断中断里只置一个标志位由主循环消费这个节拍。/* APB1定时器时钟84MHzPSC8399、ARR999时中断频率84MHz/8400/100010Hz */ htim2.Init.Prescaler 8399; htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 999; HAL_TIM_Base_Init(htim2); HAL_TIM_Base_Start_IT(htim2); volatile uint8_t g_tick 0; void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { g_tick 1; /* 只在中断里置标志不处理游戏逻辑 */ } }中断频率公式是定时器时钟 / ((PSC1) * (ARR1))。84MHz除以8400再除以1000得到10Hz即每100ms跳一次作为蛇的初始爬行速度正好。回调里绝不调HAL_GPIO_ReadPin、不刷屏、不printf把中断代码压到最短避免和主循环共享变量时出现临界区问题。CubeMX里还要把TIM2的抢占优先级调成低于SysTick的值保证HAL_Delay这类基础服务不被饿死。注意游戏变速不要靠改PSC或ARR的数值组合实现。ARR在后面的难度阶梯里会被运行时改写PSC始终保持不变这样速度表只影响节拍频率不影响定时器配置本身。3. 贪吃蛇的数据结构与状态机蛇身存储、转向缓存与碰撞判定3.1 定长数组加环形队列存蛇身裸机上不用链表的原因蛇身数据结构有三种常见写法。链表最直观但每次吃到食物都要malloc裸机工程没有配套的内存回收策略时跑几十局就出现碎片系统行为变得不可预测。数组平移法每走一步要把全部节点向前搬一格蛇长80时一次移动要做80个赋值操作虽然F4主频压得住代码却不好看。推荐做法是定长数组加环形队列数组一次开够head指向最新一节tail指向最老一节移动只是修改两个下标。#define GRID_W 16 /* 128像素/8像素每格16列 */ #define GRID_H 8 /* 64像素/8像素每格8行 */ #define MAX_SNAKE_LEN (GRID_W * GRID_H) typedef struct { uint8_t x; uint8_t y; } Cell; typedef struct { Cell body[MAX_SNAKE_LEN]; uint16_t head; uint16_t tail; uint16_t len; uint8_t dir; /* 0上 1右 2下 3左 */ uint8_t want_dir; /* 缓存玩家想转的方向 */ } Snake;用uint8_t存坐标范围上限是16和8蛇身最多占用128*2256字节SRAM对F4来说可以忽略。环形下标的精髓是取模(head 1) % MAX_SNAKE_LEN当head走到数组末尾时自动回到0蛇身像在循环缓冲区里滚动。整个游戏生命周期内没有一次堆分配也不存在越界风险。3.2 四个状态的状态机与want_dir转向缓存贪吃蛇至少有四个稳定状态主菜单、游戏中、暂停、游戏结束。用枚举加switch实现足够清晰不必引入状态机框架。状态迁移如下当前状态输入下一状态处理动作ST_MENU确认键ST_PLAY重置蛇长、蛇头位置、分数生成第一个食物ST_PLAY暂停键ST_PAUSE冻结节拍计数ST_PAUSE确认键ST_PLAY恢复节拍计数ST_PLAY撞墙/咬尾ST_OVER显示最终分数等待确认返回菜单转向缓存是这里最容易忽略的细节。蛇向右移动时玩家快速按“上”再按“左”如果每次都直接修改dir第一次按键后dir变成上第二次按键时系统看到当前还是向右仍然判合法结果蛇在下一帧直接掉头咬进自己身体。处理办法是只更新want_dir等蛇头真正前进时先做一次180度反转检查if (snake.want_dir ! snake.dir (snake.want_dir 2) % 4 ! snake.dir) { snake.dir snake.want_dir; }这段代码放在Game_Tick入口逻辑上是“对向禁止、同向与垂直才放行”玩家快速连按也只保留最后一次合法方向。3.3 碰撞检测顺序与食物生成中的随机种子每帧核心逻辑按下述顺序执行先做转向检查再计算新蛇头坐标先判墙再判自身最后判食物。顺序不能反过来——先写蛇头再查自身会把“吃到自己”误判成合法移动。void Game_Tick(void) { /* 转向保护禁止180度反转 */ if (snake.want_dir ! snake.dir (snake.want_dir 2) % 4 ! snake.dir) { snake.dir snake.want_dir; } Cell new_head { snake.body[snake.head].x dx[snake.dir], snake.body[snake.head].y dy[snake.dir] }; int eat (new_head.x food.x new_head.y food.y); /* 1. 撞墙判定x/y下溢-1会回绕成255也会被拦下 */ if (new_head.x GRID_W || new_head.y GRID_H) { Game_SetOver(); return; } /* 2. 咬自身判定没吃到时蛇尾会前移尾格不算障碍 */ uint16_t check_len eat ? snake.len : snake.len - 1; for (uint16_t i 0; i check_len; i) { Cell *c snake.body[(snake.tail i) % MAX_SNAKE_LEN]; if (c-x new_head.x c-y new_head.y) { Game_SetOver(); return; } } /* 3. 落子写入新头决定是否缩尾 */ snake.head (snake.head 1) % MAX_SNAKE_LEN; snake.body[snake.head] new_head; if (eat) { snake.len; Game_ScoreUp(); Food_Generate(); } else { snake.tail (snake.tail 1) % MAX_SNAKE_LEN; } }dx/dy方向表建议写成常量数组static const int8_t dx[4] {0,1,0,-1}; static const int8_t dy[4] {-1,0,1,0};让方向枚举值和坐标偏移一一对应避免写四个if分支。咬自身检查里(snake.tail i) % MAX_SNAKE_LEN必须取模环形队列回绕后最老一节的物理位置可能在数组中间。食物生成最省事的随机源是TIM5的计数器值取出来给srand当种子循环检查新食物是否落在蛇身上在就重新生成蛇快占满全屏时这个循环可能退化成长时间空转限定最多重试64次超过直接判赢处理。4. 基于HAL库的OLED渲染与按键扫描跑通最小可玩版本4.1 SSD1306画点函数页地址模式下的显存缓冲思路SSD1306把128x64像素划分为8个page每个page高8像素。芯片内部显存布局是128列x8页写一像素最稳妥的做法是本地先存一份1KB缓冲修改缓冲对应字节后再整字节回写。注意I2C模式下直接读SSD1306内部显存是不标准的做法在线回读慢且容易出错。static uint8_t oled_buf[128 * 8]; /* 1KB显存缓冲 */ void OLED_DrawPixel(uint8_t x, uint8_t y, uint8_t on) { if (x 128 || y 64) return; uint16_t idx x (y / 8) * 128; uint8_t bit 1u (y % 8); if (on) oled_buf[idx] | bit; else oled_buf[idx] ~bit; } void OLED_Flush(void) { HAL_I2C_Mem_Write(hi2c1, 0x3C 1, 0x00, 1, oled_buf, 128 * 8, 100); }画点函数里y / 8算出像素落在哪个pagey % 8算出页内位偏移。oled_buf的顺序和SSD1306的页地址模式保持一致Flush时把1024字节一次连续写出。HAL_I2C_Mem_Write的参数分别是I2C句柄、器件地址0x3C左移一位、控制字节0x00表示后续是显存数据、数据长度与超时毫秒数。像素Y范围所在Page缓冲偏移范围0 ~ 7Page 00 ~ 1278 ~ 15Page 1128 ~ 25556 ~ 63Page 7896 ~ 1024如果换用SPI接TFT画点逻辑类似只是y/8变成直接操作RGB565的每个像素双字节显存缓冲要扩到3202402约150KBF4的192KB SRAM放得下Flush建议改DMA。4.2 渲染管线选择整帧缓冲刷新与局部涂擦每帧画面其实只变化两处蛇头前进一格蛇尾缩回一格。整帧刷新实现简单但I2C上约20ms的传输时间里主循环被堵住玩起来能感觉到闪烁。局部涂擦的渲染函数如下void Game_Render(void) { /* 先擦老尾巴再画新蛇头顺序不能反 */ Cell *tail snake.body[snake.tail]; OLED_FillBlock(tail-x, tail-y, 0); Cell *head snake.body[snake.head]; OLED_FillBlock(head-x, head-y, 1); if (food_dirty) { OLED_FillBlock(old_food.x, old_food.y, 0); food_dirty 0; } OLED_FillBlock(food.x, food.y, 1); }OLED_FillBlock对8x8像素格子做两层循环逐点画整个格子点亮或熄灭。局部刷新把每帧I2C传输量从1024字节降到几十字节玩家视角里蛇身移动就是两个小方块的变化画面干净对I2C带宽压力也小。擦尾和画头不要颠倒否则蛇头到达新格而旧尾还没擦必须等下一帧补齐视觉上会断帧。4.3 10ms轮询消抖的按键扫描按键到want_dir的映射表按键扫描放在主循环里配合另一个10ms的定时器标志执行连续读到3次相同电平才认为按键有效。它比EXTI更适合这个游戏因为玩家按住方向键时动作游戏需要周期性读键边沿中断只给一次机会。uint8_t key_state[4]; /* 消抖后状态1按下 */ void Key_Scan(void) { static uint8_t stable[4], cnt[4]; for (uint8_t i 0; i 4; i) { uint8_t level !HAL_GPIO_ReadPin(KEY_PORT[i], KEY_PIN[i]); if (level ! stable[i]) { stable[i] level; cnt[i] 1; } else if (cnt[i] 3) { cnt[i]; } if (cnt[i] 3) { key_state[i] level; } } }消抖原理是电平连续稳定20ms才认账。KEY_PORT[i]和KEY_PIN[i]用宏定义集中管理方便换引脚。拿到key_state后按表映射到want_dir按键方向方向枚举dxdyKEY_UP上00-1KEY_RIGHT右110KEY_DOWN下201KEY_LEFT左3-10映射只更新want_dir不直接写dir把第3章的180度反转保护放在Game_Tick入口统一执行。按键扫描、逻辑移动、渲染三者完全解耦后续换摇杆或编码器只需要改映射层。5. STM32F4贪吃蛇的速度阶梯与在线调试技巧5.1 速度阶梯表放在数据段运行时改ARR的注意事项游戏好玩与否难度曲线比画面细节更关键。把等级对应的时间放到const数组里每吃4个食物升一级在主循环里按分数改写ARR。const uint16_t speed_table[] {1000, 800, 600, 450, 300, 200, 150}; uint8_t level score / 4; if (level 6) level 6; __HAL_TIM_SET_AUTORELOAD(htim2, speed_table[level] - 1);__HAL_TIM_SET_AUTORELOAD在运行时修改ARRTIM2的更新中断频率立即变化PSC保持不变。修改ARR前先调用__HAL_TIM_DISABLE(htim2)关中断改完再重新启动避免ARR切换瞬间产生一次多余的更新中断打乱节拍。难度曲线只改表里的数值不动游戏逻辑。5.2 用逻辑帧串口打印替代断点板子脱机后调试信息很难看最有效的手段是打开串口按逻辑帧输出状态。每10帧打印一次蛇头坐标、方向、蛇长与当前状态足以定位大多数转向或碰撞错误。串口输出走HAL_UART_Transmit阻塞版本时打印期间主循环被暂停按键采样会掉拍建议把波特率拉到115200以上打印内容精简到最短。验证方法很直接先用固定方向表跑20局记录每局时长和死亡位置若在某关必死说明该等级速度下转向窗口不够回查速度表和want_dir缓存逻辑。5.3 后续三个可动手的方向DMA刷屏、掉电保存最高分、ADC摇杆在现有代码上还剩三个改动点。第一把OLED_Flush改成DMA传输游戏在Flush进行时继续处理按键帧率提升同时主循环不再被I2C阻塞第二把最高分写入F4内置Flash扇区HAL里对应操作是先擦除再编程写到最后一个扇区避免覆盖程序区第三把四个按键换成ADC摇杆键值映射依然留在main.csnake模块完全不感知输入来源这种分层方式继续上FreeRTOS时逻辑层可以原样搬进独立任务只需重写显示和输入两个驱动。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/10 15:43:35

SpringBoot驾校学员管理系统设计与实现

1. 项目概述:SpringBoot驾校学员管理系统设计与实现 去年帮朋友驾校做信息化改造时,发现市面上多数学员管理系统要么功能冗余要么操作复杂。于是基于SpringBoot设计了一套轻量级解决方案,从报名到拿证全流程数字化管理,特别适合中…

2026/9/10 15:43:34

锂枝晶抑制与电池隔膜优化技术解析

1. 锂枝晶生长机制与电池安全痛点锂金属负极因其极高的理论比容量(3860 mAh/g)和最低的电极电位(-3.04 V vs. SHE)被视为下一代高能量密度电池的"圣杯"。但在实际应用中,锂枝晶的生长问题就像一把悬在头顶的…

2026/9/10 15:43:34

PHP协程调度器原理与Swoole高性能实现

1. PHP协程调度器实现原理与核心价值在传统PHP开发中,阻塞式I/O操作一直是性能瓶颈的根源。当我们需要处理高并发请求时,通常会采用多进程或多线程方案,但这会带来显著的内存开销和上下文切换成本。PHP协程调度器的出现,本质上是通…

2026/9/10 15:38:34

龙珠数字藏品与游戏模组的技术实现与市场分析

1. 项目背景解析 "dragonballz_e209-2"这个看似神秘的代号,实际上蕴含着丰富的文化基因和技术内涵。作为一名长期关注二次元文化和技术交叉领域的从业者,我首次看到这个命名时就意识到它可能代表着某种融合了经典动漫元素与现代技术理念的创新…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/10 11:16:38

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/10 0:00:55

目录对比去重实战:用哈希算法精准清理重复文件

我电脑里现在还有一块换了三次机的“数据墓地”硬盘,里面存着2016年以前所有旧笔记本的完整备份。平时不觉得有什么,直到前阵子想把它整理归档,发现同一个安装包、同一批照片、同一份论文草稿,在几个不同的备份目录里反复出现。更…

2026/9/10 0:00:55

Leaflet离线地图完整Demo合集:内网部署与坐标纠偏实战

简介:这是一份面向Web GIS开发者的LeafLet离线地图示例合集,帮助开发者快速掌握离线地图从搭建到交互的完整流程。压缩包共723个文件,大小14.06MB,以319个js脚本、175个html页面和29个css样式文件为主体,配合png/svg图…

2026/9/10 0:00:55

MATLAB读取Rinex 3.02观测文件:多系统GNSS数据解析实战

简介:基于MATLAB开发的Rinex3.02版观测文件(o文件)读取代码包,面向卫星定位导航方向的学习者与研究人员,用于解决新版观测文件的数据解析、历元提取与时间转换问题。压缩包共4个文件,包含两个m脚本、一个19…

2026/9/10 12:32:02

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/10 15:19:50

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码