基于STM32的五子棋对战平台:从硬件选型到AI算法实战指南

基于STM32的五子棋对战平台:从硬件选型到AI算法实战指南
简介基于STM32F4原子探索者的五子棋对战平台主要面向嵌入式系统学习者与游戏开发爱好者完整实现触摸下子、人机对战、人人对战、悔棋以及音量开关等实用功能且工程结构清晰便于在不同开发板或平台上参考移植。压缩包共274个文件大小5.44MB核心代码由55个C源文件和50个头文件组成涵盖LCD显示、触摸交互、游戏逻辑、AI对战算法等关键模块包内另附编译生成的axf、hex固件、工程配置文件、文档说明以及界面预览图可直接烧录体验也能对照阅读代码理解实现细节。目前已有2166人学习下载。工程代码注释明确、模块划分合理适合希望通过实际项目掌握STM32外设驱动、游戏程序设计或人机博弈算法的开发者可在阅读后快速提取所需模块并迁移至自己的项目中。 做嵌入式这些年经常有人问我“想做个有点技术含量、能拿得出手又不至于烂大街的STM32项目做什么好” 我一般会推荐一个方向——游戏类应用。而这里面五子棋对战平台是最恰到好处的一个。它不像贪吃蛇那样光是逻辑堆砌也不像智能小车那样依赖机械结构它把显示、输入、算法、状态机、甚至通信都揉在了一起难度曲线非常平滑特别适合作为毕业设计或者求职简历上的实战项目。这篇文章我会完整拆解一个“基于STM32的五子棋对战平台”该怎么做从硬件选型、电路设计、软件架构、AI算法到环境搭建和调试排坑一条龙讲清楚。项目本身不复杂但深入去挖能挖出很多底层和算法的东西。不管是刚学完51准备进阶的学生还是想做个完整项目给自己加分的嵌入式爱好者这篇文章都值得你从头看到尾。1. 项目整体设计与方案选型很多新手拿到这个题目第一反应是这不就是一块屏幕加几个按键吗确实功能上就是这么回事。但“做出来”和“做好”之间隔着一层选型的功力。1.1 主控芯片怎么选F103C8T6为什么是黄金起点以五子棋的数据规模和运算量来看几乎任何一款STM32都跑得动但要选一个“性价比、学习成本、资料丰富度”三者平衡的型号我首选STM32F103C8T6。这颗芯片是Cortex-M3内核主频72MHzFlash 64KBSRAM 20KB。五子棋15×15棋盘用 uint8_t 二维数组存储也就225字节加上LCD显存、AI搜索的临时数组20KB SRAM绰绰有余。72MHz的主频做贪心评分AI是毫秒级响应做3层极小化极大搜索也不会有明显的卡顿感。如果后续想扩展功能比如加语音播报需要音频解码、加触摸屏需要FSMC总线、或者上FreeRTOS做多任务F103C8T6依然可以战一战只是RAM会有点吃紧。我的建议是预算有限、入门为主F103C8T6最小系统板10块钱左右想玩得更从容、后续扩展空间大STM32F407ZET6主频168MHzRAM大得多还能用FSMC直驱大屏。但五子棋这个项目F103C8T6是完全够用的。别一上来就追高配把精力花在算法和代码结构上收获更大。1.2 显示方案怎么选OLED还是TFT-LCD显示方案直接决定了项目的观感和用户体验。我见过有人用0.96寸OLED做五子棋屏幕上一格格非常小棋子只能用像素点表示玩起来费眼睛。也有人用12864这种字符屏做压根画不出来棋子的感觉。推荐两种方案方案屏幕接口优点缺点入门方案OLED SSD1306 0.96寸I2C/SPI便宜、驱动简单、库多屏幕小视觉效果差推荐方案TFT-LCD ILI9341 2.8寸/3.5寸SPI/FSMC尺寸大、颜色丰富、棋盘清晰接线较多初始化稍复杂TFT-LCD里ILI9341是最经典的控制器网上驱动代码一抓一大把2.8寸款用4线SPI就能驱动占用的GPIO引脚不多画一个15×15的棋盘绰绰有余。驱动库方面可以用逐飞、正点原子的LCD驱动或者找一份SPI版ILI9341驱动移植代码量不大移植也不是难事。我建议你直接买带触摸的3.5寸版本虽然触摸在这个项目中不是必需的但后续如果你想把“按键落子”升级成“触摸落子”硬件上就不用再折腾了。1.3 输入方案的取舍独立按键还是矩阵键盘落子需要输入坐标或者移动光标这里有两个方向独立按键方案4个方向键 1个确认键 1个返回键共6个GPIO。优点按键定义清晰、代码好写、不容易误触。缺点是按键数量多接线有点乱。矩阵键盘方案4×4矩阵键盘使用8个IO口扫描16个键。可以设计成“数字坐标输入”比如按“A”代表横坐标按“5”代表纵坐标再按“确认”落子。也可以做方向键映射。优点锻炼扫描算法、看起来专业。缺点是代码稍微复杂需要处理消抖和组合按键。两种方案我都在实际项目中试过。如果是毕设我强烈推荐矩阵键盘——答辩时“矩阵键盘扫描原理”是一个很好的加分点能讲的东西多。而且4×4键盘在项目展示时视觉上也更有“平台感”。需要注意的是不管选哪种方案尽量避开已经占用的特殊功能引脚。STM32F103C8T6的PA13/PA14/PA15、PB3/PB4默认是SWD和JTAG引脚如果做IO使用需要额外配置或会导致无法下载程序。这是个经常踩的坑后面还会细说。1.4 系统架构裸机还是RTOS五子棋这个项目裸机大循环完全够用我建议入门者不要为了“显得高级”而强行上FreeRTOS。裸机代码的可读性更高调试也更直观。裸机架构的主循环大概长这样int main(void) { SystemInit(); Delay_Init(); LCD_Init(); KEY_Init(); Game_Init(); while (1) { Key_Scan(); // 扫描按键设置事件标志 Game_Process(); // 根据事件更新游戏状态 Display_Update(); // 刷新LCD画面 } }这个结构简单清晰三个环节各自独立。按键扫描可以放在主循环里也可以放到定时器中断里比如10ms一次但注意中断里只做“记录按键事件”真正处理逻辑放到主循环避免中断服务函数里跑复杂逻辑导致的卡顿和误触发。如果你后续想优化可以把显示刷新也拆出去用DMA传输LCD数据主循环只负责逻辑运算。不过对于当前项目裸机就足够重点还是要放在算法上。2. 硬件电路设计与核心模块详解一个完整的五子棋对战平台硬件上包括最小系统、显示模块、输入模块以及一些补充电路。这个部分我拆开讲清楚每个电路的原理和关键参数。2.1 STM32最小系统与电源电路STM32F103C8T6最小系统看起来简单但有些细节影响很大供电一般USB的5V进来先经过AMS1117-3.3降压到3.3V。稳压芯片前后各加一个10μF和0.1μF滤波电容。电感布局上尽量靠近芯片电源引脚别拉长走线。8MHz晶振电路这是最容易出问题的地方。晶振两端要接两个负载电容典型值20pF左右。复位电路NRST引脚接一个10kΩ上拉电阻到3.3V再接一个0.1μF电容到GND。按下复位键时把引脚拉低实现复位。BOOT0引脚一定要接一个10kΩ下拉电阻到GND让芯片从主Flash启动。如果悬空可能偶尔启动到系统存储器变成ISP模式现象是程序不跑、下载后无反应。关于晶振电容很多人直接抄电路图不知道为什么要接20pF。这个值其实有计算公式负载电容CL ≈ (C1 × C2) / (C1 C2) Cstray其中Cstray是PCB走线和引脚寄生电容大约5~10pF。如果目标负载电容CL是20pF那么两个外接电容分别取22pF左右算下来(22×22)/(2222)6 ≈ 17pF差不多在合理范围内。选电容时不用过分纠结10~22pF都能让晶振正常起振关键是板子布线时晶振要靠近MCU引脚走线短且不要有长距离平行线。2.2 TFT-LCD显示模块接线与驱动要点ILI9341的2.8寸屏SPI模式引脚不算多LCD引脚连接到 STM32说明VCC3.3V电源GNDGND地CSPB12片选低有效RESETPB13复位低有效DC/RSPB14数据/命令选择SCLKPB13SPI时钟SPI2 SCKMOSI/SDAPB15SPI数据SPI2 MOSILED3.3V串电阻背光串联一个10Ω~100Ω限流这里SCLK和MOSI的组合我推荐直接使用STM32硬件SPI2而不是GPIO模拟。硬件SPI速率可以达到9MHz刷屏流畅。注意SPI模式要设置成SPI_MODE0CPOL0CPHA0这是大多数LCD控制器的要求。初始化时序上有个容易忽略的点LCD的RESET引脚需要在初始化时拉低至少10ms再拉高然后延时等待控制器内部复位完成。很多人LCD白屏复位时序不对是常见原因之一。2.3 4×4矩阵键盘的原理与接线矩阵键盘节省IO的原理很简单4根行线作为输出4根列线作为输入每个按键位于行列交叉点。扫描逻辑如下把4根行线全部拉高然后逐行拉低比如先拉低第1行其他行高读取4根列线的电平哪一列是低电平就说明第1行和该列交叉点的按键被按下同一个时刻只有一行被拉低所以行与行之间不会互相干扰配合10~20ms延时做软件消抖。STM32的GPIO初始化为开漏输出拉高列线初始化为浮空输入内部上拉这样可以省略外部上拉电阻。代码示意// 第1行拉低其他行拉高 GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET); // 行1 GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET); // 行2 低 GPIO_WriteBit(GPIOA, GPIO_Pin_2, Bit_SET); // 行3 GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_SET); // 行4 // 读取列电平 uint8_t col GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);有了矩阵键盘之后可以设计一个简单直观的下棋输入方式第一键输入横坐标A~O第二键输入纵坐标0~14第三键按“确认”落子按“返回”重新输入。缺点是需要三次按键不如方向键直觉。我最终采用的方向键思路是用2×2矩阵键盘映射成“上/下/左/右”加两个独立确认/返回。这种混搭方案兼顾了代码复杂度与交互体验。2.4 电源与供电注意事项整个平台的功耗不高LCD带背光亮起时电流也就几十毫安到一百多毫安USB供电完全没问题。有一点要提醒如果用电脑USB口供电有些老电脑的USB口电压不太稳LCD背光会闪烁程序运行也可能随机复位可以在USB电源输入端并联一个470μF电解电容能显著改善稳定性。3. 软件框架与核心算法说实话硬件接线只是体力活真正的技术含量主要体现在软件架构和AI对弈算法上。这个部分也是写论文或者面试时最值得展开讲的。3.1 数据结构与状态机设计先用最简单且可读性高的数据结构来存储棋盘状态#define BOARD_SIZE 15 uint8_t board[BOARD_SIZE][BOARD_SIZE]; // 0: 空; 1: 黑棋; 2: 白棋 uint8_t current_player; // 0: 玩家 1: AI uint8_t game_state; // 0: 对局中 1: 分出胜负 2: 平局游戏流程设计成状态机STATE_MENU主菜单可选“双人对战”“人机对战”STATE_PLAYING对局中接收输入并落子STATE_END结束画面显示胜方可按任意键返回菜单状态机的好处是代码逻辑清晰不容易出现“下完棋还能继续落子”这种bug。我用一个 switch 语句处理状态流转配合按键事件结构体typedef struct { uint8_t key_code; uint8_t key_pressed; } KeyEvent;按键扫描函数只负责填充这个结构体主循环读它按键和逻辑完全解耦后面改输入方式比如换成触摸屏都不用动游戏逻辑。3.2 胜负判定落子后的四个方向检查五子棋的胜负判定并不复杂因为只需要在每次落子后检查当前落点周围是否有五连不必全盘扫描。高效且清晰的写法是从落子点出发向正反两个方向累计相同棋子数量。static int CheckDirection(int x, int y, int dx, int dy, uint8_t piece) { int count 1; for (int i 1; i 5; i) { int nx x dx * i; int ny y dy * i; if (nx 0 || nx BOARD_SIZE || ny 0 || ny BOARD_SIZE) break; if (board[nx][ny] ! piece) break; count; } for (int i 1; i 5; i) { int nx x - dx * i; int ny y - dy * i; if (nx 0 || nx BOARD_SIZE || ny 0 || ny BOARD_SIZE) break; if (board[nx][ny] ! piece) break; count; } return count; }四个方向分别是(1,0) 横、(0,1) 纵、(1,1) 斜向下、(1,-1) 斜向上。只要其中任一条连子数量大于等于5就判胜利。注意边界检查要放在访问数组之前否则数组越界可能把程序搞挂。3.3 人机AI从贪心评分到极小化极大人机对战是这个平台的核心亮点也是拉开档次的地方。入门版贪心评分法给每个空位打分分值依靠该位置形成的棋型五连100000分活四50000分冲四8000分活三20000分眠三3000分活二1000分AI计算时分别站在自己角度和对手角度给所有空位打分然后选择综合分最高的点落子for (每1个空位 p) { ai_score Evaluate(p, AI_PIECE); player_score Evaluate(p, PLAYER_PIECE); total_score ai_score * 1.2 player_score; if (total_score best_score) { best_pos p; } }这个“1.2”是防守偏向系数没有绝对标准但建议大于1.0这样AI在“进攻”和“防守”之间稍微偏重进攻棋风更有压迫感。贪心评分法的优点是计算量极小STM32F103跑起来完全无压力缺点是只有一层视野遇到复杂局面容易中招。进阶版极小化极大 α-β剪枝如果想让AI真正会“思考”就要用搜索树。每次AI落子假设玩家也会做出最优应对构建出一棵博弈树然后通过负极大值Negamax或者极小化极大来计算最佳落子。伪代码int Minimax(int depth, int alpha, int beta, uint8_t currentPlayer) { if (depth 0 || IsTerminalNode()) return Evaluate(); for (int i 0; i candidate_count; i) { PlacePiece(candidate[i], currentPlayer); int score -Minimax(depth - 1, -beta, -alpha, opponent); RemovePiece(candidate[i]); if (score beta) return beta; if (score alpha) alpha score; } return alpha; }这里最关键的是候选点的选择不能遍历全部225个空位计算量会爆炸。一个常见的优化是只考虑“已有棋子周边2格范围内”的空位候选点一般能降到30个以内搜索深度设为3层在72MHz的F103上大约需要几百毫秒体验可以接受。3.4 显示刷新与界面设计界面设计直接影响用户体验。我的做法是15×15棋盘用两重循环画网格线每隔4格画一条稍粗的线方便定位棋子的绘制是实心圆黑棋用黑色填充白棋用白色填充再加一个1像素的黑色描边这样在LCD上视觉效果更清晰当前选中的交叉点用一个红色小方框或圆圈标记方便键盘操作定位顶部留一条状态栏显示当前回合、玩家信息和提示。LCD刷新是个容易拖低流畅度的地方。我的经验是不需要每帧重画整个棋盘正常情况下只有“一个棋子”和“光标位置”发生变化所以只把这些区域设置为脏矩形局部更新即可。这样刷屏延迟会从几十毫秒降到几毫秒操作体验完全不同。4. 开发环境搭建与程序部署调试这个项目开发中很多零基础的同学卡在最开始环境装不好、工程建不对、程序下载不进去。这个章节我从头梳理。4.1 Keil5安装与芯片包部署STM32开发最常用的IDE是Keil MDK5也可以选STM32CubeIDE但Keil在国内资料多调试方便我推荐新手用Keil。安装后重点提醒MDK5的芯片支持不再内置必须额外安装STM32F1系列芯片包。很多人的MDK装了却发现“没有STM32F103C8”的可选项就是因为缺这一步。正确做法打开Keil点击 Pack Installer 图标搜索“STM32F1xx”下载对应芯片支持包网络不好的时候去官网手动下载 Keil.STM32F1xx_DFP.pack双击也能安装。装好芯片包后新建工程时选 STM32F103C8 即可。4.2 标准库新建工程目录结构和编译配置STM32标准库虽然“退出历史舞台”但它的代码清晰度远胜HAL库而且网上教程最多。新建工程时我建议这样组织目录Project/ ├── CORE/ // 启动文件、core_cm3.h ├── FWLib/ // STM32标准外设库 ├── HARDWARE/ // LCD、KEY、LED等硬件驱动 ├── SYSTEM/ // 延时、串口、中断 ├── USER/ // main.c、stm32f10x_it.c └── OBJ/ // 编译输出配置要点C/C选项卡里Define填USE_STDPERIPH_DRIVER, STM32F10X_MDInclude Paths把上面每个目录都加进去漏一个就报头文件找不到Debug选项卡选择ST-Link DebuggerSettings里确认能读到芯片IDUtilities选项卡勾选“Flash Download”烧录算法添加 STM32F10x Med-density FlashF103C8是中等容量别选错了。4.3 下载失败error: no stm32 target found! 的排查实录这个报错我相信很多新手都被劝退过完整的报错是error: no stm32 target found! if your product embeds debug authentication, please这个提示虽然带debug authentication新芯片默认启用的一种安全保护但F103C8T6通常不是这个原因。排查顺序如下接线检查SWD接口只需要3根线——SWDIO、SWCLK、GND。杜邦线超过20cm或接触不良都会导致找不到目标。直连ST-Link尽量缩短线长。供电确认目标板必须先上电。有些ST-Link的3.3V输出可以给板子供电但电流有限带LCD时候容易电压跌落建议外接USB供电ST-Link只接数据线。SWD引脚被复用最常见的原因如果板子上一次烧录的程序把PA13/PA14SWDIO/SWCLK配置成了普通IO下一次就连不上。解决方法是按住复位键点下载然后立刻松开复位。有些场景还要按久一点全片擦除后再烧录正常的程序。BOOT0被拉高BOOT0为1时芯片进入系统存储器模式不会执行用户代码也可能提示找不到目标。检查BOOT0跳线帽是否在0端。如果以上都排查完还不行用STM32 ST-LINK Utility做一次“Full Chip Erase”能解决绝大部分顽固问题。4.4 程序烧录后不运行的常见原因烧录成功后板子没反应我的排查经验是启动文件选错F103C8T6是中等容量64KB Flash必须用 startup_stm32f10x_md.s如果用成高容量 hd 的程序大概率跑飞。很多直接从网上抄工程的同学都栽在这。时钟配置错误系统初始化时要经过PLL把8MHz倍频到72MHz。如果晶振没焊接好或电容不匹配PLL锁不住程序会卡在时钟初始化死循环。GPIO初始化遗漏某个外设没开RCC时钟程序执行到GPIO配置时表现很奇怪。新手最容易漏的不要笑我见过好多次是LCD复位引脚没初始化为输出导致复位悬空LCD白屏。数组越界或栈溢出程序跑着跑着死机多半是某个索引越界把数据写坏了。可以把HardFault_Handler里加一个断点直接定位在哪行触发。5. 常见问题与排查技巧实录5.1 LCD白屏但程序在跑这个是出现频率最高的问题。我的排查顺序先确认背光亮不亮。不亮——查LED引脚和限流电阻亮但纯白——查硬件复位时序复位引脚必须拉低再拉高初始化序列对不对——ILI9341的初始化配置寄存器序列必须完整有些屏幕用编译器优化选项不同会导致延时不准初始化失败。可以把SPI速度降到1MHz试试SPI模式对不对——ILI9341习惯SPI Mode0个别屏支持Mode2看规格书或驱动代码说明。5.2 按键抖动导致的误触发矩阵键盘消抖最稳妥的做法是用定时器中断固定周期扫描比如10ms扫一次连续两次检测结果一致才确认按键触发。不要在主循环里用while等按键释放的方式那样会阻塞游戏逻辑。另外按键接的GPIO要配置输入上拉否则浮空状态下容易误触发。内部弱上拉几十千欧通常够用。5.3 延时函数卡死“delay卡死”这个坑最常见的两个原因用了SysTick延时但没有正确初始化SysTick或配置中断。标准库默认的Delay_Init里设置SysTick时钟源和重载值如果漏了会停在那里死等。优化级别过高比如-O3把延时循环里的空转优化掉了导致延时时间变成0。解决方法是把延时函数里的变量加volatile修饰或者干脆用SysTick。5.4 ST-Link虚拟串口出现黄色感叹号设备管理器里ST-Link的Virtual COM Port有感叹号本质不是ST-Link坏了而是驱动没装好。Windows 10/11下ST-Link驱动有时候会在系统更新后被替换成通用驱动重新安装STSW-LINK009或者直接用STM32CubeProgrammer自带的驱动即可。另外有些USB线只支持充电不支持数据传输插上去会反复断连果断换线。5.5 两台STM32联机对战怎么扩展如果你想在这个平台基础上加一个“双机联机对战”的功能最简单是UART串口互联// 发送落子坐标 uint8_t buf[4] {0xAA, x, y, 0x55}; HAL_UART_Transmit(huart1, buf, 4, 100);接收方校验帧头0xAA、帧尾0x55再把坐标写入本地棋盘。这种自定义帧协议不复杂但需要处理粘包和丢帧是练通信协议的一个好题目。我在推荐这个扩展时都会强调别一上来就上完整协议栈先定好帧格式用串口助手两边互发调通了再接入真实数据。5.6 程序跑飞的通用排查手段开发过程中我习惯在HardFault_Handler里加上按键复位比如无限循环等待复位然后用调试器的寄存器窗口查看PC指针位置直接定位到跳飞行的代码行。如果没有调试器也可以在关键函数入口做个LED翻转看哪个环节没跑到二分定位。6. 写在最后的一点经验说实话五子棋对战平台这个项目硬件部分两天就能搭完真正耗时间的是AI算法调优和一堆莫名其妙的小bug。我做这个项目的感受是别急着追求AI棋力多强先把“一台能稳定运行、交互顺手、代码结构清晰”的平台做出来AI再从贪心评分升级到搜索算法每一步都能拿出可演示的成果做汇总材料时也更有层次感。最后分享一个小编程技巧棋盘坐标横坐标用A~O而不是0~14来表示这样按键输入的数字和字母能对应上且界面不会拥挤。如果你打算做毕业设计还可以把“AI搜索深度的动态调整”做成一个特色功能——玩家选择不同难度时AI自动在“深度1层的贪心”和“深度3层的搜索”之间切换。这样一个看似普通的五子棋平台在技术点的深度和丰富度上就比大多数同学的项目高出一个段位了。本文还有配套的精品资源点击获取

最新新闻

日新闻

周新闻

月新闻