1. 项目概述与核心价值如果你刚拿到一块ESP32开发板看着上面密密麻麻的引脚和芯片可能会有点无从下手。是直接上Arduino IDE写C还是试试更“高级”的玩法几年前当我第一次接触ESP32时也有同样的困惑。传统的嵌入式开发光是搭建环境、配置工具链就能劝退不少人更别提那些复杂的编译、烧录流程了。直到我遇到了CircuitPython它彻底改变了我对嵌入式开发的看法。简单来说CircuitPython是Python语言在微控制器上的一个实现。它把Python的简洁、易读带到了ESP32这类硬件上。你不再需要复杂的IDE和编译过程只需要一个文本编辑器把代码保存为code.py放到开发板的U盘里它就会自动运行。这种“即写即得”的体验对于快速验证想法、制作原型来说效率提升不是一点半点。物联网项目的核心无非是“感知-处理-通信”。CircuitPython让“处理”和“通信”这两部分变得异常简单你可以用几行Python代码就读取传感器、控制LED或者连接WiFi、发送HTTP请求。本文将以一块常见的ESP32-S2开发板为例带你从最经典的“Hello World”——闪烁LED开始一步步走到连接互联网、获取网络数据让你亲身体验用CircuitPython进行物联网开发的完整流程和独特魅力。2. 环境准备与开发板初识2.1 硬件选择与连接工欲善其事必先利其器。首先你需要一块支持CircuitPython的ESP32开发板。市面上有很多选择比如Adafruit的Feather ESP32-S2、Metro ESP32-S2或者国内一些厂商生产的兼容板。它们核心的芯片都是乐鑫的ESP32-S2或ESP32-S3这些芯片的特点是集成了WiFi和USB非常适合物联网入门。拿到板子后第一步是连接电脑。使用一根质量可靠的USB数据线最好是数据线而非仅能充电的线将开发板的USB接口连接到电脑的USB端口。此时电脑通常会发出识别新硬件的提示音。如果这是你第一次使用这块板子它可能处于出厂状态运行着厂商的测试程序或者干脆没有任何程序。我们的首要任务就是为它刷入CircuitPython固件。注意很多新手会忽略USB线的质量。劣质或纯充电线无法进行数据传输会导致电脑根本无法识别开发板让你在第一步就卡住。如果你连接后电脑毫无反应首先换一根确认可以传输数据的手机数据线试试。2.2 刷入CircuitPython固件CircuitPython并非ESP32芯片原生自带我们需要为其安装“操作系统”。这个过程叫“刷固件”。确定你的板子型号精确的型号至关重要。是ESP32-S2还是ESP32-S3是Feather版型还是Metro版型通常在产品页面或板子丝印上可以找到。例如“Adafruit Feather ESP32-S2”。下载对应固件访问CircuitPython官网在下载页面根据你的板子型号选择最新的稳定版.uf2或.bin文件。.uf2文件使用起来更简单。进入Bootloader模式这是让开发板准备接收新固件的状态。对于大多数ESP32-S2/S3板子操作方法如下找到板子上的两个按钮“Reset”复位和“Boot”或标为DFU/BOOT0。先按住“Boot”按钮不松开。然后短按一下“Reset”按钮。最后松开“Boot”按钮。 操作成功后电脑上会出现一个名为ESP32-S2或类似的可移动磁盘U盘容量可能只有几MB。如果没出现请重复上述步骤确保按键顺序和时长正确。拖放固件将下载好的.uf2文件直接拖拽或复制到这个新出现的U盘里。开发板会自动重启。几秒钟后这个U盘会消失然后重新出现一个名为CIRCUITPY的新U盘。恭喜这说明CircuitPython固件已经刷写成功这个CIRCUITPY盘就是你未来存放代码和库文件的地方。2.3 配置代码编辑器虽然任何文本编辑器都能编写code.py但我强烈推荐使用专为CircuitPython优化的编辑器如Mu Editor或Visual Studio Code with CircuitPython插件。Mu Editor这是最省心的选择。它内置了串行监视器Serial Console可以实时查看板子打印的调试信息并且有一个“刷入模式”按钮能帮你轻松进入Bootloader。界面简洁对新手极其友好。VS Code功能更强大适合喜欢定制和拥有多个扩展的开发者。安装“CircuitPython”插件后可以获得代码自动补全、库管理等功能体验更接近现代IDE。安装好编辑器后用USB线连接好开发板确保CIRCUITPY盘符正常显示我们的软硬件环境就准备就绪了。3. 从“Hello World”开始闪烁LED3.1 理解CircuitPython程序的基本结构在嵌入式编程里让一个LED闪烁相当于编程世界的“Hello World”。它虽简单却包含了与硬件交互的核心概念。用CircuitPython实现你会发现它简单得不可思议。打开你的代码编辑器并确保它指向CIRCUITPY驱动器。你会看到里面可能已经有一个code.py文件。如果没有就新建一个。我们将编写以下代码# SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries # SPDX-License-Identifier: MIT CircuitPython Blink Example - the CircuitPython Hello, World! import time import board import digitalio 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)将这段代码保存到CIRCUITPY驱动器的根目录并确保文件名为code.py。保存的瞬间你就会看到板载的LED开始闪烁了这就是CircuitPython的“热更新”特性无需任何编译和上传操作。3.2 代码逐行解析与硬件抽象思想我们来拆解这段代码理解每一行在做什么导入模块import time, board, digitaliotime提供时间相关函数如sleep用于延时。board这是CircuitPython硬件抽象层的核心。它定义了你这块开发板上所有可用的引脚和硬件资源如board.LED代表板载LED连接的引脚。不同板子的board模块内容不同CircuitPython会根据你的板子型号自动适配。digitalio用于控制数字输入输出Digital Input/Output的模块比如控制LED亮灭、读取按钮状态。设置LED引脚led digitalio.DigitalInOut(board.LED)这行代码创建了一个数字IO对象led并告诉它我们要操作的是board.LED这个引脚。board.LED是一个预定义的常量指向你板子上那个方便用户测试的LED通常是红色或蓝色。led.direction digitalio.Direction.OUTPUT设置这个引脚的方向为“输出”。因为我们是要用代码控制LED输出信号而不是读取它的状态输入。主循环while True:一个无限循环。微控制器程序通常没有“结束”的概念上电后就一直运行直到断电。led.value True将LED引脚设置为高电平在大多数板子上高电平点亮LED。time.sleep(0.5)程序暂停0.5秒。这就是LED亮着的持续时间。led.value False将LED引脚设置为低电平熄灭LED。time.sleep(0.5)再暂停0.5秒。这就是LED灭的持续时间。 循环往复LED就实现了每秒闪烁一次的效果。实操心得board.LED是最简单的方式但并非所有板子都有或者你可能想控制其他LED。你可以通过查看板子的原理图或文档找到LED对应的具体GPIO引脚编号然后用board.GPIOxx来指定。例如对于某些ESP32-S2板子用户LED可能在GPIO13上那么就可以用led digitalio.DigitalInOut(board.GPIO13)。使用board.LED是跨板兼容性最好的做法。3.3 尝试修改与实验理解了基础之后可以立刻尝试修改代码观察变化改变闪烁频率修改time.sleep()里的参数比如改成0.1更快或1.0更慢。实现呼吸灯效果这需要用到PWM脉冲宽度调制。虽然稍微复杂一点但CircuitPython也提供了pwmio模块来轻松实现。你可以尝试搜索“CircuitPython PWM LED”来找到相关教程这是你从数字输出迈向模拟控制的第一步。这个简单的例子展示了CircuitPython与硬件交互的基本模式导入模块 - 定义硬件对象并配置 - 在循环中控制。掌握了这个模式你就已经入门了。4. 迈向物联网连接WiFi网络让LED闪烁只是控制了板子自身。物联网的关键在于“联”接下来我们让ESP32连接上WiFi这是它通往互联网世界的大门。4.1 准备网络配置文件在互联网上安全第一。我们不应该把WiFi密码等敏感信息硬编码在code.py里尤其是当你打算分享代码时。CircuitPython社区推荐使用一个名为settings.toml的配置文件来管理这些秘密。在CIRCUITPY驱动器根目录下新建一个文本文件命名为settings.toml注意扩展名。然后用编辑器打开输入以下内容# 这是存储密码、密钥等敏感信息的地方。 # 本示例只需要WiFi信息但你也可以添加Adafruit IO密钥等其他凭证。 CIRCUITPY_WIFI_SSID 你的WiFi名称 CIRCUITPY_WIFI_PASSWORD 你的WiFi密码请务必将你的WiFi名称和你的WiFi密码替换成你实际的2.4GHz WiFi网络信息大多数ESP32系列目前仅支持2.4GHz频段。保存文件。重要警告settings.toml文件绝对不能上传到GitHub、论坛或其他公开场合。它应该被添加到你的.gitignore文件中。这个文件是你的私钥泄露它意味着别人可以连接你的网络。4.2 编写网络测试代码接下来我们创建一个新的code.py覆盖之前的闪烁LED代码来实现网络连接测试。这段代码会执行以下操作扫描周围WiFi、连接指定网络、获取IP地址、测试网络连通性Ping、最后从网站获取一些数据。# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries # SPDX-License-Identifier: MIT import os import ipaddress import ssl import wifi import socketpool import adafruit_requests # 定义要访问的测试URL TEXT_URL http://wifitest.adafruit.com/testwifi/index.html JSON_QUOTES_URL https://www.adafruit.com/api/quotes.php JSON_STARS_URL https://api.github.com/repos/adafruit/circuitpython print(ESP32-S2 网络客户端测试) print( * 40) # 1. 打印MAC地址设备的物理网络标识 print(f我的MAC地址: {[hex(i) for i in wifi.radio.mac_address]}) # 2. 扫描并列出所有可用的WiFi网络 print(可用的WiFi网络:) for network in wifi.radio.start_scanning_networks(): # 打印网络名称、信号强度(RSSI)和信道 print(f\t{str(network.ssid, utf-8)}\t信号强度: {network.rssi} dBm\t信道: {network.channel}) wifi.radio.stop_scanning_networks() # 停止扫描 print( * 40) # 3. 从 settings.toml 读取WiFi信息并连接 ssid os.getenv(CIRCUITPY_WIFI_SSID) password os.getenv(CIRCUITPY_WIFI_PASSWORD) print(f正在连接到网络: {ssid}) wifi.radio.connect(ssid, password) print(f已连接到: {ssid}!) print(f我的IP地址: {wifi.radio.ipv4_address}) print( * 40) # 4. 测试网络连通性Ping谷歌DNS服务器 ping_ip ipaddress.IPv4Address(8.8.8.8) ping_time wifi.radio.ping(ipping_ip) # 如果第一次Ping超时返回None再试一次 if ping_time is None: ping_time wifi.radio.ping(ipping_ip) if ping_time is None: print(无法成功Ping通 google.com) else: # ping返回的是秒转换成毫秒显示更直观 print(fPing google.com 耗时: {ping_time * 1000:.2f} ms) print( * 40) # 5. 创建网络会话准备进行HTTP请求 pool socketpool.SocketPool(wifi.radio) requests adafruit_requests.Session(pool, ssl.create_default_context()) # 6. 示例1获取纯文本网页内容 print(f正在从 {TEXT_URL} 获取文本...) response requests.get(TEXT_URL) print(- * 40) print(response.text) # 直接打印网页文本内容 print(- * 40) # 7. 示例2获取并解析JSON数据名言 print(f正在从 {JSON_QUOTES_URL} 获取JSON数据...) response requests.get(JSON_QUOTES_URL) print(- * 40) print(response.json()) # .json()方法直接解析返回的JSON数据 print(- * 40) # 8. 示例3获取特定JSON字段CircuitPython的GitHub星标数 print(f正在从 {JSON_STARS_URL} 获取并解析JSON...) response requests.get(JSON_STARS_URL) data response.json() print(- * 40) print(fCircuitPython GitHub 星标数: {data[stargazers_count]}) print(- * 40) print(网络测试完成)将这段代码保存为CIRCUITPY驱动器上的code.py。保存后打开Mu Editor或VS Code的串行监视器Serial Console设置波特率为115200你将看到程序输出的日志信息。4.3 代码深度解析与网络层原理这段代码比闪烁LED复杂它完整展示了一个物联网设备上网的流程环境变量读取os.getenv(“CIRCUITPY_WIFI_SSID”)从settings.toml文件中安全地读取配置。这是管理密钥的最佳实践。WiFi扫描wifi.radio.start_scanning_networks()调用ESP32的无线射频模块扫描周围的无线接入点。返回的信息中RSSIReceived Signal Strength Indicator是信号强度单位为dBm数值越接近0例如-50信号越好越小例如-80信号越差。channel是信道号。连接与DHCPwifi.radio.connect(ssid, password)执行连接操作。成功后ESP32会通过DHCP协议从你的路由器自动获取一个本地IP地址ipv4_address。这个过程背后是完整的802.11协议握手和TCP/IP网络栈的初始化。网络诊断wifi.radio.ping()向目标IP这里是谷歌的公共DNS8.8.8.8发送ICMP回显请求包。如果能收到回复说明从你的设备到公网的路由是通的。这是判断网络连通性最基础有效的方法。我们之所以尝试两次是因为在连接刚建立时偶尔第一次ping可能会因ARP解析等原因失败。HTTP客户端adafruit_requests库是对底层socket的封装提供了类似Python标准库requests的简易接口。SocketPool管理网络套接字资源。ssl.create_default_context()为HTTPS请求提供安全套接字层支持。数据处理我们演示了三种常见的网络数据交互获取纯文本response.text。获取并自动解析JSONresponse.json()得到一个Python字典或列表。从JSON中提取特定字段data[‘stargazers_count’]。如果一切顺利你将在串行监视器中看到你的MAC地址、扫描到的WiFi列表、成功获取的IP地址、Ping延迟以及从三个测试网址拉取回来的数据。这意味着你的ESP32已经正式接入了互联网。5. 进阶实战获取网络时间与构建服务联网成功后我们可以做更多有意义的事情。一个非常常见的物联网需求是获取准确的时间。无论是记录传感器数据的时间戳还是让设备在特定时间执行任务都需要时间同步。5.1 利用Adafruit IO时间服务自己实现NTP客户端并处理时区、夏令时非常复杂。Adafruit提供了一个免费且简单的时间服务。你需要先注册一个免费的Adafruit IO账户。注册并获取密钥访问Adafruit IO官网注册。登录后点击右上角“My Key”你会看到AIO_USERNAME和AIO_KEY。这就是你的凭证。更新配置文件在CIRCUITPY的settings.toml文件中添加你的Adafruit IO信息和时区。CIRCUITPY_WIFI_SSID 你的WiFi名称 CIRCUITPY_WIFI_PASSWORD 你的WiFi密码 ADAFRUIT_AIO_USERNAME 你的Adafruit IO用户名 ADAFRUIT_AIO_KEY 你的Adafruit IO Active Key # 时区字符串参考 http://worldtimeapi.org/timezones TIMEZONE Asia/Shanghai编写时间获取代码创建一个新的code.py。import os import ssl import wifi import socketpool import adafruit_requests # 从 settings.toml 读取配置 ssid os.getenv(CIRCUITPY_WIFI_SSID) password os.getenv(CIRCUITPY_WIFI_PASSWORD) aio_username os.getenv(ADAFRUIT_AIO_USERNAME) aio_key os.getenv(ADAFRUIT_AIO_KEY) timezone os.getenv(TIMEZONE, America/New_York) # 提供默认值 # 构建请求Adafruit IO时间服务的URL # fmt参数指定返回时间的格式%Y-%m-%d %H:%M:%S.%L %j %u %z %Z TIME_URL fhttps://io.adafruit.com/api/v2/{aio_username}/integrations/time/strftime TIME_URL f?x-aio-key{aio_key}tz{timezone} TIME_URL fmt%25Y-%25m-%25d%25H%3A%25M%3A%25S.%25L%25j%25u%25z%25Z print(ESP32-S2 Adafruit IO 时间测试) print( * 40) # 连接WiFi (代码同上略) wifi.radio.connect(ssid, password) print(f已连接到 {ssid}) print(fIP地址: {wifi.radio.ipv4_address}) # 创建请求会话 pool socketpool.SocketPool(wifi.radio) requests adafruit_requests.Session(pool, ssl.create_default_context()) print(f正在从Adafruit IO获取时间...) try: response requests.get(TIME_URL) if response.status_code 200: current_time_str response.text print(- * 40) print(获取到的原始时间字符串:, current_time_str) print(- * 40) # 解析字符串示例格式2023-10-27 14:30:15.123 300 4 0800 CST parts current_time_str.split() if len(parts) 1: print(f当前日期与时间: {parts[0]} {parts[1]}) print(f今天是今年的第 {parts[2]} 天星期 {parts[3]}) print(f时区: {parts[4]} {parts[5]}) else: print(f错误: HTTP {response.status_code}) except Exception as e: print(f获取时间失败: {e}) print( * 40)这段代码通过向Adafruit IO的特定API发送请求并附带你的密钥和时区参数返回一个格式化的时间字符串。你可以解析这个字符串来获得年、月、日、时、分、秒、毫秒、一年中的第几天、星期几以及时区信息。有了准确的时间你的物联网设备就可以执行定时任务或者为采集的数据打上正确的时间戳。5.2 构建一个简单的HTTP服务器除了作为客户端获取数据ESP32也可以作为一个微型服务器提供简单的Web接口。例如你可以创建一个网页来远程控制LED或者查看传感器读数。CircuitPython标准库中没有现成的HTTP服务器但我们可以用socket库手动实现一个简单的。下面是一个示例它创建一个Web服务器当访问根路径/时返回一个简单的HTML页面显示LED状态并通过/toggle路径来控制LED开关。import wifi import socketpool import digitalio import board import os # 网络配置 ssid os.getenv(CIRCUITPY_WIFI_SSID) password os.getenv(CIRCUITPY_WIFI_PASSWORD) # 初始化LED led digitalio.DigitalInOut(board.LED) led.direction digitalio.Direction.OUTPUT led_state OFF # 连接WiFi print(正在连接WiFi...) wifi.radio.connect(ssid, password) print(连接成功! IP地址:, wifi.radio.ipv4_address) # 创建socket池 pool socketpool.SocketPool(wifi.radio) # 创建一个TCP socket并监听80端口HTTP默认端口 server_socket pool.socket(pool.AF_INET, pool.SOCK_STREAM) server_socket.bind((0.0.0.0, 80)) server_socket.listen(5) # 允许最多5个等待连接 print(HTTP服务器已启动等待连接...) def handle_request(client_socket, client_addr): global led_state try: # 接收客户端请求数据只读第一行 request client_socket.recv(1024).decode(utf-8) if not request: return request_line request.split(\n)[0] print(f收到来自 {client_addr} 的请求: {request_line}) # 解析请求方法和路径 parts request_line.split() if len(parts) 2: return method, path parts[0], parts[1] # 路由处理 if path /: # 返回主页显示LED状态和控制链接 html fhtmlheadtitleESP32 Control/title/head bodyh1ESP32 Web Control/h1 pLED状态: strong{led_state}/strong/p pa href/toggle点击这里切换LED状态/a/p /body/html response fHTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n{html} elif path /toggle: # 切换LED状态 led.value not led.value led_state ON if led.value else OFF # 重定向回主页 response HTTP/1.1 303 See Other\r\nLocation: /\r\n\r\n else: # 404 页面未找到 response HTTP/1.1 404 Not Found\r\n\r\nh1404 Not Found/h1 # 发送HTTP响应 client_socket.send(response.encode(utf-8)) except Exception as e: print(f处理请求时出错: {e}) finally: client_socket.close() # 主服务器循环 while True: try: # 等待客户端连接 client_socket, client_addr server_socket.accept() print(f接受来自 {client_addr} 的连接) # 处理请求 handle_request(client_socket, client_addr) except Exception as e: print(f服务器错误: {e}) break server_socket.close()将代码保存为code.py并运行。在串口监视器中你会看到ESP32的IP地址例如192.168.1.123。在同一局域网内的电脑或手机浏览器中输入这个IP地址你就能看到一个简单的控制页面点击链接即可远程控制板载LED的开关。这个例子虽然简单但它揭示了一个强大的模式物联网设备作为服务端。你可以扩展这个框架添加更多路径/temperature、/humidity来返回传感器数据或者接受POST请求来设置参数从而构建出功能丰富的设备管理界面。6. 项目优化、问题排查与资源管理6.1 内存管理与优化技巧ESP32虽然功能强大但其内存RAM资源相比PC仍然有限。在编写复杂的CircuitPython程序时需要关注内存使用。避免在循环中创建大对象例如不要在while True循环里反复创建大的列表或字符串。应在循环外初始化或在循环内重用。使用gc.collect()CircuitPython有垃圾回收机制。在完成大量数据操作如处理完一个HTTP响应后可以手动调用import gc; gc.collect()来触发垃圾回收释放不再使用的内存。注意字符串操作字符串拼接尤其是使用运算符会产生新的字符串对象。对于频繁的字符串构建考虑使用””.join(list_of_strings)的方式或者使用io.StringIO。监控内存你可以在代码中打印import gc; gc.mem_free()来查看当前剩余内存帮助定位内存泄漏。6.2 常见问题与排查指南在开发过程中你肯定会遇到各种问题。下面是一个快速排查清单问题现象可能原因排查步骤电脑无法识别CIRCUITPY盘1. USB线仅能充电无数据功能。2. 板子未进入正确模式需复位。3. CircuitPython固件损坏或未刷入。1. 更换USB数据线。2. 按BootReset键尝试进入Bootloader模式。3. 重新刷写CircuitPython固件。代码保存后无反应1. 文件未以code.py命名。2. 代码存在语法错误。3. 程序崩溃并进入了安全模式。1. 检查文件名和扩展名。2. 打开串行监视器查看错误信息通常是红色的Python traceback。3. 查看CIRCUITPY根目录是否出现了safe-mode文件删除它并修复代码错误。WiFi连接失败1.settings.toml中SSID或密码错误。2. 网络是5GHzESP32-S2通常只支持2.4GHz。3. 路由器设置了MAC地址过滤等限制。4. 信号太弱。1. 仔细核对settings.toml注意大小写和特殊字符。2. 确保连接的是2.4GHz网络。3. 检查路由器设置或尝试用手机热点测试。4. 查看扫描结果中的RSSI值确保信号足够强大于-70dBm。Ping通但HTTP请求失败1. 防火墙或路由器策略阻止。2. 使用的库adafruit_requests未安装。3. 目标服务器问题或URL错误。1. 尝试Ping其他地址如1.1.1.1。2. 检查CIRCUITPY/lib文件夹下是否有adafruit_requests.mpy等库文件缺失需从库捆绑包复制。3. 在电脑浏览器中测试URL是否可访问。程序运行一段时间后崩溃/重启1. 内存泄漏耗尽RAM。2. 网络操作阻塞导致看门狗WatchDog复位。3. 电源不稳定ESP32射频工作时峰值电流较大。1. 优化代码使用gc.collect()。2. 为网络操作设置超时requests.get(url, timeout10)。3. 使用外部5V/1A以上电源供电而非电脑USB口特别是当使用其他外设时。6.3 资源获取与社区支持官方文档与库CircuitPython和Adafruit的文档非常详尽。遇到任何模块或函数的问题首先查阅ReadTheDocs上的官方API文档。Adafruit还提供了庞大的“CircuitPython Library Bundle”包含了几乎所有常见传感器、显示器的驱动库直接下载解压到CIRCUITPY/lib即可使用。社区论坛Adafruit官方论坛是寻求帮助的绝佳场所。提问时请务必提供尽可能多的信息你的板子型号、CircuitPython版本、完整的错误信息从串口监视器复制、你的code.py内容以及settings.toml记得删除密码的截图。清晰的描述能极大提高获得帮助的效率。项目灵感Adafruit Learn平台上有成千上万个基于CircuitPython的项目教程从简单的传感器读取到复杂的物联网仪表盘应有尽有。模仿这些项目是学习最快的方式。从我个人的经验来看CircuitPython最大的优势在于其极快的迭代速度。你修改代码、保存、看到结果整个周期可能不到10秒。这种即时反馈对于学习和原型开发是无价的。它可能不是最终产品中性能最高的选择但对于验证想法、教育、艺术创作和大多数中小型物联网应用来说其开发效率的提升远远超过了潜在的性能开销。现在你的ESP32已经不再是一块孤立的开发板而是一个连接物理世界与数字世界的智能节点了。接下来结合具体的传感器和执行器你可以开始构建真正属于自己的物联网项目了。