1. 项目概述为什么选择CircuitPython如果你之前接触过Arduino或者MicroPython可能会觉得微控制器编程总是绕不开复杂的C/C语法、繁琐的编译烧录流程以及让人头疼的内存管理。CircuitPython的出现正是为了解决这些问题。它本质上是一个基于Python 3的开源微控制器解释器由Adafruit主导开发并维护。它的核心价值在于让嵌入式开发变得像在电脑上写Python脚本一样简单直观。想象一下你拿到一块支持CircuitPython的开发板比如Adafruit的Feather系列、Circuit Playground Express等当你用USB线将它连接到电脑时电脑上会直接弹出一个名为CIRCUITPY的U盘。你的代码文件code.py就放在这个U盘里。你只需要用任何文本编辑器修改这个文件保存代码就会自动在板子上重启运行。整个过程没有编译没有烧录工具就像在修改一个文本文档一样自然。这种“编辑-保存-运行”的即时反馈循环极大地加速了原型开发和调试过程尤其适合教育、艺术装置、物联网设备原型等需要快速迭代的场景。我最初被CircuitPython吸引就是因为它的“无感部署”。你不需要安装复杂的IDE不需要配置编译链甚至初学者用系统自带的记事本都能开始编程。这种极低的入门门槛并不意味着它功能薄弱。相反通过其强大的库生态系统你可以轻松驱动各种传感器、显示屏、执行器处理网络请求甚至实现USB HID设备如键盘、鼠标功能。接下来我将从最基础的代码编辑讲起带你走完从点亮一个LED到管理复杂项目依赖的全流程。2. 核心工作流代码编辑、保存与自动重载CircuitPython的核心体验建立在“文件即代码”的简单哲学上。理解并熟练运用这一工作流是高效开发的基础。2.1 理解code.py与自动执行机制当你将CircuitPython固件刷入开发板后首次连接电脑CIRCUITPY驱动器内通常会有一个默认的code.py文件。这个文件就是板子上电或复位后自动执行的入口脚本。CircuitPython会按照code.txt-code.py-main.txt-main.py的顺序寻找并执行第一个找到的文件。虽然code.py是推荐名称但了解其他选项很重要。一个常见的坑是如果你不小心创建了main.py即使你一直在修改code.py板子也只会执行main.py。所以如果你的修改似乎没有生效第一件事就是检查CIRCUITPY根目录下是否有其他优先级更高的代码文件。让我们从最经典的“Hello, World!”硬件版——闪烁LED开始。将以下代码保存为CIRCUITPY驱动器根目录下的code.pyimport board import digitalio import time # 初始化板载LED引脚大多数Adafruit板子都适用 led digitalio.DigitalInOut(board.LED) led.direction digitalio.Direction.OUTPUT while True: led.value True # LED亮 time.sleep(0.5) # 等待0.5秒 led.value False # LED灭 time.sleep(0.5) # 等待0.5秒保存文件后你会立刻看到板载LED开始以1秒为周期亮0.5秒灭0.5秒闪烁。这就是CircuitPython的“热重载”特性当你保存code.py时解释器会检测到文件变化自动重启并运行新代码。2.2 实时代码编辑与参数调整实践现在让我们进行第一次交互式实验。打开你的code.py找到time.sleep(0.5)这两行。尝试将第一个0.5改为0.1然后保存。while True: led.value True time.sleep(0.1) # 改为0.1秒 led.value False time.sleep(0.5)保存瞬间LED的闪烁行为就改变了它会非常短暂地亮一下0.1秒然后熄灭0.5秒。接着把第二个0.5也改成0.1while True: led.value True time.sleep(0.1) led.value False time.sleep(0.1) # 也改为0.1秒现在LED会以很高的频率周期0.2秒闪烁由于人眼的视觉暂留你可能会觉得它是在持续发光但亮度较低。反之如果你把两个参数都改成1.0LED就会以2秒为周期缓慢闪烁。实操心得理解阻塞与非阻塞延迟time.sleep()是一个“阻塞”函数它会让整个程序暂停指定的秒数。在简单的闪烁LED例子中这没问题但在需要同时处理多个任务比如同时读取传感器和响应按钮时长时间的sleep会导致程序“卡住”。在后续进阶项目中我们会介绍使用time.monotonic()进行非阻塞定时的方法。这个简单的练习演示了CircuitPython开发的核心循环编辑 - 保存 - 观察硬件反应。所有复杂的项目迭代都建立在这个快速反馈之上。2.3 禁用自动重载以应对特殊场景绝大多数情况下自动重载Auto-reload是我们需要的。但在某些特殊场景下比如你正在通过串口与板子进行复杂交互或者代码重启会导致外部硬件状态异常时你可能希望临时禁用它。这可以通过在code.py文件开头添加两行代码实现import supervisor supervisor.runtime.autoreload False # 你其余的代码... import board import digitalio ...设置autoreload False后保存code.py将不会触发自动重启。你需要手动复位板子按复位键或通过串口发送CTRLD后面会讲到来重新加载代码。请注意这个设置本身也是代码的一部分修改它并保存后需要一次手动重启才能生效。通常只在调试特定问题时才需要关闭自动重载日常开发建议保持开启。3. 串口控制台你的调试与交互窗口如果说CIRCUITPY驱动器是代码的“入口”那么串口控制台Serial Console就是程序的“输出窗口”和“调试终端”。它是连接你的电脑和微控制器内部运行状态的桥梁对于排查问题、输出信息、甚至交互式编程至关重要。3.1 串口控制台的作用与连接在桌面编程中我们使用print(“Hello”)在屏幕上输出信息。在CircuitPython中print函数的内容会被发送到串口我们需要一个终端程序来接收和显示这些信息。这就是串口控制台。连接串口控制台通常有三种方式使用Mu编辑器推荐给初学者Mu是一款专为教育设计的Python编辑器内置了CircuitPython模式和串口控制台。安装Mu后插入开发板打开Mu并选择“CircuitPython”模式点击顶部的“串行”按钮底部就会弹出控制台窗口。Mu会自动检测并连接你的板子是最省心的选择。使用其他编辑器独立终端程序如果你使用VS Code、Thonny或其他编辑器你需要一个独立的终端程序。Windows常用PuTTY或Tera Term。你需要知道板子的COM端口号在设备管理器中查看。macOS/Linux系统自带终端就好用。使用ls /dev/tty.*或ls /dev/cu.*macOS以及ls /dev/ttyACM*Linux查找设备然后用screen命令连接例如screen /dev/ttyACM0 115200。使用Web串口终端Chrome/Edge一些在线工具或本地Web服务如https://adafruit.github.io/Adafruit_WebSerial_ESTool/可以利用浏览器的Web Serial API连接设备无需安装任何软件。连接参数CircuitPython串口通常使用115200波特率、8位数据位、无奇偶校验、1位停止位8N1无需流控制。3.2 使用Print语句进行调试让我们修改之前的闪烁代码加入print语句来观察程序运行。import board import digitalio import time led digitalio.DigitalInOut(board.LED) led.direction digitalio.Direction.OUTPUT counter 0 # 新增一个计数器 while True: led.value True print(“LED ON, Counter:“, counter) # 打印状态和计数器 time.sleep(0.5) led.value False print(“LED OFF, Counter:“, counter) time.sleep(0.5) counter 1 # 每次循环计数器加1保存代码后打开串口控制台你会看到源源不断的输出LED ON, Counter: 0 LED OFF, Counter: 0 LED ON, Counter: 1 LED OFF, Counter: 1 ...这就是“打印调试法”Print Debugging是最简单直接的调试手段。你可以通过打印变量值、函数执行到哪一步等信息快速定位程序逻辑问题。3.3 解读错误信息Traceback程序出错时串口控制台是你最好的朋友。我们故意制造一个错误来看看。将上面代码中的led.value True改为led.value Tru去掉e然后保存。led.value Tru # 这里拼写错误保存后LED会停止闪烁板子上的状态灯可能改变颜色例如变成黄色表示代码出错。此时查看串口控制台你会看到类似这样的信息Traceback (most recent call last): File “code.py“, line 10, in module NameError: name ‘Tru‘ is not defined这个“回溯”Traceback信息非常宝贵Traceback (most recent call last):告诉你接下来是错误堆栈。File “code.py“, line 10, in module明确指出错误发生在code.py文件的第10行在主模块中。NameError: name ‘Tru‘ is not defined这是具体的错误类型和描述名称错误Tru这个变量没有被定义。根据这些信息你就能快速定位到第10行发现True拼写错误。修复后保存程序恢复正常。养成一出错就第一时间查看串口控制台的习惯能节省大量盲目排查的时间。注意事项跨平台文件保存问题有时在Windows上你可能会遇到保存code.py后板子没有反应控制台也没有输出新错误的情况。这可能是由于文件系统同步或编辑器锁定问题。一个经过验证的解决方法是在code.py文件的最顶端在所有import之前添加以下两行代码import storage storage.remount(“/“, readonlyFalse, disable_concurrent_write_protectionTrue)这段代码会以禁用并发写保护的方式重新挂载文件系统可以解决大多数Windows下的保存问题。请注意这并非官方推荐的首选方案仅在遇到保存问题时尝试。更通用的建议是使用Mu编辑器或确保你的编辑器如VS Code在保存后完全释放了文件句柄。4. 深入REPL交互式编程与探索REPLRead-Eval-Print Loop是CircuitPython的交互式编程环境。如果说串口控制台是“听”程序说话那么REPL就是让你和程序“对话”。你可以在这里逐行执行代码、查询模块、测试函数而无需修改和保存code.py文件。4.1 进入与退出REPL要进入REPL你需要先连接到串口控制台用Mu或终端程序。连接成功后按下CtrlC。这会中断当前正在运行的code.py程序。如果程序被中断你会看到提示Press any key to enter the REPL. Use CTRL-D to reload.此时按键盘任意键。你会看到提示符这就表示你已经进入了REPL环境。进入REPL后你可能会先看到一些板子信息例如Adafruit CircuitPython 8.2.10 on 2024-06-17; Adafruit Feather RP2040 with rp2040 要退出REPL并重新运行code.py只需在REPL中输入CtrlD。这会软复位板子重新执行主程序。4.2 使用REPL进行探索与测试在提示符后你可以直接输入Python代码并立即看到结果。获取帮助输入help()然后回车会显示基础帮助信息其中最重要的一条是To list built-in modules type help(“modules”)。查看内置模块输入help(“modules”)会列出当前固件版本中所有内置的模块比如board,time,digitalio,analogio,pwmio等。这是了解板子能力的好方法。探索board模块board模块包含了板子上所有可用的引脚定义。 import board dir(board) # 列出board模块的所有属性 [‘A0‘, ‘A1‘, ‘A2‘, ‘A3‘, ‘D4‘, ‘D5‘, ‘LED‘, ‘SCL‘, ‘SDA‘, ‘TX‘, ‘RX‘, ...] board.LED # 查看LED引脚对应的具体对象 board.Pin object at 2000a000执行单行代码你可以直接测试代码片段。 import time start time.monotonic() time.monotonic() - start # 计算从start到现在过了多少秒 5.123456 print(“Hello from REPL!“) Hello from REPL!测试硬件你甚至可以不写完整程序直接操作硬件。 import board, digitalio, time led digitalio.DigitalInOut(board.LED) led.direction digitalio.Direction.OUTPUT led.value True # LED亮 led.value False # LED灭 for i in range(5): ... led.value not led.value # 状态翻转 ... time.sleep(0.2) ... 注意在REPL中输入多行代码如for循环时次级行以...开头需要手动缩进。输入空行结束多行输入并执行。重要警告REPL的临时性在REPL中输入的所有代码都是临时的一旦你按下CtrlD复位或断开连接这些代码就会消失。REPL是一个强大的测试和调试工具但永远不要把它当作编写最终程序的地方。任何你想保留的代码都必须写在code.py或其他文件里。5. 库管理扩展CircuitPython的能力CircuitPython的内置模块提供了访问硬件的基础能力但它的强大之处在于其丰富的库生态系统。库Libraries是别人写好的、实现特定功能的代码包比如驱动某个型号的传感器、显示屏或者实现网络协议。学会管理库是构建复杂项目的关键。5.1 库的存放位置与类型CircuitPython的库文件存放在CIRCUITPY驱动器下的lib文件夹内。如果你的板子第一次使用可能没有这个文件夹手动创建一个即可。库文件主要有两种格式.mpy文件这是经过编译压缩的库文件体积小加载快是分发时的首选格式。你从官方Bundle下载的通常就是这种。.py文件这是纯Python源代码文件。有时你可能需要查看或修改库的源码或者某个库暂时没有.mpy版本就会使用.py文件。一个库可能由单个.mpy/.py文件构成也可能是一个包含多个文件的文件夹。库分为两大类内置库随着CircuitPython固件一起烧录到芯片中的库如board,time,digitalio等。它们不需要放在lib文件夹里。外置库需要手动下载并放入lib文件夹的库。这包括Adafruit官方维护的库和社区贡献的库。5.2 如何获取所需的库项目包与库包方法一使用项目包Project Bundle - 最推荐给初学者在Adafruit的教程网站Learn Adafruit上几乎每个项目教程的完整代码部分都会有一个“Download Project Bundle”按钮。点击它会下载一个zip文件这个文件里包含了运行这个项目所需的一切code.py、lib文件夹内含所有依赖库、图片、字体等资源文件。操作步骤找到教程中的“Download Project Bundle”按钮并下载ZIP。解压ZIP文件。打开解压后的文件夹根据你的CircuitPython版本如7.x找到对应的目录。将该目录下的所有内容特别是code.py和lib文件夹复制到你的CIRCUITPY驱动器根目录。如果提示覆盖选择“是”。警告覆盖风险项目包会覆盖你CIRCUITPY盘上现有的所有文件在复制之前请务必备份你已有的code.py和其他重要文件。方法二手动下载并安装库包Library Bundle当你需要为现有项目添加新功能或者项目教程没有提供Bundle时就需要手动管理库。你需要去下载对应你CircuitPython版本的“库包”。确定你的CircuitPython版本查看CIRCUITPY盘根目录下的boot_out.txt文件第一行就是版本信息或者进入REPL时第一行也会显示。下载对应的库包Adafruit官方库包包含Adafruit维护的所有传感器、显示驱动等库。访问circuitpython.org/libraries下载与你的主版本号匹配的adafruit-circuitpython-bundle-py-版本-mpy-日期.zip例如adafruit-circuitpython-bundle-py-8.x-mpy-20240617.zip。选择-mpy版本。社区库包包含社区成员贡献的库。在同一页面下载circuitpython-community-library-bundle-py-版本-日期.zip。解压并查找库解压下载的zip文件库文件都在解压出的lib文件夹内。复制所需库打开你的CIRCUITPY盘下的lib文件夹从解压的lib文件夹中找到你需要的库文件或文件夹复制进去。5.3 如何确定需要哪些库解读Import语句当你拿到一段示例代码如何知道需要安装哪些库呢答案就在import语句里。假设你看到这样一段代码开头import time import board import neopixel import adafruit_lis3dh import usb_hid from adafruit_hid.consumer_control import ConsumerControl from adafruit_hid.consumer_control_code import ConsumerControlCode分析步骤区分内置模块与外置库首先运行help(“modules”)在REPL中可以列出所有内置模块。对比列表time,board,usb_hid通常在列表里 →它们是内置的无需安装。neopixel,adafruit_lis3dh不在列表里 →它们是外置库需要安装。处理from ... import ...语句这种语句中from后面的部分指明了库的位置。from adafruit_hid.consumer_control import ...这里的库名是adafruit_hid。你需要去Bundle的lib文件夹里找到adafruit_hid这个文件夹并将整个文件夹复制到你的lib下。查找并复制在Adafruit库包的lib文件夹中找到neopixel.mpy文件复制到CIRCUITPY/lib。找到adafruit_lis3dh.mpy文件复制到CIRCUITPY/lib。找到adafruit_hid文件夹整个文件夹复制到CIRCUITPY/lib。一个关键技巧依赖关系有时库A会依赖库B。如果你只安装了库A运行代码时可能会在串口控制台看到ImportError: no module named ‘some_dependency‘。这时你需要根据错误信息再去Bundle里找到some_dependency库并安装。这就是为什么对于复杂项目直接使用“项目包”往往更省心。5.4 解决“ImportError”实战让我们模拟一个常见的错误。假设你的lib文件夹是空的然后你运行了下面这个需要simpleio库的代码import board import time import simpleio # 这个库我们没有安装 led simpleio.DigitalOut(board.LED) while True: led.value True time.sleep(0.5) led.value False time.sleep(0.5)保存后LED不会闪烁。打开串口控制台你会看到类似这样的错误Traceback (most recent call last): File “code.py“, line 3, in module ImportError: no module named ‘simpleio‘解决流程阅读错误明确告诉你缺少simpleio模块。查找库打开你下载的对应版本的Adafruit库Bundle在lib文件夹中搜索simpleio。安装库找到simpleio.mpy文件将其复制到CIRCUITPY/lib文件夹。自动重载因为code.py保存后出错导致程序停止复制库文件这个操作本身不会重启程序。你需要按一下板子上的复位按钮或者**在串口控制台如果还连着按CtrlD**来软复位。复位后程序会重新运行此时应该就能正常闪烁了。这个过程就是解决库依赖问题的标准流程看错误 - 找库 - 放库 - 重启。6. 高级技巧与最佳实践掌握了基础工作流后一些技巧能让你的开发过程更加顺畅。6.1 组织大型项目多文件与模块化当项目代码超过几百行后全部堆在code.py里会难以维护。CircuitPython支持模块化。你可以在CIRCUITPY盘上创建其他.py文件然后在code.py中导入它们。例如创建一个sensor_reader.py文件# sensor_reader.py import time import analogio import board def read_temperature(pin): # 模拟读取温度传感器的值此处为示例 sensor analogio.AnalogIn(pin) raw_value sensor.value # 假设的转换公式 temperature (raw_value / 65535) * 100 return temperature然后在code.py中导入并使用# code.py import board import time import sensor_reader # 导入我们自定义的模块 while True: temp sensor_reader.read_temperature(board.A0) print(“Temperature:“, temp) time.sleep(1)注意自定义模块需要和code.py放在同一级目录或者放在Python的搜索路径中。对于简单项目放在CIRCUITPY根目录即可。6.2 资源管理与性能考量尽管CircuitPython易用但微控制器的资源内存、存储有限。内存避免创建过大的列表、字符串或频繁进行内存分配。使用gc.mem_free()需要import gc可以查看剩余内存辅助调试内存问题。存储空间CIRCUITPY驱动器的剩余空间是有限的。定期清理不需要的.py文件、旧的库或测试文件。.mpy库比.py库更省空间。执行效率对于实时性要求高的任务如精确控制PWM频率、读取高速传感器纯Python的解释执行可能不够快。此时可以考虑使用内置的硬件模块如pwmio,countio它们由底层C代码驱动效率高。将最关键的循环部分用viper或native装饰器优化高级用法。如果性能是首要考虑可能需要评估是否换用ArduinoC或MicroPython部分场景性能更好等方案。6.3 版本兼容性固件与库的匹配这是一个极易踩坑的地方CircuitPython的库与固件主版本号必须匹配。CircuitPython 8.x 的库不能用在 7.x 的固件上反之亦然。每次升级CircuitPython固件后通常需要重新下载并安装对应版本的库包。检查与更新流程查看当前版本boot_out.txt文件或REPL启动信息。访问circuitpython.org/downloads为你的开发板下载最新固件.uf2文件。进入板子的BOOTLOADER模式通常双击复位键将下载的.uf2文件拖入出现的RPI-RP2或BOOT驱动器完成固件升级。访问circuitpython.org/libraries下载与新固件主版本号相同的库包如固件是8.2.10就下载8.x的库包。用新库包里的库替换CIRCUITPY/lib下的旧库。遵循“固件与库版本匹配”原则可以避免绝大多数莫名其妙的ImportError或运行时错误。从在code.py里修改一个数字改变LED闪烁频率到通过串口控制台洞察程序内部状态再到在REPL中交互式探索硬件最后通过管理库来构建功能丰富的项目——这就是CircuitPython为开发者铺就的一条从入门到精通的平滑路径。它用Python的优雅语法掩盖了底层硬件的复杂性却又通过串口和文件系统保留了足够的透明度和控制力。这种设计哲学使得无论是教育领域的初学者还是需要快速验证想法的专业工程师都能从中获得极高的开发效率。真正的熟练始于动手现在就去拿一块支持CircuitPython的板子从让一颗LED听你指挥开始一步步构建属于你的智能设备吧。