CircuitPython嵌入式开发实战:从SAMD21板卡入门到环境光控灯项目
1. 项目概述为什么选择CircuitPython如果你和我一样是从Arduino的C/C世界或者MicroPython社区转过来的第一次接触CircuitPython时可能会觉得有点“过于简单”。不就是把Python解释器塞进单片机里吗但真正上手几个项目后你会发现它的设计哲学远不止于此。CircuitPython的核心价值在于它将嵌入式开发的“编译-烧录-调试”循环简化到了近乎“即写即得”的程度。你不再需要复杂的IDE、交叉编译工具链甚至不需要按复位键。只需一个文本编辑器修改CIRCUITPY盘符下的code.py文件保存代码立刻自动重启运行。这种开发体验的流畅性对于快速原型验证、教学和创意实现来说是革命性的。本次实践我们将以Adafruit的SAMD21系列非Express板卡如Trinket M0、GEMMA M0作为硬件平台。这类板卡的特点是内置闪存容量有限通常256KB没有外置的SPI Flash来存储文件系统因此其CIRCUITPY驱动器空间非常宝贵可能只有几十KB。这既是挑战也是学习资源管理的好机会。我们将从最底层的系统安装与恢复讲起逐步深入到控制板载NeoPixel、读取模拟信号等核心操作过程中会穿插大量我在实际项目中踩过的坑和总结的优化技巧。无论你是想为智能家居设备添加一个小巧的控制器还是制作一个可穿戴的互动艺术品这篇指南都能为你铺平道路。2. 硬件准备与系统安装工欲善其事必先利其器。在开始编程之前确保你的硬件和软件环境就绪至关重要。对于SAMD21非Express板卡这个过程有一些独特的细节需要注意。2.1 认识你的开发板与UF2引导程序首先你需要明确你的板卡类型。Adafruit的SAMD21板卡主要分为两类Express和非Express。两者的核心区别在于存储架构Express板卡如Feather M0 Express、Metro M0 Express它们除了主控芯片的内置闪存还额外搭载了一片SPI Flash芯片通常是2MB或更大专门用于存放CircuitPython的文件系统CIRCUITPY驱动器和用户库。因此空间充裕。非Express板卡如Trinket M0、GEMMA M0、QT Py M0。它们仅依赖SAMD21芯片内置的256KB闪存。这片闪存需要同时存放CircuitPython固件、引导程序、文件系统和你的代码。CIRCUITPY驱动器的可用空间可能只有20-30KB比一张老式软盘还小。对于非Express板卡引导程序又分为两种UF2引导程序和非UF2引导程序。UF2是微软推出的一种特殊的文件格式其最大优点是在支持它的操作系统如Windows 10、macOS、Linux上你可以像拷贝文件一样“拖拽”固件进行烧录无需额外工具。如何判断你的板卡是否有UF2引导程序一个简单的方法是将板卡通过USB连接到电脑然后快速双击板载的复位按钮Reset。如果电脑上出现一个名为TRINKETBOOT、GEMMABOOT或类似[板卡名]BOOT的可移动磁盘那么恭喜你的板卡搭载了UF2引导程序。如果没有出现新的磁盘而是串口设备重新枚举那么它可能使用的是传统的bossac引导程序。2.2 安装CircuitPython固件对于有UF2引导程序的板卡安装过程非常直观下载固件访问CircuitPython官网找到对应你板卡型号的最新.uf2固件文件并下载。进入引导模式用USB数据线连接板卡和电脑。快速双击板卡上的复位按钮。此时电脑上应该会出现一个名为[板卡名]BOOT的驱动器。拖拽烧录将下载好的.uf2文件直接拖拽或复制到[板卡名]BOOT驱动器中。驱动器会自动弹出板卡将重启。完成几秒钟后电脑上会出现一个新的名为CIRCUITPY的可移动磁盘。这表明CircuitPython已成功运行。注意首次完成安装后CIRCUITPY驱动器里通常只有boot_out.txt和一个空的lib文件夹。code.py文件需要你自己创建。这是你的主程序入口。对于没有UF2引导程序的板卡如Feather M0 Basic Proto你需要使用bossac命令行工具进行刷写。这需要从Adafruit的GitHub仓库下载bossac并通过命令行执行刷写命令。虽然步骤稍多但官网通常有详细的指南。核心命令类似于bossac -e -w -v -R --portCOMxx firmware.bin。其中-e代表擦除-w是写入-v是校验-R是复位COMxx需要替换成你的板卡实际占用的串口号。2.3 空间危机管理微小的CIRCUITPY驱动器这是使用SAMD21非Express板卡时最先、也最常遇到的问题。你的“硬盘”只有几十KB放几个库文件可能就满了。别慌我们有多种策略来应对。策略一精简文件手动瘦身最直接的方法是删除不必要的文件。清理库文件检查lib文件夹。CircuitPython固件本身包含一些核心模块如time,board,digitalio但像neopixel、adafruit_bus_device等常用库需要额外安装。只保留你当前项目确实需要的库用完后及时删除。一个.mpy编译后的库文件可能就有几KB到十几KB。删除驱动文件Adafruit的板卡CIRCUITPY里有时会附带一个Windows 7 Driver文件夹这是为旧版Windows准备的串口驱动。如果你使用的是Windows 10/11或macOS/Linux可以安全删除这个文件夹它能释放大约12KB的空间。合并代码如果你的code.py很长可以考虑将部分功能模块化但要注意每个额外的.py文件都会占用空间。在空间极度紧张时将所有代码写在一个文件里反而是更节省的虽然不利于维护。策略二代码格式化技巧——用Tab代替空格这是一个非常实用但容易被忽略的技巧。Python依靠缩进来定义代码块通常建议使用4个空格。但在字节寸土寸金的微控制器上一个Tab字符\t只占1个字节而4个空格占4个字节。如果你的代码有很深的嵌套层次这个改动能省下可观的空间。大多数现代代码编辑器如VS Code、Atom都可以设置“用Tab代替空格”或“保存时将缩进转换为Tab”。策略三对抗macOS的“隐藏文件”如果你用的是macOS那么CIRCUITPY可能会被系统“悄悄”塞满一堆隐藏文件如._.DS_Store、._.fseventsd等这些文件会迅速吞噬宝贵空间。永久性预防推荐 在终端中执行以下命令可以禁止macOS在CIRCUITPY卷上创建这些文件# 首先找到你的CIRCUITPY卷的挂载点通常是 /Volumes/CIRCUITPY ls /Volumes # 禁用该卷的Spotlight索引 sudo mdutil -i off /Volumes/CIRCUITPY # 进入该卷并清理/阻止特定隐藏文件 cd /Volumes/CIRCUITPY sudo rm -rf .{,_.}{fseventsd,Spotlight-V*,Trashes} sudo mkdir .fseventsd sudo touch .fseventsd/no_log .metadata_never_index .Trashes cd -执行后这些烦人的隐藏文件就不会再自动生成了。使用CircuitPython命令擦除治本 在CircuitPython的REPL串行交互界面中你可以直接擦除并重建整个文件系统这会得到一个“干净”的、已针对macOS优化过的文件系统。 import storage storage.erase_filesystem()警告这个命令会立刻、不可逆地清除CIRCUITPY上的所有文件请务必先备份你的code.py和必要的库文件。策略四安全的文件拷贝方式即使做了预防从网上下载的文件比如从GitHub下载的库被拷贝到macOS时仍可能产生._前缀的扩展属性文件。为了避免这种情况永远不要用Finder直接拖拽复制。请使用终端的cp命令并加上-X参数# 拷贝单个文件不复制扩展属性 cp -X neopixel.mpy /Volumes/CIRCUITPY/lib/ # 递归拷贝整个文件夹不复制扩展属性 cp -rX my_library_folder /Volumes/CIRCUITPY/lib/-X参数告诉macOS不要拷贝扩展属性从而杜绝了隐藏文件的产生。3. 从“Hello, World!”开始闪烁NeoPixel在编程世界“Hello, World!”是起点。在嵌入式世界这个起点就是让一个LED闪烁。CircuitPython板卡通常都板载了一个RGB NeoPixel LED它就是我们完美的“Hello, World!”对象。3.1 NeoPixel工作原理简介NeoPixel是Adafruit对WS2812系列可寻址RGB LED的商标名称。它的神奇之处在于只需要一根信号线就能控制成百上千个LED并且每个LED的颜色和亮度都可以独立编程。板载的这颗NeoPixel本质上是一个集成了红色、绿色、蓝色三个LED芯片以及一个WS2812驱动IC的微型封装。在CircuitPython中我们通过neopixel库来控制它。通信协议是严格的单线时序信号库函数帮我们处理了所有底层的时序细节我们只需要关心颜色值。3.2 第一个程序让LED呼吸起来在CIRCUITPY根目录下创建或编辑code.py文件输入以下代码# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries # SPDX-License-Identifier: MIT CircuitPython NeoPixel闪烁示例 import time import board import neopixel # 1. 初始化NeoPixel对象 # board.NEOPIXEL 是这块板子上NeoPixel的预定义引脚 # 第二个参数 1 表示我们只控制1个NeoPixel板载的就这一个 pixel neopixel.NeoPixel(board.NEOPIXEL, 1) # 2. 设置亮度可选范围0.0-1.0 pixel.brightness 0.3 # 设置为30%亮度防止太刺眼 # 3. 主循环 while True: # 填充红色 (R, G, B) pixel.fill((255, 0, 0)) time.sleep(0.5) # 等待0.5秒 # 关闭LED (0, 0, 0) pixel.fill((0, 0, 0)) time.sleep(0.5)保存文件。你会立刻看到板载的RGB LED开始以红色闪烁。代码逐行解析与避坑指南导入库time用于延时board包含了这块板子所有引脚的预定义名neopixel是控制LED的核心库。如果保存后提示ModuleNotFoundError: No module named neopixel说明neopixel.mpy库文件不在/lib文件夹中。你需要从Adafruit的CircuitPython库包中获取并拷贝进去。对象初始化neopixel.NeoPixel()是构造函数。第一个参数是引脚使用board.NEOPIXEL这个常量是最可靠的方式它适配了当前板卡的正确引脚。第二个参数是LED数量。这里有一个大坑如果你要控制外接的LED灯带数量一定要填对。填少了后面的LED不受控填多了程序可能会因为访问超出范围的内存而崩溃。亮度设置brightness属性是一个全局乘数。(255, 0, 0)在亮度0.3下实际输出的红色值只有255*0.3≈76。重要提示修改brightness会影响颜色准确性尤其是低亮度时。对于颜色要求高的项目建议在代码中直接计算低亮度下的RGB值而将brightness保持在1.0。颜色元组(R, G, B)。每个值范围0-255。(255,0,0)是纯红(0,255,0)是纯绿(0,0,255)是纯蓝。(255,255,255)是白色(0,0,0)是熄灭。主循环while True:是一个无限循环。嵌入式程序几乎都是这种结构因为没有操作系统来调度任务你的程序需要一直运行来处理逻辑。实操心得如何调试没有输出的程序如果LED没亮按以下步骤排查检查电源USB口供电是否稳定外接LED灯带时务必单独供电USB的500mA可能带不动很多LED。检查接线信号线Din是否接对了LED灯带的数据流向是单向的Din接控制信号Dout接下一个LED的Din。检查库文件确认neopixel.mpy或neopixel.py文件在/lib目录下。检查引脚确认代码中使用的引脚号与物理连接一致。对于外接灯带可能要用board.D5这样的具体数字引脚而不是board.NEOPIXEL。查看串口输出连接串行终端如Mu编辑器、PuTTY、screen /dev/ttyACM0 115200看看是否有错误信息打印出来。这是最有效的调试手段。4. 与物理世界交互数字输入与模拟读取让LED闪烁只是单向输出。一个完整的嵌入式项目必须能感知外部世界。这主要通过数字输入和模拟输入来实现。4.1 数字输入用按钮控制LED数字信号只有两种状态高电平通常为3.3V或5V代表逻辑1和低电平0V代表逻辑0。最常见的数字输入设备就是按钮。我们将实现“按下按钮点亮LED松开按钮熄灭LED”的功能。这需要用到digitalio模块。硬件连接 大多数开发板都有一个预定义的BOOT或BUTTON按钮其引脚已经在board模块中定义好如board.BUTTON或board.D0。如果你的板子没有或者你想用外接按钮需要连接一个 tactile switch。连接方式按钮一脚接开发板的某个数字引脚如board.D2另一脚接地GND。通常不需要额外电阻因为我们会使用芯片内部的上拉电阻。代码实现# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries # SPDX-License-Identifier: Unlicense CircuitPython数字输入示例 - 使用按钮开关控制板载NeoPixel import board import digitalio import neopixel # 初始化NeoPixel pixel neopixel.NeoPixel(board.NEOPIXEL, 1) pixel.brightness 0.1 # 亮度调低按钮控制时看着舒服 # 初始化按钮引脚为输入模式并启用内部上拉电阻 button digitalio.DigitalInOut(board.BUTTON) # 使用板载BOOT按钮 button.switch_to_input(pulldigitalio.Pull.UP) while True: # 读取按钮状态 # 当按钮按下时引脚被拉到GND低电平button.value 为 False # 当按钮松开时内部上拉电阻将引脚拉到3.3V高电平button.value 为 True if not button.value: # 如果按钮被按下值为False pixel.fill((0, 255, 0)) # 点亮为绿色 else: pixel.fill((0, 0, 0)) # 熄灭关键原理与技巧上拉电阻pulldigitalio.Pull.UP这行代码启用了微控制器内部的电阻将引脚电压“拉高”到3.3V。当按钮未按下时引脚通过这个电阻连接到3.3V我们读到True高电平。当按钮按下时引脚直接短路到GND0V电压被“拉低”我们读到False。这种方式可以避免引脚悬空时产生不确定的随机值。消抖机械按钮在按下和松开的瞬间金属触点会发生物理弹跳导致电平在极短时间内快速变化多次。上面的简单代码没有处理这个问题在要求严格的场合如计数可能会误触发。一个简单的软件消抖方法是检测到状态变化后延时一小段时间如10毫秒再读取一次确认状态。CircuitPython的keypad库提供了更专业的消抖和事件处理功能。not button.value因为上拉模式下按下是False松开是True。所以判断按下的条件就是not button.value非真即假取反。4.2 模拟输入读取电位器与电压值模拟信号是连续变化的电压值。微控制器通过模数转换器ADC将这个连续的电压值离散化成一个数字。对于SAMD21其ADC是12位的但CircuitPython为了统一接口将其值映射到了0-6553516位无符号整数的范围。硬件连接电位器作为电压分压器我们用一个10KΩ的电位器可变电阻来演示。电位器有三个引脚两端假设为A和C接电源3.3V和地GND中间引脚B也叫滑片接ADC引脚如board.A0。当滑片转到A端B点电压等于A点电压3.3VADC读到接近65535。当滑片转到C端B点电压等于C点电压0VGNDADC读到接近0。滑片在中间B点电压是3.3V的一半1.65VADC读到大约32767。代码实现读取原始ADC值# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries # SPDX-License-Identifier: MIT CircuitPython模拟引脚原始值读取示例 import time import board import analogio # 初始化A0引脚为模拟输入 analog_pin analogio.AnalogIn(board.A0) while True: # 读取原始ADC值范围 0-65535 raw_value analog_pin.value print(Raw ADC:, raw_value) time.sleep(0.1) # 延时100ms避免串口输出刷屏太快打开串行终端波特率115200旋转电位器你会看到数值在0-65535之间变化。进阶将ADC值转换为实际电压原始ADC值不够直观。我们更关心的是实际的电压值。转换公式基于一个比例关系电压 (ADC原始值 / ADC最大可能值) * 参考电压在CircuitPython中ADC最大可能值是65535参考电压通常是3.3V对于SAMD21。def get_voltage(pin): 将ADC原始值转换为电压值 (单位: 伏特) return (pin.value * 3.3) / 65535 while True: voltage get_voltage(analog_pin) print(Voltage: {:.2f} V.format(voltage)) # 格式化输出保留两位小数 time.sleep(0.1)现在串口输出的就是直观的电压值了范围大约在0V到3.3V之间。实操心得ADC的精度与噪声分辨率12位ADC的理论分辨率是 3.3V / 4096 ≈ 0.8mV。但实际受噪声影响有效位数会降低。噪声电源噪声、数字电路干扰都会影响ADC读数。你可能会发现即使电位器不动读到的最后几位数字也在跳动。对于要求不高的应用如音量调节可以软件滤波比如连续采样10次取平均值。参考电压公式中的3.3V是假设ADC的参考电压就是系统电压。对于高精度测量有些MCU有独立的、更稳定的参考电压引脚。SAMD21非Express板卡通常直接用3.3V作为参考其精度取决于LDO稳压器的质量。5. 深入核心读取CPU温度与更多传感器微控制器内部往往集成了许多有用的外设其中一个就是温度传感器。它可以用来监测芯片的工作温度对于评估散热情况或制作温度相关的应用很有帮助。5.1 读取内部CPU温度CircuitPython通过microcontroller模块提供了访问CPU温度的接口非常简单import microcontroller import time while True: temp_c microcontroller.cpu.temperature print(CPU Temperature: {:.1f} C.format(temp_c)) time.sleep(2) # 每2秒读一次温度变化没那么快原理与注意事项 这个温度传感器测量的是CPU芯片内部结温而不是环境温度。当CPU全速运行复杂代码时其温度会比环境温度高不少。你可以尝试运行一个计算密集型的循环比如for i in range(1000000): x i*i同时打印温度会看到明显的上升。因此它更适合用于监控芯片自身是否过热而不是精确测量室温。转换为华氏度 如果需要可以轻松转换temp_f temp_c * 9/5 32。5.2 连接外部I2C传感器以MCP9808为例内部传感器功能有限。要测量环境温度、湿度、气压等需要连接外部传感器。I2C总线是连接这类传感器的常用方式它只需要两根线SDA数据线SCL时钟线就能连接多个设备。我们以高精度温度传感器MCP9808为例。首先你需要将传感器用STEMMA QT/Qwiic连接线或杜邦线连接到开发板的I2C引脚上。对于大多数板子I2C引脚是board.SDA和board.SCL。步骤一安装库MCP9808不是内置库。你需要从Adafruit的CircuitPython库包中找到adafruit_mcp9808.mpy文件并将其拷贝到CIRCUITPY驱动器的lib文件夹中。同时它可能依赖adafruit_bus_device和adafruit_register库确保一并拷贝。步骤二编写代码import time import board import busio import adafruit_mcp9808 # 1. 创建I2C总线对象 i2c busio.I2C(board.SCL, board.SDA) # 2. 创建传感器对象传入I2C总线对象 # 0x18是MCP9808的默认I2C地址 sensor adafruit_mcp9808.MCP9808(i2c, address0x18) while True: # 3. 读取温度摄氏度 temp_c sensor.temperature temp_f temp_c * 9 / 5 32 print(Temperature: {:.2f} C {:.2f} F.format(temp_c, temp_f)) time.sleep(1)I2C排查技巧 如果运行后没有输出或报错可以尝试以下方法检查接线SDA、SCL、VCC3.3V、GND四根线是否接对、接牢。检查地址使用I2C扫描程序确认设备地址。很多传感器有地址选择引脚可以改变地址。import board import busio i2c busio.I2C(board.SCL, board.SDA) while not i2c.try_lock(): pass try: print(I2C addresses found:, [hex(addr) for addr in i2c.scan()]) finally: i2c.unlock()检查上拉电阻I2C总线需要上拉电阻通常4.7KΩ将SDA和SCL线拉到高电平。许多开发板包括大多数Adafruit板和传感器模块如MCP9808 breakout已经内置了这些电阻。如果使用裸传感器芯片你必须自己添加外部上拉电阻。6. 项目实战制作一个环境光控灯让我们把前面学到的知识组合起来做一个简单但完整的小项目一个根据环境光强度自动调节亮度的“小夜灯”。我们用光敏电阻或任何模拟输出的光传感器读取环境光用NeoPixel LED作为灯。硬件清单CircuitPython开发板如Trinket M0光敏电阻模块或模拟环境光传感器如Adafruit ALS-PT1910KΩ电阻如果使用裸光敏电阻面包板和杜邦线电路连接使用光敏电阻分压电路将光敏电阻一端接3.3V。将光敏电阻另一端接一个10KΩ固定电阻固定电阻另一端接GND。光敏电阻与固定电阻的连接点接到开发板的模拟输入引脚A0。这样光照越强光敏电阻阻值越小A0点电压越接近3.3V光照越弱A0点电压越接近0V。代码实现import time import board import analogio import neopixel # 初始化硬件 light_sensor analogio.AnalogIn(board.A0) led neopixel.NeoPixel(board.NEOPIXEL, 1) led.brightness 0.5 # 设置一个基础亮度上限防止太亮 # 校准参数需要在实际环境中测试得到 DARK_THRESHOLD 15000 # ADC值低于此值认为环境很暗 BRIGHT_THRESHOLD 50000 # ADC值高于此值认为环境很亮 def map_value(value, in_min, in_max, out_min, out_max): 将一个数值从一个区间线性映射到另一个区间 return (value - in_min) * (out_max - out_min) / (in_max - in_min) out_min while True: # 读取光照强度 light_level light_sensor.value # 根据光照计算LED亮度 (反向越暗LED越亮) if light_level DARK_THRESHOLD: # 环境很暗LED全亮暖黄色 led.fill((255, 150, 0)) # 暖黄色 elif light_level BRIGHT_THRESHOLD: # 环境很亮LED关闭 led.fill((0, 0, 0)) else: # 中间状态亮度随光照平滑变化 # 将光照水平映射到亮度系数 1.0 - 0.0 (暗-亮) brightness_factor map_value(light_level, DARK_THRESHOLD, BRIGHT_THRESHOLD, 1.0, 0.0) # 将亮度系数限制在0.0-1.0之间 brightness_factor max(0.0, min(1.0, brightness_factor)) # 计算实际RGB值 (暖黄色 * 亮度系数) r int(255 * brightness_factor) g int(150 * brightness_factor) b 0 led.fill((r, g, b)) time.sleep(0.1) # 每100ms更新一次项目优化与思考校准DARK_THRESHOLD和BRIGHT_THRESHOLD需要你在实际使用环境中测试确定。可以在代码开始时加一个校准模式打印出当前的light_sensor.value然后你在明暗环境下分别记录数值。滤波光敏电阻的响应可能有噪声或抖动。可以在代码中加入一个简单的移动平均滤波维护一个最近N次读数的列表取平均值作为当前光照水平。省电如果使用电池供电可以考虑在环境很亮时让MCU进入轻睡眠模式time.sleep()更长的时间以节省电力。扩展可以加入一个按钮切换自动模式/手动模式/常亮模式。在手动模式下可以用另一个电位器来调节LED亮度。7. 故障排除与高级技巧即使按照指南操作你也难免会遇到问题。这里汇总了一些常见问题的排查思路和高级使用技巧。7.1 常见问题速查表问题现象可能原因排查步骤连接电脑后无CIRCUITPY盘符1. 板卡未进入CircuitPython模式还在引导程序模式。2. USB线仅供电无数据功能。3. 驱动问题Windows旧系统。4. 固件损坏。1. 按复位键一次等待几秒。2. 换一根已知良好的USB数据线。3. 检查设备管理器尝试重新安装Adafruit驱动。4. 重新进入引导模式刷写固件。代码保存后不运行或报错1. 语法错误。2. 缺少库文件。3. 文件命名错误必须是code.py或main.py。4. 硬件初始化失败。1.连接串口终端查看具体错误信息。这是最重要的步骤2. 检查lib文件夹确认所需库文件存在且版本匹配。3. 确认主程序文件名为code.py。4. 检查硬件连接是否正确、牢固。NeoPixel不亮或颜色异常1. 电源不足尤其对外接灯带。2. 信号线接错或接触不良。3. 初始化时LED数量参数错误。4. 代码中亮度设置为0。1. 外接灯带务必使用独立电源并将电源地与开发板地相连。2. 确认信号线Din连接正确。3. 检查NeoPixel()构造函数第二个参数。4. 检查pixel.brightness或颜色值是否为(0,0,0)。模拟读数不稳定跳动1. 电源噪声。2. 信号干扰。3. 传感器或电位器本身噪声。1. 在模拟输入引脚和GND之间加一个0.1uF的电容滤波。2. 使用屏蔽线远离数字信号线。3. 软件滤波连续采样多次取平均值。CIRCUITPY盘符突然消失/只读1. 文件系统损坏常见于不正常弹出。2. 代码陷入死循环或硬件错误导致系统崩溃。1. 安全弹出硬件后再拔线。2. 进入安全模式快速按两次复位如果第二次在CIRCUITPY出现前按住LED会呈绿色呼吸此时code.py和boot.py不执行可以修复或删除问题文件。3. 严重时需用storage.erase_filesystem()或重新刷固件。7.2 高级技巧优化性能与内存对于SAMD21这类资源受限的MCU优化至关重要。使用.mpy文件库文件有.py源代码和.mpy预编译字节码两种格式。.mpy文件加载更快占用内存更少。始终优先使用.mpy版本。冻结模块对于永远不会更改的核心库甚至是你自己写的常用函数可以将其“冻结”到CircuitPython固件中。这需要从源码编译固件属于高级操作但可以极大节省CIRCUITPY空间和启动时的RAM占用。管理内存避免在循环中创建大的列表或字典。使用gc.collect()手动触发垃圾回收需要先import gc特别是在完成一个大任务后。使用sys.getsizeof()查看对象占用内存情况import sys。使用array代替list处理大量数值数据array模块提供了紧凑的数值数组比Python列表更节省内存。谨慎使用浮点数SAMD21没有硬件浮点单元FPU浮点运算由软件模拟非常慢。在性能关键的循环中尽量使用整数运算。例如颜色计算可以全部用0-255的整数完成。7.3 深入理解boot.py与代码执行顺序CIRCUITPY根目录下有两个特殊的Python文件boot.py在CircuitPython启动时首先执行且只执行一次。通常用于进行一些非常早期的硬件初始化或者修改USB配置例如将板卡设置为MIDI设备而非串行设备。注意在boot.py中打印信息是看不到的因为此时USB可能还未就绪。code.py在boot.py执行完毕后自动开始执行的主程序文件。你的主要代码在这里。main.py如果存在code.py则main.py不会被执行。只有当code.py不存在时CircuitPython才会尝试执行main.py。这是一种备选机制。一个boot.py的典型用例——禁用自动重载 当你通过串口与传感器通信时每次保存code.py导致的自动重载可能会打断通信。可以在boot.py中加入以下代码来禁用自动重载import supervisor supervisor.runtime.autoreload False这样只有手动按复位键时代码才会重启。从点亮第一个LED到读取传感器数据再到构建一个完整的互动项目CircuitPython以其极低的入门门槛和强大的表达能力让嵌入式开发变得直观而有趣。对于SAMD21非Express这类小容量板卡虽然空间限制带来了挑战但也迫使你养成精简、高效编码的好习惯。记住遇到问题第一反应是打开串口终端看错误信息空间不够了就想想哪些文件能删代码能不能用Tab缩进想做复杂项目时先评估资源必要时升级到Flash更大的Express板卡。嵌入式开发的世界很大CircuitPython是你探索这个世界的一把顺手且快乐的钥匙。