STM32驱动ILI9341实现黑白棋对战:SPI屏幕驱动、游戏逻辑与Proteus仿真详解

📅 发布时间:2026/9/1 7:29:51
STM32驱动ILI9341实现黑白棋对战:SPI屏幕驱动、游戏逻辑与Proteus仿真详解
简介面向嵌入式初学者的STM32黑白棋对战完整工程主控采用STM32F103R6通过SPI接口驱动ILI9341彩色液晶屏实时显示棋盘与落子效果并支持在Proteus中直接仿真运行。工程基于STM32CubeMX初始化配置使用HAL库开发源码覆盖GPIO、SPI、SysTick等底层驱动并包含完整游戏逻辑、中断处理、硬件抽象层适配及ILI9341专用显示驱动等模块能够清晰看到应用层与驱动层的拆分目录分工明确。资源包共597个文件、约25.76MB除大量C/H源文件与启动汇编文件外还带有.uvprojx/.uvoptx等Keil MDK-ARM工程配置、.pdsprj/.pdsbak等Proteus仿真设计、.hex/.axf等编译产物、电路原理图、.ioc初始化配置以及实机演示视频STM32_BWGame.wmv其中Proteus仿真文件可直接加载运行硬件接线方式在原理图中一目了然方便对照理解。全部代码已验证可编译通过配合Proteus仿真可实时观察黑白棋对弈流程、落子刷新与屏幕交互效果适合嵌入式初学者系统掌握SPI外设驱动、LCD显示控制与人机交互逻辑。目前已有56人学习。 先说一个我个人的感受做嵌入式小项目最期待、也最有成就感的一刻通常不是程序写完整而是代码烧进去、屏幕“啪”地一下亮起来光标能移动、棋子能落下去。这篇文章要拆解的就是这样一个工程STM32F103R6驱动ILI9341液晶屏实现黑白棋对战附带Proteus仿真、Keil源码和硬件电路设计。如果你想用一个小而完整的项目把单片机外设、屏幕驱动、游戏逻辑和仿真调试串起来这篇文章可以给你一个从零到一的完整参考。黑白棋这个题材选得好。它的规则够简单但落子合法判定、吃子翻转、AI策略、无子可下结束这些逻辑又足够有含金量比单纯跑个流水灯或者驱动个传感器更能训练系统思维。工程本身也不复杂适合做课设、毕设的练手项目或者作为嵌入式GUI开发的第一块跳板。1. 项目全貌一颗64脚芯片怎么装下一整局黑白棋1.1 黑白棋规则与产品形态黑白棋英文名Reversi也叫Othello8x8棋盘黑先白后。每回合必须在空位上落一颗子落子的八个方向中至少有一个方向能把对方的连续棋子“夹住”然后把这些被夹住的棋子翻过来变成己方颜色。如果当前玩家所有空位都无法合法落子就自动跳过由对方继续。双方都无棋可下时对局结束棋盘上棋子数多的一方获胜。这套规则写代码时不复杂但很容易出错尤其是“夹住”的判定和翻转的边界处理。实际上黑白棋应用在这个项目里还有一个额外好处它几乎没有连续动画和实时数据对MCU性能要求不高整个游戏逻辑用纯C就能跑得很流畅非常适合作为STM32驱动TFT屏幕的入门应用形态。我做的工程形态是STM32F103R6作为主控通过SPI接口驱动ILI9341屏幕用独立按键完成光标移动、落子、模式选择等操作。屏幕上实时显示8x8棋盘、当前光标位置、当前轮次、比分以及提示文字。对战模式包括人机对弈和双人对战两种人机模式下MCU扮演AI用评分策略落子。1.2 STM32F103R6的资源账本与系统分层先算一笔资源账因为这个决定了后面所有设计边界。STM32F103R6是Cortex-M3内核72MHz主频64KB Flash、20KB SRAMLQFP64封装。20KB的RAM对一块240x320的屏幕来说完全够用因为ILI9341内部自带GRAM我们不需要在MCU内存里开整帧的显存只需要维护8x8棋盘数组和少量状态变量。压力主要在Flash上ILI9341初始化序列、字模、游戏逻辑和驱动代码加起来在标准库下一般能控制在20KB以内剩余容量依然充足。系统架构我分成三层底层驱动SPI初始化、ILI9341复位和初始化、画点/填充/绘图API游戏逻辑棋盘状态、落子合法性判断、翻转、胜负统计、AI选择界面层棋盘绘制、光标绘制、文字显示、按键扫描和状态机切换。这个分层的实际好处是调试方便。我在写AI逻辑时可以在main函数里直接跳过屏幕初始化只调用游戏逻辑接口用串口把每一步棋盘打印出来验证规则屏幕显示有问题时又可以拿固定棋盘数据喂给界面层。分层清晰对后面排错帮助非常大。2. 点亮屏幕ILI9341的SPI驱动与初始化细节2.1 四线SPI接线省引脚不是唯一理由ILI9341是一款非常常见的TFT液晶驱动芯片支持8080并口和SPI接口。8080并口速度快但要占掉十几个IO这在64脚的F103R6上很不划算所以我选择四线SPI模式。需要的信号一共六根SCKSPI时钟MOSI主出从进数据线CS片选DC数据/命令选择线RST硬件复位BL背光控制这里最容易忽略的是DC线的含义发送命令字节时DC必须为低发送数据字节时必须为高。整个时序过程就是先设置DC电平拉低CS然后通过SPI发送一个字节。我用的引脚分配是PA5SCK、PA7MOSI、PA4CS、PA1DC、PA0RST、PB0BL。这个映射完全可改但建议把屏幕信号放在SPI1而不是SPI2上。为什么优先SPI1因为SPI1挂在APB2总线上外设时钟最高36MHzSPI2挂在APB1上最高18MHz。虽然屏幕刷新未必总是满载跑但SPI1的余量更大同样的分频数下实际吞吐更高刷屏流畅度有差异。像素格式选RGB56516位色一个像素两个字节理论上SPI 16MHz时钟带宽足够撑起流畅的局部刷新。2.2 初始化序列这块屏真正的“脾气”ILI9341的初始化序列是出了名的“玄学”。不同厂家模组的初始化向量不同同一条命令在不同屏幕上效果可能完全不同。最典型的现象是同一套初始化代码在A模组上正常到B模组上就偏色、花屏甚至白屏。所以初始化序列一定要保留成独立的命令表方便换屏时调整。我工程里的初始化流程大概是这样的硬件复位RST拉低至少10ms再拉高延时120ms以上等内部状态稳定发送软件复位命令0x01发送退出睡眠命令0x11延时足够配置电源、VCOM电压、显示时序相关寄存器常见有0xC0、0xC1、0xC5等设置像素格式0x3A为0x55即16位色设置内存访问控制0x36这一步决定扫描方向和坐标原点配置Gamma校正寄存器0xE0、0xE1最后发送0x29开启显示。第6步的0x36是方向相关的关键如果屏幕显示方向不对比如坐标翻转、上下倒置多半是这里没配好。同一个屏在Proteus仿真里和实物上也可能需要不同的MADCTL值因为两者的坐标映射方式不完全一致。这个坑我在移植实物时踩过后来老老实实在初始化代码旁边写了注释提醒自己换屏时优先检查这里。初始化期间每条命令之间要留足延时。很多网上代码为了“快”把延时压得特别短结果屏幕偶发不亮或者开机花屏。我建议把延时函数封装成宏统一调参而不是散落在各处。2.3 绘图封装窗口模式是性能核心屏幕驱动最底层是画点函数。ILI9341的GRAM在写入像素时是有“窗口”概念的先发送0x2A设置列地址范围0x2B设置行地址范围然后连续写0x2C命令并发送像素数据GRAM会按设定自动填充不需要每个像素单独设置地址。基于这个原理我把核心API设计成三个LCD_SetWindow(x0, y0, x1, y1)设置矩形窗口LCD_FillRect(x0, y0, x1, y1, color)窗口模式下整块填充LCD_DrawPoint(x, y, color)单个像素写入。画棋盘时我整屏先填背景色再按格子坐标画9条横线和9条竖线形成网格初始4颗棋子用实心圆函数画。实心圆最省事的方式不是Bresenham圆种子填充而是两层循环直接判断每个像素是否满足“到圆心距离小于半径”代码短、不容易错对屏幕这种像素级绘图来说也够用。文字显示我用了字模取模只取“黑白棋”“黑”“白”“胜”“负”“思考中”“轮到你”这些必要汉字16x16点阵一个字32字节全部字模加起来不到1KBFlash完全没压力。取模软件我用的是PCtoLCD2002取模时要勾选逐行提取和字节倒置否则显示会镜像或者错位。屏幕刷新最核心的经验是局部刷新。Proteus仿真里SPI刷屏本来就比实物慢如果每次按键都全屏重绘光标移动都会卡成幻灯片。我实现时落子后只重绘当前格子和受影响区域光标移动只擦除旧光标位置并绘制新位置。这个优化对体验提升非常显著。3. 黑白棋逻辑与AI让MCU学会“思考”3.1 棋盘建模与8方向搜索算法棋盘数据结构我用最简单的uint8_t board[8][8]值0表示空1表示黑子2表示白子。为什么不用位棋盘位棋盘运算更“高级”但对20KB RAM的MCU来说没必要8x8数组更直观Keil调试时能直接看到每个元素排错效率高。方向数组是整个黑白棋逻辑的灵魂const int8_t dir[8][2] { {-1, -1}, {-1, 0}, {-1, 1}, { 0, -1}, { 0, 1}, { 1, -1}, { 1, 0}, { 1, 1} };所有规则逻辑都可以统一成一句话从落点出发沿八个方向寻找被夹住的对方棋子。落子合法性判断的完整流程如下落点必须在棋盘内且当前位置为空从落点沿某方向迈出一步如果越界或踩到空格这个方向直接失效继续沿该方向走只要还是对方棋子就继续向前如果最终遇到自己棋子那么这个方向上的所有对方棋子都可以被翻转只要存在一个满足条件的方向这个落点就是合法的。实现时最容易犯的错误是只判断“下一步是对方棋子”没有追踪到“再往后能遇到自己棋子”。另一个常见问题是合法判定和翻转逻辑不一致导致判断结果和实际棋盘变化对不上。我的做法是拆成两个函数isValidMove只看合法性doMove负责翻转两者复用同一套方向扫描逻辑保证判定和翻转的结果始终一致。3.2 落子翻转、跳过回合与胜负判定翻转逻辑上我的做法是对每个合法方向从落点沿该方向往回扫描把中途所有对方棋子翻成当前落子方。这个“反方向扫描”比从落点往外走更稳妥因为不需要额外记录坐标数组。然后是“无子可下”的处理。黑白棋规则允许无合法位置时跳过一回合但跳过后要把回合交给对方。如果双方都无子可下对局才真正结束。很多实现里只判断自己无子可下就结束结果对手还有棋可下时流程就断了。我维护了一个检查函数每次都要数清楚黑白双方当前的合法落子总数都为0才结束游戏。胜负判定最简单遍历棋盘统计黑、白棋子数多者胜一样多算平局。游戏结束后的界面我做成“再来一局”选项确认后重新初始化棋盘。3.3 AI策略评分表而不是搜索树人机对战的AI我没有用深搜而是用局面评分加权因为STM32虽然跑得动两层搜索但对这种小工程而言性价比不高。黑白棋的胜负关键更多体现在占角和控边而不是算得深。与其让AI“深谋远虑”不如让它“懂棋理”。评分表设计成8x8权重矩阵核心思路是角落权重最高100分占住角就能稳稳控制一整条对角线方向边的权重次之10分角落周围的位置是高风险区给负分-20到-50防止AI主动给对手留角中间区域给低分。AI的落子算法是遍历所有合法落点在临时棋盘上模拟落子并翻转统计落子后己方与对方棋子数量的差值再叠加该位置的权重分数取总分最高的位置落子。这个简单AI已经能占到角、避开高风险区作为对战对手不会显得太笨但也不至于强到让人抓狂。如果想调整难度我有两个建议降低难度时可以让AI在开局前几个回合随机走提高难度时可以在评分中加入“行动力”因子也就是落子后己方合法可落点数减去对手合法可落点数数值越高越有利。这个因子对黑白棋中后期判断很有价值。我实际测试时发现AI不宜做得太强。课设演示或者自己娱乐时AI太强反而没有交互乐趣而且不利于展示“这个AI到底怎么思考的”。这个项目的核心展示价值在于规则实现正确、界面操作流畅AI能占到角不犯明显错误就已经是相当合格的教学级AI了。4. Proteus仿真搭建、接线与避开三个大坑4.1 仿真工程搭建与最小系统接线Proteus仿真是这套工程快速复现的关键我用到的元件大致有U1STM32F103R6Category里直接搜索型号U2ILI9341屏幕模型不同Proteus版本库里名称可能略有差异搜索ILI9341或TFT8MHz晶振22pF电容x2复位按键、10k上拉电阻、100nF电容3.3V电源终端和GND终端若干按键模拟方向键、确认键。接线时最容易出问题的是STM32的电源引脚。F103R6有VDDA、VSSA、VDD、VSS仿真中要把所有电源脚接3.3V所有地脚接GND。还有一个专门的稳压器引脚不同库模型里标注可能是VCAP或者VREF需要接一个1μF电容到地否则仿真可能报错或者MCU不运行。实物上这个引脚同样要接电容不是仿真特有的。复位电路在仿真中不接外部电阻也能跑但我还是按实物画了上拉电容的链路这样能同时验证复位时序对开机显示是否有影响。晶振部分我直接放8MHz晶振并接22pF电容。这里有个细节MCU元件属性里如果设置了默认时钟频率和外接晶振冲突会导致仿真时钟异常建议检查一下属性页别让两处配置打架。4.2 仿真调试最容易踩的三个坑第一个坑屏幕不亮或者白屏。按我的经验Proteus白屏问题90%不是接线错而是初始化序列和仿真模型不兼容。Proteus里的ILI9341模型对某些命令的响应和实物有差异我换过一套初始化序列之后立刻正常。定位思路是在初始化代码的每个关键命令之后加一个GPIO翻转用虚拟示波器观察运行到哪一步卡住逐步缩小问题范围。第二个坑仿真速度太慢。Proteus对SPI刷屏的模拟确实吃力如果初始化后立刻全屏刷一张大图仿真能卡到一秒刷新几帧。我的办法是SPI预分频调到较低的档位提升SPI时钟同时坚持界面层局部刷新。如果还是慢就把Proteus右下角的仿真帧率调高默认的一本文还有配套的精品资源点击获取