CircuitPython入门:从LED闪烁到硬件交互式编程
1. 项目概述与核心价值如果你刚接触嵌入式开发面对C语言里繁琐的寄存器配置和复杂的编译链可能会感到一阵头大。几年前我第一次尝试用Arduino点亮一个LED光是搭建环境、理解引脚模式就花了大半天。后来接触到CircuitPython它用我们熟悉的Python语法来直接操作硬件那种“原来可以这么简单”的感觉至今记忆犹新。简单来说CircuitPython是运行在微控制器比如Adafruit的Feather、Trinket系列或者树莓派Pico上的Python 3的一个精简实现。它的核心价值就是极大地降低了硬件编程的门槛让你能用写Python脚本的思维去控制LED、读取传感器、驱动电机快速把想法变成可交互的原型。这个项目我们就从最经典的“Hello, World!”硬件版——LED闪烁开始。别小看这个闪烁它涵盖了CircuitPython开发的完整闭环从代码编写、文件管理、硬件控制到串口调试和库管理。你会发现原本在传统嵌入式开发中需要多个步骤、多个工具才能完成的事情在CircuitPython里可能就是几行直白的Python代码。对于物联网设备原型、互动艺术装置、教育机器人或者简单的自动化工具开发来说这无疑能节省大量初期探索的时间。无论你是想快速验证一个传感器方案的学生还是希望为产品增加智能交互功能的设计师CircuitPython都能让你更专注于逻辑本身而非底层细节。2. 环境准备与开发板连接2.1 硬件选择与固件刷写开始之前你需要一块支持CircuitPython的开发板。市面上主流的选择包括Adafruit的系列产品如Feather M4 Express、ItsyBitsy M4和树莓派Pico。选择时可以关注几点处理器性能对于复杂逻辑或大量数据处理、内存大小决定能跑多复杂的程序、以及板载的外设如NeoPixel LED、加速度计等。对于入门一块带有单色用户LED的板子就足够了。拿到板子后第一件事是刷入CircuitPython固件。这通常比想象中简单访问 circuitpython.org/downloads 根据你的开发板型号找到对应的.uf2文件并下载。将开发板通过USB线连接到电脑。大多数板子会以一个可移动磁盘的形式出现名称可能是FEATHERBOOT、RPI-RP2等。将下载好的.uf2文件直接拖入这个磁盘。磁盘会自动弹出几秒后电脑会识别到一个名为CIRCUITPY的新磁盘。这就意味着固件刷写成功你的板子现在已经是一个CircuitPython解释器了。注意刷写固件会清空板载存储的所有数据。如果板子上有旧项目请先做好备份。2.2 代码编辑器与串口终端选择接下来需要一个文本编辑器来写代码。虽然任何纯文本编辑器都可以但集成串口终端的编辑器会极大提升效率。首选推荐Mu EditorMu是一个专为初学者设计的Python编辑器对CircuitPython支持极好。它的优势在于“开箱即用”自动识别打开Mu插上板子它会自动检测到CIRCUITPY盘并切换到CircuitPython模式。内置串口控制台编辑器下方直接集成了终端无需额外配置点击“串口”按钮即可看到程序输出和错误信息。代码检查与提示虽然简单但能提供基本的语法高亮和检查。在Windows或macOS上直接从Mu官网下载安装即可。Linux用户可以通过包管理器安装如sudo apt install mu-editor。备选方案VS Code 插件如果你已经是VS Code用户可以安装CircuitPython和Serial Monitor这类插件。它们能提供代码补全、库管理等功能串口监视器则需要单独打开一个终端标签页。这种方式更灵活适合有一定经验的开发者。关于串口驱动在Windows 7上你可能需要手动安装Adafruit提供的USB串口驱动。Windows 10及以上和macOS通常能自动识别。Linux用户如果遇到连接延迟或乱码可能是modemmanager服务在干扰可以通过命令sudo apt purge modemmanager将其移除。如果遇到权限问题连接时提示无权限通常需要将当前用户加入dialout组sudo adduser $USER dialout然后重启电脑。3. 第一个程序深入理解LED闪烁3.1 代码文件命名与执行机制连接好开发板后打开CIRCUITPY磁盘你会看到一些默认文件。CircuitPython启动时会按顺序寻找并执行以下四个文件中的第一个code.txt,code.py,main.txt,main.py。官方推荐且最常用的名字是code.py。你只需要在CIRCUITPY根目录下创建或编辑这个文件板子就会自动运行它。这里有个新手常踩的坑你明明在改code.py但板子就是不更新效果。这时候请检查根目录下是不是还存在code.txt或main.py等文件。CircuitPython会优先执行它找到的第一个文件如果你的旧main.py还在那么code.py的修改就不会生效。我的习惯是在项目开始时直接删除其他三个候选文件只保留code.py避免混淆。3.2 代码逐行解析与硬件原理现在在CIRCUITPY根目录下创建一个新文件命名为code.py并用编辑器打开输入以下代码import board import digitalio import time led digitalio.DigitalInOut(board.LED) led.direction digitalio.Direction.OUTPUT while True: led.value True time.sleep(0.5) led.value False time.sleep(0.5)保存文件CtrlS。你会发现板子上的LED通常是红色开始以1秒为周期亮0.5秒灭0.5秒稳定闪烁。我们来拆解每一行导入模块 (import): 这行代码告诉CircuitPython“我接下来要用到这些工具箱。”import board: 这是你的开发板的“地图”。它定义了所有物理引脚如D5、A0和板载资源如board.LED的名称。通过它代码才能知道“LED”对应着板子上的哪个具体引脚。import digitalio: 数字输入输出模块。微控制器的引脚可以配置为输入读取开关状态、传感器信号或输出驱动LED、继电器。这个模块提供了配置和控制这些数字引脚的所有方法。import time: 时间模块。最重要的功能就是time.sleep(seconds)它让程序暂停指定的秒数。在嵌入式系统中延时是控制节奏、防止程序跑飞的关键。设置LED引脚:led digitalio.DigitalInOut(board.LED): 这行代码创建了一个数字IO对象并把它关联到板子映射中名为LED的那个引脚。board.LED是一个常量指向板载用户LED的物理连接点。你可以把它理解为给这个硬件引脚起了个软件代号叫led。led.direction digitalio.Direction.OUTPUT: 设置这个引脚的工作模式为“输出”。因为我们要驱动LED发光需要向引脚输出高电平或低电平信号。如果是要读取按钮状态这里就需要设置为INPUT。主循环 (while True): 这是嵌入式程序的灵魂。while True:创建了一个无限循环因为条件True永远为真所以花括号内的代码会一遍又一遍地执行直到断电或复位。led.value True: 将led引脚设置为高电平在大多数开发板上高电平约3.3V。对于共阳极接法的LED这相当于接通正极LED点亮。time.sleep(0.5): 程序暂停0.5秒。在这0.5秒内led.value保持为TrueLED持续发光。led.value False: 将引脚设置为低电平0VLED两端电压差消失LED熄灭。time.sleep(0.5): 再暂停0.5秒LED保持熄灭状态。 之后循环回到开头重复这个过程就产生了闪烁效果。3.3 参数修改与现象探究理解了原理修改就变得直观了。尝试把两个0.5都改成0.1并保存。你会发现LED闪烁频率变快了因为亮和灭的时间都缩短到了0.1秒周期变成了0.2秒。反之如果都改成1闪烁就会变慢。这里可以深入思考一下time.sleep()的参数是秒可以是小数。那么如果设置成0.0110毫秒呢人眼可能会看到LED在快速闪烁但已经有些难以分辨。如果设置成0.0011毫秒呢对于普通的LED由于视觉暂留效应你可能会觉得它只是变暗了而不是在闪烁。这就引出了“脉宽调制”PWM的概念——通过高速开关来控制平均电压从而实现调光但那是更进阶的内容了。实操心得每次修改code.py并保存后CircuitPython都会自动重启并重新运行新代码。你会看到板子上的彩色状态灯如果有的话快速闪烁一下这就是重启的标志。如果修改后没反应首先检查是否保存成功然后观察状态灯是否闪烁了。如果没有可以尝试按一下板子的复位RESET按钮。3.4 无循环程序的行为与复位机制一个关键概念是当code.py中的代码执行完毕后CircuitPython会复位整个微控制器。这意味着所有引脚状态、变量都会被清除恢复到初始状态。我们可以做个实验来验证。把上面的代码改成没有循环的版本import board import digitalio import time led digitalio.DigitalInOut(board.LED) led.direction digitalio.Direction.OUTPUT led.value True time.sleep(2) # 让LED亮2秒方便观察 # 程序到此结束保存后你会看到LED亮起2秒然后永久熄灭。这是因为2秒后代码执行完毕系统复位led对象被销毁引脚恢复默认状态通常是高阻输入LED不再被驱动。因此在CircuitPython中除了极少数一次性任务比如上电后配置一次参数几乎所有的硬件控制程序都需要一个主循环while True:来保持状态。如果程序逻辑上确实需要结束但又不想让硬件复位一个常见的技巧是使用一个空的无限循环来“挂起”程序# ... 你的主要逻辑代码 ... print(程序执行完毕进入空闲状态。) while True: pass # pass是空语句什么都不做但循环让程序不会退出这样你的硬件状态比如某个引脚保持高电平就能在主要逻辑执行完毕后继续保持。4. 串口控制台不可或缺的调试利器4.1 连接与基础使用硬件编程离不开调试而串口控制台Serial Console就是CircuitPython最强大的调试工具。它像一条双向通道你的程序可以通过它向电脑发送文本信息输出你也可以通过它向板子发送简单的命令输入。如果你使用Mu连接非常简单确保板子已连接点击编辑器上方的“串口”按钮底部就会弹出终端窗口。在其他编辑器或独立终端软件如PuTTY、screen、Arduino IDE的串口监视器中你需要手动选择正确的串口设备在Windows设备管理器中是COMx在Linux/Mac上是/dev/ttyACMx或/dev/ttyUSBx并设置波特率CircuitPython固定使用115200。连接成功后终端里可能会显示一些启动信息或者一片空白如果当前code.py没有输出。在Mu的串口窗口中按CtrlD可以软复位板子这会重新执行code.py并看到启动输出。4.2 使用print语句进行输出与调试让我们修改之前的闪烁程序加入输出功能import board import digitalio import time led digitalio.DigitalInOut(board.LED) led.direction digitalio.Direction.OUTPUT counter 0 # 新增一个计数器 while True: print(Loop count:, counter, - LED is ON) led.value True time.sleep(0.5) print(Loop count:, counter, - LED is OFF) led.value False time.sleep(0.5) counter 1 # 计数器加1保存后观察串口控制台。你会看到“LED is ON/OFF”的信息伴随着计数不断滚动。这就是print()函数的作用将括号内的内容转换成文本发送到串口。你可以打印字符串、变量、甚至表达式的结果。“打印调试法”Print Debugging是排查问题的黄金手段。当程序行为异常时在关键位置插入print()语句可以帮你确认程序执行到了哪一步变量的值是否符合预期。比如一个传感器读数总是异常你可以在读取数据后立刻print(“Raw sensor value:”, raw_value)看看硬件到底传回了什么。4.3 解读错误信息Traceback故意制造一个错误来看看串口控制台如何帮助我们。将上面代码中的led.value True改成led.value Tru去掉e保存。 你的LED会停止闪烁串口控制台会立刻显示类似这样的错误信息Traceback (most recent call last): File code.py, line 10, in module NameError: name Tru is not defined这就是Python的“回溯”Traceback信息极其宝贵Traceback (most recent call last):告诉你接下来是错误发生时的调用栈。File code.py, line 10, in module明确指出错误发生在code.py文件的第10行在模块的主代码中。NameError: name Tru is not defined是错误类型和描述名称错误Tru这个变量名没有被定义过。结合行号你就能快速定位到出错的代码行。在这个例子里我们一眼就知道是True拼错了。但在复杂的项目中它可能是你忘记导入某个模块或者变量名打错了。学会阅读Traceback是独立解决问题的第一步。注意事项每次你保存code.py文件CircuitPython都会自动复位并重新运行。因此Traceback的第一行Traceback (most recent call last):后面显示的总是上一次运行即你保存前的那次运行的最后状态。这是正常现象并非当前代码有问题。5. REPL交互式编程与实时探索5.1 进入与退出REPLREPLRead-Evaluate-Print-Loop是CircuitPython的交互式解释器。你可以把它想象成一个“Python命令行”直接运行在板子上。在串口控制台连接的状态下按CtrlC即可中断当前正在运行的程序并进入REPL。如果当前有程序在运行比如我们的闪烁程序你会先看到程序被中断的Traceback然后提示Press any key to enter the REPL. Use CTRL-D to reload.此时按任意键即可进入。如果code.py是空的或者没有循环可能会直接显示Code done running.同样按任意键进入。进入后你会看到提示符以及类似下面的欢迎信息Adafruit CircuitPython 7.3.3 on 2022-08-29; Adafruit Feather M4 Express with samd51j19 这告诉你固件版本、日期和板子型号。要退出REPL并重新运行code.py按CtrlD。板子会复位你的主程序将重新开始。重要警告REPL中输入的代码是临时性的一旦按CtrlD复位或断电所有输入的内容都会消失。任何你想保留的代码都必须复制出来保存到code.py或其他文件中。5.2 使用REPL进行硬件探索与测试REPL的强大之处在于实时交互。你可以逐行执行代码立即看到结果非常适合探索和学习。获取帮助在后输入help()会显示基本帮助信息其中最关键的一行是To list built-in modules type help(modules)。输入help(modules)会列出所有内置模块比如board、digitalio、time、analogio等。这就像一份硬件功能清单。探索板子引脚让我们看看board模块里有什么。 import board dir(board)dir()函数会列出对象的所有属性。你会看到一个列表里面包含了像LED、D5、A0、SCL、SDA、TX、RX这样的名称。这些就是你可以在代码中使用的所有引脚标识符。找到LED说明你的板载LED在board模块中的名字就是LED。实时控制硬件不用写完整的code.py直接在REPL里点亮LED试试。 import board import digitalio import time led digitalio.DigitalInOut(board.LED) led.direction digitalio.Direction.OUTPUT led.value True # LED应该立刻点亮 led.value False # LED熄灭 led.value True time.sleep(1) # 这里REPL会阻塞1秒期间LED保持亮 led.value False看到没你就像在用Python命令行直接跟硬件对话。这对于测试某个引脚是否工作、传感器是否连接正确或者快速验证一小段逻辑效率极高。计算与测试REPL也是一个计算器。你可以进行数学运算、字符串操作或者测试一个你不太确定的函数用法。6. 库管理扩展CircuitPython的无限可能6.1 理解库与模块CircuitPython的强大很大程度上来自于其丰富的库生态。库Library就是别人写好的、实现特定功能的代码包。前面用的board、digitalio、time是内置模块它们被固化在CircuitPython固件里。而更多的功能比如驱动特定型号的显示屏、读取复杂传感器、连接Wi-Fi则需要外部库。这些外部库以文件的形式存放在CIRCUITPY磁盘的lib文件夹内。当你的代码执行import something时CircuitPython会先在内置模块中找如果找不到就会去lib文件夹里找对应的.mpy或.py文件。这种设计使得功能扩展非常灵活你不需要为了用一个新传感器而刷写整个固件。6.2 获取库文件两种官方Bundle库文件从哪里来官方提供了两种“捆绑包”Bundle里面集合了成百上千个库。Adafruit电路Python库包Adafruit CircuitPython Library Bundle 这是由Adafruit官方维护的支持其出售的绝大多数传感器、显示屏、扩展板等硬件。这是最常用、支持最完善的库集合。下载地址 CircuitPython Libraries关键点必须下载与你的CircuitPython固件主版本号匹配的Bundle例如你运行的是CircuitPython 7.x就必须下载7.x的Bundle。混合使用会导致mpy文件不兼容的错误。你可以在CIRCUITPY盘根目录的boot_out.txt文件里或者REPL启动的第一行信息中查看固件版本。社区库包CircuitPython Community Library Bundle 这是由全球CircuitPython社区开发者贡献的库集合包含了许多Adafruit Bundle中没有的、针对其他硬件或特殊项目的驱动。下载地址 CircuitPython Community Bundle注意事项这些库由社区成员个人维护响应速度和支持力度可能不如官方库。如果遇到问题可以去该库的GitHub仓库提交Issue但需要多一些耐心。6.3 库的安装与管理实操假设你已经下载了匹配版本的Adafruit Bundle一个ZIP文件。解压与查找解压ZIP文件。里面通常有lib和examples两个主要文件夹。lib文件夹里就是所有可用的库。复制所需库打开CIRCUITPY磁盘确保里面有一个lib文件夹如果没有就新建一个。然后从解压后的lib文件夹里找到你项目需要的库文件或文件夹。情况一单个.mpy文件。例如你需要使用adafruit_bme280库一个温湿度气压传感器驱动。在Bundle的lib里找到adafruit_bme280.mpy直接复制到CIRCUITPY/lib/下即可。情况二一个库文件夹。很多复杂库由多个文件组成。例如adafruit_display_text库。在Bundle的lib里你会看到一个同名的文件夹。你需要将整个文件夹复制到CIRCUITPY/lib/下。检查依赖很多库依赖于其他库。例如adafruit_bme280可能依赖于adafruit_bus_device。在Bundle的库说明或对应GitHub页面的README中通常会写明依赖。你必须把所有依赖库也一并复制到lib文件夹下否则import时会报ModuleNotFoundError。避坑指南新手最容易犯的错误就是库复制不全。如果你的代码在电脑模拟器上运行正常但放到板子上就报导入错误99%的原因是缺少某个.mpy文件或整个依赖库文件夹。养成习惯根据错误信息回头仔细检查lib文件夹里的内容。6.4 使用项目包Project Bundle在Adafruit的学习系统Learn Guide中许多复杂项目页面会提供一个“Download Project Bundle”按钮。这是一个极大的便利。这个ZIP包不仅包含了code.py还包含了该项目所需的所有库文件已经在lib文件夹里甚至可能还有图片、字体等资源文件。使用方法点击下载项目包并解压。解压后根据你的CircuitPython版本进入对应文件夹如7.x。你会看到完整的项目结构包括code.py和lib文件夹。重要将解压出的code.py和lib文件夹及其全部内容整体复制到CIRCUITPY磁盘的根目录。系统会提示覆盖点击确认。警告复制项目包会覆盖你CIRCUITPY盘上现有的code.py和lib文件夹在操作前请务必将你当前正在工作的code.py备份到电脑上。我个人的工作流是在电脑上为每个项目建立一个文件夹code.py只在电脑上编辑调试时才复制到CIRCUITPY盘。lib文件夹则相对稳定只有添加新库时才去更新。7. 从示例到创作构建完整项目工作流掌握了以上核心技能你已经可以开始创作自己的项目了。一个典型的CircuitPython项目工作流如下明确需求与硬件选型我想做一个温湿度显示器一个蓝牙控制的灯根据需求选择主板和传感器。搭建硬件按照传感器数据手册或教程正确连接线路VCC, GND, SCL, SDA等。寻找驱动库在Adafruit Bundle或社区Bundle中查找传感器对应的库。通常库名与传感器型号相关如adafruit_si7021。安装库将库文件及依赖复制到CIRCUITPY/lib/。编写与迭代代码在电脑上新建一个code.py文件开始编写。从示例开始很多库的examples文件夹里都有简单的示例代码。复制过来修改引脚定义从board.SCL/board.SDA开始尝试先确保能跑通。增量修改在示例基础上加入你的逻辑。频繁使用print()在串口控制台输出传感器数据、状态变量验证每一步。利用REPL遇到问题用CtrlC进入REPL手动import库创建对象调用方法看哪一步出错。测试与调试将写好的code.py复制到CIRCUITPY盘根目录观察现象。利用串口控制台的Traceback和print输出定位问题。优化与固化代码稳定后可以考虑优化逻辑、减少print输出以提升性能、添加错误处理等。如果项目是独立的可以最后移除调试用的print语句。在这个过程中串口控制台和REPL是你最忠实的伙伴。它们让你能“看见”代码在硬件上的运行过程将黑盒变成白盒。而清晰的库管理则是项目可维护、可复现的基石。记住这个流程结合LED闪烁中学到的硬件控制、循环、延时等核心概念你已经具备了用CircuitPython探索更广阔硬件世界的基础能力。