☰
ESP32保龄球计分系统实战:从硬件到算法全解析
2026/10/22 20:30:49 网站建设 项目流程

做保龄球计分这类互动硬件时,最容易被低估的往往不是代码,而是“规则、传感器和状态机”三者怎么咬合的问题。ESP32 上能跑的计分方案很多,但真正把球瓶检测、补投规则、LED 反馈、手机控制串成一套完整系统的教程很少。这篇文章围绕一个 ESP32 保龄球计分系统展开,从硬件选型、环境搭建、计分算法、传感器采集到无线控制扩展,把完整技术方案拆开讲清楚,适合正在做 ESP32 项目、想从点灯进阶到完整交互系统的开发者参考。

1. ESP32 保龄球系统概述与整体架构

1.1 这个项目到底做什么

一个 ESP32 保龄球系统,本质上是把传统的保龄球记分流程自动化。传统场景里,玩家掷球、球瓶倒下、人工计分、重新摆瓶,整个过程依赖人眼判断。而 ESP32 方案要完成的工作包括:

  • 检测球是否已经滚出,触发一轮计分流程;
  • 识别 10 个球瓶当前是站立还是倒下;
  • 根据保龄球官方计分规则计算当前得分;
  • 通过灯带、OLED 屏幕或手机 App 实时展示分数;
  • 在每一轮结束后提醒玩家复位球瓶,并开启下一轮。

这类系统的应用场景很多,比如桌面迷你保龄球台、商场互动游戏设备、创客教育项目,甚至可以作为体育训练时的辅助计分设备。相比使用摄像头做视觉识别,传感器方案的成本更低、实时性更好,也更适合从 Arduino 或 ESP32 入门的新手学习和二次开发。

1.2 系统整体架构

整个系统可以分成三层来看:

层级负责内容典型硬件
传感层检测发球、检测球瓶状态红外避障传感器、倾斜开关、MCP23017 I/O 扩展
逻辑层计分规则、状态机、数据存储ESP32 主控、计分算法代码
展示与控制层分数显示、灯光反馈、远程控制WS2812B 灯带、OLED、手机蓝牙或 WiFi

这里的关键设计思路是:不要把 10 个球瓶传感器直接接到 ESP32 的 GPIO 上。ESP32 虽然有较多引脚,但接 10 路传感器后可用引脚已经很紧张,而且后续还要接灯带、蜂鸣器、按键、无线模块。更好的做法是使用 MCP23017 这类 I2C I/O 扩展芯片,用两根线扩展出 16 路数字输入,只占用 ESP32 的 SDA 和 SCL。

1.3 为什么选 ESP32 而不是 Arduino Uno 或树莓派

相比 Arduino Uno,ESP32 的优势非常明显:主频更高、内存更大、自带 WiFi 和蓝牙,可以方便地实现手机控制、局域网数据上报。相比树莓派,ESP32 成本更低、启动更快、没有操作系统负担,适合做这种“传感器采集 + 简单展示”的嵌入式系统。另外,ESP32 开发板型号很多,比如常见的 ESP32 Dev Kit、ESP32-S2、ESP32-S3,一般的计分系统使用双核 ESP32 就完全足够。

2. 硬件选型与接线方案

2.1 完整硬件清单

下面是一套完整的硬件清单,按功能模块分组:

模块型号/器件数量作用
主控ESP32 DevKit 开发板1运行计分逻辑与控制逻辑
I/O 扩展MCP23017 模块1扩展 16 路数字输入,连接球瓶传感器
倾斜传感器SW-520D 振动/倾斜开关10判断每个球瓶是否倒下
发球检测红外避障传感器模块1检测球是否通过发球区
显示WS2812B 灯带 或 8x8 LED 点阵若干计分板与灯光反馈
可选显示SSD1306 0.96 英寸 OLED1显示帧数、分数、提示信息
输入按键开关1复位球瓶、开始新一轮
报警有源蜂鸣器1全中、补中等事件提示音
电源5V 2A USB 电源1系统供电

如果暂时没有 MCP23017,也可以用 CD74HC4067 模拟开关多路复用器,4 个选择引脚加 1 个信号引脚就能读 16 路开关,但代码上需要用查表方式切换通道,比 MCP23017 稍微麻烦一些。

2.2 引脚分配参考

不同 ESP32 开发板的引脚布局有差异,以下是一份经过验证的参考分配方案,实际制作时需要根据开发板丝印调整:

ESP32 GPIO连接对象
GPIO21I2C SDA,接 MCP23017 SDA
GPIO22I2C SCL,接 MCP23017 SCL
GPIO4红外避障传感器数字输出
GPIO5WS2812B 灯带数据输入
GPIO15复位按键,接一个 10kΩ 上拉电阻到 3.3V
GPIO18蜂鸣器正极
GND所有模块共地
3.3V / 5V按模块要求分别供电

MCP23017 的 A0、A1、A2 地址引脚全部接 GND,I2C 地址为 0x20。球瓶倾斜开关接入 MCP23017 的 GPA0 到 GPA7,以及 GPB0 和 GPB1,正好对应通道 0 到 9。

2.3 接线与供电注意事项

接线时需要注意以下几点:

电源不要走杜邦线串联到底。ESP32、灯带、传感器如果全部从一个 USB 口取电,灯带全亮时电流很容易超过 500mA,导致电压跌落、传感器误判。推荐使用 5V 2A 电源,灯带单独供电,ESP32 和传感器共用一组电源,并且所有模块共地。

倾斜开关需要上拉。SW-520D 这类机械式倾斜开关相当于一个开关量输出,悬空时电平不稳定。MCP23017 内部有可配置的上拉电阻,初始化时通过pullUp()开启即可,不需要每个引脚外接上拉电阻。

红外避障传感器要调节灵敏度。模块上通常有一个蓝色电位器,用来调节检测距离。球从传感器前方经过时输出低电平,如果检测距离太近或者太远,会导致触发失败。

3. 开发环境搭建:Arduino IDE + ESP32

3.1 Arduino IDE 安装 ESP32 支持

Arduino IDE 默认不包含 ESP32 开发板,需要先在“开发板管理器”中安装支持包。

打开 Arduino IDE,进入“文件 -> 首选项”,在“附加开发板管理器网址”中添加:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

然后进入“工具 -> 开发板 -> 开发板管理器”,搜索esp32,选择 Espressif Systems 官方提供的esp32包并安装。如果在线下载较慢或失败,可以先把官方打包好的离线安装包下载到本地,解压或放到 Arduino15 对应目录后重新打开 IDE,也能完成安装。

3.2 选择开发板与烧录设置

安装完成后,在“工具 -> 开发板 -> ESP32 Arduino”中选择ESP32 Dev Module。串口选择实际识别的 COM 口或/dev/ttyUSB0。上传速度建议设置为921600,如果烧录不稳定可以降到115200。

如果开发板无法识别,通常是因为 USB 转串口芯片驱动没有安装。使用 CH340 芯片的开发板需要安装 CH340 驱动,使用 CP2102 芯片的则安装 CP210x 驱动。

烧录失败时,可以按住开发板上的 BOOT 按键不放,点击“上传”,等待出现Connecting...提示后再松开 BOOT。ESP32 烧录后出现无法烧录的情况,多数也是这个问题。

3.3 项目所需 Arduino 库

本系统需要安装以下第三方库:

库名安装来源用途
Adafruit MCP23017库管理器搜索MCP23017操作 I2C I/O 扩展
Adafruit NeoPixel库管理器搜索NeoPixel控制 WS2812B 灯带
Adafruit SSD1306库管理器搜索SSD1306驱动 OLED 显示
Adafruit GFX库管理器搜索GFXSSD1306 的图形依赖
BluetoothSerial无需安装ESP32 经典蓝牙串口通信

库管理器搜索时要注意,Adafruit 的 MCP23017 库和 Adafruit SSD1306 库都依赖 Adafruit GFX,后者会作为依赖自动安装。

4. 保龄球计分规则与程序设计

4.1 规则梳理

保龄球标准计分规则有一些容易混淆的地方,先把它整理清楚:

  • 一局共 10 轮。
  • 前 9 轮每轮最多投两次球,第 10 轮比较特殊。
  • 如果第一球就打倒全部 10 个球瓶,称为全中,这一轮立即结束,不需要投第二球。
  • 如果两球打倒全部球瓶,称为补中。
  • 全中的作用是“本次 10 分 + 之后两次投球的被击倒数”。
  • 补中的作用是“本次 10 分 + 之后一次投球的被击倒数”。
  • 第 10 轮如果投出全中或补中,还可以额外追加投球,最多允许三球。

写计分代码时,最容易出错的就是“当前这一轮还没结束,不能把奖励分算进去”。所以计分函数不能只累加当前数据,必须判断后续投球数据是否已经产生。

4.2 计分引擎设计

把计分逻辑独立成一个函数,输入是每次投球击倒的球瓶数数组,输出是某一轮结束后的累计分数。设计如下:

int computeScoreAt(const int rolls[], int rollCount, int frame) { int score = 0; int roll = 0; for (int f = 0; f < frame; f++) { if (roll + 1 >= rollCount) break; // 数据不足,无法结算 if (rolls[roll] == 10) { // 全中:必须等后面两次投球数据才能累计 if (roll + 2 < rollCount) { score += 10 + rolls[roll + 1] + rolls[roll + 2]; } roll++; } else if (rolls[roll] + rolls[roll + 1] == 10) { // 补中:必须等后面一次投球数据才能累计 if (roll + 2 < rollCount) { score += 10 + rolls[roll + 2]; } roll += 2; } else { score += rolls[roll] + rolls[roll + 1]; roll += 2; } } return score; }

rolls数组按顺序保存每一次实际投球的击倒数。比如玩家第 1 轮投出全中,rolls[0] = 10,此时rollCount只有 1,computeScoreAt(..., 1)会因为数据不足而返回 0。等第 2 轮第一球结束后rollCount变为 2,仍然无法补采第 3 个数据,直到第 2 轮第二球结束,rollCount变为 3,第 1 轮分数才能结算为10 + rolls[1] + rolls[2]。

4.3 主循环状态机设计

系统的主循环不能简单地“一检测到球就立即读传感器”。球滚动、碰撞、球瓶摇晃都需要时间,所以这里使用状态机把流程拆开:

状态行为
WAIT_BALL等待红外传感器检测到球通过
WAIT_SETTLE球瓶摇晃等待,1.2 秒后读取球瓶状态
SHOW_RESULT显示本轮分数,等待玩家复位球瓶并按键确认

这个状态机的好处是把“检测事件”和“处理结果”解耦。如果

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询