ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于STM32 HAL库的篮球计时记分器:从Proteus仿真到Keil实战开发

基于STM32 HAL库的篮球计时记分器:从Proteus仿真到Keil实战开发 简介本资源是一套完整的基于STM32的篮球比赛计时记分器嵌入式课程设计实现方案面向高校电子类、自动化及物联网专业学生解决单片机综合应用中LCD显示、矩阵按键扫描、定时器中断、多状态逻辑控制等典型工程问题。压缩包共183个文件含53个.h头文件定义外设驱动与功能接口、24个.c源文件含HAL库底层驱动与主控逻辑、26个.o目标文件及配套Keil工程.uvprojx/.uvoptx、STM32CubeMX配置文件.ioc、可烧录.hex与调试用.axf文件整体7.7MB结构清晰便于理解软硬件协同开发流程。已有2623人学习下载涵盖从Proteus仿真验证到真实硬件调试的完整链路提供可直接运行的Keil工程、模块化代码注释、LCD双时间显示与双队比分刷新逻辑、蜂鸣器LED得分反馈机制以及矩阵按键功能分配说明是嵌入式入门与课程设计落地的高实用性参考范例。1. 项目缘起从需求到方案选型最近在整理一些嵌入式开发的实战项目发现很多朋友对如何将单片机应用到具体的、有交互感的场景中很感兴趣。一个常见的需求就是制作一个体育比赛用的计时记分器比如篮球比赛。这听起来简单但真要自己动手从零搭建从硬件选型、软件架构到人机交互每一步都有不少门道。这次我们就来聊聊如何基于STM32配合LCD显示屏和矩阵按键实现一个功能完整的篮球计时记分器并且会通过Proteus仿真来验证整个系统的逻辑最后在Keil环境下用HAL库完成代码编写。为什么选这个组合STM32F103系列比如常见的“蓝桥杯”板子用的C8T6性价比高、资源丰富完全能胜任这种实时性要求不极端但外设需求多的场景。LCD1602或者LCD12864这类字符/图形点阵屏成本低、接口简单显示比赛时间和比分信息绰绰有余。矩阵按键则是为了在有限的IO口上实现尽可能多的功能按键比如开始/暂停、加减分、复位等。使用HAL库而不是标准库主要是考虑到其更好的可移植性和更现代的代码结构虽然初期学习曲线可能稍陡但长远来看项目管理和维护会更方便。而Proteus仿真则能在焊接实物之前最大限度地验证硬件电路设计和核心软件逻辑的正确性避免“一焊就错一错就废”的尴尬。这个项目麻雀虽小五脏俱全。它涵盖了嵌入式开发的几个核心环节MCU的GPIO控制驱动LCD和扫描按键、定时器的精准运用实现1秒的计时、中断的处理确保按键响应的实时性以及模块化编程的思想。无论你是正在学习STM32的学生还是想找个具体项目练手的工程师跟着走一遍都能对嵌入式系统开发有一个更立体、更实战化的理解。2. 系统架构设计与核心器件选型在动手写代码和画原理图之前我们必须先把整个系统的框架和需要用到的核心部件确定下来。一个好的架构是项目成功的一半它能让我们在开发过程中思路清晰避免后期陷入“打补丁”的混乱。2.1 整体功能框架与数据流一个篮球计时记分器核心功能无非是“计时”和“记分”。我们需要一个倒计时器比如单节12分钟以及为两支队伍A队和B队记录比分。此外还需要一些控制功能启动/暂停计时、为指定队伍加分1分、2分、3分、复位比赛等。基于这些需求系统的数据流可以这样设计输入层由4x4矩阵按键组成共16个按键。我们不需要全部用完可以规划如下按键0-9作为数字输入备用或用于设置初始时间另外定义几个功能键例如“开始/暂停”、“A队1分”、“A队2分”、“A队3分”、“B队1分”、“B队2分”、“B队3分”、“复位”。处理核心STM32F103C8T6。它负责扫描按键获取输入指令根据指令更新内部的计时和记分变量并驱动LCD显示最新状态。输出层LCD1602液晶屏。第一行显示比赛时间格式如“TIME: 12:00”。第二行显示两队比分格式如“A:000 B:000”。定时基准使用STM32的一个基本定时器如TIM2产生精确的1秒中断用于递减比赛时间。状态管理系统需要一个简单的状态机至少包含“就绪”、“运行”、“暂停”三个状态以正确处理“开始”和“暂停”按键的逻辑。2.2 关键器件参数与接口定义接下来我们具体看看每个部分怎么选型和连接。主控MCUSTM32F103C8T6这款芯片有48个引脚足够我们使用。它拥有3个USART、2个SPI、2个I2C、多达37个GPIO以及多个定时器。我们主要用到它的GPIO和定时器资源。在Proteus中我们可以直接搜索“STM32F103C8”找到模型。显示模块LCD16025V供电并行接口为什么选1602而不是更复杂的12864对于只显示数字和字母的场景1602完全够用且驱动简单节省代码空间。我们采用经典的4位数据线并行模式这样可以节省4个IO口。需要连接的引脚有RS寄存器选择接STM32的某个GPIO用于区分发送的是指令还是数据。RW读写通常直接接地因为我们只向LCD写数据不读取。E使能接STM32的某个GPIO在下降沿锁存数据。D4-D7数据线高4位接STM32的4个GPIO。4位模式下一次传输4位数据分两次传输一个字节。VCC/GND接5V和地。注意STM32的GPIO是3.3V电平而LCD1602是5V器件。在仿真中我们可以忽略电平匹配问题但在实际硬件中需要在数据线上加电平转换电路如74HC245或使用开漏模式加上拉电阻或者直接选用3.3V供电的LCD1602型号。输入模块4x4矩阵键盘矩阵键盘的原理是通过行扫描和列检测来定位按下的键。我们需要8个GPIO口4个配置为推挽输出行线4个配置为上拉输入列线。在Proteus中搜索“KEYPAD-PHONE”可以得到一个标准的4x4键盘模型。定时器TIM2我们使用TIM2的基本定时功能。假设系统时钟为72MHz我们设置预分频器PSC为7199这样计数器时钟为72MHz / (71991) 10kHz。然后设置自动重装载值ARR为9999这样每计数10000次10kHz / 10000 1Hz产生一次更新中断即1秒一次。这个精度对于篮球计时足够了。3. Proteus仿真电路搭建与关键细节仿真可以让我们在没有实物的情况下验证硬件连接和核心逻辑。在Proteus中搭建这个系统需要注意几个容易出错的点。3.1 原理图绘制与网络标注首先新建一个Proteus工程从库中放置以下元件STM32F103C8(Microprocessor ICs - ARM - Cortex-M3)LM016L(Optoelectronics - LCDs - Alphanumeric LCDs) 这就是LCD1602。KEYPAD-PHONE(Switches Relays - Keypads)RES(Resistors) 和CRYSTAL(Miscellaneous) 用于STM32的最小系统晶振和复位电路虽然仿真中不接也能运行但为了仿真完整性建议加上。POWER和GROUND端子。连接线路时强烈建议使用“网络标号”功能而不是全部用导线连接。这样图纸清晰也方便后续检查。例如将STM32的PC0-PC3引脚分别标记为“D4”、“D5”、“D6”、“D7”然后将LCD的D4-D7引脚也标记上同样的名字Proteus会自动认为它们连接在一起。对于矩阵键盘将行线ROW0-ROW3和列线COL0-COL3也分别标好。一个关键细节LCD的背光和控制电压。LM016LLCD1602模块上有三个引脚VEE对比度调节、VDD电源正极5V、VSS电源负极。VEE通常通过一个10k电位器接VDD来调节对比度在仿真中我们可以直接将其接到一个可调电阻上或者简单接一个固定电阻如1k到地确保有合适的电压差通常0.5V到1V左右才能显示字符。如果仿真时LCD上只有方块而没有字符首先检查VEE电压是否合适。3.2 仿真调试技巧与常见问题排查电路连接好后我们需要加载编译好的程序文件.hex或.elf。在STM32元件的属性中点击“Program File”右边的文件夹图标选择你从Keil生成的可执行文件。通常在Keil中编译成功后会在工程目录的Objects文件夹下生成.axf文件ELF格式Proteus可以直接加载这个文件这样还能进行源码级调试。点击Proteus左下角的运行按钮系统开始仿真。常见的仿真问题及排查思路LCD不显示或显示乱码检查接线确认RS、RW、E、D4-D7的连线或网络标号是否正确特别是数据线高低位顺序是否与代码中一致。检查初始化时序LCD1602上电后需要一段延时通常15ms以上才能进行初始化指令的发送。在代码的LCD_Init()函数开始处必须有一个足够长的延时HAL_Delay(50)是个保险的做法。检查初始化流程4位模式的初始化流程比较固定必须严格按照“发送三次0x30指令 - 发送功能设置指令如0x28表示4位模式2行显示5x8点阵- 发送显示开关指令 - 发送清屏指令 - 发送进入模式设置指令”的顺序。顺序错一步后面就全乱了。检查VEE对比度电压用Proteus的电压探针测量VEE引脚对地电压调整电位器使其在0.5V-1V之间通常能看到清晰的字符。按键无反应或反应错乱检查GPIO模式确认行线GPIO配置为推挽输出初始输出高电平还是低电平取决于你的扫描逻辑常见是初始输出低电平扫描时逐行拉低。列线GPIO必须配置为上拉输入这样无按键时读到的就是高电平有按键被按下时对应的行线低电平会通过按键把列线拉低。检查扫描逻辑在代码中扫描函数是否被周期性地调用比如放在main函数的while(1)循环中防抖处理做了吗通常采用“先延时消抖再确认状态”的方法。仿真中的按键按下在Proteus中用鼠标点击键盘上的按钮需要按住一会儿再松开模拟真实的按下动作。太快的点击可能被程序扫描错过。定时器计时不准检查定时器配置重点检查预分频器PSC和自动重装载值ARR的计算是否正确。如前所述目标是产生1秒中断。检查中断服务程序在HAL_TIM_PeriodElapsedCallback回调函数中是否清晰地对1秒事件进行了处理中断函数里不要做耗时操作更不要使用HAL_Delay。通过仿真我们可以反复调整电路和代码直到LCD正确显示按键能控制计时和记分计时功能准确运行为止。这为后续的实物制作打下了坚实的基础。4. Keil工程创建与HAL库环境配置仿真电路调通后我们就要在Keil MDK中创建工程编写真正的固件代码了。使用HAL库可以大大简化外设的初始化过程。4.1 使用STM32CubeMX初始化工程最推荐的方法是使用STM32CubeMX工具进行图形化配置然后生成Keil工程。这样做的好处是引脚配置、时钟树、外设参数一目了然且能保证HAL库底层驱动的正确性。新建项目打开STM32CubeMX选择MCU型号为STM32F103C8Tx。配置时钟在“Clock Configuration”标签页将系统时钟源选择为外部高速晶振HSE然后将系统时钟SYSCLK设置为72MHz。这是F103系列在3.3V供电下的典型最高频率性能足够。配置GPIOLCD引脚假设我们使用PC0-PC3作为数据线D4-D7PC4作为RSPC5作为E。将它们配置为“GPIO_Output”输出模式默认推挽上拉低速即可。矩阵键盘引脚假设使用PA0-PA3作为行线ROW0-ROW3配置为“GPIO_Output”。使用PA4-PA7作为列线COL0-COL3配置为“GPIO_Input”并激活内部上拉Pull-up。配置定时器激活TIM2。在“Parameter Settings”中将“Prescaler”PSC设置为7199“Counter Period”ARR设置为9999。这样定时器时钟为72MHz/(71991)10kHz计数10000次ARR1产生一次更新中断即1秒。然后开启“NVIC Settings”中的TIM2全局中断。生成代码在“Project Manager”标签页设置项目名称和路径选择“Toolchain / IDE”为MDK-ARM V5。然后点击“GENERATE CODE”。4.2 Keil工程导入与基础代码结构CubeMX生成代码后用Keil打开工程文件.uvprojx。生成的代码结构非常清晰Core/Inc和Core/Src用户的主要应用代码放在这里尤其是main.c。Drivers/STM32F1xx_HAL_DriverHAL库的源文件和头文件。在main.c中SystemClock_Config()函数已经根据我们的设置写好了72MHz的时钟配置。MX_GPIO_Init()和MX_TIM2_Init()函数也完成了对应外设的初始化。我们需要做的就是在/* USER CODE BEGIN 2 */和/* USER CODE END 2 */之间开启定时器中断HAL_TIM_Base_Start_IT(htim2);然后在/* USER CODE BEGIN 4 */和/* USER CODE END 4 */之间重写定时器更新中断的回调函数void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)。接下来我们需要在Core/Src文件夹下新建两个源文件lcd1602.c和keypad.c并在Core/Inc下创建对应的头文件lcd1602.h和keypad.h。将LCD驱动和键盘扫描的函数分别封装在这两个模块中这样main.c的代码会非常简洁只负责业务逻辑的调度。5. LCD1602驱动模块的HAL库实现与优化LCD驱动是显示的基础其稳定性和效率直接影响用户体验。我们用HAL库的GPIO写函数来实现。5.1 4位数据模式下的底层读写时序首先在lcd1602.h中定义好引脚映射这样如果需要更换引脚只需修改头文件即可。// lcd1602.h #define LCD_RS_Pin GPIO_PIN_4 #define LCD_RS_GPIO_Port GPIOC #define LCD_E_Pin GPIO_PIN_5 #define LCD_E_GPIO_Port GPIOC #define LCD_D4_Pin GPIO_PIN_0 #define LCD_D4_GPIO_Port GPIOC #define LCD_D5_Pin GPIO_PIN_1 #define LCD_D5_GPIO_Port GPIOC #define LCD_D6_Pin GPIO_PIN_2 #define LCD_D6_GPIO_Port GPIOC #define LCD_D7_Pin GPIO_PIN_3 #define LCD_D7_GPIO_Port GPIOC在lcd1602.c中我们需要实现几个最基础的函数void LCD_WriteNibble(uint8_t nibble, uint8_t rs_flag)向LCD写入4位数据一个半字节。rs_flag为0表示写指令为1表示写数据。这个函数要严格按照LCD1602的时序图来写先设置RS电平然后设置数据线D4-D7的电平接着给E一个高脉冲至少450ns最后拉低E。HAL库的HAL_GPIO_WritePin函数和HAL_Delay_us需要自己实现一个微秒延时函数或者用定时器可以帮我们完成这些操作。void LCD_SendCommand(uint8_t cmd)发送一个8位指令。调用两次LCD_WriteNibble先发高4位再发低4位。void LCD_SendData(uint8_t data)发送一个8位数据。同样调用两次LCD_WriteNibble。一个重要的微秒延时实现HAL库只有毫秒延时HAL_Delay。对于LCD时序中要求的微秒级延时我们可以用简单的空循环实现。因为系统时钟是72MHz一个NOP指令大约消耗一个时钟周期约13.9ns。我们可以写一个粗略的微秒延时函数void Delay_us(uint16_t us) { uint32_t delay us * (SystemCoreClock / 1000000) / 5; // 根据实际测试调整这个除数 while(delay--) { __NOP(); } }这个除数值“5”需要在实际硬件上通过示波器或逻辑分析仪校准。在仿真中数值要求不严格。5.2 初始化流程与显示功能封装有了底层读写函数就可以编写初始化函数LCD_Init()了。4位模式的初始化序列必须严格遵守上电后等待大于15msHAL_Delay(50)。发送三次0x30指令功能设置8位模式每次间隔至少4.1ms。这里我们仍然用LCD_WriteNibble(0x3, 0)来发送高4位因为此时LCD还处于8位模式但只接收高4位。发送0x28指令功能设置4位模式2行5x8点阵。发送0x0C指令显示开光标关闪烁关。发送0x06指令写入新数据后光标右移屏幕不移动。发送0x01指令清屏。初始化完成后就可以封装上层应用函数了如LCD_SetCursor(uint8_t row, uint8_t col)设置光标位置LCD_PrintString(char *str)显示字符串。对于显示时间和比分我们可以写一个专用的函数LCD_DisplayScore(uint8_t scoreA, uint8_t scoreB)和LCD_DisplayTime(uint8_t min, uint8_t sec)内部将数字转换为字符串再显示。一个显示优化技巧频繁调用LCD_PrintString刷新整个屏幕如果字符串长度不变会造成屏幕闪烁。我们可以只更新变化的部分。例如比分从“005”变为“006”只需定位到最后一个字符的位置重新发送‘6’而不是重写整个“A:005”字符串。这需要更精细的光标控制。6. 矩阵键盘扫描模块的软件消抖与状态机矩阵键盘的驱动核心是扫描函数它需要周期性地被执行以检测按键事件。我们采用“行扫描列检测”法。6.1 扫描算法与防抖处理在keypad.c中我们定义一个二维数组key_map[4][4]来映射键值例如{{1,2,3,A}, {4,5,6,B}, ...}。扫描函数char KEYPAD_Scan(void)的逻辑如下将4根行线ROW全部设置为低电平。依次将每一根行线单独设置为低电平其他行线设置为高电平。在每一行被拉低时立即读取4根列线COL的电平状态。如果某一列为低电平由于内部上拉无按键时为高说明该行该列交叉点的按键被按下。根据当前的行号和列号查询key_map得到键值。检测到按键后不能立即返回必须进行软件消抖。常见的做法是检测到低电平后延时10-20msHAL_Delay(20)再次读取该列线如果仍然是低电平则确认按键被按下。然后等待按键释放列线变回高电平再返回键值。这样可以有效消除触点机械抖动造成的误触发。这个扫描函数应该被放置在main.c的while(1)主循环中不断调用。但要注意如果主循环中还有其他耗时任务可能会造成按键响应迟钝。更高级的做法是将扫描放在定时器中断中但中断中不宜使用HAL_Delay这就需要使用状态机和非阻塞式的延时来判断消抖时间。6.2 基于状态机的非阻塞式按键扫描为了解决主循环阻塞和中断中不能用延时的问题我们可以实现一个基于状态机的非阻塞扫描。在keypad.c中定义一个结构体来管理每个按键或整个键盘的状态typedef struct { uint8_t current_state; uint8_t last_state; uint32_t press_start_time; char key_value; uint8_t is_pressed; uint8_t is_long_pressed; } Key_HandleTypeDef; Key_HandleTypeDef key;然后创建一个任务函数void KEYPAD_Task(void)它需要被周期性地调用比如放在1ms的SysTick中断里或者主循环中快速执行。在这个任务函数里实现状态机状态0释放状态扫描键盘。如果发现有按键按下记录键值进入状态1并记录当前时间HAL_GetTick()。状态1消抖确认状态等待一段时间如20ms。再次扫描如果该键仍被按下则确认按键有效设置is_pressed标志进入状态2。如果已释放则回到状态0。状态2按下保持状态持续检测按键是否释放。同时可以检查按下的持续时间如果超过某个阈值如2秒可以触发“长按”事件设置is_long_pressed标志。一旦检测到释放清除is_pressed和is_long_pressed标志返回状态0。在主循环中我们只需要检查key.is_pressed标志如果为1则去处理key.key_value对应的功能然后清除该标志。这样按键检测与主业务逻辑就解耦了响应也非常及时。7. 核心业务逻辑计时、记分与状态管理硬件驱动都准备好后我们就可以在main.c中编写篮球计时记分器的核心业务逻辑了。这部分代码要清晰、健壮易于维护和扩展。7.1 全局变量与系统状态定义首先定义一些全局变量来存储系统状态// 比赛时间单位秒 uint32_t game_time_seconds 12 * 60; // 初始12分钟 // 队伍得分 uint16_t score_team_a 0; uint16_t score_team_b 0; // 系统状态0-就绪/停止1-运行2-暂停 uint8_t game_state 0; // 0: READY, 1: RUNNING, 2: PAUSED // 定时器1秒标志位在中断回调函数中置1 volatile uint8_t one_second_flag 0;定时器中断回调函数非常简单void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { one_second_flag 1; // 设置1秒标志 } }7.2 主循环逻辑与按键功能映射主循环while (1)是整个程序的核心调度器它需要做以下几件事调用键盘任务周期性地调用KEYPAD_Task()更新按键状态。处理1秒事件检查one_second_flag如果为1则根据game_state状态决定是否递减game_time_seconds然后清除标志位并调用显示更新函数。处理按键事件检查key.is_pressed标志根据按下的键值key.key_value执行相应的功能函数。更新显示无论是否有按键都需要定期更新LCD显示确保信息实时。可以将显示更新放在1秒事件处理中或者单独用一个定时器触发。按键功能映射可以这样设计键‘A’开始/暂停。逻辑如果状态是READY或PAUSED则切换到RUNNING如果状态是RUNNING则切换到PAUSED。键‘B’复位。逻辑将game_time_seconds、score_team_a、score_team_b恢复为初始值状态切回READY。键‘1’/‘2’/‘3’为A队加1/2/3分。逻辑只在RUNNING或PAUSED状态下有效比赛进行中或暂停中可以修改比分对score_team_a进行加法操作注意防止溢出篮球比分通常不会超过255。键‘4’/‘5’/‘6’为B队加1/2/3分。逻辑同上。其他键可以预留比如用于设置初始时间等。一个关键细节时间显示格式处理。game_time_seconds是一个从720开始递减的整数。我们需要在显示前将其转换为“分:秒”的格式。uint8_t minutes game_time_seconds / 60; uint8_t seconds game_time_seconds % 60;然后调用LCD_DisplayTime(minutes, seconds)。注意当seconds小于10时要在前面补‘0’。8. 系统集成、调试与功能扩展思路当所有模块都编写完成后就需要将它们集成起来进行系统级的调试并思考可能的优化和扩展方向。8.1 模块整合与联调步骤在main.c中正确的初始化顺序很重要HAL_Init()(CubeMX已生成)SystemClock_Config()(CubeMX已生成)外设初始化MX_GPIO_Init(),MX_TIM2_Init()(CubeMX已生成)自定义模块初始化LCD_Init(),KEYPAD_Init()如果有的话开启定时器中断HAL_TIM_Base_Start_IT(htim2);初始化显示显示初始时间和比分如“TIME: 12:00”, “A:000 B:000”进入主循环while (1)联调时建议使用分段调试的方法第一步调通LCD注释掉键盘和定时器相关代码只测试LCD能否正确显示固定的字符串。确保初始化序列和底层读写函数无误。第二步加入键盘在LCD显示正常的基础上加入键盘扫描。每按一个键在LCD上显示该键的字符验证扫描和消抖逻辑。第三步加入定时器开启定时器让LCD上显示一个自增的数字代替时间验证1秒中断是否准确。第四步整合业务逻辑将按键功能映射到计时和记分操作上。此时一个基本可用的系统就完成了。8.2 常见问题与进阶优化在实际制作中你可能会遇到以下问题及解决方案LCD显示暗淡或不稳定检查电源电压是否稳定背光限流电阻是否合适。VEE对比度电压需要仔细调整。按键偶尔失灵或连击检查消抖延时是否足够或者尝试使用状态机非阻塞扫描。确保扫描周期稳定建议1-5ms扫描一次。计时误差累积定时器中断是准确的但中断服务函数如果执行时间过长或者被更高优先级中断打断会影响计时精度。确保中断函数尽可能短小精悍。如果需要极高精度可以使用定时器的捕获/比较功能或者使用RTC实时时钟模块。系统功耗如果考虑电池供电可以在暂停状态下关闭LCD背光甚至让MCU进入低功耗模式Sleep或Stop模式通过按键外部中断唤醒。功能扩展思路24秒进攻计时增加一个24秒的倒计时器与比赛主计时并行用另一个定时器或者同一个定时器的不同通道实现。需要一个独立的按键来重置24秒。队名显示使用LCD1602的第二行可以滚动显示自定义的队名而不是固定的“A”和“B”。数据存储使用STM32的内部Flash或外接EEPROM在断电前保存当前的比赛时间、比分和状态上电后可以恢复比赛。声音提示连接一个蜂鸣器在计时结束、24秒违例、每节结束时发出不同声音提示。无线控制集成蓝牙模块如HC-05或Wi-Fi模块如ESP8266通过手机APP远程控制计时记分器。更大显示将LCD1602换成OLED屏或TFT彩屏可以显示更丰富的图形信息如球队Logo、动画效果等。这个基于STM32的篮球计时记分器项目从仿真到实战完整地走了一遍嵌入式开发流程。它不仅仅是一个具体的作品更是一个学习和理解单片机如何与外部世界交互的绝佳范例。当你亲手做出实物看到LCD上跳动的数字随着你的按键而改变时那种成就感是看多少遍教程都无法替代的。希望这个详细的拆解能帮助你少走弯路顺利实现自己的设计。本文还有配套的精品资源点击获取
返回列表