基于 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 项目概述

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

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


2 机械结构设计

2.1 机柜结构

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

设计要点:

  1. 结构连接方式

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

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

  2. 底部搁板与电源布置

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

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

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

  3. 台面与前部空腔

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

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

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

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

2.2 倾角与机脚设计

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

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

调节方法:

2.3 上盖与透明面板

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

机柜背面还固定一条 电源插排,所有电源统一插入,便于通过一个总开关控制整机上电;同时预留一条 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);

接线方式:

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

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

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

解决方案:

原因:

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

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

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 开关电源

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

4.2 线径选择

根据原文经验:


5 关键弹珠机构与配件

5.1 弹簧拉杆(Plunger)

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

5.2 台面与装饰

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

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

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

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

5.3 Flippers(弹板)

弹板组件包括:

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

5.4 Slingshots(侧击机构)

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

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

5.5 Pop Bumpers

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

安装时需在台面钻三孔:

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

接线方式统一:


6 总结与展望

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

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

代码:

  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;

  }

}


关键词:

加入微信
获取电子行业最新资讯
搜索微信公众号:EEPW

或用微信扫描左侧二维码

相关文章

查看电脑版