基于 Arduino Mega 2560 的全尺寸電動彈珠機設計與實現

摘要
本文介紹了一臺以 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 長
設計要點:
結構連接方式
板材采用斜接(miter)切割,使用餅干榫(biscuit joiner)與木膠拼接;
也可用暗扣螺絲 + 木膠組合,保證強度——彈珠機在游戲中會承受頻繁沖擊。
底部擱板與電源布置
不與側板膠合,以適應木材膨脹收縮;
用于放置主電源、LED 燈電源等模塊。
在底部上方 1 英寸位置開 ? 英寸寬的榫槽(dado),做一個“浮動”擱板:
臺面與前部空腔
前方預留約 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 控制,主要承擔四大任務:
采集開關與傳感器狀態
目標靶(Target Switch)
滾輪開關(Rollover Switch)
Pop Bumper 開關(通過電壓分壓)
彈球發射道開關
壓力傳感器(用于監測丟球)
控制燈光與效果
目標靶對應燈
滾輪燈
Pop Bumper 燈
Game Over 時全場閃爍效果
得分與規則管理
各類元件命中得分;
所有同類元件被依次命中后,觸發燈光全閃與分值提升(倍乘);
記錄當前球數、是否 Game Over 等。
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 控制代碼,核心思想如下:
變量設計
Score:當前得分Target,Pop,Roll:三類目標的基礎得分值Targets[8],Rolls[3],Pops[4]:記錄每個目標是否被命中Ball:當前球號(1–5)Flash:燈光閃爍延時Pressure:力傳感器觸發閾值目標靶(Targets)處理流程
所有目標燈閃爍若干次;
燈全部熄滅;
增加 Target 的分值(
Target = Target * 5),提高之后的得分獎勵。循環掃描 8 個目標輸入(數字引腳 2–9);
某個為 LOW → 標記該 Target 已命中、增加
Score += Target、點亮對應燈;若 8 個目標全部命中(
Sum == 8):Rollovers 處理
對應燈全閃;
分值倍乘并清零標記(代碼中示例為
Score = Score * 2,Roll = Roll * 10)。類似邏輯,掃描 3 個 Rollover 開關;
全部命中后:
Pop Bumper 處理
所有 Pop 燈閃爍;
分值提升(
Pop = Pop * 2),形成累進獎勵。標記該 Pop;
增加
Score += Pop;點亮對應燈;
使用
analogRead(i)讀取 4 個模擬輸入;若某一路超過閾值(>500),認為 Pop Bumper 被觸發:
當 4 個 Pop 全部被觸發后:
球數與 Game Over 判定
若在某次檢測中讀數持續高于
Pressure且Ball == 5,則:依次閃爍 Rollovers、Pop Bumpers 與 Targets 所有燈;
在 LCD 顯示“Game Over!!!”。
若這是本球首次檢測到,
Ball++,并設置Shot標記;超出設定最大球數(如
Ball == 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 的太空照片作為背景:
在板面噴涂噴膠(Contact Adhesive);
將海報覆于板面,刮平氣泡;
背面修剪多余邊緣,使整體尺寸精確符合 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;
}
}












評論