基于Arduino的双电子骰子:从LED阵列到随机数生成的嵌入式实践 1. 项目概述从传统骰子到电子骰子的创客实践桌上游戏、概率实验甚至是一些决策小工具都离不开骰子这个古老而经典的元素。作为一个玩了十几年电子和嵌入式开发的爱好者我始终觉得把物理世界的随机性用代码和灯光“翻译”出来是一件特别有魅力的事情。今天要分享的这个“基于Arduino的双电子骰子”项目就是一个绝佳的入门实践。它不仅仅是一个简单的LED闪烁玩具而是一个融合了嵌入式系统基础、互动电子设计以及创客精神的完整案例。这个项目的核心目标是用最基础的电子元件——Arduino UNO、几颗LED和一个按钮——来模拟两个传统六面骰子的投掷过程。与市面上常见的单电子骰子或依赖液晶屏显示数字的方案不同我们直接用两组LED阵列来“画”出骰子的点数图案。当你按下按钮两组LED会像真正的骰子一样“翻滚”闪烁最终定格在两个1到6之间的随机点数上。这种视觉反馈直接、生动充满了游戏的仪式感远比看一串数字来得有趣。为什么选择这个项目作为入门或练手首先它覆盖了创客项目的完整闭环从电路设计在Tinkercad上仿真、元件选型、焊接或面包板搭建到Arduino编程涉及随机数生成、数字I/O控制、状态机逻辑最后得到一个可以实际把玩、用于真实游戏的作品。其次它所需的元件成本极低几乎每个玩电子的人手边都有但实现的效果却足够“炫酷”能给你带来实实在在的成就感。无论你是刚接触Arduino的学生还是想找个周末小项目放松的工程师这个双电子骰子都能让你在动手的过程中巩固数字I/O、随机数算法、去抖动处理等核心概念并直观地看到代码如何驱动物理世界。2. 核心设计思路与方案解析2.1 为何选择LED阵列而非LCD或单点显示在规划一个电子骰子时显示方案是第一个需要决策的点。常见的有三种七段数码管、液晶显示屏LCD和LED阵列。七段数码管只能显示数字失去了骰子的图案感LCD虽然能显示图案甚至动画但成本较高接线复杂通常需要I2C或SPI且显示效果偏“电子化”缺乏传统骰子的物理质感。而采用LED阵列模拟骰子面则完美地兼顾了成本、趣味性和教学价值。一个标准的六面骰子其点数排列是固定的比如一点在中心两点在对角等。我们用7个LED排列成中心一点加周围六点的近似圆形或方形就能模拟出1到6的所有图案。这种方案直观还原LED亮起的图案与传统骰子完全一致视觉识别零成本。硬件简单只需要普通的发光二极管和限流电阻无需驱动芯片因为Arduino的I/O口足以驱动低电流的LED。编程清晰每个骰子图案对应一个固定的LED亮灭组合在代码中可以用数组预定义逻辑非常清晰。扩展性强很容易扩展到双骰子如本项目、甚至多个骰子只需复制LED组和对应的代码逻辑。因此选择两组7颗LED一组我用绿色另一组用红色便于区分来构建双骰子是一个在趣味性、复杂度和学习价值上取得最佳平衡的方案。2.2 系统工作流程与状态机设计整个双电子骰子系统的工作流程可以抽象为一个简单的状态机。理解这个状态机是写好程序的关键。等待状态系统上电后进入等待状态。两个骰子的LED显示为初始状态比如全部熄灭或显示一个默认图案。程序持续检测按钮是否被按下。投掷动画状态当检测到按钮被有效按下经过去抖动处理后系统进入“投掷”状态。此时两个骰子的LED开始快速、随机地切换显示不同的点数图案模拟骰子在桌上旋转、翻滚的视觉效果。这个阶段会持续一个随机或固定的时间例如0.5秒到2秒以增加不确定性。结果锁定状态投掷动画结束后系统为每个骰子生成一个1到6之间的最终随机数。然后根据这个数字点亮对应的LED图案并保持显示直到下一次按钮按下。这里的一个关键细节是按钮去抖动。机械按钮在按下和弹起的瞬间会因为触点物理振动产生一系列快速的通断信号如果不处理一次按压可能会被误判为多次。因此在代码中我们必须加入去抖动逻辑通常的做法是在检测到引脚电平变化后延迟10-50毫秒再次检测以确认状态稳定。2.3 元件选型与电路设计考量原始材料清单已经给出了核心元件。这里我深入解释一下选型原因和电路设计要点Arduino UNO R3这是最经典、资源最丰富的入门级开发板。它有14个数字I/O口和6个模拟输入口足以驱动本项目所需的14个LED和1个按钮且留有充足余量。其5V输出电压和40mA的单I/O口驱动能力也完全能满足LED的驱动需求。LED7绿7红选择红绿两种颜色纯粹是为了视觉上清晰区分两个骰子。你也可以用同一种颜色。注意选择普通的3mm或5mm草帽LED即可其正向压降约为2V工作电流在5-20mA。220Ω电阻4个这是LED的限流电阻。计算很简单Arduino I/O口输出高电平时电压为5VLED压降约2V那么电阻需要分担3V的电压。为了让LED亮度适中且安全我们设定电流为10mA0.01A。根据欧姆定律 R V / I 3V / 0.01A 300Ω。选择220Ω是一个接近且非常常见的标准值能提供约13.6mA的电流LED亮度足够且绝对安全。注意这里4个电阻的接法很关键。原始原理图可能是将每组骰子的7个LED的阴极负极分别接到7个I/O口而阳极正极通过一个公共的220Ω电阻接到5V。这是一种“共阳极”接法。另一种更常见的接法是“共阴极”所有LED阴极接地阳极通过独立的220Ω电阻接到各个I/O口。两种都可以但代码逻辑点亮LED时输出高电平还是低电平会相反。本项目分析将采用更常见的“共阴极”接法。10kΩ电阻1个这是按钮的上拉电阻。当按钮未按下时这个电阻将信号引脚稳定地“拉”至高电平5V。当按钮按下时引脚直接接地变为低电平。使用内部上拉电阻在代码中设置pinMode(pin, INPUT_PULLUP)可以省略这个外部电阻但外部上拉是更传统和明确的做法。按钮选择最普通的四脚常开触点的轻触开关即可。面包板和跳线用于快速原型搭建。注意关于“共阴”与“共阳”接法务必在搭建电路前确定你的接法并据此编写代码。如果接法是共阴极LED负极共地则digitalWrite(pin, HIGH)点亮LED如果是共阳极LED正极共接5V则需要digitalWrite(pin, LOW)来点亮。弄反了LED不会亮。建议新手统一使用共阴极接法逻辑更直观。3. 硬件搭建与电路连接详解3.1 电路原理图解读与实物连接根据“共阴极”接法的设计我们来详细拆解连接步骤。请对照你的面包板和元件进行操作。双骰子LED阵列布局 每个骰子需要7个LED。为了最直观地模拟骰子点数我们按以下方式排列俯视图数字代表Arduino引脚编号用于控制该LED骰子1 (绿色LED) LED1 (引脚2) LED2 (引脚3) LED3 (引脚4) LED4 (引脚5) LED5 (引脚6) LED6 (引脚7) LED7 (引脚8) 这是一个近似正方形的布局中心是LED1上下左右各两个LED 骰子2 (红色LED) LED8 (引脚9) LED9 (引脚10) LED10 (引脚11) LED11 (引脚12) LED12 (引脚13) LED13 (引脚A0) LED14 (引脚A1)连接步骤电源与地在面包板上建立公共的5V和GND总线。将Arduino的5V引脚和GND引脚分别连接到这两条总线。连接LED共阴极将所有14个LED的短脚阴极负极分别通过一根跳线连接到面包板的GND总线上。每个LED的长脚阳极正极不要直接接Arduino先接一个220Ω电阻的一端。每个220Ω电阻的另一端用跳线连接到对应的Arduino数字引脚如上图规划所示骰子1的LED1-7接引脚2-8骰子2的LED8-14接引脚9, 10, 11, 12, 13, A0, A1。注意A0-A1作为数字引脚使用。连接按钮按钮有四个引脚通常两两内部连通。找到一组对角的引脚。将其中一个引脚用跳线连接到面包板的GND总线。将另一组对角的其中一个引脚连接一个10kΩ电阻的一端该电阻的另一端连接到5V总线这就是上拉电阻。从按钮与10kΩ电阻相连的那个引脚引出一根跳线连接到Arduino的某个数字引脚例如引脚A2。这样未按下时该引脚被上拉到5V高电平按下时接地变为0V低电平。3.2 搭建过程中的避坑指南LED极性务必分清长脚为正阳极短脚为负阴极。接反了不会亮也不会损坏但排查起来麻烦。可以在搭建前用万用表二极管档或电池简单测试一下。电阻不可或缺绝对不能将LED直接接到Arduino引脚和地之间没有限流电阻过大的电流可能损坏LED或Arduino的I/O口。面包板布局要清晰建议将两个骰子的LED在面包板上分区域摆放比如左边一组绿右边一组红中间放置按钮和Arduino。清晰的布局有助于后续检查和调试。上拉电阻的作用如果你发现不按按钮时读取的引脚状态不稳定随机触发那很可能是因为上拉电阻没接好或接触不良。确保10kΩ电阻一端接5V一端接按钮引脚和信号线。实操心得关于原始提示的“上传代码时断开GND线”原始材料中提到上传代码时要断开GND线这通常是为了避免因电路中有其他电源或大电流负载导致USB端口冲突或Arduino IDE检测不到端口。但在我们这个纯由USB供电的简单电路中通常不需要。如果你在上传时遇到“avrdude: stk500_getsync()”之类的错误可以尝试1) 关闭所有可能占用串口的软件2) 在IDE中正确选择板和端口3) 按下Arduino板上的复位按钮并在其复位完成前立即点击上传。断开GND是一个比较“硬核”的解决方法可以作为一种备选。4. 软件设计与代码实现深度解析硬件搭建完毕接下来是赋予它灵魂的代码部分。我们将编写一个结构清晰、易于理解和扩展的Arduino程序。4.1 核心代码结构与变量定义首先我们需要定义引脚映射、骰子图案数据和一些状态变量。// 引脚定义 // 骰子1 (绿色LED) 引脚对应LED1-LED7 const int dice1Pins[7] {2, 3, 4, 5, 6, 7, 8}; // 骰子2 (红色LED) 引脚对应LED8-LED14 const int dice2Pins[7] {9, 10, 11, 12, 13, A0, A1}; // 按钮引脚 const int buttonPin A2; // 骰子点数图案定义 // 数组的每个元素代表一个点数1-6其内容是一个7位的二进制数或布尔数组 // 每一位对应一个LED顺序与diceXPins数组一致1表示点亮0表示熄灭。 // 这里我们定义一个二维数组dicePatterns[点数][LED索引] const byte dicePatterns[6][7] { {0, 0, 0, 1, 0, 0, 0}, // 点数1: 只有中心LED亮 (索引3即第4个LED) {1, 0, 0, 0, 0, 0, 1}, // 点数2: 左上和右下亮 (索引0和6) {1, 0, 0, 1, 0, 0, 1}, // 点数3: 点数1 点数2 {1, 0, 1, 0, 1, 0, 1}, // 点数4: 四个角亮 (索引0,2,4,6) {1, 0, 1, 1, 1, 0, 1}, // 点数5: 点数4 中心点 {1, 1, 1, 0, 1, 1, 1} // 点数6: 上下两排亮 (索引0,1,2,4,5,6)中心不亮 // 注意这个图案排列取决于你实际LED的物理布局可能需要调整。 }; // 状态变量 int dice1Value 1; // 骰子1当前显示的点数 int dice2Value 1; // 骰子2当前显示的点数 bool rolling false; // 是否正在“投掷”动画中 unsigned long rollStartTime 0; // 投掷动画开始的时间 const unsigned long rollDuration 1000; // 投掷动画持续时长毫秒 // 按钮去抖动相关变量 int buttonState; int lastButtonState HIGH; // 假设初始为上拉状态高电平 unsigned long lastDebounceTime 0; const unsigned long debounceDelay 50; // 去抖动延时毫秒4.2 关键函数显示骰子点数与投掷动画我们需要两个核心函数一个用于在LED上显示特定点数的图案另一个用于生成投掷时的随机闪烁动画。// 函数显示一个骰子的点数 void showDice(int diceNumber, int value) { // 参数检查 if (value 1 || value 6) return; const int* ledPins; if (diceNumber 1) { ledPins dice1Pins; } else if (diceNumber 2) { ledPins dice2Pins; } else { return; } // 根据预定义的图案设置每个LED的状态 for (int i 0; i 7; i) { digitalWrite(ledPins[i], dicePatterns[value - 1][i]); // value-1 因为数组索引从0开始 } } // 函数执行投掷动画 void rollAnimation() { // 为两个骰子生成一个1-6的随机数并显示 int random1 random(1, 7); // random(min, max) 生成 min 到 max-1 的随机数 int random2 random(1, 7); showDice(1, random1); showDice(2, random2); }4.3 主循环逻辑与状态机实现最后在setup()和loop()中实现整个状态机。void setup() { // 初始化所有LED引脚为输出模式 for (int i 0; i 7; i) { pinMode(dice1Pins[i], OUTPUT); pinMode(dice2Pins[i], OUTPUT); } // 初始化按钮引脚为输入模式并启用内部上拉电阻如果使用外部上拉则用INPUT pinMode(buttonPin, INPUT_PULLUP); // 初始化随机数种子。用模拟引脚A5的“悬空”噪声作为种子使每次启动的随机序列不同。 randomSeed(analogRead(A5)); // 初始显示点数1 showDice(1, 1); showDice(2, 1); Serial.begin(9600); // 可选用于调试 } void loop() { // 1. 读取按钮状态并进行去抖动处理 int reading digitalRead(buttonPin); if (reading ! lastButtonState) { // 状态发生变化重置去抖动计时器 lastDebounceTime millis(); } if ((millis() - lastDebounceTime) debounceDelay) { // 经过去抖动延时后状态稳定 if (reading ! buttonState) { buttonState reading; // 检测按钮是否被按下从高电平变为低电平因为使用了上拉 if (buttonState LOW) { // 按钮被按下开始投掷动画 rolling true; rollStartTime millis(); Serial.println(Rolling started!); } } } lastButtonState reading; // 2. 状态机核心 if (rolling) { // 投掷动画状态 if (millis() - rollStartTime rollDuration) { // 动画期间快速随机显示点数模拟翻滚 rollAnimation(); delay(80); // 控制翻滚速度可根据感觉调整 } else { // 动画结束生成最终结果 rolling false; dice1Value random(1, 7); dice2Value random(1, 7); showDice(1, dice1Value); showDice(2, dice2Value); Serial.print(Final: ); Serial.print(dice1Value); Serial.print(, ); Serial.println(dice2Value); } } // 如果不在投掷状态则保持显示当前点数loop()会持续检测按钮 }这段代码实现了一个完整的、带防抖按钮检测和投掷动画的双电子骰子。randomSeed(analogRead(A5))这行代码很重要它利用未连接的模拟引脚的随机噪声来初始化随机数发生器确保每次上电后的随机序列都不同避免了每次投掷结果都一样的尴尬情况。5. 功能优化与扩展思路基础功能实现后我们可以让这个骰子变得更智能、更好玩。这里分享几个我实践过的优化方向。5.1 提升随机性与视觉效果更真实的随机性除了用analogRead悬空引脚还可以结合millis()甚至读取一些环境传感器如麦克风的微小波动来增强随机性。动画效果升级加速-减速动画投掷开始时LED切换频率快接近结束时变慢最后定格模拟骰子动能衰减的过程。逐点熄灭再点亮在显示最终结果前让所有LED快速闪烁几次增加悬念。声音反馈增加一个无源蜂鸣器在按钮按下和结果揭晓时发出不同的音效体验更沉浸。骰子点数记忆增加一个功能长按按钮可以切换显示模式比如轮流显示上一次投掷的两个点数之和。5.2 硬件扩展与外壳制作电源独立化使用一块9V电池和稳压模块如LM7805为Arduino供电摆脱USB线的束缚做成一个真正的便携设备。定制PCB如果你希望作品更稳固、更专业可以用Eagle或KiCad设计一块PCB将Arduino Nano更小巧、LED、电阻、按钮全部集成在一块板子上。3D打印外壳用Tinkercad、Fusion 360等软件设计一个骰子形状或游戏主题的外壳将电路板、电池封装进去留出LED显示窗和按钮孔。这是将电子项目转化为成熟产品的关键一步成就感爆棚。无线化加入蓝牙模块如HC-05或Wi-Fi模块如ESP8266让手机App可以遥控投掷骰子或者实现多人游戏的同步显示。5.3 代码结构优化建议对于想深入学习的爱好者可以尝试用更结构化的方式重写代码面向对象定义一个Dice类封装引脚数组、当前点数、显示方法等。这样主程序会非常简洁Dice dice1(...), dice2(...);。状态机库使用FiniteStateMachine等库来管理“等待”、“投掷”、“显示”等状态使逻辑更清晰。非阻塞式定时将delay(80)替换为基于millis()的非阻塞定时让系统在投掷动画期间也能灵敏响应其他输入例如双按钮。6. 常见问题排查与调试心得即使按照步骤操作也可能会遇到问题。这里汇总了一些我踩过的坑和解决方法。6.1 LED相关问题问题部分或全部LED不亮。检查1极性确认所有LED方向正确长脚接电阻/信号短脚接地。检查2电阻确认每个LED都串联了220Ω电阻电阻焊接或插接牢固。检查3引脚映射核对代码中dice1Pins和dice2Pins数组的定义是否与实际接线完全一致。检查4共阴/共阳确认代码中的digitalWrite电平逻辑与你的硬件接法匹配。用万用表测量引脚输出或写个简单测试程序让单个引脚输出高/低电平看LED反应。问题LED亮度不一致或太暗。原因不同颜色的LED正向压降有差异红/黄约1.8-2.0V绿/蓝/白约3.0-3.4V。使用相同的限流电阻压降高的LED得到的实际电流小就会更暗。解决为不同颜色的LED分别计算并匹配限流电阻。例如绿色LED用220Ω红色LED可以尝试用330Ω或470Ω以达到相近亮度。或者使用PWM引脚并调整亮度值。6.2 按钮与逻辑问题问题按钮不灵敏有时按了没反应。检查1去抖动参数尝试增大debounceDelay值如从50ms调到100ms。机械按钮的抖动时间可能较长。检查2上拉电阻确保外部上拉电阻10kΩ连接正确且接触良好或者代码中使用了INPUT_PULLUP模式。检查3接线确认按钮的引脚连接正确一端接地信号端接上拉电阻和Arduino引脚。问题投掷动画结束后两个骰子点数总是相同或很有规律。原因random()函数在未正确播种时每次程序运行的随机序列是固定的。解决确保在setup()中调用了randomSeed(analogRead(A5))并且A5引脚悬空不接任何东西。也可以尝试读取其他悬空模拟口的噪声。问题动画卡顿或者按钮响应在动画期间失灵。原因使用了delay()函数它会阻塞整个程序。解决将动画循环改造成基于millis()计时的非阻塞模式。这是Arduino编程进阶的必修课。6.3 调试技巧串口打印大法在代码关键位置如按钮状态变化、生成随机数时加入Serial.print()语句通过串口监视器观察程序实际运行逻辑这是最有效的调试手段。分模块测试不要一次性写完所有代码。先写一个让单个骰子所有LED依次点亮的测试程序确保硬件连接无误。再写按钮检测程序确保能可靠读取。最后整合动画和主逻辑。简化排查如果整个系统不工作尝试拔掉一部分LED先让最小系统一个骰子按钮跑起来再逐步添加。这个双电子骰子项目从构思到实现再到优化和排错完整地走完了一个嵌入式小产品的开发流程。它麻雀虽小五脏俱全。我最深的体会是硬件项目和纯软件最大的不同在于你需要同时和物理世界的“不确定性”接触不良、元件公差、噪声干扰做斗争。每一次成功点亮每一次稳定的投掷都是对耐心和细心的一次奖赏。当你拿着自己做的这个骰子和朋友玩一局飞行棋时那种感觉是单纯买一个成品无法比拟的。希望这个详细的分享能帮你少走弯路更快地享受到创造和学习的乐趣。如果在此基础上做出了更酷的变种比如加上蜂鸣器、做成钥匙扣或者接上了网络那便是属于你自己的独一无二的创作了。