用CircuitPython在PyRuler上实现Simon记忆游戏:嵌入式开发实战

发布时间:2026/5/19 7:44:06

用CircuitPython在PyRuler上实现Simon记忆游戏:嵌入式开发实战 1. 项目概述与核心价值如果你手头恰好有一块Adafruit PyRuler又对嵌入式编程和互动游戏开发感兴趣那么这个项目绝对能让你玩上一下午。PyRuler本质上是一把集成了Trinket M0微控制器、多个LED和电容触摸板的“尺子”它原本是工程师的参考工具但我们今天要做的是把它变成一个经典的Simon记忆游戏机。Simon游戏也叫“西蒙说”是上世纪七八十年代风靡一时的电子记忆游戏。它的玩法很简单设备会按顺序点亮一组彩灯并发出音调玩家需要按照相同的顺序重复按下对应的按钮。随着关卡推进序列会越来越长挑战你的短期记忆极限。将这个经典游戏移植到PyRuler上不仅仅是为了怀旧更是一个绝佳的嵌入式开发实战案例。它涵盖了微控制器编程、GPIO控制、电容式触摸传感和游戏状态机逻辑等核心概念代码量不大但“麻雀虽小五脏俱全”。对于初学者来说这个项目是进入CircuitPython世界一个非常友好的切入点。CircuitPython是MicroPython的一个分支由Adafruit主导开发其最大特点就是极简——你不需要复杂的IDE和编译工具链只需一个文本编辑器把.py文件拖到名为CIRCUITPY的U盘里代码就自动运行了。对于有经验的开发者这个项目则展示了如何利用有限的硬件资源4个LED、4个触摸点、1个RGB LED设计出一个完整、流畅的交互体验其中对非标准硬件触摸引脚的软件模拟实现更是一个有趣的硬件技巧。整个项目的核心就是理解如何将PyRuler的物理接口那几个标着CAP0-CAP3的触摸焊盘和旁边的LED映射为游戏输入输出并编写逻辑将它们串联成一个可玩的游戏。接下来我会带你从环境准备开始一步步拆解代码直到你完全理解并能在自己的PyRuler上运行起这个游戏。2. 环境准备与工具链搭建在开始写代码之前我们需要确保手头的“家伙事儿”都齐全且工作正常。这个过程虽然基础但很多坑往往就埋在这里。2.1 硬件清单与检查首先确认你拥有以下硬件Adafruit PyRuler这是我们的主角。拿到手先检查一下尺子上的Trinket M0微控制器、四个并排的LED通常标有LED4-LED7、四个电容触摸焊盘CAP0-CAP3以及那个彩色的DotStar RGB LED是否完好。USB数据线Micro-B接口这一点至关重要。请务必使用一条能传输数据的USB线而不是只能充电的“电源线”。很多新手卡在电脑无法识别设备这一步八成就是用了充电线。一个简单的判断方法是这条线之前是否成功连接过手机和电脑进行文件传输。硬件连接非常简单用USB线将PyRuler连接到电脑的USB端口即可。通电后Trinket M0板载的红色电源LED应该常亮如果PyRuler之前已经刷过CircuitPython电脑上会弹出一个名为CIRCUITPY的U盘盘符。2.2 CircuitPython固件安装与更新PyRuler可能预装了CircuitPython但为了获得最佳兼容性和最新功能我建议检查并更新到最新固件。访问 CircuitPython官网 找到对应PyRuler的.uf2文件并下载。更新固件需要进入PyRuler的引导加载模式确保PyRuler通过USB连接到电脑。找到Trinket M0芯片附近一个非常小的复位按钮通常标记为“RST”。快速双击这个复位按钮。此时板载的DotStar RGB LED应该会闪烁绿色如果闪红色请检查USB线和端口。电脑上会出现一个名为TRINKETBOOT的新磁盘驱动器。将下载好的.uf2文件直接拖入TRINKETBOOT驱动器。驱动器会自动消失稍等片刻一个名为CIRCUITPY的新驱动器会出现。这表明CircuitPython固件已刷写成功。注意双击复位按钮需要一点手感如果第一次没成功TRINKETBOOT盘没出现多试几次掌握好双击的节奏。2.3 代码编辑器的选择与配置接下来我们需要一个编辑器来编写和修改Python代码。Adafruit官方推荐使用Mu Editor它专为教育场景和CircuitPython设计内置了串行监视器REPL能直接看到板子的打印输出非常方便。下载安装Mu从 codewith.mu 下载对应你操作系统Windows, macOS, Linux的安装包并安装。首次启动设置首次打开Mu时它会让你选择模式。请务必选择“CircuitPython”模式。你可以在编辑器窗口右下角确认当前模式。连接板子在启动Mu之前最好先将PyRuler连接到电脑并确保CIRCUITPY盘符已出现。这样Mu能自动检测到板子避免一些路径警告。如果你已经是资深程序员习惯使用VS Code、PyCharm等专业IDE也完全可以。但需要特别注意在保存代码文件到CIRCUITPY驱动器后必须执行“安全弹出”操作Windows/Linux或使用sync命令Linux以确保文件完全写入。否则极易因直接拔线导致文件系统损坏。Mu编辑器会自动处理这个步骤这是它最大的便利之处之一。实操心得在Windows上如果你在保存code.py后遇到板子无反应或文件消失可以尝试在代码文件最开头加上两行import supervisor supervisor.runtime.autoreload False这行代码会禁用CircuitPython的自动重载功能有时能解决一些文件写入冲突的问题。但更治本的方法是养成用Mu或“安全弹出”的好习惯。3. 项目代码深度解析与实现环境就绪后我们把焦点放回核心——游戏代码。我将不仅仅贴出代码更会逐段解释其背后的设计思路、硬件工作原理和编程技巧。3.1 库导入与硬件初始化任何单片机程序的第一步都是“认门”告诉代码硬件资源在哪里、怎么用。import time import random import board from rainbowio import colorwheel from digitalio import DigitalInOut, Direction import touchio import adafruit_dotstarboard这是CircuitPython的硬件抽象层board.CAP0、board.LED4这些常量对应着PyRuler上具体的物理引脚。digitalio用于控制数字输入输出比如点亮LED。touchio用于读取电容触摸传感器的库。但这里有个特殊情况。adafruit_dotstar用于控制APA102DotStarRGB LED的库我们的彩色状态灯就是它。rainbowio提供颜色轮辅助函数用于生成彩虹色。# 初始化DotStar LED pixels adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness0.1) red (255,0,0) green (0,255,0) blue (0,0,255)这里初始化了DotStar LED。APA102_SCK和APA102_MOSI是硬件SPI引脚。brightness0.1设置亮度为10%因为全亮度在近距离下非常刺眼。同时定义了红、绿、蓝三个颜色元组方便后续使用。led DigitalInOut(board.D13) led.direction Direction.OUTPUT这初始化了板载的红色状态LEDD13虽然不是游戏主逻辑的一部分但可以用来调试。核心难点电容触摸输入的初始化PyRuler的四个触摸焊盘中CAP1-CAP3由硬件电容触摸模块支持但CAP0是特殊的它只是一个普通的数字IO引脚。因此初始化需要“区别对待”touches [DigitalInOut(board.CAP0)] for p in (board.CAP1, board.CAP2, board.CAP3): touches.append(touchio.TouchIn(p))touches列表的第一个元素是DigitalInOut对象用于CAP0后三个是TouchIn对象用于CAP1-CAP3。cap_touches [False, False, False, False]这个列表则用于存储四个触摸点的当前状态是否被触摸。LED输出的初始化就直观多了leds [] for p in (board.LED4, board.LED5, board.LED6, board.LED7): led DigitalInOut(p) led.direction Direction.OUTPUT leds.append(led)创建一个leds列表依次初始化LED4到LED7并将它们设置为输出模式。这样我们就能通过leds[0].value True来点亮第一个LED了。3.2 核心功能函数剖析游戏逻辑被封装在几个函数里这让主循环清晰易懂。3.2.1 软件模拟电容触摸 (read_caps)这是整个项目中最精妙也最需要理解的部分。CAP0没有硬件触摸传感器如何检测触摸def read_caps(): t0_count 0 t0 touches[0] # CAP0是一个DigitalInOut对象 t0.direction Direction.OUTPUT t0.value True # 将引脚设置为高电平输出相当于给它充电 t0.direction Direction.INPUT # 迅速切换为输入模式 # 关键多次读取引脚电平并累加 t0_count t0.value t0.value t0.value t0.value t0.value \ t0.value t0.value t0.value t0.value t0.value \ t0.value t0.value t0.value t0.value t0.value cap_touches[0] t0_count 2 # 如果累加值大于2则认为被触摸 cap_touches[1] touches[1].raw_value 3000 # 硬件触摸传感器读取原始值 cap_touches[2] touches[2].raw_value 3000 cap_touches[3] touches[3].raw_value 3000 return cap_touches原理当人体触摸CAP0焊盘时会形成一个对地的电容。代码先将引脚输出高电平充电然后立即改为输入模式。如果引脚悬空未触摸电荷会保持较久多次读取t0.value应为1累加值会很高。如果被触摸人体电容会更快地泄放掉引脚上的电荷导致读取到低电平0的次数变多累加值t0_count就会变小。通过判断t0_count 2这个经验阈值就能检测触摸。这是一种巧妙的软件RC延时测量法。注意事项3000是硬件触摸传感器的阈值这个值可能需要根据你的具体环境和触摸灵敏度微调。如果发现某个触摸点不灵敏或过于灵敏可以尝试调整这个值。CAP0的2这个阈值也同样可以调整。3.2.2 带超时的触摸等待 (timeout_touch)游戏需要玩家在限定时间内输入这个函数实现了带超时的阻塞式触摸检测。def timeout_touch(timeout3): start_time time.monotonic() # 获取单调递增的当前时间单位秒 while time.monotonic() - start_time timeout: caps read_caps() # 读取所有触摸状态 for i,c in enumerate(caps): if c: # 如果任何一个触摸点被按下 return i # 立即返回该触摸点的索引(0,1,2,3) return None # 超时未触摸返回None在主循环中会引发游戏结束time.monotonic()是处理计时和延时的最佳选择因为它不会受系统时间调整的影响总是单调递增的。3.2.3 游戏核心序列播放与读取light_cap(cap, duration)函数很简单就是点亮并熄灭指定索引的LED。play_sequence(seq)函数负责向玩家展示需要记忆的序列。这里有个细节duration max(0.1, 1 - len(sequence) * 0.05)。随着序列sequence长度增加每个LED点亮/熄灭的持续时间会线性减少每次减0.05秒最低不低于0.1秒。这让游戏随着难度增加节奏也变快是经典Simon游戏的设定。read_sequence(seq)函数是游戏逻辑的核心先将DotStar灯设为绿色提示玩家“请开始输入”。遍历序列中的每个元素一个数字代表触摸点索引调用timeout_touch()等待玩家输入。如果玩家在超时前触摸了但触摸的索引i不等于当前序列元素cap则判定错误返回False。如果触摸正确则调用light_cap点亮对应的LED作为反馈然后继续下一个。整个序列都输入正确则返回True。3.3 主游戏循环逻辑理解了所有函数后主循环就一目了然了while True: # 游戏整体循环一局结束后回到这里开始新游戏 # 1. 游戏开始动画DotStar变蓝所有LED依次点亮再熄灭 pixels.fill(blue) time.sleep(1) for led in leds: led.value True time.sleep(0.25) for led in leds: led.value False sequence [] # 初始化空序列 while True: # 单局游戏循环直到玩家出错 pixels.fill(blue) # DotStar变蓝提示“即将展示序列” time.sleep(1) # 2. 序列增长在序列末尾添加一个0-3的随机数 sequence.append(random.randint(0, 3)) # 3. 播放序列给玩家看 play_sequence(sequence) # 4. 读取玩家输入并判断对错 if not read_sequence(sequence): # 如果输入错误 # 游戏结束DotStar变红3秒 pixels.fill(red) time.sleep(3) print(gameover) # 在串行监视器可以看到 break # 跳出内层循环回到外层开始新游戏 else: # 5. 输入正确庆祝动画彩虹循环然后继续下一轮 print(Next sequence unlocked!) rainbow_cycle(0) pixels.fill(0) # 熄灭DotStar time.sleep(1)这个状态机清晰地定义了游戏的完整流程开始动画 - 展示序列 - 等待输入 - 判断 - 正确则庆祝并增加难度错误则结束并重启。4. 项目部署、调试与深度优化现在我们已经透彻理解了代码。接下来就是把它运行起来并解决可能遇到的问题。4.1 库依赖安装与项目部署我们的代码依赖adafruit_dotstar库。最方便的方法是使用“项目捆绑包”。获取项目文件从Adafruit学习指南页面原资料中的链接下载整个项目ZIP包通常叫Project Bundle。解压并部署解压ZIP文件你会看到一个包含code.py和一个lib文件夹的目录。将lib文件夹内的库文件如adafruit_dotstar.mpy和根目录下的code.py全部复制到你的PyRuler的CIRCUITPY驱动器的根目录下。检查文件结构完成后你的CIRCUITPY驱动器应该看起来像这样CIRCUITPY/ ├── code.py ├── lib/ │ └── adafruit_dotstar.mpy └── ... (其他可能存在的文件)自动运行一旦文件复制完成CircuitPython会自动检测到code.py的变化并重新运行。你应该立刻看到PyRuler上的LED开始执行游戏开始动画DotStar变蓝四个LED依次点亮。4.2 常见问题排查与解决即使按照步骤操作你也可能会遇到一些问题。这里是我在实际操作中总结的排查清单问题现象可能原因解决方案电脑不识别CIRCUITPY驱动器1. USB线是充电线。2. CircuitPython固件未正确刷入。3. 板子损坏较少见。1.更换为已知可传数据的USB线。2. 重新执行2.2节的固件刷写步骤确保双击RST后出现TRINKETBOOT盘。代码复制后无任何反应1. 库文件缺失或路径错误。2.code.py文件中有语法错误。3. 文件未完全写入。1. 确认lib文件夹及其内容已正确复制到CIRCUITPY根目录。2. 使用Mu编辑器打开code.py检查底部是否有错误提示红色文字。3.安全弹出CIRCUITPY驱动器后再重新插拔。触摸不灵敏或完全无反应1. 触摸阈值不合适。2. CAP0的软件模拟检测不稳定。3. 手未直接接触金属焊盘。1. 调整read_caps()函数中的阈值硬件触摸的3000和CAP0的2。2. 确保手指直接触摸金属焊盘而不是旁边的丝印。可以尝试用指尖而非指腹。3. 对于CAP0可以尝试增加t0_count的采样次数代码中是15次。游戏序列播放太快/太慢延时参数不符合个人偏好。修改play_sequence函数中的duration计算公式或直接修改light_cap中的固定延时。Mu编辑器无法连接串行REPL1. 板子未正确连接。2. 串口被其他程序占用。3. Mu模式设置错误。1. 重启Mu并确保PyRuler已连接。2. 关闭其他可能占用串口的软件如Arduino IDE。3. 确认Mu编辑器右下角模式为“CircuitPython”。点击“串行”按钮打开控制台。实操心得利用REPL进行交互式调试。当游戏运行时你可以在Mu的串行监视器REPL里看到print(“gameover”)和print(“Next sequence unlocked!”)的输出。这是最简单的调试手段。你还可以在代码中临时添加print语句来输出变量值比如触摸原始值touches[1].raw_value帮助调整触摸阈值。4.3 功能扩展与创意改装基础游戏运行稳定后你可以尝试以下扩展这能让你更深入地掌握CircuitPython和硬件交互添加声音反馈PyRuler没有扬声器但你可以通过外接一个无源蜂鸣器到空闲的GPIO引脚如board.A0并使用pulseio库生成不同频率的PWM信号来模拟Simon游戏的经典音调。正确时播放上升音阶错误时播放低沉警示音。增加难度模式修改play_sequence函数让序列播放速度不仅随长度增加还可以设置一个“急速模式”或者加入“干扰项”播放完序列后额外亮一个无关的灯。记录最高分利用CircuitPython的storage模块将最高分序列长度保存到CIRCUITPY驱动器的一个文本文件中。每次游戏结束后读取并更新实现持久化存储。改变视觉主题修改rainbow_cycle函数或游戏状态灯的颜色。例如游戏过关时让四个LED也来一段流水灯动画而不仅仅是DotStar变化。移植到其他硬件理解代码逻辑后你可以尝试将其移植到其他Adafruit开发板如Circuit Playground Express。你需要做的主要是修改board引脚定义将CAP0-CAP3和LED4-LED7映射到新板子上的对应引脚。这个项目虽然小但它完整地展示了一个嵌入式交互应用从硬件初始化、输入检测、逻辑处理到输出反馈的全过程。通过动手实践和后续的扩展修改你不仅能收获一个有趣的游戏更能切实理解如何用代码“对话”硬件这才是嵌入式开发最大的乐趣所在。

相关新闻