简介:本资源是一套基于STM32F103C8T6的嵌入式篮球记分器完整工程,面向高校电子类、自动化及物联网专业学生,适用于课程设计与毕业设计实践。系统集成OLED显示、红外遥控交互与高精度定时功能,支持比赛启停、0.01秒倒计时、比分增减、半场切换、24/14秒规则设置、学院名称轮换等真实赛事需求,具备完整的软硬件协同开发范例价值。压缩包共159个文件(7.01MB),含35个头文件(h)、34个源码文件(c)构成核心逻辑与外设驱动,21个编译中间文件(o/d)及Keil工程配置(uvprojx、uvoptx)、固件输出(hex、axf)、位图资源(bmp、png)和说明文档(pdf),结构规范,便于理解工程组织与调试流程。已有4115人学习下载,提供可直接编译运行的完整项目,涵盖TIM定时器高精度计时、OLED图形界面刷新、红外协议解析及多级菜单状态机实现等关键技术点,是掌握ARM Cortex-M3平台开发的优质实战参考。
1. 项目概述与核心价值
最近在整理资料时,翻到了几年前带学生做的一个STM32小项目——篮球记分器。这玩意儿乍一看挺简单,不就是按个按钮加加分、减减分嘛。但真要让它在实际比赛里能稳定、可靠、直观地工作,从硬件选型、软件架构到人机交互,每一步都有不少门道。它不仅是单片机入门后一个绝佳的练手项目,更是很多电子信息、自动化专业同学课程设计或毕业设计的经典选题。为什么?因为它麻雀虽小,五脏俱全,几乎涵盖了嵌入式开发的完整流程:从需求分析、原理图设计、PCB绘制、元器件焊接,到固件编程、驱动编写、逻辑实现,再到最后的整机调试与功能验证。整个过程下来,你对一个完整电子产品的诞生会有非常深刻的理解。
这个项目最核心的价值,在于它连接了“学习”与“应用”。你不再只是对着开发板点个灯、调个串口,而是亲手打造一个能解决实际问题的设备。篮球记分器需要实时显示双方分数、节次、倒计时,可能还要有犯规次数、暂停次数显示,以及蜂鸣器提醒等功能。这些需求迫使你去思考如何高效管理多个外设(如数码管、LED点阵屏、按键、蜂鸣器),如何设计清晰的状态机来处理复杂的比赛规则逻辑,如何确保系统在长时间运行下的稳定性。对于即将毕业的同学来说,这样一个有明确应用场景、功能完整、且能做出实物的项目,写在简历里是非常加分的,面试官一眼就能看出你的动手能力和工程思维。
2. 整体系统设计与方案选型
2.1 需求分析与功能定义
在做任何硬件选型和软件设计之前,必须把需求理清楚。一个基础的篮球记分器通常需要满足以下功能点:
- 分数显示:清晰显示主队(A队)和客队(B队)的当前得分,通常每队分数范围是0-199(实际比赛极少超过199分)。
- 比赛时间管理:显示当前节次(如1-4节,或上下半场)和该节剩余时间。国际篮联(FIBA)每节10分钟,NBA每节12分钟,倒计时精度到秒。
- 基本操作:为每队提供“+1分”、“+2分”、“+3分”、“-1分”(用于纠错)的按键。提供“节次+”、“节次-”、“时间设置/启动/暂停/复位”等比赛控制按键。
- 辅助信息显示:可选的,显示每队当前节犯规次数(最多5次),全队犯规次数,以及剩余的暂停次数。
- 声光提示:每节结束、全场结束或有犯规发生时,蜂鸣器鸣响,可能配合LED闪烁。
- 供电与便携:通常采用电池供电(如9V方块电池或锂电池),需要考虑低功耗设计,确保整场比赛中途不掉链子。
基于这些需求,我们的系统框图就出来了:一个主控MCU,连接显示模块、输入模块、声音提示模块和电源模块。
2.2 主控芯片选型:为什么是STM32?
提到单片机,51、AVR、STM32都是常见选择。对于这个项目,我强烈推荐STM32,尤其是STM32F1系列(如STM32F103C8T6),原因如下:
- 性能与资源充足:相比传统的51单片机,STM32是32位的ARM Cortex-M内核,主频更高(72MHz),计算能力强。篮球记分器的逻辑虽然不复杂,但涉及到多路显示扫描、精确计时(可能用到定时器中断)、多个按键扫描防抖等,STM32处理起来游刃有余,代码结构可以更清晰。其丰富的片上资源(多个定时器、串口、GPIO、中断)为功能扩展(比如未来加无线模块、连接上位机)留足了空间。
- 开发环境成熟:STM32拥有非常完善的生态系统。标准外设库(SPL)、HAL库、LL库以及各种第三方组件(如RT-Thread、FreeRTOS)让开发效率大大提升。对于学生来说,使用Keil MDK或STM32CubeIDE这些官方或主流IDE,资料和教程遍地都是,遇到问题容易找到解决方案。
- 成本与采购:以STM32F103C8T6(常说的“蓝色药丸”)为例,它价格已经非常亲民,且引脚数量(48脚)和Flash/RAM大小(64KB/20KB)对于本项目绰绰有余。其封装形式(LQFP)也方便手工焊接和调试。
注意:如果项目预算极其紧张,或者课程要求必须用51内核,那么STC89C52RC等芯片也能做,但可能需要额外的芯片来扩展IO口(如74HC595驱动数码管),软件上需要更精巧地优化代码效率和时序。
2.3 显示方案选择:数码管 vs. LED点阵 vs. LCD
显示部分是用户交互的核心,选择哪种方案直接影响了成本、功耗和视觉效果。
数码管(7-Segment):
- 优点:驱动简单,显示数字非常清晰、亮度高、成本低。非常适合只显示数字的场合。
- 缺点:显示信息有限(主要是数字和少量字母),功耗相对较高(特别是多位一体动态扫描时),体积较大。
- 本项目适用性:高。如果只显示分数、节次和倒计时(时间也用数字表示,如“12:34”),使用4-6位一体(每队2-3位分数,加上时间显示)的共阳或共阴数码管是非常经典和可靠的选择。驱动方式上,为了节省IO口,强烈建议使用串行移位寄存器,如74HC595。一颗STM32用3个IO口(数据、时钟、锁存)就可以驱动几乎任意多位的数码管,极大地简化了硬件连接和PCB布线。
LED点阵屏(如8x8, 16x32):
- 优点:可以显示任意自定义图形、汉字、更复杂的动画效果,视觉上更酷炫。
- 缺点:驱动复杂(需要行列扫描),软件编写难度高(涉及字库、取模),在强光环境下可能不如数码管清晰,功耗控制更复杂。
- 本项目适用性:中。如果你想让记分器显示“第1节”、“犯规5次”这样的汉字,或者想做一个滚动显示效果的比分牌,那么点阵屏是很好的选择。但这对编程能力和硬件驱动电路(可能需要专用的行列驱动芯片,如74HC138、74HC595组合,或使用MAX7219这类集成驱动芯片)提出了更高要求。
LCD液晶屏(字符型如1602,图形点阵型如12864,TFT彩屏):
- 优点:显示内容极其丰富,可以显示任何字符、图形和界面,人机交互设计空间大。
- 缺点:成本较高(尤其是TFT),需要更多的驱动代码(初始化、指令复杂),在户外强光下可能看不清(除非是阳光可视屏),功耗通常比LED高。
- 本项目适用性:中高。对于毕业设计,如果想体现更高的完成度和软件设计能力,使用一块小尺寸的OLED或TFT屏来设计一个图形化界面(GUI)会非常出彩。你可以用LVGL、emWin等嵌入式GUI库来绘制精美的按钮、数字和状态指示。但这会将项目重点从“嵌入式系统综合应用”部分转移到“嵌入式GUI应用”上,需要评估自己的时间和技能重心。
我的建议:对于大多数以“稳定实现核心功能”为目标的课程设计,采用数码管方案是最务实、最不容易出错的选择。它让你把精力集中在系统逻辑、稳定性和低功耗等更本质的嵌入式问题上。本篇文章后续的详细设计也将以“STM32 + 74HC595驱动多位数码管”为核心方案展开。
2.4 输入方案:独立按键 vs. 矩阵键盘 vs. 编码器
操作要简单可靠。我们需要大约10-15个按键(两队各有加减分键、共用控制键)。
- 独立按键:每个按键占用一个GPIO口。优点是程序简单(直接检测高低电平),可靠性高。缺点是当按键数量多时,非常占用IO资源。STM32的IO口虽然多,但也要省着用。
- 矩阵键盘:将按键排列成矩阵(如4x4),用8个IO口可以管理16个键。优点是节省IO口。缺点是程序需要行列扫描,稍复杂,且存在“鬼影”问题需要软件处理,实时性不如独立按键。
- 旋转编码器:可以用来调节时间、分数,手感好,一个编码器可以替代多个加减按键。但需要用到定时器输入捕获或外部中断来解码,增加了复杂度。
我的选择:为了追求极致的可靠性和编程简便性,我仍然推荐使用独立按键,但通过74HC165这类并行输入串行输出移位寄存器来扩展按键输入。这样,用STM32的3-4个IO口,就可以读取十几个按键的状态,既节省了主控IO,又保持了独立按键编程简单的优点。对于记分器这种对按键实时性要求高(不希望有扫描延迟)的设备,这个方案很合适。
2.5 系统架构框图
综合以上选择,我们得到如下系统架构:
+-------------------+ +-------------------------+ | 电源模块 | | STM32F103C8T6 | | (电池/稳压电路) |----->| (主控制器) | +-------------------+ +------------+------------+ | +-----------------------------+-----------------------------+ | | | +------------------+ +---------------------+ +-------------------+ | 输入模块 | | 显示模块 | | 声音提示模块 | | (按键 via | | (数码管 via | | (有源蜂鸣器) | | 74HC165) |<-------+ 74HC595) | | | +------------------+ +---------------------+ +-------------------+STM32作为大脑,通过SPI或模拟时序读取74HC165传来的按键状态,处理后,再通过SPI或模拟时序将显示数据发送给74HC595来驱动数码管,同时根据比赛状态控制GPIO驱动蜂鸣器。
3. 硬件电路设计与核心细节
3.1 主控最小系统与电源设计
STM32F103C8T6的最小系统包括:芯片本身、复位电路、时钟电路(8MHz晶振+两个负载电容)、启动模式选择电路(BOOT0/BOOT1)、下载调试接口(SWD)以及电源滤波电路。
- 电源部分:这是硬件稳定的基石。如果使用9V电池,需要通过一个线性稳压芯片(如AMS1117-3.3)将电压降至3.3V给STM32和大部分外围器件供电。务必注意:数码管和74HC595的工作电压可能是5V。这里需要一个电平转换方案。
- 方案一(推荐):所有芯片统一使用5V供电。选择STM32F103C8T6的宽电压版本,它支持2.0V-3.6V和1.8V-5.5V两种电压范围。这样,我们可以直接用AMS1117-5.0将电池电压稳到5V,供给STM32、74HC595、74HC165和数码管。STM32的IO口输出高电平就是5V,可以直接驱动74HC595,无需电平转换。
- 方案二:如果使用3.3V供电的STM32,而外围是5V器件。那么STM32的IO口输出3.3V高电平,对于74HC595来说,其输入高电平最低阈值通常是0.7*Vcc=3.5V(当Vcc=5V时),3.3V可能处于不确定状态。此时必须使用电平转换电路(如MOS管电路TXB0108等),或者选用输入阈值兼容3.3V的芯片(有些74HC595在3.3V供电时也能被3.3V信号可靠驱动,但需要查具体型号手册)。
实操心得:为了省事和保证可靠性,我强烈推荐方案一。使用支持5V供电的STM32F103C8T6,整个系统统一5V电压。这样设计电路最简单,噪声容限也更高。记得在电源入口和每个芯片的VCC附近放置一个0.1uF的陶瓷电容进行高频去耦,在稳压芯片的输入输出端放置一个10uF以上的电解电容或钽电容进行低频滤波。
3.2 显示驱动电路:74HC595级联详解
这是我们项目的核心驱动电路。74HC595是一个8位串行输入、并行输出的移位寄存器,带输出锁存。三线控制(SER数据,SRCLK移位时钟,RCLK锁存时钟)可以无限级联。
假设我们需要驱动6位8段数码管(共阳)。每位需要8个段选信号(a,b,c,d,e,f,g,dp)。那么总共需要6*8=48个输出口。我们可以用6片74HC595级联来实现。
电路连接:
- 级联方式:第一片595的
Q7'引脚(串行输出)连接到第二片595的SER引脚,以此类推。所有595的SRCLK(移位时钟)和RCLK(锁存时钟)分别并联在一起,由STM32的两个GPIO控制。 - 输出连接:每片595的8个并行输出(Q0-Q7)直接驱动一位数码管的8个段(a-g, dp)。注意数码管是共阳的,所以595输出低电平时对应的段亮。
- 位选控制:数码管的公共端(共阳端)不能直接接电源,否则所有位会同时显示相同内容。我们需要用另一组信号来控制哪一位数码管被点亮。这称为“位选”或“片选”。通常有两种方法:
- 使用晶体管阵列:每个数码管的公共阳极端通过一个PNP三极管(如8550)或PMOS管连接到电源。STM32用额外的GPIO(或另一组595)控制这些三极管的基极,实现动态扫描。这是最灵活、最常用的方法。
- 使用专用驱动芯片:如TM1637、MAX7219,它们集成了扫描逻辑,但成本稍高,且编程接口与595不同。
动态扫描原理:由于我们只有一套段选信号(来自595链),要显示不同的数字在不同的位上,就必须采用动态扫描。即:在极短的时间内(如每位数码管点亮2-5ms),先通过595送出第一位要显示的数字的段码,然后打开(置低电平)第一位的位选三极管,点亮第一位;关闭第一位,再送出第二位的段码,打开第二位的位选三极管……如此循环。只要扫描频率足够快(>60Hz),由于人眼的视觉暂留效应,看起来就像是所有位同时在稳定显示。
3.3 按键输入电路:74HC165级联详解
74HC165是8位并行输入、串行输出的移位寄存器。正好与595相反。我们可以用它将多个按键的并行状态,串行地读入STM32。
电路连接:
- 将8个独立按键的一端接地,另一端分别接到一片74HC165的8个并行输入引脚(A-H)。165的
SH/LD引脚(移位/装载控制)由STM32的一个GPIO控制。 - 当
SH/LD为低电平时,165将并行输入口的当前状态锁存到内部寄存器。然后STM32将SH/LD拉高,并通过CLK(时钟)和Q7(串行数据输出)引脚,配合CLK INH(时钟禁止,通常接地)将8位数据一位一位地读进来。 - 如果需要更多按键,可以将多片165级联。前一片的
Q7接后一片的SER(串行输入),所有片的CLK和SH/LD并联。
这样,我们仅用STM32的3个GPIO(SH/LD,CLK,DATA)就可以读取大量按键状态,极大地节省了资源。
3.4 蜂鸣器驱动电路
蜂鸣器分有源和无源。有源蜂鸣器内部有振荡电路,给电就响,频率固定;无源的需要给脉冲信号才能响,可以控制音调。这里我们用简单的有源蜂鸣器即可。 驱动电路很简单:蜂鸣器正极接电源(5V),负极接一个NPN三极管(如8050)的集电极,三极管发射极接地。STM32的一个GPIO通过一个限流电阻(如1kΩ)连接到三极管的基极。当GPIO输出高电平时,三极管导通,蜂鸣器通电鸣响。记得在三极管基极和发射极之间加一个10kΩ的下拉电阻,确保默认状态不导通。
4. 软件设计与核心代码实现
4.1 开发环境与工程配置
我使用的是Keil MDK(V5)搭配STM32标准外设库(SPL)。虽然HAL库更现代,但SPL对于理解底层寄存器操作更有帮助,代码效率也更高。当然,用STM32CubeMX生成HAL库代码初始化,再结合LL库或直接写寄存器也是可以的,看个人习惯。
工程关键配置:
- 系统时钟:使用外部8MHz晶振,通过PLL倍频到72MHz,作为系统主时钟(SYSCLK)。
- 定时器:配置一个基本定时器(如TIM6)用于产生精确的1ms中断,作为系统的“心跳”。这个1ms时基将用于按键扫描、数码管动态扫描、比赛计时等所有需要精确计时的地方。
- GPIO:将连接74HC595的
SER、SRCLK、RCLK引脚,连接74HC165的SH/LD、CLK、DATA引脚,以及蜂鸣器控制引脚、位选控制引脚都配置为推挽输出模式(除了165的DATA是输入模式)。 - 中断:开启TIM6的更新中断,并在中断服务函数(ISR)里进行1ms定时任务调度。
4.2 核心模块驱动代码
4.2.1 74HC595驱动数码管模块
首先,我们需要定义数码管的段码表(共阳,0-F,以及一些特殊符号如“-”、“空”)。
// 共阳数码管段码 (a,b,c,d,e,f,g,dp),0为点亮 const uint8_t SEG_CODE[] = { 0xC0, // 0 0xF9, // 1 0xA4, // 2 0xB0, // 3 0x99, // 4 0x92, // 5 0x82, // 6 0xF8, // 7 0x80, // 8 0x90, // 9 0x88, // A 0x83, // b 0xC6, // C 0xA1, // d 0x86, // E 0x8E, // F 0xBF, // - (减号) 0xFF // 空 (全灭) };然后编写595的发送函数。我们采用软件模拟SPI时序,因为可能只需要几MHz的速度,且引脚分配灵活。
// 假设引脚定义 #define HC595_SER_PIN GPIO_Pin_0 #define HC595_SRCLK_PIN GPIO_Pin_1 #define HC595_RCLK_PIN GPIO_Pin_2 #define HC595_PORT GPIOA void HC595_SendByte(uint8_t data) { for (int i = 0; i < 8; i++) { if (data & 0x80) { GPIO_SetBits(HC595_PORT, HC595_SER_PIN); } else { GPIO_ResetBits(HC595_PORT, HC595_SER_PIN); } // 产生一个上升沿,将数据移入595 GPIO_SetBits(HC595_PORT, HC595_SRCLK_PIN); delay_us(1); // 短暂延时,确保稳定 GPIO_ResetBits(HC595_PORT, HC595_SRCLK_PIN); data <<= 1; } } void HC595_UpdateDisplay(uint8_t *display_buffer, uint8_t length) { // display_buffer 存储了要显示在每一位上的段码,length是位数 // 注意:由于595是级联的,先发送的数据会到链子的最末端。 // 如果硬件连接是595链的末端对应数码管的最后一位,那么我们需要从最后一位的数据开始发。 for (int i = length - 1; i >= 0; i--) { HC595_SendByte(display_buffer[i]); } // 所有数据移位完成后,产生一个锁存时钟上升沿,将移位寄存器的数据更新到输出锁存器 GPIO_SetBits(HC595_PORT, HC595_RCLK_PIN); delay_us(1); GPIO_ResetBits(HC595_PORT, HC595_RCLK_PIN); }最后,在1ms定时器中断里实现动态扫描:
// 全局变量 uint8_t digit_buffer[6]; // 6位数码管的段码缓冲区 uint8_t current_digit = 0; // 当前扫描到的位 void TIM6_IRQHandler(void) { // 1ms中断 if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM6, TIM_IT_Update); // 1. 先关闭所有位选(消隐) Disable_All_Digits(); // 2. 更新595数据,发送当前位要显示的内容 HC595_UpdateDisplay(&digit_buffer[current_digit], 1); // 只发送当前位的数据,需要根据硬件连接调整 // 3. 打开当前位的位选 Enable_Digit(current_digit); // 4. 指向下一位 current_digit++; if (current_digit >= 6) { current_digit = 0; } // 其他1ms任务,如按键扫描 Key_Scan_Task(); } }Disable_All_Digits和Enable_Digit函数通过控制连接到位选三极管基极的GPIO来实现。
4.2.2 74HC165读取按键模块
#define HC165_SH_LD_PIN GPIO_Pin_3 #define HC165_CLK_PIN GPIO_Pin_4 #define HC165_DATA_PIN GPIO_Pin_5 #define HC165_PORT GPIOA uint16_t Read_HC165(void) { uint16_t key_value = 0; // 1. 产生一个低脉冲,将并行数据锁存到165内部 GPIO_ResetBits(HC165_PORT, HC165_SH_LD_PIN); delay_us(1); GPIO_SetBits(HC165_PORT, HC165_SH_LD_PIN); // 2. 连续读取16位数据(假设级联了两片165) for (int i = 0; i < 16; i++) { key_value <<= 1; // 先左移,先读到的数据是高位还是低位取决于硬件连接,这里假设先读最高位 if (GPIO_ReadInputDataBit(HC165_PORT, HC165_DATA_PIN)) { key_value |= 0x01; } // 产生一个时钟上升沿,移位 GPIO_SetBits(HC165_PORT, HC165_CLK_PIN); delay_us(1); GPIO_ResetBits(HC165_PORT, HC165_CLK_PIN); } return key_value; }在Key_Scan_Task()中,我们每隔一定时间(如20ms)调用Read_HC165(),然后进行按键消抖和状态判断。消抖逻辑一般是:连续几次采样到稳定的按下状态,才认为按键有效。
4.3 核心业务逻辑:比赛状态机
这是整个记分器软件的灵魂。我们需要用一个清晰的状态机(State Machine)来管理比赛的各种状态和切换。状态至少包括:设置模式、比赛运行、比赛暂停、节间休息、比赛结束。
typedef enum { GAME_MODE_SETUP, // 设置时间、分数 GAME_MODE_RUNNING, // 比赛进行中 GAME_MODE_PAUSED, // 比赛暂停 GAME_MODE_BREAK, // 节间休息 GAME_MODE_END // 比赛结束 } GameMode_t; typedef struct { uint8_t score_a; uint8_t score_b; uint8_t period; // 当前节次 (1-4) uint16_t time_left; // 当前节剩余时间 (单位:秒) uint8_t foul_a; // A队本节犯规 uint8_t foul_b; // B队本节犯规 GameMode_t mode; // ... 其他状态,如暂停次数等 } GameState_t; GameState_t game;在1s定时任务中(可以用一个计数器,每1000个1ms中断为1秒),根据当前game.mode执行不同操作:
GAME_MODE_RUNNING:time_left减1。如果减到0,则判断:如果是第四节且比分不同,则进入GAME_MODE_END;否则,进入GAME_MODE_BREAK,并鸣响蜂鸣器。GAME_MODE_PAUSED: 时间不减少。GAME_MODE_BREAK: 可以有一个休息倒计时,结束后自动进入下一节或结束。
按键处理函数根据当前模式,对按键进行不同的响应:
- 在
GAME_MODE_SETUP下,某些按键用于调整初始时间和分数。 - 在
GAME_MODE_RUNNING下,加分按键有效,按下后更新对应队伍的分数,并立即刷新显示。 - 在
GAME_MODE_PAUSED下,只有“启动/继续”键和“设置”键有效。
这种状态机的设计,使得程序逻辑非常清晰,易于调试和维护。
4.4 显示数据刷新
我们需要一个函数,根据game结构体中的状态,更新digit_buffer这个显示缓冲区。
void Refresh_Display_Buffer(void) { // 假设显示布局:[A十位][A个位] [B十位][B个位] [节次] [时间分十位][时间分个位][时间秒十位][时间秒个位] // 实际可能需要6-8位数码管 digit_buffer[0] = SEG_CODE[game.score_a / 10]; // A队十位 digit_buffer[1] = SEG_CODE[game.score_a % 10]; // A队个位 digit_buffer[2] = SEG_CODE[game.score_b / 10]; // B队十位 digit_buffer[3] = SEG_CODE[game.score_b % 10]; // B队个位 digit_buffer[4] = SEG_CODE[game.period]; // 节次 // 时间显示,例如 12:30 digit_buffer[5] = SEG_CODE[(game.time_left / 60) / 10]; // 分钟十位 digit_buffer[6] = SEG_CODE[(game.time_left / 60) % 10]; // 分钟个位 digit_buffer[7] = SEG_CODE[(game.time_left % 60) / 10]; // 秒钟十位 digit_buffer[8] = SEG_CODE[(game.time_left % 60) % 10]; // 秒钟个位 // 如果某位是0且不需要显示前导零,可以将其设为“空”段码 // 例如,如果分钟小于10,则不显示十位的0 if ((game.time_left / 60) < 10) { digit_buffer[5] = SEG_CODE[17]; // 空 } }这个函数需要在分数、时间等状态发生变化时被调用。
5. 调试、优化与功能扩展
5.1 硬件调试与常见问题
- 电源问题:上电后STM32不工作。首先检查3.3V/5V电压是否正常稳定。用万用表测量STM32的VDD和VSS之间电压。检查复位电路,确保NRST引脚在上电后为高电平。检查BOOT0和BOOT1是否按需求接地(通常从主Flash启动,两者都接地)。
- 下载问题:SWD接口连接正确(SWDIO, SWCLK, GND),但无法识别芯片。检查电源,检查SWDIO和SWCLK线是否接反或被其他电路拉低。尝试降低下载速度。确保芯片没有进入休眠或待机模式。
- 显示问题:数码管不亮、乱码或闪烁。
- 全不亮:检查595的电源和地,检查
SRCLK和RCLK信号是否有波形(用示波器或逻辑分析仪)。检查位选控制电路,三极管是否导通。 - 乱码:检查段码表是否正确(共阴/共阳搞反)。检查595输出到数码管段选的连接顺序(a-g,dp)是否与段码表定义一致。检查动态扫描的顺序和速度,扫描太快或太慢都会导致显示异常。
- 闪烁或有重影:这是动态扫描的典型问题。“消隐”没做好。必须在切换位选前,先关闭所有位选(
Disable_All_Digits),然后更新段选数据,再打开新的位选。否则,在数据变化过程中,错误的段码可能会短暂地显示在错误的位上。
- 全不亮:检查595的电源和地,检查
- 按键问题:按键不响应或连击。
- 不响应:检查165的电路,特别是
SH/LD和CLK时序。用逻辑分析仪抓取STM32发出的时钟和读取的数据,看是否正确。检查按键是否接错(应一端接地,一端接165输入)。 - 连击:软件消抖没做好。确保使用了稳定的消抖算法(如状态机消抖或连续采样消抖)。
- 不响应:检查165的电路,特别是
5.2 软件优化与稳定性
- 中断服务函数(ISR)要短小精悍:1ms定时器中断里只做最必要的事情(更新扫描位、调用按键扫描任务标记)。绝对不要在中断里进行复杂计算、延时或调用可能阻塞的函数。将耗时的操作(如处理按键事件、更新游戏逻辑)放到主循环中基于标志位来执行。
- 避免全局变量冲突:显示缓冲区
digit_buffer、游戏状态game等会被中断和主循环同时访问的变量,在读写时可以考虑暂时关闭中断(__disable_irq()和__enable_irq())进行保护,或者确保读写操作是原子的(对于8位、16位变量,在32位机上通常是原子的)。 - 低功耗考虑:如果使用电池供电,在比赛暂停或节间休息时,可以适当降低系统时钟频率,或者让STM32进入睡眠模式(Sleep Mode),通过按键外部中断唤醒。也可以考虑关闭不必要的外设时钟。
5.3 功能扩展思路
一个基础版本完成后,你可以考虑以下扩展,让项目更出彩:
- 无线遥控:增加一个NRF24L01或ESP8266/ESP32模块,实现无线遥控器控制记分器。这需要设计一对一的通信协议,处理无线数据的可靠传输。
- 数据存储:增加一片EEPROM(如AT24C02)或利用STM32内部的Flash,保存多场比赛的历史比分、设置参数等,实现断电记忆。
- 语音播报:通过一个语音合成模块(如SYN6288),在得分、犯规、节末时进行语音播报,体验更佳。
- 上位机软件:通过串口或蓝牙将比赛数据实时发送到电脑或手机,在上位机上显示更丰富的统计图表。
- 网络同步:如果使用ESP32,可以做成Wi-Fi接入点,让手机通过网页浏览器访问一个控制页面来操作记分器,实现“无线记分牌+手机控制端”的豪华组合。
6. 从课程设计到毕业设计的升华
对于课程设计,完成上述所有基础功能并稳定运行,已经能拿到一个不错的分数。但如果作为毕业设计,你需要体现更深入的研究和更系统的工程能力。
- 文档的完备性:毕业设计论文需要包含完整的绪论(背景、意义、现状)、系统总体设计、硬件详细设计(原理图、PCB图、元器件清单)、软件详细设计(程序流程图、状态机图、关键代码说明)、系统测试(功能测试、性能测试、稳定性测试)以及总结展望。每一部分都要论述清晰,有理有据。
- 理论的深入性:不能只讲“怎么做”,还要讲“为什么这么做”。例如,在硬件设计中,要论述为什么选择74HC595和74HC165,分析其时序参数如何与STM32的GPIO速度匹配。在软件设计中,要阐述状态机设计的优越性,对比前后台系统和RTOS系统的优劣(如果你的项目引入了FreeRTOS,那将是一个很大的亮点)。
- 创新点:在基础功能上,提出并实现一个你自己的创新点。例如:
- 基于FreeRTOS的多任务设计:将显示刷新、按键扫描、比赛逻辑、网络通信等任务分别放在不同的RTOS任务中,并处理好任务间的通信和同步。这能很好地展示你对实时操作系统的理解。
- 引入触摸屏或OLED图形界面:用LVGL等库设计一个酷炫的交互界面,通过触摸或旋转编码器进行操作。
- 实现复杂的比赛规则:如24秒进攻违例计时、加时赛自动判断、个人犯规累计等。
- 进行功耗分析与优化:实际测量系统在不同工作模式下的电流,计算电池续航时间,并尝试通过软件手段进一步降低功耗。
- 系统测试与数据分析:设计详细的测试用例,记录测试数据。例如,测试按键响应时间、显示刷新率、无线通信距离与丢包率、电池续航时间等。用图表展示数据,并进行分析。
这个基于STM32的篮球记分器项目,就像一颗种子。从最简单的点灯、按键开始,你可以让它生长出硬件设计、嵌入式编程、实时系统、低功耗设计、无线通信等多个枝丫。无论你做到哪一步,这个亲手将想法变为现实的过程,以及过程中解决无数个大小问题的经历,才是它带给你的最大财富。
本文还有配套的精品资源,点击获取