新闻中心

EEPW首页 > 嵌入式系统 > 设计应用 > 基于 Arduino Mega 2560 的全尺寸电动弹珠机设计与实现

基于 Arduino Mega 2560 的全尺寸电动弹珠机设计与实现

作者: 时间:2025-11-21 来源: 收藏

Arduino Controlled Pinball Machine

摘要

本文介绍了一台以 Arduino Mega 2560 为核心控制器的全尺寸电动弹珠机(Pinball Machine)。整机采用木制机柜结构,配合市售弹珠机标准配件(弹板、弹簧、挡片、Pop Bumper 等),通过 Arduino 实现 得分逻辑、灯光控制、目标判定以及游戏流程管理。项目从机械结构、电子系统到软件逻辑均由作者自行设计与实现,历时约六个月间断完成,是一个集木工、机械设计、电力电子与嵌入式编程于一体的复杂系统工程。

文中详细说明机柜结构尺寸、可调节机脚设计、弹珠台倾角控制、24V 电源与 5V 逻辑电路隔离、Pop Bumper 高压检测的电压分压方案,以及基于 Arduino Mega 的得分与游戏逻辑实现方法,可为有意自制弹珠机的工程爱好者提供系统参考。


1 项目概述

作者在项目开始时,几乎找不到完整的“如何从零自制弹珠机”的资料,因此采用了如下策略:

  • 使用 备用胶合板 作为原型试验平台:
    每个机构(Flipper、Slingshot、Pop Bumper、目标靶等)都先在测试板上安装与调试,待可靠后才移植到正式机台。

  • 采用 标准弹珠机配件 + 自制木柜 的混合方式:
    机柜、上盖、内部支撑结构自己木工制作;弹板、Pop Bumper、目标靶等则尽量使用成熟的替换件。

  • 逻辑控制全部交给 Arduino Mega 2560
    使用其丰富的数字与模拟 I/O,既负责采集各类开关信号,又负责控制灯光与得分显示。

结果是一台结构接近商用尺寸的弹珠机,但游戏规则与灯光逻辑完全可编程。


2 机械结构设计

2.1 机柜结构

机柜采用 橡木贴面胶合板 制作,基本尺寸(单位:英寸)如下:

  • 前后面板:20 高 × 23 宽

  • 侧板:20 高 × 47 长

  • 台面(Playfield):22 宽 × 42 长

设计要点:

  1. 结构连接方式

    • 板材采用斜接(miter)切割,使用饼干榫(biscuit joiner)与木胶拼接;

    • 也可用暗扣螺丝 + 木胶组合,保证强度——弹珠机在游戏中会承受频繁冲击。

  2. 底部搁板与电源布置

    • 不与侧板胶合,以适应木材膨胀收缩;

    • 用于放置主电源、LED 灯电源等模块。

    • 在底部上方 1 英寸位置开 ¾ 英寸宽的榫槽(dado),做一个“浮动”搁板:

  3. 台面与前部空腔

    • 前方预留约 3 英寸空间,用于布线、安装弹簧拉杆(Plunger)和前部灯光等;

    • Playfield 长度 42 英寸,明显短于机柜 47 英寸:

    • 台面通过侧板上的小木块支撑,距离上沿约 4 英寸;

    • 维护时,只需打开上盖,将台面整体抬出并翻转即可检修底面机构与线束。

2.2 倾角与机脚设计

弹珠机的“节奏”很大程度取决于台面的倾角,通常在 1°–7° 之间。角度越大,球速越快,游戏越刺激。

为方便调节倾角,作者自制了可调机构的木质机脚

  • 使用市售木质桌腿,底部钻入约 12 英寸深孔;

  • 在孔底用双组份环氧胶固定 3/8 英寸 Tee Nut(内螺母),注意避免胶体污染螺纹;

  • 将 12 英寸长的 3/8 英寸螺纹杆旋入腿内,末端安装可调水平脚(Leveling Foot),中间加锁紧螺母;

  • 橱柜底部通过金属腿板(Table Leg Mounting Plate)与木腿连接。

调节方法:

  • 松开锁紧螺母 → 旋入 / 旋出螺杆以升降台面 → 锁紧螺母固定。

2.3 上盖与透明面板

上盖(Lid)与机柜同外形尺寸,材质为橡木框架:

  • 上侧与两边框宽约 1.5 英寸;

  • 底边加宽至 5 英寸,用于遮挡台面与机柜间的间隙;

  • 内侧开槽嵌入 亚克力板(Plexiglas)

    • 相比玻璃更轻、更安全,也更易切割;

  • 采用暗扣螺丝(Pocket Screw)装配,便于今后更换亚克力板;

  • 内侧安装一圈彩色 LED 灯带,用于装饰;

  • 上盖与机柜通过 钢琴合页(Piano Hinge) 铰接,并在两侧开槽嵌入,提升外观与强度。

机柜背面还固定一条 电源插排,所有电源统一插入,便于通过一个总开关控制整机上电;同时预留一条 USB 延长线,方便在不拆台面的前提下更新 Arduino 程序。


3 控制与电子系统

3.1 Arduino 控制逻辑概述

整机由一块 Arduino Mega 2560 控制,主要承担四大任务:

  1. 采集开关与传感器状态

    • 目标靶(Target Switch)

    • 滚轮开关(Rollover Switch)

    • Pop Bumper 开关(通过电压分压)

    • 弹球发射道开关

    • 压力传感器(用于监测丢球)

  2. 控制灯光与效果

    • 目标靶对应灯

    • 滚轮灯

    • Pop Bumper 灯

    • Game Over 时全场闪烁效果

  3. 得分与规则管理

    • 各类元件命中得分;

    • 所有同类元件被依次命中后,触发灯光全闪与分值提升(倍乘);

    • 记录当前球数、是否 Game Over 等。

  4. LCD 显示

    • 当前得分(Score)

    • 当前球号(Ball)

    • 使用一个简单串口 LCD 显示:

3.2 开关输入与上拉配置

大部分目标开关采用“输入 + 内部上拉”的方式接入 Arduino:

pinMode(pinNumber, INPUT_PULLUP);

接线方式:

  • 一端接 Arduino 数字引脚;

  • 另一端接 GND;

  • 开关未触发时引脚为 HIGH(被上拉);

  • 开关闭合(被弹珠撞击)时,引脚被拉低为 LOW。

在代码中,检测到输入由 HIGH → LOW,即认为该目标被击中。

3.3 Pop Bumper 与高压检测:电压分压

与普通目标与滚轮使用 Arduino 5V 供电不同,Pop Bumper 需要强劲的冲击力,因此采用独立的 25V–24V 电源 驱动线圈。Pop Bumper 的“触发开关”处于高压侧,不能直接接入 Arduino。

解决方案:

  • 将 Pop Bumper 开关输出接入 电压分压电路

  • 通过适当的电阻比,将高压侧信号衰减到不超过 5V;

  • 分压后的信号接入 Arduino 的 模拟输入端口(Analog Input)

原因:

  • 即使在未触发情况下,分压器也会漏少量电流,导致信号有一定电压噪声;

  • 使用模拟口可以设置阈值,如 analogRead(i) > 500,更容易区分“真触发”与“背景泄漏”。

3.4 球的丢失检测:力传感器

系统还使用一个小型 力/压力传感器(Force Sensor),通常布置在“落球区”或回收通道。当球掉回底部时,会压到传感器:

  • 用力传感器读数作为“丢球”判定依据;

  • 同时配合发射道开关(上、下发射口),区分“球刚被射出”与“球已丢失”;

  • 每次检测到丢球,球数(Ball)+1;

  • 当 Ball 达到最大值(如 5 球),触发 Game Over 流程。

3.5 分数与效果逻辑(来自示例代码)

原文给出了完整的 Arduino 控制代码,核心思想如下:

  1. 变量设计

    • Score:当前得分

    • Target, Pop, Roll:三类目标的基础得分值

    • Targets[8], Rolls[3], Pops[4]:记录每个目标是否被命中

    • Ball:当前球号(1–5)

    • Flash:灯光闪烁延时

    • Pressure:力传感器触发阈值

  2. 目标靶(Targets)处理流程

    • 所有目标灯闪烁若干次;

    • 灯全部熄灭;

    • 增加 Target 的分值(Target = Target * 5),提高之后的得分奖励。

    • 循环扫描 8 个目标输入(数字引脚 2–9);

    • 某个为 LOW → 标记该 Target 已命中、增加 Score += Target、点亮对应灯;

    • 若 8 个目标全部命中(Sum == 8):

  3. Rollovers 处理

    • 对应灯全闪;

    • 分值倍乘并清零标记(代码中示例为 Score = Score * 2, Roll = Roll * 10)。

    • 类似逻辑,扫描 3 个 Rollover 开关;

    • 全部命中后:

  4. Pop Bumper 处理

    • 所有 Pop 灯闪烁;

    • 分值提升(Pop = Pop * 2),形成累进奖励。

    • 标记该 Pop;

    • 增加 Score += Pop

    • 点亮对应灯;

    • 使用 analogRead(i) 读取 4 个模拟输入;

    • 若某一路超过阈值(>500),认为 Pop Bumper 被触发:

    • 当 4 个 Pop 全部被触发后:

  5. 球数与 Game Over 判定

    • 若在某次检测中读数持续高于 PressureBall == 5,则:

    • 依次闪烁 Rollovers、Pop Bumpers 与 Targets 所有灯;

    • 在 LCD 显示“Game Over!!!”。

    • 若这是本球首次检测到,Ball++,并设置 Shot 标记;

    • 超出设定最大球数(如 Ball == 6)时,清零分数与倍率参数重新开始。

    • 通过发射通道下方两个开关(下、上射出),当球通过时:

    • 通过力传感器读数判断球是否落入最终回收区:

  6. LCD 显示输出

    • 清屏 → 打印当前得分 → 换行显示 Ball = x

    • 通过 SoftwareSerial 驱动串口 LCD;

    • Score 发生变化时:

    • TxPin 引脚用于向 LCD 发送串口数据。


4 电源与配线

4.1 24V 电源系统

弹珠机中的电磁组件(弹板、Slingshot、Pop Bumper 等)需较大的瞬时电流,故使用 24V 开关电源

  • 24V 电源主要供给:

    • 弹板线圈(Flipper Assembly)

    • Slingshot 组件

    • Pop Bumper 线圈

  • 24V 输出先接至安装在台面底部的 配电铜排(Bus Bar),再由铜排为各组件分配电源,这样布线更整齐、维护方便。

对于 Pop Bumper 等高压部分,需要特别注意:

  • 若改用更高电压电源(>25V),则现成的电压分压模块可能不适用,需自行设计分压电路,确保 Arduino 端电压不超过 5V。

4.2 线径选择

根据原文经验:

  • 接 24V 高压线圈类负载:约 16 AWG 线材(粗线,承载大电流);

  • 接 Arduino I/O 与传感器信号:约 22 AWG 细线即可。


5 关键弹珠机构与配件

5.1 弹簧拉杆(Plunger)

Plunger 组件为标准件,从专业弹珠配件商处购入。安装流程:

  • 在机柜前板上钻孔,使 Plunger 穿出前面板;

  • 内部用螺丝固定;

  • 高度对齐:

    • 将直尺沿台面延伸至前内侧板;

    • 在该点处标记 Plunger 的中心位置;

    • 垂直方向上使 Plunger 中心略高于弹珠直径的一半(标准弹珠为 1 1/16 英寸)。

5.2 台面与装饰

台面使用 1/4 英寸的桦木贴面胶合板。为提升视觉效果,作者选取了一张 NASA 的太空照片作为背景:

  1. 在板面喷涂喷胶(Contact Adhesive);

  2. 将海报覆于板面,刮平气泡;

  3. 背面修剪多余边缘,使整体尺寸精确符合 22W × 42L。

5.3 Flippers(弹板)

弹板组件包括:

  • Flipper 机械总成

  • Flipper Bat(塑料挡板)

  • Flipper Switch(高压开关)

  • 侧面按钮

购买后通常没有任何接线说明,需要参考弹珠维修资料中的典型接线方式(原文链接中给出示意图)。核心要点:

  • 线圈通常包含高阻/低阻两组绕组,用于启动与保持;

  • 开关结构在按钮与线圈之间,配合机械角度限制,实现可靠回弹。

5.4 Slingshots(侧击机构)

Slingshot 组件为弹珠经过时会被“侧推”的机构:

  • 需要完整的 Slingshot Assembly、支撑销与专用橡胶圈;

  • 安装在 Flipper 上方两侧,形成典型的下方区域拱形布局;

  • 底部布线同样接至 24V 总线与 Arduino 触发信号。

作者还自己制作了 金属防护栏(Rails)

  • 使用家居店购买的镀铬钢丝(原用途为草坪标记线);

  • 按需切割、弯折成形;

  • 在台面钻孔后,用环氧胶固定。

5.5 Pop Bumpers

Pop Bumper 由底座、线圈、顶盖、支撑杆与触发轴组成:

  • 从上方看为一个白色圆盘;

  • 当球撞入圆盘时,盘面被压下,带动中心轴下移;

  • 轴底部触发叶片开关,进而驱动线圈通电;

  • 线圈带动金属环向下拉,向上反弹时将球弹回。

安装时需在台面钻三孔:

  • 两个用于支撑杆固定;

  • 一个用于中心轴通过与触发叶片开关。

5.6 目标靶(Targets)、Rollovers 与灯具

  • 目标靶:立式靶板,球撞击后触发后方微动开关;

  • Rollover Switch:嵌在台面上的小金属片或塑料结构,球滚过即触发;

  • LED Lamp:用于标记目标状态与装饰。

接线方式统一:

  • 一端接 Arduino 输入(或输出控制灯);

  • 一端接地或电源;

  • 通过 Mega 2560 的数字口进行采集与控制。


6 总结与展望

本文系统介绍了一台 Arduino 控制的自制全尺寸弹珠机 的设计实现过程。项目的特点在于:

  • 结构工程

    • 采用标准尺寸木柜结构,使用可调机脚控制倾角;

    • 台面可整体抬出翻转,便于维护与调试。

  • 电子与电源架构

    • 使用 24V 高压系统驱动线圈部件;

    • 使用电压分压将 Pop Bumper 的高压信号安全地引入 Arduino;

    • 将高压总线分发至配电铜排,提高布线整洁性。

  • 逻辑控制与玩法扩展

    • 基于 Mega 2560 的多路数字与模拟输入输出,实现完整得分、倍分、全亮闪烁、Game Over 等玩法;

    • 利用力传感器判断丢球,使游戏流程自动化;

    • LCD 显示当前得分与球号,增强交互性。

作者也指出,一旦开始搭建,你很可能会不断添加新元素(声效、更多灯效、奖励关卡等),但有了本文所述的结构与控制架构,后续扩展将更加容易。

代码:

  const int TxPin = 17;

  long Score = 0;

  long OldScore = 0;

  long Target = 1;

  long Pop = 1;

  long Roll = 10;

  int Targets[8];

  int Rolls[3];

  int Pops[4];

  int Milli = 10;

  int Sum = 0;

  int Flash = 100;

  int Ball = 0;

  int i=0;

  int Shot = 0;

  int Lost = 0;

  int Pressure = 1024;

  

#include <SoftwareSerial.h>;

SoftwareSerial mySerial = SoftwareSerial(255, TxPin);



void setup() {

  /* Words without an s are the value achieved by interacting with a device. 

   * Works with an s keep track of which individual ones were interacted with. 

   * The latter is needed to determine when all have been hit and the value needs upgrading

   * and the lights need turning off.

   */

  pinMode(TxPin, OUTPUT);

  digitalWrite(TxPin, HIGH);

  mySerial.begin(9600);

  mySerial.write(12);                 // Clear             

  mySerial.write(17);                 // Turn backlight on

  

  //target inputs

  pinMode(2,INPUT_PULLUP);

  pinMode(3,INPUT_PULLUP);

  pinMode(4,INPUT_PULLUP);

  pinMode(5,INPUT_PULLUP);

  pinMode(6,INPUT_PULLUP);

  pinMode(7,INPUT_PULLUP);

  pinMode(8,INPUT_PULLUP);

  pinMode(9,INPUT_PULLUP);

  //rollover inputs

  pinMode(10,INPUT_PULLUP);

  pinMode(11,INPUT_PULLUP);

  pinMode(12,INPUT_PULLUP);

  //lower ball shot switch

  pinMode(15,INPUT_PULLUP);

  //upper ball shot switch

  pinMode(16,INPUT_PULLUP);

  //lcd output

  pinMode(17,OUTPUT);

  //target lights, respective

  pinMode(32,OUTPUT);

  pinMode(33,OUTPUT);

  pinMode(34,OUTPUT);

  pinMode(35,OUTPUT);

  pinMode(36,OUTPUT);

  pinMode(37,OUTPUT);

  pinMode(38,OUTPUT);

  pinMode(39,OUTPUT);

  //rollover lights, respective

  pinMode(40,OUTPUT);

  pinMode(41,OUTPUT);

  pinMode(42,OUTPUT);

  //pop bumper lights

  pinMode(50,OUTPUT);

  pinMode(51,OUTPUT);

  pinMode(52,OUTPUT);

  pinMode(53,OUTPUT);

}


void loop() {

  // put your main code here, to run repeatedly:

  //If a pull-down resistor is used, the input pin will be LOW when the switch is open and HIGH when the switch is closed. 

  //check if a target was hit


//****** Targets *****


  for (int i=0; i<8; i++){

    if (digitalRead(i+2) == LOW){

      //Target activated

      Targets[i]=1;

      Score = Score + Target;

      //turn on Target light

      digitalWrite(i+32,HIGH);

      //delay so as not get multiple points for one hit

      delay(Milli);

      break;

    }

  }

  Sum = 0;  

  for (int i=0; i<8; i++){

    Sum = Sum + Targets[i];

  }

  if (Sum == 8){

    //all Targets lit, so flash and then turn off.

    for (int j=0; j<3; j++){

      for (int i=0; i<8; i++){

        digitalWrite(i+32, LOW);

      }

      delay(Flash);

      for (int i=0; i<8; i++){

        digitalWrite(i+32, HIGH);

      }

      delay(Flash);

    }

    for (int i=0; i<8; i++){

      digitalWrite(i+32, LOW);

      Targets[i]=0;

    } 

    delay(Flash);   

    //Multiply target value by 10

    Target = Target * 5;

    //goto Skip;  

  }

  


// ***********  Rollovers *********


  

   for (int i=0; i<3; i++){

    if (digitalRead(i+10) == LOW){

      //rollover activated

      Rolls[i]=1;

      Score = Score + Roll;

      //turn on rollover light

      digitalWrite(i+40,HIGH);

      //delay so as not get multiple points for one hit

      delay(Milli);

      break;

    }

  }

  Sum = 0;  

  for (int i=0; i<3; i++){

    Sum = Sum + Rolls[i];

  }

  if (Sum == 3){

    //all rollovers lit, so flash and then turn off.

    for (int j=0; j<3; j++){

      for (int i=0; i<3; i++){

        digitalWrite(i+40, LOW);

      }

      delay(Flash);

      for (int i=0; i<3; i++){

        digitalWrite(i+40, HIGH);

      }

      delay(Flash);

    }

    for (int i=0; i<3; i++){

      digitalWrite(i+40, LOW);

      Rolls[i]=0;

    } 

    delay(Flash);   

    //Multiply score by 2

    Score = Score * 2;

    Roll = Roll * 10;

    //goto Skip;  

  }

  

  //**********  Pop Bumpers **********

  

   for (int i=0; i<4; i++){

    if (analogRead(i) > 500){

      //pop activated

      Pops[i]=1;

      Score = Score + Pop;

      //turn on pop bumper light

      digitalWrite(i+50,HIGH);

      //delay so as not get multiple points for one hit

      //mySerial.print(analogRead(i));

      //mySerial.print(" ");

      delay(Milli);

      break;

    }

  }

  Sum = 0;  

  for (int i=0; i<4; i++){

    Sum = Sum + Pops[i];

  }

  if (Sum == 4){

    //all pop bumpers lit, so flash and then turn off.

    for (int j=0; j<3; j++){

      for (int i=0; i<4; i++){

        digitalWrite(i+50, LOW);

      }

      delay(Flash);

      for (int i=0; i<4; i++){

        digitalWrite(i+50, HIGH);

      }

      delay(Flash);

    }

    for (int i=0; i<4; i++){

      digitalWrite(i+50, LOW);

      Pops[i]=0;

    } 

    delay(Flash);   

    //Multiply target value by 10

    Pop = Pop * 2;

    //goto Skip;  

  }

Skip:

  

  //Determine ball number

  if (digitalRead(15) == LOW){

    //ball hit lower alley switch

    //if not already done so, increase Ball 

    if (Shot == 0){

      //Set Lost = 0 since not on pressure pad

      Lost = 0;

      Pressure = analogRead(7) + 20;

      //set OldScore so as to reprint ball value on LCD

      OldScore =-1;

      Ball = Ball + 1;

      if (Ball == 6){

        Ball = 1;

        Score = 0;

        Target = 1;

        Roll = 1;

        Pop = 1;

      }

      Shot = 1;

    }

  }

  if (digitalRead(16) == LOW){

    //ball hit lower alley switch

    //if not already done so, increase Ball

    if (Shot == 0){

      //Set Lost = 0 since not on pressure pad

      Lost = 0;

      Pressure = analogRead(7) + 15;  

      //set OldScore so as to reprint ball value on LCD

      OldScore =-1;

      Ball = Ball + 1;

      if (Ball == 6){

        Ball = 1;

        Score = 0;

        Target = 1;

        Roll = 1;

        Pop = 1;

      }

      Shot = 1;

    }

  }


  if (analogRead(7) > Pressure){

    //ball on pressure pad

    Shot = 0;

    if (Lost == 0){

      //mySerial.print(analogRead(7));

      //Score = Score + 100;

      Lost = 1;

      if (Ball == 5){

        //Game Over

        //flash rollovers and then turn off.

        for (int j=0; j<3; j++){

          for (int i=0; i<3; i++){

            digitalWrite(i+40, LOW);

          }

          delay(Flash);

          for (int i=0; i<3; i++){

            digitalWrite(i+40, HIGH);

          }

          delay(Flash);

        }

        for (int i=0; i<3; i++){

          digitalWrite(i+40, LOW);

          Rolls[i]=0;

        } 

        // flash pop bumpers and then turn off

        for (int j=0; j<3; j++){

          for (int i=0; i<4; i++){

            digitalWrite(i+50, LOW);

          }

          delay(Flash);

          for (int i=0; i<4; i++){

            digitalWrite(i+50, HIGH);

          }

          delay(Flash);

        }

        for (int i=0; i<4; i++){

          digitalWrite(i+50, LOW);

          Pops[i]=0;

        } 

        //Flash Targets and then turn off.

        for (int j=0; j<3; j++){

          for (int i=0; i<8; i++){

            digitalWrite(i+32, LOW);

          }

          delay(Flash);

          for (int i=0; i<8; i++){

            digitalWrite(i+32, HIGH);

          }

          delay(Flash);

        }

        for (int i=0; i<8; i++){

          digitalWrite(i+32, LOW);

          Targets[i]=0;

        } 

        mySerial.write(12);                 // Clear

        delay(5);

        // Required delay

        mySerial.print(Score);  // First line

        mySerial.write(13);                 // Form feed

        mySerial.print("Game Over!!!");   // Second line

      }

    }

  }

  //print to LCD

  if (Score != OldScore){ 

  mySerial.write(12);                 // Clear

  delay(5);                           // Required delay

  //mySerial.print(analogRead(7));

  mySerial.print(Score);  // First line

  mySerial.write(13);                 // Form feed

  mySerial.print("Ball = ");   // Second line

  mySerial.print(Ball);

  OldScore = Score;

  }

}



关键词:

评论


相关推荐

技术专区

关闭