news 2026/5/16 7:04:07

CircuitPython入门:从零开始构建物联网原型,简化嵌入式开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CircuitPython入门:从零开始构建物联网原型,简化嵌入式开发

1. 项目概述与CircuitPython核心价值

如果你对硬件编程感兴趣,但又觉得C/C++的编译、烧录过程过于繁琐,或者对Arduino的语法感到有些束手束脚,那么CircuitPython很可能就是你一直在寻找的“捷径”。它本质上是一个基于Python 3的轻量级解释器,被设计成可以直接运行在微控制器上,比如Adafruit的QT Py ESP32-S3。这意味着,你写好的.py文件,可以直接拖拽到设备上名为CIRCUITPY的U盘里,代码就会自动运行。这种“所见即所得”的开发体验,极大地模糊了软件开发和硬件交互之间的界限。

它的核心价值在于“降本增效”。对于教育者、创客、产品原型开发者而言,CircuitPython将开发重心从复杂的底层驱动和编译环境配置,转移到了业务逻辑和交互设计本身。你不需要理解中断向量表,也不用担心内存对齐,更不用在Makefile里挣扎。你只需要关注“按下按钮时灯要变红”,或者“温度超过30度就发送一个网络请求”这样的高层逻辑。Python庞大的生态系统,包括其清晰的语法、丰富的内置库和活跃的社区,都成为了嵌入式开发的助力。这使得快速验证一个物联网点子、制作一个交互式艺术装置,或者教授编程与硬件的结合,变得前所未有的简单和直观。

2. 环境搭建与核心概念解析

2.1 硬件准备与固件刷写

要开始CircuitPython之旅,你首先需要一块支持它的开发板。本文以Adafruit QT Py ESP32-S3为例,这是一款集成了WiFi/蓝牙功能的紧凑型板卡,性能足够应对大多数物联网项目。

第一步是刷入CircuitPython固件。这个过程比传统单片机开发友好得多:

  1. 访问官方下载页:前往CircuitPython官网,找到QT Py ESP32-S3的专用页面,下载最新的.uf2固件文件。
  2. 进入下载模式:用USB数据线连接开发板和电脑。先按住板载的Boot按钮(通常标有“BOOT”或位于USB口旁边),然后短暂按一下Reset按钮,最后松开Boot按钮。此时,电脑上会出现一个名为UF2BOOT或类似的可移动磁盘。
  3. 拖拽刷写:将下载好的.uf2文件直接拖入这个磁盘。完成后,开发板会自动重启,一个名为CIRCUITPY的新磁盘就会出现。这不仅是你的代码存储盘,也是串行控制台的入口。

注意:务必使用质量可靠的USB数据线。有些充电线只有电源线,没有数据线,会导致电脑无法识别设备,这是新手最常见的“坑”。

2.2 理解CIRCUITPY磁盘与核心工作流

成功刷写后,你的工作流将完全围绕CIRCUITPY磁盘展开。这个磁盘里有几个关键文件和文件夹:

  • code.py:这是主程序入口。设备上电或复位后,会自动执行这个文件里的代码。你可以用任何文本编辑器(推荐VS Code、Mu Editor或Thonny)直接编辑它,保存后代码立即生效。
  • lib/文件夹:用于存放第三方库文件(通常是.mpy格式)。当你需要控制特定传感器或模块时,就需要从CircuitPython库捆绑包中下载对应的库文件,并放入此文件夹。
  • boot.py:这是一个特殊的启动脚本,在code.py之前运行。通常用于一些底层配置,比如我们后面会讲到的存储模式切换。

这种“文件系统即编程接口”的模式,使得调试和迭代变得极其快速。你可以一边在串行监视器(REPL)里查看打印信息,一边在编辑器里修改代码,保存后立刻看到效果,无需漫长的编译和烧录等待。

2.3 串行REPL:交互式调试利器

REPL(Read-Eval-Print Loop)是CircuitPython的交互式命令行环境,是调试和探索的绝佳工具。连接方法如下:

  1. 使用Mu Editor、Thonny或Putty、screen等串口工具。
  2. 选择正确的串行端口(在Windows设备管理器中查看COM口,在macOS/Linux上通常是/dev/tty.usbmodemXX/dev/ttyACM0)。
  3. 连接后,按一下板子的复位键,你会看到CircuitPython的版本信息和>>>提示符。

在这里,你可以直接输入Python命令并立即执行,比如import board查看所有引脚定义,或者import digitalio测试一个LED。当你的code.py因为错误而停止时,REPL会显示详细的错误追踪信息,帮助你快速定位问题所在。

3. 从“Hello World”到硬件交互:Blink与数字输入

3.1 NeoPixel Blink:你的第一个硬件程序

在软件世界,“Hello World”是打印一行文字;在硬件世界,它就是让一个LED闪烁。QT Py ESP32-S3板载了一个RGB NeoPixel LED,我们用它来开始。

首先,你需要neopixel库。从Adafruit的CircuitPython库捆绑包中,找到neopixel.mpy和其依赖的adafruit_pixelbuf.mpy,将它们复制到CIRCUITPY磁盘的lib文件夹内。

然后,在code.py中写入以下代码:

import time import board import neopixel # 初始化板载NeoPixel。参数1:控制引脚(board.NEOPIXEL是板载LED的专用引脚名)。 # 参数2:LED的数量,我们只有1个。 pixel = neopixel.NeoPixel(board.NEOPIXEL, 1) while True: # 填充红色 (R, G, B) 每个值范围0-255 pixel.fill((255, 0, 0)) time.sleep(0.5) # 等待0.5秒 # 关闭LED (R, G, B) = (0, 0, 0) pixel.fill((0, 0, 0)) time.sleep(0.5)

保存文件,板载的彩色LED应该开始以0.5秒的间隔闪烁红光。这段代码揭示了几个核心概念:

  • 硬件抽象board.NEOPIXEL是一个预定义的常量,它隐藏了具体的物理引脚号。这使得代码在不同型号的Adafruit板卡间具有更好的可移植性。
  • 库导入neopixel库封装了与WS2812系列LED通信的底层时序协议,你只需要关心颜色和亮度。
  • 主循环while True:是嵌入式程序的标准模式,让代码持续运行。

实操心得:如果你发现LED没亮,首先检查lib文件夹里是否有正确的库文件。其次,有些板子的NeoPixel默认亮度可能被设置为0,可以尝试pixel.brightness = 0.3来调亮。最后,确保你没有意外地初始化了多个NeoPixel对象去控制同一个引脚,这会导致冲突。

3.2 引入交互:用按钮控制NeoPixel

让灯自己闪没什么意思,加上交互才有灵魂。我们利用板载的Boot按钮(在代码中通常映射为board.BUTTON)来控制LED。

更新你的code.py

import board import digitalio import neopixel # 初始化NeoPixel pixel = neopixel.NeoPixel(board.NEOPIXEL, 1) # 初始化按钮引脚为输入模式,并启用内部上拉电阻 button = digitalio.DigitalInOut(board.BUTTON) button.switch_to_input(pull=digitalio.Pull.UP) while True: # 按钮按下时,值为False(因为上拉到高电平,按下接地) if not button.value: pixel.fill((255, 0, 0)) # 按下,亮红灯 else: pixel.fill((0, 0, 0)) # 松开,灯灭

这里引入了digitalio模块,它是处理数字输入输出的核心。pull=digitalio.Pull.UP启用了内部上拉电阻,这是一个关键细节。在没有按下按钮时,引脚通过电阻连接到3.3V(高电平,valueTrue);按下按钮时,引脚直接接地(低电平,valueFalse)。这样可以确保引脚有一个明确的状态,避免因悬空而产生随机抖动信号。

注意事项:机械按钮在按下和弹起时会产生“抖动”,即电平在短时间内快速变化多次。对于简单的开关控制,这里的代码足够用。但如果要检测“单击”、“双击”,则需要加入“消抖”逻辑,通常通过延时采样来实现。

4. 感知模拟世界:ADC与电位器应用

4.1 模拟信号与ADC原理

数字世界只有0和1,但真实世界是连续的。温度、光线强度、声音音量都是模拟量。微控制器通过模数转换器(ADC)来读取这些连续变化的电压。

QT Py ESP32-S3的ADC分辨率是12位,这意味着它可以将0到3.3V的参考电压划分为2^12 = 4096个等级。但CircuitPython的analogio库为了统一接口,将读数映射到了0-65535(16位)的范围。你需要了解你所用板卡的ADC上限值,对于ESP32-S3,最大值大约是61285(对应约3.09V)。

4.2 读取电位器电压值

电位器是一个经典的模拟输入器件。我们将其连接为分压电路:一端接3.3V,一端接地,中间抽头(滑动端)接模拟输入引脚A0。

硬件连接如下表所示:

电位器引脚连接至 QT Py ESP32-S3线缆颜色(参考)
左/外侧引脚1GND黑色
中间抽头引脚A0红色
右/外侧引脚23.3V白色

代码实现如下:

import time import board import analogio # 初始化A0引脚为模拟输入 analog_pin = analogio.AnalogIn(board.A0) def get_voltage(pin): """将ADC原始值转换为电压值(单位:伏特)""" # ESP32-S3的ADC参考电压约为3.09V,最大读数约为61285 return (pin.value * 3.09) / 61285 while True: raw_value = analog_pin.value voltage = get_voltage(analog_pin) print(f"Raw: {raw_value:6d} | Voltage: {voltage:.2f}V") time.sleep(0.1) # 降低打印频率,便于观察

打开串行监视器,旋转电位器,你会看到原始数值和计算出的电压值在同步变化。这个get_voltage函数是一个实用的工具函数,它将抽象的ADC数值转换回了我们熟悉的电压单位,这对于连接其他模拟传感器(如光敏电阻、模拟温度传感器)至关重要。

排查技巧:如果读数始终为0或接近最大值且不变化,首先检查接线是否正确,特别是电位器的两端是否分别接在了电源和地上。其次,ESP32-S3的某些引脚在启动时可能有默认功能,确保你使用的A0引脚在CircuitPython中是可用的普通模拟输入引脚。最后,ADC读数可能存在轻微噪声,在需要稳定读数的场合,可以尝试连续采样多次然后取平均值。

5. 连接网络世界:WiFi测试与配置

5.1 从secrets.py到settings.toml

早期CircuitPython使用secrets.py文件存储WiFi密码等敏感信息。从CircuitPython 8开始,官方推荐使用settings.toml文件。TOML格式更清晰,且支持注释。

CIRCUITPY磁盘的根目录下,创建一个名为settings.toml的文本文件,内容如下:

# 你的WiFi网络名称和密码 CIRCUITPY_WIFI_SSID = "你的WiFi名称" CIRCUITPY_WIFI_PASSWORD = "你的WiFi密码" # 可以添加其他自定义配置,例如API密钥 # MY_API_KEY = "abc123"

重要:请务必用你的实际WiFi信息替换引号内的内容,并确保文件以.toml扩展名保存,且使用UTF-8编码(无BOM)。这个文件应该直接放在CIRCUITPY根目录,而不是任何文件夹里。

5.2 实现网络连接测试

有了配置文件,我们就可以编写连接测试代码。将以下代码保存为code.py

import ipaddress import wifi import socketpool from os import getenv # 从settings.toml中读取WiFi配置 ssid = getenv("CIRCUITPY_WIFI_SSID") password = getenv("CIRCUITPY_WIFI_PASSWORD") # 安全检查:确保配置已填写 if ssid is None or password is None: raise RuntimeError( "WiFi配置信息缺失!请检查CIRCUITPY根目录下的settings.toml文件," "确保已设置CIRCUITPY_WIFI_SSID和CIRCUITPY_WIFI_PASSWORD。" ) print("正在连接WiFi...") try: wifi.radio.connect(ssid, password) except Exception as e: print(f"连接失败: {e}") # 可能是密码错误、信号弱或SSID隐藏 raise print("WiFi连接成功!") print("-" * 30) # 创建网络套接字池(为后续高级网络操作做准备) pool = socketpool.SocketPool(wifi.radio) # 打印本机网络信息 print(f"MAC地址: {[hex(i) for i in wifi.radio.mac_address]}") print(f"IP地址: {wifi.radio.ipv4_address}") print(f"子网掩码: {wifi.radio.ipv4_subnet}") print(f"网关: {wifi.radio.ipv4_gateway}") print(f"DNS服务器: {wifi.radio.ipv4_dns}") # 测试网络连通性:ping一个公共DNS服务器 print("-" * 30) print("正在测试网络连通性...") try: # 使用Google的公共DNS服务器IP ping_target = ipaddress.ip_address("8.8.8.8") ping_time_ms = wifi.radio.ping(ping_target) * 1000 # 转换为毫秒 if ping_time_ms is not None: print(f"Ping {ping_target} 成功!延迟: {ping_time_ms:.2f} ms") else: print(f"Ping {ping_target} 超时。") except Exception as e: print(f"Ping测试出错: {e}")

保存代码后,打开串行监视器。如果一切顺利,你将看到连接成功的消息、获取到的IP地址以及ping测试的结果。这个脚本不仅测试连接,还输出了完整的网络配置信息,对于调试网络问题非常有帮助。

常见问题与排查

  1. 连接超时或失败:首先检查settings.toml中的SSID和密码是否正确,注意大小写。其次,检查路由器是否设置了MAC地址过滤。你可以尝试将手机热点作为测试网络,排除复杂路由器设置的影响。
  2. 能连接但无法Ping通:可能是开发板获取到了IP地址,但无法访问外网。检查路由器的防火墙设置,或尝试Ping路由器本身的IP地址(通常是网关地址,如192.168.1.1)。
  3. 反复断开重连:可能是WiFi信号太弱。ESP32-S3的PCB天线性能在复杂环境中可能一般,尽量让设备靠近路由器。

6. 数据记录与存储:打造简易温度监测器

6.1 理解CircuitPython的文件系统与读写权限

CIRCUITPY磁盘默认由你的电脑挂载为可读写状态,方便你编辑代码。但CircuitPython运行时也想往这个磁盘写数据(比如记录日志),就会产生冲突。为了解决这个问题,CircuitPython设计了一个精巧的“单写者”模式:同一时间,只能由电脑CircuitPython程序其中之一进行写入操作。

boot.py文件正是在这个环节发挥作用。它在CircuitPython启动时(硬复位或重新上电)运行,比code.py更早。我们可以在boot.py里通过检测一个按钮的状态,来决定将文件系统的写入权限分配给谁。

6.2 实现双模式启动与温度记录

这个项目将实现一个功能:通过按住按钮启动,让板子进入“数据记录模式”,每隔10秒将CPU温度写入文件;正常启动则为“编程模式”,电脑可以自由编辑文件。

第一步:创建boot.pyCIRCUITPY根目录创建boot.py,内容如下:

import time import board import digitalio import storage import neopixel # 初始化硬件 pixel = neopixel.NeoPixel(board.NEOPIXEL, 1) button = digitalio.DigitalInOut(board.BUTTON) button.switch_to_input(pull=digitalio.Pull.UP) # 提示用户:NeoPixel亮起白色时,按下按钮进入记录模式 pixel.fill((255, 255, 255)) # 白色 time.sleep(1) pixel.fill((0, 0, 0)) # 熄灭 # 关键操作:根据按钮状态重新挂载文件系统 # button.value 为 True(按钮未按下)时,readonly=True,CircuitPython只读(电脑可写) # button.value 为 False(按钮按下)时,readonly=False,CircuitPython可写(电脑只读) storage.remount("/", readonly=button.value) # 根据模式给出灯光提示 if button.value: # 未按下,编程模式 pixel.fill((0, 255, 0)) # 绿色 time.sleep(0.5) pixel.fill((0, 0, 0)) else: # 按下,记录模式 pixel.fill((0, 0, 255)) # 蓝色 time.sleep(0.5) pixel.fill((0, 0, 0))

第二步:创建code.py这是主数据记录程序:

import time import board import microcontroller import neopixel pixel = neopixel.NeoPixel(board.NEOPIXEL, 1) LOG_FILE = "/temperature_log.txt" LOG_INTERVAL = 10 # 记录间隔,单位:秒 try: # 尝试以追加模式打开日志文件。如果文件系统对CircuitPython可写,则进入此分支。 with open(LOG_FILE, "a") as log_file: print("进入数据记录模式。开始记录温度...") while True: # 读取微控制器内部温度传感器数值(单位:摄氏度) temp_c = microcontroller.cpu.temperature # 转换为华氏度(可选) # temp_f = temp_c * 9 / 5 + 32 # 构建日志行:时间戳 + 温度 timestamp = time.monotonic() # 自开机以来的秒数,单调递增 log_line = f"{timestamp:.1f}, {temp_c:.2f}\n" # 写入文件并立即刷新缓冲区,确保数据落盘 log_file.write(log_line) log_file.flush() print(f"已记录: {log_line.strip()}") # 视觉反馈:记录时红灯亮1秒 pixel.fill((255, 0, 0)) time.sleep(1) pixel.fill((0, 0, 0)) # 等待剩余间隔时间 time.sleep(LOG_INTERVAL - 1) except OSError as e: # 如果发生OSError,说明文件系统对CircuitPython是只读的(编程模式),或者已满。 print(f"无法写入文件系统或发生错误: {e}") blink_delay = 0.5 # 默认慢闪(0.5秒),表示只读模式 if e.args[0] == 28: # 错误码28: 文件系统已满 print("警告:文件系统已满!") blink_delay = 0.15 # 快闪(0.15秒),表示空间不足 # 进入错误指示循环:根据blink_delay闪烁红灯 while True: pixel.fill((255, 0, 0)) time.sleep(blink_delay) pixel.fill((0, 0, 0)) time.sleep(blink_delay)

第三步:操作流程

  1. 正常启动(编程模式):直接给板子上电或按复位键。NeoPixel会闪一下绿色然后熄灭。此时,电脑可以读写CIRCUITPY磁盘,你可以自由修改code.py等文件。板子上的程序不会记录数据(红灯不会周期性闪烁)。
  2. 进入记录模式: a.按住板子上的Boot按钮不放。 b. 在按住按钮的同时,按一下Reset复位键(或者拔插USB线)。 c. 你会看到NeoPixel先亮白色1秒(提示窗口),然后闪一下蓝色。此时松开按钮。 d. 现在,CIRCUITPY磁盘在电脑上会显示为“只读”。板子开始每10秒闪一次红灯,表示正在记录温度数据到temperature_log.txt文件。
  3. 读取数据:要读取记录的数据,你需要先退出记录模式。方法是在不按按钮的情况下,按一下Reset键或重新上电。板子会进入编程模式(闪绿光),此时电脑可以访问CIRCUITPY磁盘,打开temperature_log.txt文件就能看到按时间戳和温度值排列的数据。

深度解析与避坑指南

  1. storage.remount()的机制:这个函数改变了CircuitPython内核对待CIRCUITPY文件系统的方式。当设置为readonly=False(CircuitPython可写)时,它会卸载当前文件系统,并以一种防止电脑写入的方式重新挂载。这就是为什么电脑端会看到磁盘变成“只读”。
  2. 数据安全与flush():在嵌入式系统中,突然断电可能导致正在写入的数据丢失。file.flush()方法强制将Python缓冲区中的数据写入到存储介质中,虽然会降低一些写入速度,但极大地提高了数据可靠性,对于数据记录应用至关重要。
  3. 温度数据的意义microcontroller.cpu.temperature读取的是芯片内核的温度,通常比环境温度高。它不适合做高精度的室温测量,但非常适合监测设备自身的运行状态,比如判断是否过热。其变化趋势也能反映环境温度的变化。
  4. 文件系统满的处理:代码中特意处理了OSError: [Errno 28] No space left on device错误。当磁盘空间耗尽时,程序会进入快闪红灯状态提醒用户。此时需要在编程模式下连接电脑,删除或备份temperature_log.txt文件以释放空间。
  5. 时间戳的选择:这里使用了time.monotonic(),它返回一个从开机开始持续递增的秒数,不受系统时间设置影响,适合用于计算时间间隔。如果你需要真实的日历时间,则需要通过网络协议(NTP)从互联网获取,这涉及更复杂的网络编程。

7. 项目整合与扩展思路

至此,我们已经完成了从控制一个LED,到读取模拟传感器,再到连接网络,最后实现本地数据记录的完整闭环。你可以将这些模块组合起来,构建更复杂的应用。

例如,一个简单的物联网气象站原型可以这样设计:

  1. 感知:将电位器替换为真正的数字温湿度传感器(如DHT22或AHT20,使用I2C或单总线协议)。
  2. 记录:沿用本章的存储方案,定期将温湿度数据记录到本地文件。
  3. 上报:在记录数据的同时,通过WiFi模块,每隔一段时间(如每5分钟)将数据打包发送到云服务器(如Adafruit IO、Thingspeak或自建的MQTT服务器)。
  4. 交互:保留按钮和NeoPixel。按钮可以切换工作模式(如“仅记录”、“记录并上传”),NeoPixel用不同颜色指示当前状态(绿色:正常,蓝色:发送中,红色:错误)。

这种模块化、渐进式的开发方式,正是CircuitPython的魅力所在。它让你能够快速搭建起一个可工作的原型,验证想法的可行性,然后再逐步优化和增加功能。相比于传统嵌入式开发,你节省了大量在底层驱动和调试上的时间,可以将更多精力专注于产品逻辑和用户体验本身。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/16 7:03:16

功率放大器(PA)关键性能指标怎么来的?

功率放大器(PA)关键性能指标 ——定义来源 / 测试方法 / 物理意义 / 系统应用 / 评审要点 0. 总体说明:为什么“同一张 PA 表”不同人看结论完全不同 你给的这张 PA Performance 表,本质上是一个 “工程可用性边界表”,而不是性能极限表。 PA 的这些指标不是独立存在的…

作者头像 李华
网站建设 2026/5/16 7:03:13

3D打印与手工缝纫融合:制作个性化可穿戴徽章全流程指南

1. 项目概述:当3D打印遇见手工缝纫如果你和我一样,既沉迷于3D打印机那“无中生有”的魔力,又享受手工缝纫带来的踏实感,那么这个项目绝对能让你眼前一亮。它不是什么高精尖的工业应用,而是一个巧妙地将数字制造与传统手…

作者头像 李华
网站建设 2026/5/16 7:00:07

JDBG:基于JDWP的Java动态调试与诊断工具实践指南

1. 项目概述:一个为Java开发者量身打造的调试利器如果你是一名Java开发者,肯定对调试这件事又爱又恨。爱的是,它能帮你精准定位那些让人抓狂的Bug;恨的是,传统的调试流程——设置断点、启动调试模式、在IDE里一步步跟—…

作者头像 李华
网站建设 2026/5/16 6:57:03

LabVIEW PID高级整定技术与工程应用

PID 是工业控制中占比超 90% 的经典算法,适用于线性时不变系统,但面对非线性、扰动、大滞后等场景时性能受限。本文基于 LabVIEW 工具包,系统讲解增益调度、抗积分饱和、串级 PID、前馈控制等高级整定方法,帮助工程师在复杂非线性…

作者头像 李华
网站建设 2026/5/16 6:54:12

AI智能体配置管理:从环境变量到结构化配置的工程实践

1. 项目概述:一个为AI智能体量身定制的配置管理中枢最近在折腾AI智能体(Agent)相关的项目,无论是基于LangChain、AutoGPT还是其他框架,一个绕不开的痛点就是配置管理。API密钥、模型参数、工具配置、环境变量……这些零…

作者头像 李华