
1. 项目概述与设计思路做电子制作的朋友对Arduino肯定不陌生。它就像电子世界的“乐高”让我们这些非科班出身的爱好者也能轻松把想法变成会动的实物。今天分享的这个项目——智能LED骰子就是一个典型的“用代码赋予硬件灵魂”的例子。它脱胎于一个经典入门项目用7个LED模拟骰子的六个面。但经典项目往往功能单一就是随机亮起1到6个点。我在实际玩桌游时发现两个痛点一是桌面空间小真骰子容易滚飞二是有些游戏规则需要连续投掷两次并记录结果手动操作麻烦还容易记错。于是我就琢磨着在经典电路上“动点小手术”加两个按钮改几行代码让它变成一个能智能响应不同游戏规则的小工具。按第一个按钮它像普通骰子一样“掷”一次按第二个按钮它能自动“掷”两次并且用灯光清晰地区分两次的结果。这个改进虽然不大但非常实用它把Arduino从简单的“闪烁LED”练习提升到了一个解决真实场景问题的“智能交互设备”。无论你是刚接触Arduino想找个有点挑战性的项目练手还是桌游爱好者想给自己的游戏之夜加点科技感这个项目都能给你带来从硬件连接到逻辑编程的完整体验。2. 核心硬件解析与选型考量2.1 主控与显示单元为什么是Arduino和7颗LED项目核心是一块Arduino开发板我选用的是最常见的Arduino Uno。选它的理由很直接资源足够、生态庞大、价格亲民。它内置的ATmega328P微控制器有14个数字I/O口和6个模拟输入口驱动我们这个项目绰绰有余。更重要的是Arduino IDE开发环境简单易用库函数丰富社区支持强大遇到问题几乎都能找到答案这对初学者和快速原型开发至关重要。显示部分用了7颗普通的5mm直径LED。为什么是7颗这是为了完美模拟骰子从1点到6点的所有图案。标准的骰子点数布局中心一个点周围六个点总共七种可能的位置。我们用7颗LED分别对应这七个位置通过控制不同LED的组合点亮就能显示出1到6的数字。这里LED的颜色可以自选我个人推荐使用白光或暖黄光在环境光下辨识度比较高。不建议用红光因为在某些光照条件下红光LED的亮度可能不足。2.2 关键外围电路限流电阻与上拉电阻的作用这是硬件部分最容易出错但也最能体现电子基础的地方。电路里用了两种电阻100Ω和10kΩ它们的作用截然不同。首先每个LED都串联了一个100Ω的限流电阻。这是必须的Arduino的数字引脚输出高电平时电压是5V而一颗典型的LED工作电压约2V不同颜色有差异工作电流在20mA左右。如果不加电阻直接连接过大的电流会瞬间烧毁LED甚至可能损坏Arduino的引脚。这个100Ω的电阻就是起限流作用。根据欧姆定律简单计算电阻需要分担的电压是5V - 2V 3V期望电流为20mA0.02A那么电阻值 R V / I 3V / 0.02A 150Ω。选择100Ω是一个常见且保守的值它能将电流限制在安全范围内约30mA同时保证LED有足够的亮度。你也可以用150Ω或220Ω亮度会略有降低但更安全。其次每个按钮连接了一个10kΩ的上拉电阻。这是为了确保按钮未按下时连接到Arduino引脚的信号是一个明确的高电平5V。Arduino的输入引脚在悬空即什么都不接时电平状态是不确定的极易受到周围电磁干扰导致误触发。我们通过一个10kΩ电阻将引脚连接到5VVCC这样平时引脚就是高电平。当按钮按下时引脚通过按钮直接连接到GND地电平被拉低至0V。Arduino程序通过检测引脚从高电平到低电平的变化来判定按钮被按下。这个10kΩ的阻值是个经验值它足够大使得按钮按下时不会从5V电源抽取过大电流又足够小能稳定地将引脚电位拉高。注意务必区分这两种电阻的接法。限流电阻100Ω是串联在LED和Arduino输出引脚之间的。上拉电阻10kΩ是一端接VCC5V另一端接按钮和Arduino输入引脚的连接点。2.3 交互输入按钮的选型与防抖考量项目用了两个常开型轻触按钮。选择这种按钮是因为它便宜、常见、手感清晰。在连接时我们采用了“上拉电阻引脚接地”的接法如上所述。这里必须提一个软件上至关重要的概念按键消抖。机械按钮在按下和弹起的瞬间内部的金属触点会发生物理震颤导致在几毫秒到几十毫秒内电信号会在高电平和低电平之间快速抖动多次。如果程序直接检测电平变化一次按压可能会被误判为多次按压。因此在代码中我们必须加入消抖逻辑。常见的做法有两种一是检测到电平变化后延迟几十毫秒再读取一次状态确认二是使用中断和状态机进行更稳定的检测。在我们的代码实现部分会详细讲解如何用简单可靠的方法实现消抖。3. 电路搭建与焊接实操要点3.1 面包板原型搭建步骤在动手焊接之前强烈建议先在面包板上搭建原型验证所有功能。这能避免焊接错误导致的反复拆焊保护元器件。布局规划先将Arduino Uno和面包板并排固定。在面包板上规划出LED阵列区域和按钮区域。尽量让走线整齐便于后续检查和故障排查。插入LED将7颗LED插入面包板。务必注意LED的极性LED的长脚是阳极正极短脚是阴极负极。通常阳极接信号阴极接GND。你可以让所有LED的阴极短脚对齐在同一行方便统一连接到GND总线。连接限流电阻用100Ω电阻连接每个LED的阳极长脚所在的行。电阻的另一端准备用杜邦线连接到Arduino的数字引脚。连接按钮与上拉电阻将两个按钮跨接在面包板的中缝两侧。对于每个按钮其一侧的一个引脚用导线连接到GND。另一侧的两个引脚是连通的任选一个连接一根导线准备接到Arduino的输入引脚同时在这个连接点上焊接或插接一个10kΩ电阻电阻的另一端连接到面包板的VCC5V总线。Arduino连线LED引脚分配将7个100Ω电阻的自由端分别用杜邦线连接到Arduino Uno的数字引脚2至8。建议按LED的物理位置顺序连接并在代码中定义好对应的数组方便管理。按钮引脚分配将两个按钮的信号线接10kΩ电阻和Arduino的那根线分别连接到数字引脚9和10。供电用杜邦线将面包板的VCC和GND总线分别连接到Arduino的5V和GND引脚。3.2 从面包板到成品焊接与封装建议原型测试成功后可以考虑制作一个更稳固的成品。PCB或万用板选择如果追求美观和稳固可以设计一块简单的PCB。对于爱好者使用洞洞板万用板是更快捷的选择。建议使用带焊盘的万用板布线会更方便。焊接顺序通常按“从低到高”的顺序焊接。先焊接电阻、按钮底座、排针用于连接Arduino最后焊接LED。焊接LED时动作要快避免过热损坏。可以在LED引脚上套一小段热缩管防止相邻引脚短路。电源考虑成品可以继续通过USB线供电也可以用一个9V电池配合电池扣连接到Arduino的Vin引脚和GND实现移动使用。注意如果使用电池需要考虑功耗。我们的LED全亮时电流较大建议在不需要时让Arduino进入休眠模式这个属于进阶优化。外壳设计可以用3D打印一个外壳或者找一个合适尺寸的塑料盒。在外壳上为LED开孔时可以考虑加装乳白色的亚克力扩散板让光点看起来更柔和、更像真正的骰子点。按钮的开孔要大小合适确保手感。实操心得在万用板上布线时不妨先用铅笔在板子背面铜箔面画一下连接草图。对于电源VCC和地GND尽量使用更粗的导线或者用焊锡直接“走线”以降低电阻保证供电稳定。所有连接完成后务必用万用表的“通断档”仔细检查确保没有短路特别是VCC和GND之间和虚焊。4. 核心代码逻辑与编程实现4.1 引脚定义与骰子点阵映射代码的第一步是做好规划。我们需要清晰地定义哪个引脚控制哪颗LED以及1到6每个数字对应哪些LED点亮。// 定义LED连接的引脚 const int ledPins[7] {2, 3, 4, 5, 6, 7, 8}; // 对应7颗LED // 为了方便我们按位置给LED编号假设中间为LED0上下左右分别为1,2,3,4, 四个角为5,6 // 实际布局需要根据你的焊接顺序调整 // 定义骰子点数图案 // 数组的每个元素代表一个点数1-6其内容是一个字节每一位bit对应一个LED的开关1开0关 // 例如数字1可能只点亮中间的LED对应bit0数字3点亮中间和两个对角等。 const byte dicePatterns[6] { B0010000, // 数字1仅中间LED假设为第4位具体根据映射调整 B0101010, // 数字2例如左上和右下 B0111010, // 数字3数字2的图案加上中间 B1101011, // 数字4四个角 B1111011, // 数字5四个角加中间 B1111111 // 数字6全部点亮假设7颗LED模拟6个点可能需要调整例如不点亮中心 }; // 定义按钮引脚 const int buttonPin1 9; // “掷一次”按钮 const int buttonPin2 10; // “掷两次”按钮关键点dicePatterns数组的定义是核心。你需要根据自己电路中7颗LED的实际布局是模拟标准骰子的7个位置还是其他排列来精确设置每个字节byte中哪一位是1。上面注释中的示例是假设性的必须根据实际硬件调整。一个可靠的调试方法是先写一个简单的测试程序依次点亮每一颗LED确定它们在ledPins数组中的索引与实际物理位置的对应关系。4.2 随机数生成与“掷骰子”函数Arduino的random(min, max)函数可以生成一个最小值为min最大值小于max的随机整数。为了模拟骰子的公平性我们需要一个尽可能随机的种子。void setup() { // 初始化所有LED引脚为输出模式 for (int i 0; i 7; i) { pinMode(ledPins[i], OUTPUT); digitalWrite(ledPins[i], LOW); // 初始状态全部熄灭 } // 初始化按钮引脚为输入模式使用内部上拉电阻这样外部电路可以简化无需10kΩ上拉 pinMode(buttonPin1, INPUT_PULLUP); pinMode(buttonPin2, INPUT_PULLUP); // 用一个未连接的模拟引脚如A0的“噪声”作为随机种子增强随机性 randomSeed(analogRead(A0)); Serial.begin(9600); // 可选用于调试输出结果 }接下来是核心的“掷骰子”函数它负责生成随机数并点亮对应的LED图案。// 函数掷一次骰子并显示结果 int rollDice() { int result random(1, 7); // 生成1到6的随机数 displayNumber(result); // 调用显示函数 return result; } // 函数根据数字显示对应的LED图案 void displayNumber(int num) { if (num 1 || num 6) return; // 错误检查 byte pattern dicePatterns[num - 1]; // 获取对应的图案字节 for (int i 0; i 7; i) { // 逐位检查pattern如果该位是1则点亮对应的LED if (pattern (1 i)) { // 位操作检查第i位是否为1 digitalWrite(ledPins[i], HIGH); } else { digitalWrite(ledPins[i], LOW); } } }4.3 按钮检测与主循环逻辑主循环loop()需要持续检测两个按钮的状态并区分“单击一次”和“单击两次”的功能。// 定义按钮状态变量用于消抖 int buttonState1 HIGH; int lastButtonState1 HIGH; int buttonState2 HIGH; int lastButtonState2 HIGH; unsigned long lastDebounceTime1 0; unsigned long lastDebounceTime2 0; const unsigned long debounceDelay 50; // 消抖延迟50毫秒 // 用于存储两次投掷的结果 int firstRoll 0; int secondRoll 0; bool isFirstRollDisplayed false; void loop() { int reading1 digitalRead(buttonPin1); int reading2 digitalRead(buttonPin2); // 按钮1消抖处理“掷一次” if (reading1 ! lastButtonState1) { lastDebounceTime1 millis(); } if ((millis() - lastDebounceTime1) debounceDelay) { if (reading1 ! buttonState1) { buttonState1 reading1; if (buttonState1 LOW) { // 按钮被按下因为使用了上拉按下为LOW Serial.println(Button 1 Pressed - Single Roll); // 执行一次投掷并显示 int result rollDice(); Serial.print(Result: ); Serial.println(result); // 保持显示一段时间比如3秒 delay(3000); clearLEDs(); // 清空显示 } } } lastButtonState1 reading1; // 按钮2消抖处理“掷两次” if (reading2 ! lastButtonState2) { lastDebounceTime2 millis(); } if ((millis() - lastDebounceTime2) debounceDelay) { if (reading2 ! buttonState2) { buttonState2 reading2; if (buttonState2 LOW) { Serial.println(Button 2 Pressed - Double Roll); // 第一次投掷 firstRoll rollDice(); Serial.print(First Roll: ); Serial.println(firstRoll); isFirstRollDisplayed true; delay(2000); // 显示第一次结果2秒 // 用一个简单的闪烁效果提示即将进行第二次投掷 for (int i 0; i 3; i) { clearLEDs(); delay(200); displayNumber(firstRoll); delay(200); } // 第二次投掷 secondRoll rollDice(); Serial.print(Second Roll: ); Serial.println(secondRoll); delay(3000); // 显示第二次结果3秒 // 可选同时显示两次结果的总和或序列 // 例如快速交替显示两次结果 for (int i 0; i 5; i) { displayNumber(firstRoll); delay(300); clearLEDs(); delay(100); displayNumber(secondRoll); delay(300); clearLEDs(); delay(100); } clearLEDs(); // 最终清空 firstRoll 0; secondRoll 0; isFirstRollDisplayed false; } } } lastButtonState2 reading2; } // 清空所有LED void clearLEDs() { for (int i 0; i 7; i) { digitalWrite(ledPins[i], LOW); } }这段代码实现了稳定的按键消抖并清晰地区分了两种模式。在“掷两次”模式中我加入了一个视觉提示闪烁让用户明确感知到两个独立的投掷事件体验更佳。5. 功能扩展与进阶优化思路基础功能实现后这个项目还有很大的玩法扩展空间可以根据你的兴趣和需求进行深化。5.1 增加视觉与听觉反馈单纯的LED显示可能有些单调。我们可以很容易地加入更多感官反馈蜂鸣器提示在按钮按下时用有源蜂鸣器发出“嘀”一声短响确认输入。在骰子点数确定时可以发出不同音调或节奏的声音增加趣味性。震动马达在“投掷”过程中让一个小型震动马达需晶体管驱动短暂震动模拟骰子在杯中摇晃的触感。动画效果在rollDice()函数中不要直接显示最终结果。可以先让LED快速随机闪烁一段时间模拟骰子旋转最后再定格到最终点数。这能极大地增强“投掷”的仪式感。5.2 引入状态记忆与复杂规则利用Arduino的EEPROM电可擦可编程只读存储器可以让骰子记住一些状态。点数历史记录最近10次的投掷结果通过特定的按钮组合如长按查询。规则模式加入第三个按钮或一个拨码开关切换不同的游戏规则模式。例如模式A是标准六面骰模式B是投掷两个六面骰并显示总和常用于飞行棋、大富翁模式C是投掷一个二十面骰D20用于桌面角色扮演游戏。这只需要修改random函数的范围和displayNumber的映射逻辑即可。胜负判定为两人对战游戏设计。双方轮流按键投掷设备自动比较大小并用不同颜色的LED或声音宣布获胜方。5.3 无线化与网络交互这是迈向“物联网”的一步。蓝牙控制集成一个HC-05或HM-10蓝牙模块。你可以用手机APP来“投掷”骰子并在手机上显示结果甚至可以实现多台手机连接同一个骰子进行同步游戏。Wi-Fi联网使用NodeMCUESP8266或Arduino MKR1000代替Arduino Uno。让骰子接入本地Wi-Fi。你可以设计一个简单的Web服务器页面通过浏览器来投掷骰子。更进阶的可以将投掷结果上传到云端服务器用于在线桌游平台的物理设备接口。5.4 功耗优化与便携性如果采用电池供电功耗是关键。休眠模式当长时间没有按钮操作时让Arduino进入深度休眠Power-down模式此时电流消耗可降至微安级别。通过外部中断将按钮连接到支持中断的引脚如2或3来唤醒单片机。LED亮度调节使用PWM脉宽调制引脚控制LED亮度。在显示结果时全亮在待机时以极低的亮度呼吸或闪烁作为状态指示能有效省电。电源管理选择高效的降压模块如TPS63000系列为系统供电相比传统的线性稳压器如LDO能延长电池寿命。6. 常见问题排查与调试技巧即使按照步骤操作也可能会遇到问题。这里汇总一些常见坑点和解决方法。问题现象可能原因排查步骤与解决方案上电后所有LED不亮1. 电源未接通或接反。2. Arduino未正确供电或程序未上传。3. 共地GND连接缺失。1. 检查USB线或电池连接用万用表测量VCC和GND之间电压是否为5V左右。2. 打开Arduino IDE上传一个最简单的Blink示例程序到板子测试板子本身是否正常。3. 确保面包板或PCB上的GND总线与Arduino的GND引脚可靠连接。部分LED不亮或常亮1. LED极性接反。2. 对应限流电阻虚焊或阻值错误。3. Arduino引脚损坏或程序中对应该引脚定义错误。1. 确认不亮的LED长脚阳极是否接信号短脚阴极是否接GND。常亮的LED可能是阴极接到了信号。2. 用万用表测量该LED通路上的电阻是否约为100Ω。3. 写一个测试程序单独循环点亮每一个LED确认硬件连接和引脚定义一一对应。按钮按下无反应1. 上拉电阻未接或接错接成了下拉。2. 按钮信号线未连接到正确的数字引脚。3. 程序中引脚模式未设置为INPUT_PULLUP或消抖逻辑有误。4. 按钮本身损坏或接触不良。1. 确认10kΩ电阻一端接5V另一端接按钮和Arduino引脚的连接点。2. 核对代码中buttonPin1和buttonPin2的定义与实际连线是否一致。3. 在setup()中开启串口监视器打印digitalRead(buttonPin)的值观察按下和松开时的变化应为HIGH-LOW-HIGH。4. 用万用表通断档测试按钮按下时是否导通。随机数感觉不“随机”每次重启后骰子序列都一样。这是random()函数在没有用randomSeed()初始化时的特性。确保在setup()中使用了randomSeed(analogRead(A0))A0引脚悬空。悬空模拟引脚会读取到环境噪声能提供不错的随机种子。“掷两次”模式显示混乱1. 状态变量如firstRoll,isFirstRollDisplayed逻辑错误。2. 延时(delay)使用不当阻塞了第二次按钮检测。1. 仔细检查“掷两次”模式下的代码逻辑确保第一次结果显示、闪烁提示、第二次投掷、最终显示这几个状态转换清晰。多用Serial.print()输出状态变量值来调试。2. 避免在loop()中使用长延时考虑用millis()进行非阻塞计时这是更专业的做法但对于简单项目当前的delay尚可接受。设备工作不稳定偶尔复位1. 电源功率不足特别是使用电池时。2. 导线接触不良尤其在移动时。3. 代码中有内存泄漏或数组越界本项目较简单可能性低。1. 7颗LED全亮时电流较大约7*20mA140mA加上Arduino自身消耗总电流可能接近200mA。确保你的USB口或电池能提供500mA以上的稳定电流。2. 将所有连接点焊接牢固避免使用松动的杜邦线插接作为最终成品。3. 检查数组索引确保dicePatterns[num-1]中的num始终在1-6之间。调试心法当遇到问题时一定要“分而治之”。硬件上用万用表是基本功测电压、测通断。软件上串口监视器是你最好的朋友把关键变量的值、程序执行到哪一步都打印出来很多逻辑错误一目了然。先从最简单的部分测试比如只点亮一颗LED只检测一个按钮确认基础功能正常再逐步叠加复杂功能。