1. 项目概述与核心价值如果你玩过桌面角色扮演游戏比如龙与地下城或者只是简单地玩过大富翁那你一定对“找骰子”这个场景不陌生。关键时刻骰子不是掉到沙发缝里就是被家里的猫当成玩具叼走了。作为一名嵌入式开发爱好者和桌游玩家我一直在想能不能把手边那块功能强大的开发板变成一个随时可用的“万能骰子”这个想法最终在Adafruit CLUE开发板上得以实现。CLUE本身集成了显示屏、按钮、加速度计和一系列传感器简直就是为这种即兴的交互应用而生的。这个基于CircuitPython的骰子模拟器项目其核心价值远不止“替代一个物理骰子”。它本质上是一个微型嵌入式交互系统的完整范例。通过它你可以学到如何在一个资源受限的微控制器上用高级语言Python处理用户输入按钮、晃动、管理应用状态、控制图形输出并实现具有反馈声音、视觉动画的交互流程。对于刚接触硬件编程的开发者来说它避开了复杂的寄存器操作和底层驱动编写让你能专注于交互逻辑和用户体验的设计。而对于有经验的开发者它展示了如何用简洁的Python代码优雅地解决硬件交互中的经典问题比如按键抖动处理、状态机管理以及实时显示更新。项目代码量不大但“麻雀虽小五脏俱全”。接下来我会带你从硬件准备开始一步步拆解代码逻辑并分享我在实现过程中积累的调试技巧和优化思路让你不仅能复现这个项目更能理解其设计精髓并在此基础上进行自己的创意扩展。2. 硬件准备与CircuitPython环境搭建2.1 核心硬件Adafruit CLUE开发板解析工欲善其事必先利其器。我们用的核心硬件是Adafruit CLUE开发板。选择它而非常见的Arduino Uno或ESP32有几个非常实际的理由首先高度集成开箱即用。CLUE板载了一块1.3英寸的彩色TFT显示屏240x240像素两个物理按钮A和B一个三轴加速度计/陀螺仪用于检测晃动一个温度传感器一个湿度传感器甚至还有一个麦克风和蜂鸣器。这意味着我们不需要焊接任何外围电路用一根USB线连接电脑就能开始编程极大降低了原型制作的物理门槛和复杂度。其次对CircuitPython的完美支持。Adafruit是CircuitPython的主要推动者之一其旗下的开发板包括CLUE通常都享有“一等公民”级别的支持。官方固件更新及时相关的传感器驱动库adafruit_clue功能完整且稳定社区资源丰富。这能避免我们在开发初期陷入“驱动不兼容”、“引脚定义错误”等泥潭。最后性能与功耗的平衡。CLUE基于Nordic Semiconductor的nRF52840芯片这是一颗带有蓝牙功能的ARM Cortex-M4F微控制器。它的计算能力足以流畅运行我们这种级别的图形界面和交互逻辑同时其低功耗特性也意味着未来你可以轻松地将它改装成一个由电池供电的便携式骰子。注意购买时请认准是“Adafruit CLUE - nRF52840 Express”。市场上可能有其他类似名称的开发板但固件和库的兼容性无法保证。另外你需要准备一根可靠的Micro-USB数据线很多手机充电线只能充电不能传输数据务必提前测试。2.2 CircuitPython固件烧录与开发环境建立CircuitPython是MicroPython的一个分支由Adafruit维护其设计哲学是“让嵌入式编程像操作U盘一样简单”。下面是最可靠的烧录步骤我会补充一些官方指南里没细说的“坑点”。第一步获取正确的固件访问 circuitpython.org 网站在下载页面找到“CLUE”并下载最新的.uf2固件文件。这里有个关键细节务必下载标有“Express”版本的固件因为CLUE有多个变体Express版本是针对我们手头这块板子的。第二步进入Bootloader模式用数据线连接CLUE和电脑。然后快速双击板子顶部的“Reset”按钮。成功的标志是板载的RGB NeoPixel LED位于USB口旁边会亮起绿色。如果它亮起红色通常意味着USB线或电脑USB口有问题请更换尝试。此时电脑上会出现一个名为CLUEBOOT的磁盘驱动器。第三步拖放烧录将下载好的adafruit-circuitpython-clue-...uf2文件直接拖拽到CLUEBOOT磁盘里。拖放完成后CLUEBOOT盘符会消失稍等片刻会出现一个新的名为CIRCUITPY的磁盘驱动器。这个过程就是固件烧录简单得令人发指。第四步验证与基础设置打开CIRCUITPY驱动器你应该能看到至少三个项目boot_out.txt包含启动信息、code.py主程序文件我们之后会替换它和一个lib文件夹用于存放第三方库。如果lib文件夹不存在可以手动创建一个。实操心得第一次烧录后如果CIRCUITPY盘没有出现可以尝试再次双击Reset键。有时需要多试几次才能掌握双击的节奏。另外在Windows系统上如果出现“需要格式化磁盘”的提示绝对不要格式化这通常意味着固件下载错误或板子进入了错误模式重新执行第二步即可。至此你的CLUE已经从一个普通的开发板变成了一个CircuitPython解释器。你可以直接像编辑文本文件一样修改code.py板子会自动重新运行新的代码这种即时反馈的开发体验是传统嵌入式编译-烧录流程无法比拟的。3. 项目代码深度解析与设计思路拿到一个项目直接复制粘贴代码或许能跑起来但理解其背后的设计思路才能让你真正掌握它。这个骰子模拟器的代码结构清晰是一个典型的状态机State Machine应用非常适合作为学习嵌入式系统设计的入门案例。3.1 全局配置与状态定义项目的“宪法”让我们打开最终的code.py从最开始的导入和定义看起import time from random import randint import board from adafruit_clue import clue from adafruit_debouncer import Debouncer import displayio from adafruit_bitmap_font import bitmap_font from adafruit_display_text import label # input constraints MAX_NUMBER_OF_DICE 6 SIDES [4, 6, 8, 10, 12, 20, 100] # modes: selecting/result SELECTING 0 ROLL_RESULT 1 # 0-relative, gets adjusted to 1-relative before display/use number_of_dice 0 side_selection 0库导入分析adafruit_clue这是CLUE板的“瑞士军刀”库它封装了访问板载所有传感器和按钮的简易接口。例如clue.button_a、clue.shake()。adafruit_debouncer这是本项目稳定性的关键。物理按钮在按下和弹起时由于机械接触会产生一系列快速的通断信号称为“抖动”。这个库提供了软件消抖功能确保一次物理按压只被识别为一次逻辑“按下”事件。displayio,bitmap_font,display_textCircuitPython的现代显示框架。displayio提供图层和组管理bitmap_font用于加载点阵字体display_text用于创建文本标签。它们共同构成了图形界面的基础。常量与变量设计逻辑MAX_NUMBER_OF_DICE 6这是一个用户体验设计。考虑到CLUE屏幕大小和实际游戏需求同时掷6个骰子已经足够。设置上限避免了用户需要按很多次按钮才能从6循环回1。SIDES [4, 6, 8, 10, 12, 20, 100]这里包含了标准多面骰D4, D6, D8, D10, D12, D20和百分骰D100。数组的顺序就是按钮切换的顺序。如果你想添加一个D2硬币或D3只需将其插入数组开头如SIDES [2, 3, 4, 6, ...]代码其他部分会自动适应。SELECTING和ROLL_RESULT这两个状态变量定义了程序的两种核心模式。状态机模式是处理复杂交互的利器它将程序逻辑清晰地划分为“选择骰子”和“显示结果”两个互斥的阶段避免了条件判断的混乱。number_of_dice和side_selection初始值为0这是编程中常见的“0基索引”习惯。在显示和计算时我们会将其转换为更符合人类习惯的“1基”表示例如number_of_dice 1。3.2 输入处理与物理世界的可靠对话硬件编程中输入处理尤其是按钮的可靠性至关重要。原始代码中直接读取clue.button_a会引入抖动问题。本项目采用了Debouncer对象来包装按钮button_a Debouncer(lambda: clue.button_a) button_b Debouncer(lambda: clue.button_b)这里使用lambda函数是一种简洁的写法它创建了一个匿名函数当Debouncer内部需要获取按钮状态时就调用这个函数返回clue.button_a的当前值。Debouncer对象会在其update()方法被调用时对原始信号进行滤波并提供清晰的pressed、released、rose从低到高即按下瞬间等状态。在主循环中我们必须每轮都调用button_a.update()和button_b.update()否则消抖逻辑不会生效。这是很多初学者容易遗漏的地方。3.3 显示系统构建让信息清晰可见显示部分代码构建了一个两层的信息结构上方较小字体显示当前选择的骰子如“3d6”下方超大字体显示掷骰结果。select_font bitmap_font.load_font(/Helvetica-Bold-36.bdf) select_font.load_glyphs(b0123456789XDd) roll_font bitmap_font.load_font(/Anton-Regular-104.bdf) roll_font.load_glyphs(b0123456789X)字体加载的优化技巧加载.bdf位图字体文件。这些字体文件需要预先存放在CIRCUITPY根目录下。load_glyphs是关键的性能优化手段。它告诉字体系统提前加载并缓存这些特定字符数字、字母X、d的点阵数据。如果不这样做每次更新文本时系统都需要从字体文件中动态查找并渲染这些字符在微控制器上这会带来明显的延迟。对于固定字符集的显示预加载能极大提升流畅度。显示坐标的居中算法select_label.x 120 - (select_label.bounding_box[2] // 2)这行代码频繁出现用于将文本标签水平居中。bounding_box是一个包含(x, y, width, height)的元组其中[2]就是文本的像素宽度。屏幕宽度是240像素中心点是120。因此将标签的x坐标设置为120 - 文本宽度的一半就能实现居中显示。这是一个在嵌入式图形界面中非常实用的技巧。3.4 核心逻辑掷骰子动画与状态机项目的灵魂在于roll()函数和主循环中的状态机。roll(count, sides)函数剖析 这个函数不仅生成一个随机数还精心模拟了骰子滚动停止的视觉效果和听觉反馈。def roll(count, sides): select_label.text # 清空选择显示提示进入“滚动模式” for i in range(15): # 滚动15次 # 核心计算模拟掷出count1个骰子并求和 roll_value sum([randint(1, sides) for _ in range(count 1)]) roll_label.text str(roll_value) roll_label.x 120 - (roll_label.bounding_box[2] // 2) # 居中 duration (i * 0.05) / 2 # 每次滚动的间隔时间逐渐变长 clue.play_tone(2000, duration) # 播放提示音 time.sleep(duration) # 等待为什么是count 1因为变量number_of_dice是0基的0代表1个骰子。在显示和用户概念里是“1个”在内部计算时我们需要加1。动画原理循环15次每次生成一个随机总和并显示。关键点是duration (i * 0.05) / 2随着循环次数i增加每次显示的停留时间和蜂鸣器发声时间会线性增加营造出骰子速度逐渐减慢直至停止的物理感。time.sleep(duration)制造了间隔clue.play_tone提供了听觉反馈多感官体验让交互更真实。为什么不直接生成一个1到count*sides的随机数直接生成一个总和其概率分布是均匀的。而模拟掷多个骰子再求和其结果的概率分布是正态分布或更准确地说是多个均匀分布的卷积中心值出现的概率更高。这对于模拟真实骰子尤其是多个骰子的统计特性更准确虽然在这个动画中用户可能察觉不到但这体现了设计的严谨性。主循环状态机 主循环是一个永恒的while True它不断检查输入并据此改变状态和行为。while True: button_a.update() button_b.update() if mode SELECTING: # 模式1选择骰子 if button_a.rose: # A按钮切换骰子数量 number_of_dice (number_of_dice 1) % MAX_NUMBER_OF_DICE update_display(number_of_dice, side_selection) elif button_b.rose: # B按钮切换骰子类型 side_selection (side_selection 1) % len(SIDES) update_display(number_of_dice, side_selection) elif clue.shake(shake_threshold25): # 晃动执行掷骰子 mode ROLL_RESULT if SIDES[side_selection] 100: # D100特殊处理强制数量为1 number_of_dice 0 update_display(number_of_dice, side_selection) roll(number_of_dice, SIDES[side_selection]) else: # mode ROLL_RESULT # 模式2显示结果 if button_a.rose or button_b.rose: # 按任意键返回选择模式 mode SELECTING update_display(number_of_dice, side_selection) elif clue.shake(shake_threshold25): # 再次晃动重新掷当前的骰子 roll(number_of_dice, SIDES[side_selection])这个状态机清晰地将用户操作映射到程序行为SELECTING模式A/B键修改参数晃动触发掷骰并进入ROLL_RESULT模式。这里有一个针对D100百分骰的特殊逻辑在桌面游戏中百分骰通常只掷一个所以这里强制将数量设为1。ROLL_RESULT模式任何按键都退出到选择模式晃动则用当前设置重新掷一次。这种设计符合直觉查看结果时你想改设置就按键想重掷就再晃一下。clue.shake(threshold25)中的threshold参数是灵敏度阈值值越小越敏感。25是一个经验值既能有效触发又避免了手持时的误触发。你可以根据实际手感调整这个值。4. 完整部署流程与库管理理解了代码之后我们需要将其部署到CLUE开发板上运行。这个过程不仅仅是复制文件还涉及到第三方库的管理这是CircuitPython项目开发中的一个重要环节。4.1 获取项目文件与依赖库最可靠的方法是下载“项目捆绑包”Project Bundle。在Adafruit的教程页面通常会提供一个“Download Project Bundle”按钮。这个ZIP文件包含了以下关键内容code.py主程序文件但通常其内容就是教程末尾展示的完整代码。lib/文件夹里面包含了本项目所需的所有第三方库文件如adafruit_debouncer、adafruit_bitmap_font等。手动部署步骤将ZIP文件解压到本地。打开解压后的文件夹找到CLUE_Dice_Roller或类似命名的目录。进入该目录你会看到针对不同CircuitPython版本的子目录如7.x.x。选择与你CLUE板上CircuitPython固件主版本号匹配的目录。将该目录下的所有文件和文件夹特别是lib文件夹和code.py复制或拖拽到你的CIRCUITPY磁盘的根目录中。如果系统询问是否覆盖或合并选择“是”。4.2 字体文件的处理代码中引用了两个字体文件/Helvetica-Bold-36.bdf和/Anton-Regular-104.bdf。注意路径前的斜杠/这表示它们位于CIRCUITPY磁盘的根目录。这些.bdf字体文件通常也包含在项目捆绑包中。如果lib文件夹同级没有你可能需要从Adafruit的官方库仓库GitHub或字体包中单独寻找并下载。确保这两个文件被正确地放置在CIRCUITPY的根目录下而不是lib文件夹内。这是初学者常犯的错误会导致程序运行时因找不到字体而显示乱码或崩溃。4.3 文件系统结构检查部署完成后你的CIRCUITPY磁盘应该呈现类似以下的结构CIRCUITPY/ ├── boot_out.txt ├── code.py ├── Helvetica-Bold-36.bdf ├── Anton-Regular-104.bdf ├── lib/ │ ├── adafruit_clue.mpy │ ├── adafruit_debouncer.mpy │ ├── adafruit_display_text/ │ ├── adafruit_bitmap_font/ │ └── ... (其他依赖库) └── ... (可能还有其他你自己创建的文件).mpy文件是CircuitPython的预编译字节码文件可以加快加载速度并保护源代码。adafruit_clue和adafruit_debouncer通常以.mpy形式提供而display_text和bitmap_font可能是包含多个文件的目录。4.4 首次运行与测试将文件复制完毕后CLUE板会自动复位并运行新的code.py。此时屏幕上应该会显示“1d4”表示1个4面骰。你可以进行以下测试测试按钮A按压按钮A观察屏幕左上角的数字是否在1到6之间循环1d4 - 2d4 - 3d4 ... - 6d4 - 1d4。测试按钮B按压按钮B观察骰子类型是否在D4, D6, D8, D10, D12, D20, D100之间循环切换。测试晃动掷骰拿起板子做一个轻微的摇晃动作。屏幕应该会清空选择信息下方出现快速变化的数字并伴有蜂鸣声最后定格在一个随机数上。测试模式切换在显示结果时按压A或B键应该会立即返回骰子选择界面。如果任何一步没有按预期工作请进入下一章节的故障排查部分。5. 常见问题排查与深度调试技巧即使完全按照步骤操作你也可能会遇到一些问题。别担心硬件项目就是这样。下面是我在多次实现和教学中总结的常见问题及其解决方法。5.1 基础问题速查表问题现象可能原因解决方案屏幕无显示或白屏1. 电源问题。2. 固件不匹配。3. 显示初始化代码错误。1. 检查USB线连接尝试不同USB口。2. 确认下载的固件是给CLUE“Express”版的。3. 检查code.py开头是否成功导入board和displayio且board.DISPLAY.root_group被正确设置。按钮操作无反应1. 按键消抖库未正确安装或更新。2. 主循环中漏掉了button_x.update()。3. 物理按钮损坏罕见。1. 确认lib文件夹内有adafruit_debouncer.mpy文件。2. 检查while True循环最开头是否有button_a.update(); button_b.update()。3. 用万用表通断档测试按钮引脚或写一个简单的测试程序如按下时点亮LED。晃动无法触发掷骰1. 加速度计库adafruit_clue未安装。2. 晃动阈值(shake_threshold)设置过高。3.clue.shake()检测逻辑有误。1. 确认lib文件夹内有adafruit_clue.mpy。2. 尝试降低阈值例如将shake_threshold25改为15测试更小的晃动是否能触发。3. 在主循环中添加print(clue.acceleration)打印加速度值观察晃动时的数值变化。显示乱码或字体缺失1. 字体文件(.bdf)未放置在根目录。2. 字体文件路径或名称错误。3. 字体所需的字符未预加载(load_glyphs)。1. 确认Helvetica-Bold-36.bdf和Anton-Regular-104.bdf在CIRCUITPY根目录。2. 检查load_font函数中的文件路径字符串是否正确。3. 确认load_glyphs包含了所有需要显示的字符数字、X、d。程序运行缓慢或动画卡顿1. 字体未预加载每次渲染都从文件读取。2. 主循环中有耗时操作阻塞。3. 内存不足。1. 确保在程序开始时调用font.load_glyphs(...)。2. 避免在roll()动画循环中进行复杂的计算或文件操作。3. CircuitPython环境下内存管理需谨慎避免创建过大的列表或字符串。可尝试减少roll()函数中的循环次数如从15次减到10次。5.2 高级调试使用REPL交互式环境当问题比较复杂或者你想深入了解程序运行状态时CircuitPython的REPLRead-Eval-Print Loop是一个无敌的调试工具。它让你可以通过串口实时与板子交互执行命令、查看变量、调试代码。如何进入REPL确保CLUE通过USB连接电脑。使用一个串口终端工具。推荐Windows/macOS/Linux:Mu Editor(内置了REPL)或ThonnyIDE。命令行爱好者:screen(macOS/Linux) 或Putty(Windows)连接对应的串口设备如/dev/ttyACM0,COM3等。在串口终端中按下键盘的CtrlC。这会中断当前正在运行的code.py程序。如果中断成功你会看到提示符。这意味着你已进入REPL。在REPL中调试本项目的实用命令# 1. 手动导入核心模块检查是否报错 import board from adafruit_clue import clue print(CLUE模块导入成功) # 2. 测试加速度计原始数据 print(clue.acceleration) # 打印(x, y, z)加速度值单位是m/s²。静止时z轴应约为9.8。 # 轻轻晃动板子观察数值变化。 # 3. 测试shake函数 for i in range(10): print(clue.shake(threshold25)) time.sleep(0.5) # 晃动板子看是否打印出True。 # 4. 测试按钮原始状态 print(clue.button_a, clue.button_b) # 按下和松开时应分别打印False和True。 # 5. 检查变量状态需在程序运行后中断 # 按CtrlC中断程序后可以直接查询全局变量 print(number_of_dice) # 查看当前骰子数量索引 print(SIDES[side_selection]) # 查看当前选择的骰子面数 print(mode) # 查看当前模式 (0: SELECTING, 1: ROLL_RESULT) # 6. 手动执行一次roll函数需先导入相关模块和变量 # 假设已中断在程序运行中可以直接调用 roll(0, 6) # 模拟掷1个6面骰通过REPL热重载代码 如果你在code.py中修改了代码除了复位板子还可以在REPL中执行import supervisor supervisor.reload()这会使板子软重启并重新加载code.py比拔插USB线更方便。调试心得当程序行为异常时首先在REPL中检查各个硬件模块按钮、加速度计是否能被正确访问和读取。这能快速定位是硬件连接问题、库缺失问题还是你自己的程序逻辑问题。把print()语句插入到代码的关键分支如if button_a.rose:内部是定位逻辑错误的经典方法虽然会在串口输出信息但非常有效。5.3 性能优化与内存管理在资源有限的微控制器上编程需要有性能意识。这个项目虽然不复杂但也有一些优化点字体预加载如前所述load_glyphs至关重要。如果不做预加载每次更新文本系统都要从慢速的Flash存储中读取字体文件并解析动画会明显卡顿。避免在循环中创建对象在roll()函数的for循环中[randint(1, sides) for _ in range(count 1)]这个列表推导式会在每次循环中创建一个新的列表。对于掷少量骰子来说没问题但如果count很大比如接近MAX_NUMBER_OF_DICE且循环15次就会创建15个列表。虽然Python有垃圾回收但在极限情况下可能影响流畅性。一个更节省内存的写法是使用循环和累加total 0 for _ in range(count 1): total randint(1, sides) roll_value total摇动检测的阈值调优shake_threshold25是一个通用值。你可以根据使用场景微调。如果放在桌面上容易误触发就调高如30。如果需要用力晃才能触发就调低如20。可以在REPL中测试不同晃动力度下clue.acceleration的变化幅度来找到合适的阈值。6. 项目扩展与创意改造原项目已经是一个完整可用的骰子模拟器但它的代码结构清晰为我们留下了丰富的扩展空间。这里提供几个改造方向你可以选择感兴趣的尝试。6.1 扩展一利用触摸板实现复杂表达式输入CLUE板除了A/B按钮还有7个电容式触摸板标有0-6。我们可以用它们来输入更复杂的骰子表达式例如“2d6 1d4 5”。设计思路定义触摸板功能例如触摸板0-9作为数字输入触摸板“*”和“#”作为“d”骰子和“”加号功能键另一个触摸板作为“掷骰”确认键。修改状态机增加一个“表达式输入模式”。在这个模式下屏幕显示一个字符串如“2d61d45”用户通过触摸板逐个输入字符。表达式解析当按下“掷骰”键时程序需要解析这个字符串。这涉及到字符串分割和求值。你可以编写一个简单的解析器或者使用CircuitPython的eval()函数需谨慎确保输入安全。解析后你需要计算多个不同骰子组的和。显示优化输入模式和结果显示模式需要不同的界面布局。技术要点你需要导入adafruit_touchscreen库如果CLUE的触摸板驱动在此库中或使用clue库中对应的触摸板接口如clue.touch_0等。触摸输入同样需要消抖处理可以复用Debouncer逻辑。6.2 扩展二更真实的物理模拟与动画当前的滚动动画只是用随机数模拟。我们可以让它更逼真。禁止连续相同数字在roll()函数的循环中记录上一次显示的数字如果本次随机数相同则重新生成直到不同为止。这模拟了骰子翻滚时数字必然会变化的效果。last_value None for i in range(15): while True: roll_value sum(...) # 计算随机和 if roll_value ! last_value: last_value roll_value break # ... 显示 roll_value ...模拟多颗骰子的独立动画目前是显示总和。更高级的模拟可以尝试在屏幕上用多个小方块或数字分别模拟每一颗骰子的滚动和停止。这对图形编程和性能要求更高但视觉效果会大幅提升。加入运动模糊或惯性效果根据clue.acceleration读取的晃动强度来调整滚动动画的初始速度和持续时间。晃得越猛滚得越快、越久。6.3 扩展三历史记录与统计功能对于认真的桌游玩家记录每次掷骰的结果并查看统计信息如平均值、分布会很有趣。数据结构在全局变量中定义一个列表roll_history []来存储每次掷骰的结果。结果可以存储为一个元组(timestamp, dice_count, dice_sides, result)。存储限制由于内存有限可以只保存最近50次或100次记录。当列表超过长度时移除最老的记录roll_history.pop(0)。界面切换可以定义一个新的模式例如通过长按A键进入“历史查看模式”。在这个模式下使用B键翻看历史记录屏幕同时显示掷骰表达式和结果。数据持久化如果想关机后仍保存记录可以将roll_history列表以JSON格式写入到CIRCUITPY磁盘上的一个文件中如/history.json。每次启动时从文件读取。但需要注意频繁写入Flash会缩短其寿命所以可以设定积累一定次数后再写入。6.4 扩展四网络化与多人游戏利用CLUE板载的蓝牙BLE可以实现骰子结果的共享用于多人线上桌游。广播模式将CLUE设置为BLE外设Peripheral广播一个自定义服务其中包含一个“骰子结果”特征Characteristic。掷骰触发每次掷骰完成后将结果写入到这个BLE特征中。接收端其他设备手机、电脑、另一块CLUE可以作为中心设备Central扫描并连接订阅这个特征实时接收掷骰结果。Adafruit库可以使用adafruit_ble库来实现此功能。这需要学习BLE的基本概念服务、特征、通知等是一个更有挑战性但也更有成就感的扩展。改造这个项目的乐趣在于它像一个乐高底座你可以根据自己的兴趣和技能树往上添加各种模块。从简单的界面调整到复杂的物理模拟、数据持久化甚至无线通信每一步都能学到新的嵌入式开发知识。最重要的是当你成功添加一个新功能并看到它运行时那种成就感是无可替代的。希望这个详细的解析和扩展思路能点燃你动手改造的热情。如果在实现过程中遇到任何新问题欢迎带着你的代码和现象来讨论。