公司动态
STM32F103R6驱动ILI9341彩屏的黑白棋游戏Proteus仿真完整方案
简介一套基于STM32F103R6主控与ILI9341彩色液晶屏的黑白棋对战完整工程面向嵌入式初学者和对LCD/SPI显示控制感兴趣的开发者可在Proteus中直接仿真运行并观察交互效果。工程使用STM32CubeMX完成初始化基于HAL库开发覆盖标准GPIO、SPI、SysTick等底层驱动同时包含完整游戏逻辑、中断服务函数、硬件抽象层适配以及ILI9341_SPI专用显示驱动并配有STM32最小系统与液晶模块连接方式的电路原理图和实机演示视频。压缩包共597个文件以C源文件、H头文件、S启动文件为主兼有Proteus设计文件、Keil MDK-ARM工程、IOC配置文件及编译产物hex/axf/map整体大小25.76MB目录结构清晰便于定位驱动、应用和仿真相关代码。所有代码已通过编译验证重点演示SPI外设驱动、LCD显示控制与简单人机交互的实现思路可直接作为课程设计或入门练手项目参考当前已有56人学习。 前阵子有学生问我想在Proteus里跑一个带液晶显示的嵌入式小项目用STM32F103R6驱动一块IPS彩屏做黑白棋奥赛罗棋还要有仿真、有源码、能直接烧录该怎么搭。我干脆把整个工程从硬件电路到软件逻辑完整过了一遍顺便把踩过的坑也整理出来这篇就当是给同样在折腾这个方向的朋友一份可以直接“抄作业”的参考。项目本身不算复杂但麻雀虽小五脏俱全——MCU选型、SPI屏驱动、游戏规则状态机、按键交互、仿真环境搭建每个环节都有值得注意的细节。1. 项目整体设计与方案选型1.1 为什么是STM32F103R6 ILI9341STM32F103R6属于F103系列中的中等容量产品64引脚封装内置32KB Flash和10KB SRAM主频最高72MHz。说实话跑一个黑白棋游戏、驱动一个240x320分辨率的TFT屏这个配置是绰绰有余的甚至有些浪费。选择它而不是C8T6之类的型号更多是出于引脚数量的考虑——R6的LQFP64封装比C8T6的LQFP48多出16个引脚给按键、指示灯、预留调试接口留足了空间不至于为了几个IO口去做引脚复用取舍。ILI9341是市场占有率很高的TFT LCD控制器支持SPI和并行两种接口方式。在这个项目里我用的是SPI模式原因很直接并行接口需要至少16根数据线加若干控制线虽然刷新速度快但占用的GPIO数量太多而SPI模式只需要SCK、MOSI、DC、CS、RST五根线就能完成全部控制把宝贵的GPIO留给按键和扩展功能。黑白棋本身对刷新率要求不高SPI接口的带宽完全够用。实际测试下来全屏填充一帧大概需要60ms左右用于静态画面和落子刷新游刃有余。1.2 双人对战模式的逻辑主体这个工程的核心是黑白棋规则不是AI。对战的交互模型是两名玩家轮流落子每个玩家各用一组按键操作光标移动到目标格子按确认键落子。黑白棋规则中最关键的一点是必须在八个方向中的至少一个方向上夹住对方的棋子否则落子无效。规则的实现分为三个层次合法性判断、翻转处理、终局判定。合法性判断只需要检查当前落子位置在八个方向上是否存在“己方棋子-连续对方棋子-空位”的排列翻转处理则是把满足条件的连续对方棋子全部翻为当前玩家颜色终局判定在一方无棋可下时自动跳过回合双方都无棋可下则游戏结束统计棋盘上黑白棋子数量多者获胜。这套逻辑用C语言实现清晰且直观。整个游戏状态机分为初始化、黑方回合、白方回合、终局四个状态按键扫描和状态迁移放在主循环里完成。2. 硬件电路设计最小系统到外设连接2.1 主控最小系统电路STM32F103R6最小系统需要的东西不多3.3V电源、8MHz外部晶振也可以只用内部RC但液晶刷新和SPI时序对时钟精度有一定要求建议用外部晶振、复位电路、BOOT配置、SWD调试接口。电源部分要注意STM32的VDDA和VDD都需要接3.3V并且每个电源引脚旁边要放一个100nF去耦电容VDDA建议再加一个1uF以上的钽电容滤波。老生常谈的话题但很多仿真的问题其实根源就在这——原理图看着没问题实际跑起来偶发复位多半是电源纹波干的。晶振电路用8MHz主晶振两个20pF负载电容。STM32F103的PLL最高可以倍频到72MHz系统时钟配置为8MHz x 9 72MHz这是F103最稳定的运行状态。很多人在Proteus仿真时忽略了一个细节仿真模型里如果不设置外部晶振频率或者设置的频率与代码初始化不一致会导致SPI时序错乱屏幕显示花屏。BOOT0和BOOT1都用10K电阻下拉到GND从主Flash启动这是最常规的配置。SWD接口引出SWDIO和SWCLK两根线方便用ST-Link或J-Link调试下载。2.2 ILI9341液晶接口与按键电路ILI9341在SPI模式下需要连接六根线到MCU具体分配如下液晶引脚功能接STM32引脚说明CS片选PA4低电平有效SCKSPI时钟PA5SPI1_SCKMOSI数据输入PA7SPI1_MOSIDC数据/命令选择PA3低电平命令高电平数据RST复位PA2低电平复位BL背光3.3V直接拉高最简方案用SPI1而不是SPI2原因是SPI1挂载在APB2总线上时钟频率最高可达72MHzSPI2挂在APB1上只有36MHz。虽然实际应用中SPI波特率一般分频到18MHz以内但挂在APB2上理论上限更高高频性能也更稳定。DC和RST用普通GPIO控制不占用SPI硬件引脚。按键部分四个方向键加一个确认键共五个按键。设计上采用独立按键接法一端接GPIO另一端接GND内部上拉使能按下为低电平。这种接法最简单可靠。按键配置在GPIOB的低五位PB0-左PB1-右PB2-上PB3-下PB4-确认。3. 软件架构与核心逻辑实现3.1 工程目录结构与代码分层整个Keil工程的代码分为四层系统层系统时钟初始化、延时函数、驱动层ILI9341底层驱动、LCD绘图库、应用层游戏逻辑、按键扫描、界面层棋盘绘制、落子动画。分层的好处是代码可复用性强。驱动层的ILI9341初始化序列和绘图函数换一个项目用OLED或者其他尺寸的TFT屏只需要改驱动层上层游戏逻辑完全不用动。这个习惯建议养成哪怕是一两百行的小工程分层做清楚了后期维护和调试都省心。系统时钟初始化用标准外设库完成核心代码大致如下void SystemClock_Config(void) { RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); while (RCC_WaitForHSEStartUp() ! SUCCESS); RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() ! 0x08); RCC_HCLKConfig(RCC_SYSCLK_Div1); RCC_PCLK1Config(RCC_HCLK_Div2); RCC_PCLK2Config(RCC_HCLK_Div1); }3.2 ILI9341 SPI驱动要点ILI9341的SPI驱动是本项目最基础的底层核心是搞清楚它和MCU的通信时序。ILI9341在SPI模式下的写数据包括两步先用DC引脚控制当前传输的是命令还是数据然后由MCU通过SPI发送8位或16位数据。这里有个新手容易踩的坑很多ILI9341模块上电后默认是并行模式必须通过特定的命令序列切换到SPI模式所以初始化序列必须严格按照数据手册来不能随便裁剪。SPI初始化代码void SPI1_Init(void) { SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }时钟极性CPOL和相位CPHA要格外留意。很多ILI9341模块的样例代码用的是Mode 0CPOLLow, CPHA1Edge但也有一些模块要求Mode 3。如果初始化后屏幕花屏、颜色不对首先检查这两位的配置是否和模块匹配。3.3 黑白棋核心算法合法性检测与翻转黑白棋的算法核心其实不复杂难在把规则写清楚。我用一个8x8的二维数组存储棋盘状态0表示空位1表示黑子2表示白子。八个方向的偏移量用一个二维数组定义const int8_t dir[8][2] { {-1,-1}, {-1,0}, {-1,1}, { 0,-1}, { 0,1}, { 1,-1}, { 1,0}, { 1,1} };合法性检测函数的核心思路从落子位置向某个方向步进记录经过的对方棋子数如果遇到己方棋子且经过的对方棋子数大于0则说明该方向可以翻转。八个方向按相同逻辑逐一检测只要有一个方向满足条件该位置就是合法落子点。uint8_t IsLegal(int8_t row, int8_t col, uint8_t player) { uint8_t opp (player 1) ? 2 : 1; int8_t i, d; int8_t r, c; if (board[row][col] ! 0) return 0; for (d 0; d 8; d) { uint8_t count 0; r row dir[d][0]; c col dir[d][1]; while (r 0 r 8 c 0 c 8 board[r][c] opp) { count; r dir[d][0]; c dir[d][1]; } if (count 0 r 0 r 8 c 0 c 8 board[r][c] player) return 1; } return 0; }这个写法是直接且高效的。翻转函数本质上就是合法性检测的延续——在确认合法后对每个方向再次遍历把路径上的对方棋子改写成己方颜色。一个容易忽略的细节黑白棋规定如果当前玩家无合法落子点必须自动跳过回合。所以每轮轮到某方时程序要遍历整个棋盘判断是否存在合法落子点如果没有就自动切换玩家。这一步放在按键响应之前游戏流程才不会被卡住。3.4 界面绘制与刷新策略界面层用LCD绘图库实现棋盘和棋子的绘制。棋盘背景为深绿色8x8的格子线用白色绘制棋子是实心圆黑子填充黑色白子填充白色。光标用红色空心圆表示当前选中的位置。性能上有个优化技巧不要每次按键移动都全屏重绘只需要在光标移动前用背景色把旧光标位置重绘再在新位置绘制光标。落子时也只需要局部刷新——在落子位置绘制新棋子再把翻转的棋子重新绘制颜色。这样SPI刷新压力很小画面也没有闪烁感。全屏重绘只用在开局和游戏结束画面。4. Proteus仿真搭建与联调要点4.1 仿真工程配置与常见坑Proteus仿真STM32F103R6需要特别注意电源网络。STM32在Proteus里不像51单片机那样自动获得电源必须手动把VDD引脚连接到3.3V电源端子VSS连接到GND。这在热词里也有人问“proteus怎么为stm32f103c8设置电源”原理是相同的——每个供电引脚都要显式接线。遗漏任何一个VDD/GND引脚仿真时MCU都会表现出各种奇怪现象不运行、运行极慢、外设无响应。Proteus中放置ILI9341模型时要注意需要从元件库中搜索“ILI9341”选择正确的LCD模型。模型放置后需要确认型号支持SPI接口以及默认的分辨率是否为240x320。如果模型分辨率设置不对初始化序列里的窗口设置会画到屏幕范围外导致显示偏移或者只有部分区域正常。晶振频率的设置在仿真中也是一个关键点。双击STM32F103R6元件在弹出的属性对话框里设置Crystal Frequency为8MHz。这个值必须和代码里的HSE配置一致。如果设置为其他频率代码里PLL倍频后得到的系统时钟会偏离72MHzSPI分频后得到的波特率也会偏离预期最终表现就是屏幕刷新慢或时序不对。4.2 将Keil生成HEX文件导入仿真在Keil中配置仿真用的HEX文件路径建议在工程属性中的Output选项卡勾选“Create HEX File”然后在Target选项卡配置好芯片型号STM32F103R6对应的是STM32F103R6。编译完成后在Proteus里双击MCU元件在Program File处选择生成的.hex文件。有个细节值得注意Proteus对HEX文件中的调试信息不敏感但会对代码大小敏感。32KB Flash不是很大如果工程开着调试信息导致代码膨胀可能在下载到Proteus模型时出现问题。建议Release模式编译关闭调试信息只保留优化。4.3 仿真与实物的差异Proteus仿真和实物有差异主要体现在三个方面时序精度、外设细节、实时性。仿真中SPI时序是基于理想化的模型不会出现信号反射、走线电容、上拉强度不足等问题所以代码在仿真上一切正常不代表实物也一定正常。反过来如果仿真跑不通实物大概率也不行——仿真对逻辑错误和初始化配置错误的排查效率很高。建议的联调顺序是先在Proteus中验证核心逻辑游戏规则、按键响应、液晶显示确认无误后再烧录到实物板子上调试显示效果和按键手感。这样可以避免在硬件上反复烧录调试浪费时间的尴尬。5. 开发环境问题与调试技巧实录5.1 Keil常见问题速查做这个项目过程中Keil本身也出了不少幺蛾子这里整理几个高频问题问题现象可能原因解决办法编译慢Browse Information开启、无优化关闭Browse Information提升优化等级J-Link提示盗版/兼容问题固件版本过旧或非官方换用ST-Link或者更新J-Link固件Pack Installer打不开网络问题或Pack路径异常检查Pack路径设置重新添加在线Pack仓库下载HEX失败下载选项配置错误检查Utilities选项卡中Flash Download配置无法识别DAP调试器驱动未安装或版本不匹配安装对应驱动选用兼容的CMSIS-DAP包如果你用的是J-Link仿真器Keil报盗版提示的解决思路是检查J-Link的固件版本老固件会出现兼容性问题不一定非要换硬件先尝试更新驱动和固件。如果还是不行直接换ST-Link更省心——STM32的生态对ST-Link支持最完善没有这些授权相关的烦恼。5.2 调试器连接与烧录建议调试这个项目时建议优先使用ST-Link。在Keil的Options for Target - Debug选项卡里选择ST-Link Debugger然后在Settings里确认能识别到芯片ID。如果识别到芯片但下载失败大概率是SWD速率太高把下载速率降到1MHz以下再试。在实际调试中我习惯把液晶驱动初始化的时间节点做一个延时标记——在LCD初始化完成后点亮背光的同时将某个GPIO拉高。示波器或者逻辑分析仪测试点可以快速确认代码执行到了哪一步。这个方法在液晶不亮、白屏、程序跑飞等场景下定位问题非常高效。另一个小技巧在代码里设置一个调试用计数器每完成一次主循环加一通过调试器实时观察这个变量的增长速度就能知道主循环是否正常运行、执行周期是否合理。如果循环周期异常长重点检查是否有阻塞式的延时函数占了太多CPU时间。5.3 踩过的坑液晶初始化序列ILI9341的初始化序列是直接从厂商驱动芯片手册中转出来的但是不同批次、不同厂家的模块对初始化序列的响应略有差异。最典型的例子是0x3A命令设置像素格式有的模块需要设为0x5516位色有的模块虽然是SPI接口但内部还是按18位色处理——如果颜色显示不对优先检查这条命令。我的建议是直接用成熟的初始化序列模板不要自己改写除非你已经明确知道每条命令的用途。市面上ST7789和ILI9341的初始化序列不能互换经常有人在移植代码时犯这个错。屏幕显示不正常时优先怀疑初始化序列其次才是SPI时序。初始化序列不对会出现花屏、白屏、显示内容错位等诡异现象排查起来极为费劲。5.4 关于工具链正版与许可的说明顺便提一句工具链的许可问题。Keil MDK的评估版对代码大小有限制32KB本项目代码量刚好在边缘徘徊所以建议使用正版授权或者学校提供的教育版。Proteus也是同样的道理需要正版许可或者学校实验室授权版本。网上流传的各种注册机、破解释放器不仅踩了版权红线还有可能带入恶意代码得不偿失。工具是拿来用的不是拿来折腾的把时间花在项目本身更划算。我在实际做这套工程时感受最深的一点仿真和实物的差距最容易在SPI驱动和电源部分体现。仿真时可以跑通的代码如果你在实物上没有处理好去耦电容、没有加电平转换、没有注意信号线长度都会出现随机性故障。反过来仿真时候的报错信息尤其是逻辑层面的问题对实物的调试有极强的指导意义。尤其是按键消抖和非法落子检测这些逻辑在仿真里验证过一遍实物基本就是一把过。本文还有配套的精品资源点击获取