从零打造STM32篮球计分器:硬件选型、代码解析与实战避坑指南
篮球计分器作为嵌入式开发的经典练手项目,既能巩固STM32基础知识,又能培养完整的产品思维。本文将带你用STM32F103C8T6核心板和0.96寸OLED显示屏,实现一个支持24秒违例规则的专业级篮球计分系统。不同于常见的矩阵键盘方案,我们创新性地采用红外遥控作为输入设备,仅需3个IO口就能实现21个按键功能,大幅降低硬件复杂度。
1. 硬件架构设计与选型策略
1.1 核心器件选型对比
选择STM32F103C8T6(蓝桥杯开发板同款)作为主控,主要基于以下考量:
- 72MHz主频足够处理计时和显示逻辑
- 内置定时器资源丰富(TIM3用于倒计时,TIM4捕获红外信号)
- 成本仅20元左右,学生党友好
显示模块选用0.96寸OLED(SSD1306驱动)而非LCD,优势明显:
| 特性 | OLED | LCD |
|---|---|---|
| 可视角度 | 170° | 120° |
| 响应速度 | 0.01ms | 10ms |
| 功耗 | 0.08W(全亮) | 0.5W |
| 接口 | I2C/SPI | 并行 |
| 焊接难度 | 4针 | 16针 |
红外接收方案对比传统矩阵键盘:
// 红外接收仅需1个GPIO配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; GPIO_Init(GPIOB, &GPIO_InitStructure);1.2 硬件连接图与功耗优化
实际焊接时注意:
- OLED的SCL接PA2,SDA接PA3
- 红外接收器OUT脚接PB9
- 核心板供电建议加装100μF电容滤波
实测整机工作电流:运行状态12mA,待机状态5mA(可通过关闭未用外设进一步优化)
2. 计时系统实现与中断管理
2.1 多层计时器架构设计
比赛计时需要三个时间维度协同工作:
- 节计时(12分钟)
- 24秒违例计时
- 百分秒精密计时
// 全局时间变量定义 u8 minute = 11, second=59, count=24; u16 ms=100; // 百分秒计数器2.2 定时器中断配置要点
TIM3配置为10ms中断,通过预分频实现:
TIM_TimeBaseStructure.TIM_Period = 100; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler = 7199; // 72MHz/(7199+1)=10kHz TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);中断服务程序中实现时间递减逻辑:
void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { ms--; if(ms<1) { ms = 100; second--; if(second<0) { second = 59; minute--; } } } TIM_ClearITPendingBit(TIM3, TIM_IT_Update); }关键点:在暂停状态需要关闭定时器中断,但保持计数器值不变
3. 红外遥控解码与按键优化
3.1 NEC协议解码流程
红外接收使用TIM4输入捕获功能,关键状态机逻辑:
- 检测4.5ms引导码
- 记录560us/1680us脉冲间隔
- 校验地址码和反码
- 处理连发码
if(Dval>4200&&Dval<4700) { // 引导码检测 RmtSta|=1<<7; RmtCnt=0; }3.2 按键防抖与事件处理
原始代码存在的"一次按键多次触发"问题,通过增加状态判断解决:
if(keys&&RmtCnt==1) { // 仅处理首次按键 switch(keys) { case 194: // 开始/暂停键 ID = (ID%2)+1; // 状态切换 TIM_ITConfig(TIM3,TIM_IT_Update,ID==1?ENABLE:DISABLE); break; } }按键功能映射表:
| 键值 | 功能 | 键值 | 功能 |
|---|---|---|---|
| 162 | 主队大比分+ | 98 | 客队大比分+ |
| 176 | 24秒复位 | 122 | 14秒复位 |
| 82 | 半场交换比分 | 194 | 开始/暂停 |
4. OLED显示优化与界面设计
4.1 多图层显示管理
采用分区域刷新策略降低闪烁:
- 球队名称区(每节刷新)
- 比分区(实时刷新)
- 计时区(100ms刷新)
- 24秒环(50ms刷新)
void LCD_time(void) { OLED_ShowNum(20-1,40,minute,2,24,1); OLED_ShowNum(56-1,40,second,2,24,1); OLED_ShowNum(90,47,ms,2,16,1); // 冒号闪烁效果 static u8 blink = 0; if(ID==1) OLED_ShowString(43-1,39,(blink^=1) ? ":" : " ",24,1); }4.2 自定义字模制作
使用PCtoLCD2002生成球队名称字模:
- 选择16x16点阵
- 设置取模方式:逐列式、顺向
- 导出C语言数组格式
// 物理学院字模示例 const u8 PHYSICS_CHN[] = { 0x00,0x40,0x00,0x20,0x7F,0xFE... // 后续数据省略 };5. 常见问题与性能优化
5.1 电源干扰解决方案
现象:遥控失灵或显示乱码
- 在STM32的3.3V引脚加装0.1μF去耦电容
- 红外接收器VCC串联100Ω电阻
- 避免杜邦线过长(建议<15cm)
5.2 显示残影消除技巧
修改SSD1306驱动:
void OLED_Refresh(void) { OLED_Write_Cmd(0x21); // 设置列地址 OLED_Write_Cmd(0x00); OLED_Write_Cmd(0x7F); OLED_Write_Cmd(0x22); // 设置页地址 OLED_Write_Cmd(0x00); OLED_Write_Cmd(0x07); // 增加5ms延时 delay_ms(5); }5.3 代码体积优化
通过以下方式将bin文件控制在32KB以内:
- 使用-O2优化等级
- 移除未用标准库函数
- 将字模数据存储在const区域
实际测试中各模块占用空间:
- 红外解码:3.2KB
- OLED驱动:2.8KB
- 主逻辑:6.4KB
- 字模数据:18KB
6. 项目扩展与进阶方向
6.1 无线升级功能实现
添加蓝牙模块(HC-05)实现:
- 通过串口IAP进行固件更新
- 手机APP远程控制计分器
- 比赛数据实时上传
// 串口接收中断中处理升级命令 if(RxBuff[0]==0xAA && RxBuff[1]==0x55) { JumpToBootloader(); }6.2 比赛数据记录功能
扩展SD卡存储:
- 每节比分变化时间戳
- 24秒违例统计
- 球队暂停次数记录
文件存储格式示例:
[Q1] 10:32 A+1 10:15 B+2 [Q2] 08:45 Timeout_A [Q3] 05:21 24s_Violation_B6.3 低功耗设计方案
针对电池供电场景优化:
- 启用STM32的Stop模式(电流<1mA)
- OLED动态刷新率调整
- 红外唤醒功能实现
void Enter_LowPower(void) { OLED_DisplayTurn(0); // 关闭显示 TIM_Cmd(TIM3,DISABLE); PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); }在完成基础功能后,尝试增加音效提示功能(使用无源蜂鸣器),当24秒违例或节末时发出不同频率的提示音。实际调试中发现,直接使用延时函数控制蜂鸣器会导致显示卡顿,最终改用PWM+DMA方案解决。