1. 从零开始:CircuitPython环境搭建与核心概念
如果你之前玩过Arduino,可能会觉得C语言写起来有点“硬核”,每次改个代码都得编译、上传,调试起来也费劲。我第一次接触CircuitPython时,感觉像是打开了一扇新的大门——它把Python的简洁和易读性直接带到了微控制器上。简单来说,CircuitPython是Adafruit主导开发的一个开源Python解释器,专门为像ESP32、RP2040这类微控制器设计。它的最大魅力在于“即插即用”:你把支持CircuitPython的开发板(比如Adafruit的Feather、QT Py系列)通过USB连上电脑,电脑上会直接弹出一个名为CIRCUITPY的U盘。你的代码文件code.py就放在这个盘里,保存即运行,修改代码就像在电脑上编辑文本文件一样直观。
这种设计彻底改变了嵌入式开发的流程。你不再需要复杂的IDE(集成开发环境)和编译工具链,任何一个能编辑文本的软件(从记事本到VS Code)都可以成为你的开发工具。这对于教育、快速原型制作以及创客项目来说,极大地降低了入门门槛。整个开发体验非常“Pythonic”——你可以直接使用import来引入丰富的内置库和社区库,控制GPIO、读取传感器、驱动显示屏,代码逻辑清晰易懂。
那么,谁适合学习CircuitPython呢?我认为主要有三类人:首先是教育者和初学者,Python本身语法友好,加上这种免编译的即时反馈,学习曲线非常平缓;其次是创客和硬件爱好者,你想快速验证一个物联网点子,或者给一个小装置添加智能功能,CircuitPython能让你专注于逻辑而非底层细节;最后,甚至是经验丰富的嵌入式工程师,当你需要快速搭建一个概念验证(PoC)或者开发一个需要复杂逻辑但硬件资源要求不高的设备时,CircuitPython也是一个高效的“瑞士军刀”。
2. 核心工作流:CIRCUITPY驱动管理与文件系统优化
当你把刷好CircuitPython固件的板子连接到电脑后,CIRCUITPY这个驱动器就是你与板子交互的主战场。理解并管理好这个空间,是高效开发的第一步。
2.1 理解CIRCUITPY的目录结构
一个典型的CIRCUITPY驱动器刚连接时,可能包含以下内容:
code.py: 这是主程序入口。板子上电或复位后,会自动执行这个文件里的代码。boot.py: 这是一个在code.py之前运行的脚本。通常用于一些启动时的特殊配置,比如在某些板子上设置特定引脚的状态。对于大多数入门项目,你可以暂时忽略它。lib/目录: 这是存放第三方库的地方。CircuitPython的核心功能是内置的,但当你需要驱动特定的传感器、显示屏或使用网络功能时,就需要把对应的库文件(通常是.mpy或.py文件)放到这个文件夹里。- 其他用户文件: 你还可以存放字体文件(
.pcf或.bdf)、图片、配置文件等。
这个驱动器的空间通常很有限,早期的一些板子可能只有2MB甚至更少。因此,高效利用每一KB空间都至关重要。
2.2 清理隐藏文件,释放宝贵空间
在MacOS系统下工作,一个常见但容易被忽视的问题是系统自动生成的隐藏文件,它们会悄无声息地占用CIRCUITPY的宝贵空间。这些文件通常以._为前缀(例如._code.py),是MacOS的AppleDouble格式文件,用于存储资源派生信息(如自定义图标),在微控制器文件系统里完全无用。
为什么必须用命令行清理?你在Finder(访达)里是默认看不到这些隐藏文件的。即使开启了显示隐藏文件(Command + Shift + .),直接手动删除也容易遗漏,而且操作繁琐。命令行工具提供了最直接、最彻底的控制。
实操步骤与命令详解:
列出所有文件(包括隐藏文件): 打开你的终端(Terminal),首先需要导航到
CIRCUITPY驱动器。它通常挂载在/Volumes/目录下。cd /Volumes/CIRCUITPY然后使用
ls命令的-a参数来显示所有文件:ls -la-l参数表示以长列表格式显示,-a参数表示显示所有文件(包括以.开头的隐藏文件)。执行后,你很可能会看到一堆._开头的文件。批量删除所有
._文件: 这是关键的一步。我们使用rm命令配合通配符*来一次性删除所有匹配的文件。rm ._*这个命令的意思是:删除当前目录下所有以
._开头的文件。通配符*可以匹配任意字符序列,非常高效。验证空间释放: 删除完成后,可以使用
df命令查看驱动器剩余空间。-h参数会让结果以人类易读的格式(如K, M, G)显示。df -h .注意命令最后的
.,它代表当前目录(即/Volumes/CIRCUITPY)。对比删除前后的可用空间,你可能会发现多出了几十甚至上百KB,这对于空间紧张的板子来说是一笔巨大的“财富”。
注意:
rm命令是直接删除,不会经过回收站。在执行rm ._*前,请务必确认你当前所在的目录是/Volumes/CIRCUITPY,并且你要删除的确实是那些._垃圾文件,避免误删重要的code.py或库文件。一个安全的习惯是,先执行ls ._*看看会匹配到哪些文件,确认无误后再执行rm。
2.3 设备异常恢复:安全模式(Safe Mode)的使用
即使是经验丰富的开发者,也难免会写出让设备“卡死”的代码。常见的情况包括:
- 死循环:
while True循环内没有合理的延时或退出条件,导致程序无法响应。 - 硬件资源冲突:错误地配置了同一个引脚,或者以错误的方式访问了某个外设。
- 内存耗尽:在循环中不断创建对象而不释放,导致内存泄漏。
当你的code.py或boot.py中存在严重错误时,板子可能表现为:连接后CIRCUITPY驱动器不出现、LED无规律闪烁( boot loop )、或者串口无输出。这时,常规的修改文件方法就失效了。
安全模式(Safe Mode)就是为此设计的“救命稻草”。当板子进入安全模式后,它会跳过执行code.py和boot.py,但依然会挂载CIRCUITPY驱动器。这样,你就可以像平常一样访问文件系统,删除或修复有问题的代码文件。
如何进入安全模式?不同板子的具体操作略有不同,但核心原理是在板子复位或上电的瞬间,让系统检测到一个特定的触发条件。最常见的方法是:
- 对于大多数板子,在按下复位(Reset)按钮的同时(或复位后立即),快速双击一下板子上的用户按钮(Boot/User Button)。
- 有些板子(如某些ESP32-S2型号)可能需要你在复位时,将某个特定引脚(如GPIO0)拉低到地(GND)。
最准确的方法是查阅你所使用板子的具体指南。通常,板子上的LED会以一种特殊的模式闪烁(比如缓慢闪烁)来指示已进入安全模式。此时,电脑上应该会重新出现CIRCUITPY驱动器。
进入安全模式后该怎么办?
- 打开
CIRCUITPY驱动器。 - 将
code.py重命名为code.bak(或其他名字),或者直接删除它。如果怀疑是boot.py的问题,也一并处理。 - 安全弹出驱动器,然后按一下板子的复位键。此时板子应该会正常启动(因为没有主程序了),并重新挂载驱动器。
- 现在,你可以重新创建一个简单的
code.py(比如只是一个print(“Hello”))来测试,或者分析刚才重命名的code.bak文件,找出问题所在。
3. 网络功能实战:WiFi连接与settings.toml配置
让设备“上网”是很多物联网项目的基础。CircuitPython通过wifi、socketpool等库让网络连接变得非常简单。从CircuitPython 8开始,官方推荐使用settings.toml文件来管理敏感信息,这比之前的secrets.py更规范。
3.1 创建并配置你的settings.toml文件
settings.toml是一个纯文本配置文件,采用TOML格式。它的核心目的是将WiFi密码、API密钥等敏感信息与你的主程序代码code.py分离。这样,你可以放心地分享代码,而不用担心泄露隐私。
文件内容示例与解析:在你的CIRCUITPY驱动器根目录下,创建一个名为settings.toml的新文件,用文本编辑器打开并输入以下内容:
# 这是一个注释,以‘#’开头 CIRCUITPY_WIFI_SSID = "你的WiFi名称" CIRCUITPY_WIFI_PASSWORD = "你的WiFi密码" # 如果你使用Adafruit IO,可以添加以下信息 ADAFRUIT_AIO_USERNAME = "你的Adafruit IO用户名" ADAFRUIT_AIO_KEY = "你的Adafruit IO Active Key" # 你可以定义其他自定义变量 MY_API_TOKEN = "some_secret_token" CITY_TIMEZONE = "Asia/Shanghai"配置要点与避坑指南:
- 格式严格:变量名(如
CIRCUITPY_WIFI_SSID)在等号左边,等号右边是字符串值,必须用双引号括起来。整数不需要引号。 - 变量名匹配:这是最容易出错的地方!代码里使用
os.getenv(“CIRCUITPY_WIFI_SSID”)来获取值,那么settings.toml里的变量名就必须一字不差地是CIRCUITPY_WIFI_SSID。有些教程或旧代码可能使用WIFI_SSID,你需要根据你运行的代码示例来调整。 - 文件位置:
settings.toml必须放在CIRCUITPY的根目录,不能放在任何子文件夹里。 - 字符编码:如果需要存储非ASCII字符(如中文WiFi名或emoji),请确保你的文本编辑器以UTF-8 without BOM的格式保存文件。在VS Code中,你可以在底部状态栏看到编码格式,并点击进行更改。
3.2 编写一个完整的网络测试程序
理解了配置,我们来写一个功能全面的网络测试脚本。这个脚本会依次执行:扫描WiFi、连接指定网络、测试网络连通性(Ping)、获取网页文本内容、解析JSON数据。
将以下代码保存为CIRCUITPY驱动器上的code.py:
# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries # SPDX-License-Identifier: MIT import os import time 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("CircuitPython网络客户端测试") print("=" * 40) # 1. 打印本机MAC地址 print(f"本机MAC地址: {[hex(i) for i in wifi.radio.mac_address]}") # 2. 扫描并列出周围的WiFi网络 print("正在扫描可用的WiFi网络...") try: networks = [] # 启动扫描,返回一个可迭代对象 for network in wifi.radio.start_scanning_networks(): # 将SSID从bytes转换为字符串,并记录信号强度(RSSI)和信道 networks.append((str(network.ssid, 'utf-8'), network.rssi, network.channel)) wifi.radio.stop_scanning_networks() # 按信号强度从强到弱排序 networks.sort(key=lambda x: x[1], reverse=True) for ssid, rssi, channel in networks: # RSSI为负值,越接近0信号越好 print(f"\t{ssid:<20} 信号强度: {rssi:>4} dBm 信道: {channel}") except Exception as e: print(f"扫描网络时出错: {e}") print("=" * 40) # 3. 连接WiFi (从settings.toml读取配置) wifi_ssid = os.getenv("CIRCUITPY_WIFI_SSID") wifi_password = os.getenv("CIRCUITPY_WIFI_PASSWORD") if not wifi_ssid or not wifi_password: print("错误: 请在 settings.toml 中设置 CIRCUITPY_WIFI_SSID 和 CIRCUITPY_WIFI_PASSWORD") while True: time.sleep(1) print(f"正在连接网络: {wifi_ssid}") try: wifi.radio.connect(wifi_ssid, wifi_password) print(f"连接成功!") print(f"获取到的IP地址: {wifi.radio.ipv4_address}") except Exception as e: print(f"连接失败: {e}") while True: time.sleep(1) print("=" * 40) # 4. 测试网络连通性 (Ping) print("正在测试网络连通性 (Ping 8.8.8.8)...") ping_target = ipaddress.IPv4Address("8.8.8.8") # Google的公共DNS try: # ping操作,返回值为延迟(秒),失败返回None delay = wifi.radio.ping(ip=ping_target) if delay is None: # 第一次失败,重试一次 delay = wifi.radio.ping(ip=ping_target) if delay is not None: print(f"Ping 成功! 延迟: {delay * 1000:.2f} 毫秒") else: print("Ping 失败,但可能不影响后续HTTP访问。") except Exception as e: print(f"Ping测试异常: {e}") print("=" * 40) # 5. 初始化网络会话 pool = socketpool.SocketPool(wifi.radio) # 创建一个requests会话,用于HTTP请求 requests_session = adafruit_requests.Session(pool, ssl.create_default_context()) # 6. 获取并显示普通网页文本 print(f"正在获取文本内容从: {TEXT_URL}") try: response = requests_session.get(TEXT_URL) # .text属性获取响应内容的文本形式 print("网页内容 (前200字符):") print("-" * 40) print(response.text[:200]) print("-" * 40) response.close() # 记得关闭响应,释放资源 except Exception as e: print(f"获取文本失败: {e}") # 7. 获取并解析JSON数据 (名言接口) print(f"\n正在获取JSON数据从: {JSON_QUOTES_URL}") try: response = requests_session.get(JSON_QUOTES_URL) # .json()方法将响应内容解析为Python字典或列表 data = response.json() print("收到一条名言:") print("-" * 40) # 该接口返回一个列表,里面包含字典 if isinstance(data, list) and len(data) > 0: quote = data[0] print(f"作者: {quote.get('author', '未知')}") print(f"内容: {quote.get('text', '无')}") response.close() except Exception as e: print(f"获取名言失败: {e}") # 8. 获取并解析更复杂的JSON (GitHub API) print(f"\n正在获取GitHub项目信息从: {JSON_STARS_URL}") try: response = requests_session.get(JSON_STARS_URL) data = response.json() print("CircuitPython仓库信息:") print("-" * 40) print(f"项目全名: {data.get('full_name')}") print(f"星标(Stars)数量: {data.get('stargazers_count')}") print(f"仓库描述: {data.get('description')}") response.close() except Exception as e: print(f"获取GitHub信息失败: {e}") print("\n" + "=" * 40) print("网络测试全部完成!")代码逐段解析与注意事项:
- 导入模块:
wifi和socketpool是核心网络模块。adafruit_requests是一个第三方库,需要提前放入lib文件夹,它提供了类似Python标准库requests的易用接口。 - 网络扫描:
wifi.radio.start_scanning_networks()返回一个生成器,遍历它可以获得所有扫描到的网络对象。务必在扫描结束后调用stop_scanning_networks()来释放无线电资源,否则可能影响后续连接。 - 连接WiFi:使用
os.getenv()从settings.toml读取配置。这里加了错误检查,如果没找到配置,程序会报错并停止,避免后续操作失败。 - Ping测试:
wifi.radio.ping()是一个简单的连通性测试。注意:并非所有网络都允许ICMP协议(Ping),有些防火墙会禁用它。因此Ping失败不一定代表网络不可用,所以代码里只是警告,并继续执行HTTP测试。 - HTTP请求:使用
adafruit_requests库。关键点在于,每次get()请求后,如果不再需要响应体,最好显式调用response.close()。虽然在某些情况下垃圾回收会处理,但在内存受限的设备上,主动管理资源是好习惯。 - 错误处理:网络操作极易因信号、配置、服务器问题而失败。用
try...except包裹关键步骤,并给出有意义的错误提示,对于调试至关重要。
运行这个脚本,通过串口监视器(如Mu编辑器、VS Code的串口插件或screen / cu命令)查看输出。你会看到从扫描网络到获取网络数据的完整流程,这为你构建任何网络应用打下了坚实基础。
4. 融入开源海洋:如何为CircuitPython社区做贡献
CircuitPython的强大,一半在于其优雅的设计,另一半则源于其活跃、友好的开源社区。参与贡献不仅是回馈,更是深入学习、结识同道中人的绝佳途径。贡献的形式多种多样,远不止提交代码。
4.1 从Discord开始:融入社区讨论
Adafruit的Discord服务器是社区的心脏。这里聚集了从世界各地的初学者到核心开发者的所有人。
- #help-with-circuitpython:这是提问的黄金频道。无论你的问题多么基础,比如“为什么我的LED不亮?”,都可以在这里发问。提问时,请尽量提供详细信息:你用的什么板子?完整的错误信息是什么?你的
code.py内容是什么?一张接线图往往抵得上千言万语。 - #show-and-tell:展示你的作品!无论是完成了第一个闪烁LED,还是做了一个复杂的天气站,分享出来。收获点赞和鼓励是持续创作的重要动力,你的项目也可能启发他人。
- #circuitpython-dev:如果你对开发本身感兴趣,可以在这里参与关于新功能、库架构等更深层次的讨论。
在Discord贡献的秘诀:贡献不一定是回答问题。当你看到一个自己也曾遇到过并已解决的问题时,可以分享你的解决步骤。当你看到别人展示酷炫的项目时,不吝啬一句“Awesome!”。这种积极的氛围营造,本身就是对社区极有价值的贡献。
4.2 通过GitHub进行代码与文档贡献
GitHub是协作开发的核心平台。CircuitPython生态系统主要分为两部分:用C语言编写的核心(在circuitpython主仓库)和用Python编写的库(众多以Adafruit_CircuitPython_开头的仓库)。
对于初学者,我强烈建议从库(Libraries)的贡献开始,因为Python更易读易懂,而且每个库功能相对独立,容易入手。
贡献路径一:解决现有问题(Issues)
- 访问 CircuitPython库贡献门户:
circuitpython.org/contributing。这个页面聚合了所有库的待处理事项。 - 点击“Open Issues”标签页。这里列出了所有库需要帮助的问题。
- 使用标签筛选器。找到“Good first issue”标签并点击。这些问题通常是文档改进、简单的bug修复或功能增强,范围明确,非常适合新手。
- 选择一个感兴趣的问题,阅读描述。如果涉及硬件,确认你手头有对应的传感器或板子。
- 在Discord相应的频道或该Issue下留言,表示你想尝试解决它。这可以避免重复劳动,有时维护者还会给你一些额外的指导。
- Fork仓库,在本地进行修改,编写测试(如果原有测试的话),然后提交Pull Request (PR)。PR的描述中应清晰说明你修改了什么,以及为什么这样修改。
贡献路径二:审查他人的代码(Pull Request Review)这是另一种极其重要且能快速学习的方式。在贡献门户的“Pull Requests”标签页下,有很多等待审查的PR。
- 你可以阅读代码变更,思考逻辑是否正确,代码风格是否符合项目规范(CircuitPython有明确的
Black代码格式化要求和pre-commit检查)。 - 即使你无法用硬件测试,也可以检查文档字符串的格式、示例代码的语法、README的拼写错误。
- 在PR下留下一条有建设性的评论,比如“代码逻辑我看懂了,没问题”,或者“这里有个拼写错误建议修改”,都是非常棒的贡献。这能大大减轻核心维护者的负担。
贡献路径三:翻译(Localization)如果你掌握英语以外的语言,可以通过Weblate平台帮助翻译CircuitPython核心的错误信息和用户界面字符串。这让非英语用户能获得更好的体验。翻译工作对编程技能要求不高,但需要对技术术语有准确的理解。
4.3 提交有效的错误报告(Bug Report)
当你使用CircuitPython或某个库时遇到了问题,提交一个清晰的错误报告是巨大的贡献。
一个糟糕的报告是:“XX库不能用。” 一个优秀的报告应包含:
- 标题:简要概括问题,如“
adafruit_bme280在读取湿度时,在特定板子上返回恒定值”。 - 环境:
- 硬件型号(如Adafruit Feather ESP32-S3)
- CircuitPython版本(在REPL中输入
import os; os.uname()查看) - 涉及的库及其版本(在
lib文件夹查看)
- 复现步骤:提供能100%复现问题的最简代码(Minimal Reproducible Example)。
import board, time, adafruit_bme280 i2c = board.I2C() sensor = adafruit_bme280.Adafruit_BME280_I2C(i2c) while True: print(f"Humidity: {sensor.humidity:.2f} %") time.sleep(1) - 实际结果:描述你看到的现象(如“湿度始终显示0.00%”),并附上完整的错误回溯信息(Traceback)。
- 期望结果:描述你认为正常的行为应该是什么。
- 额外信息:接线图照片、你已尝试过的排查步骤等。
这样一份报告能帮助开发者快速定位问题,效率极高。
5. 项目实战:从Blink到网络天气站
掌握了基础,我们来串联一个综合性的小项目:一个能通过网络获取天气信息并显示在板载屏幕上的简易天气站。我们以一块带有显示屏的ESP32-S3板为例。
5.1 硬件与库准备
- 硬件:Adafruit Feather ESP32-S3 TFT(或其他带WiFi和显示屏的板子)。
- 所需库:确保你的
CIRCUITPY/lib文件夹下有:adafruit_display_textadafruit_bitmap_font(部分字体可能需要)adafruit_requests- 用于连接特定天气API的库,例如
adafruit_io(如果使用Adafruit IO)或通用的json库(CircuitPython已内置)。
5.2 代码实现:获取并显示天气
我们将创建一个code.py,它每小时从公共天气API(例如Open-Meteo)获取一次数据,并在屏幕上实时显示。
import os import time import wifi import socketpool import adafruit_requests import board import displayio from adafruit_display_text import label import terminalio # --- 网络配置(从settings.toml读取)--- SSID = os.getenv("CIRCUITPY_WIFI_SSID") PASSWORD = os.getenv("CIRCUITPY_WIFI_PASSWORD") # 使用Open-Meteo免费API,获取柏林当前天气(可替换为你的城市) LAT = "52.52" LON = "13.41" WEATHER_URL = f"https://api.open-meteo.com/v1/forecast?latitude={LAT}&longitude={LON}¤t_weather=true" # --- 显示初始化 --- display = board.DISPLAY splash = displayio.Group() display.root_group = splash # 创建文本标签 title_label = label.Label(terminalio.FONT, text="Weather Station", color=0xFFFFFF, x=10, y=20) temp_label = label.Label(terminalio.FONT, text="Temp: -- °C", color=0x00FF00, x=10, y=50) wind_label = label.Label(terminalio.FONT, text="Wind: -- km/h", color=0x0000FF, x=10, y=80) update_label = label.Label(terminalio.FONT, text="Last: --:--", color=0xFFFF00, x=10, y=110) for lbl in [title_label, temp_label, wind_label, update_label]: splash.append(lbl) # --- 网络连接 --- def connect_wifi(): print("Connecting to WiFi...") wifi.radio.connect(SSID, PASSWORD) print("Connected! IP:", wifi.radio.ipv4_address) def fetch_weather(): """获取天气数据并更新显示""" try: pool = socketpool.SocketPool(wifi.radio) requests = adafruit_requests.Session(pool, ssl.create_default_context()) response = requests.get(WEATHER_URL) data = response.json() response.close() current = data.get("current_weather", {}) temperature = current.get("temperature", "N/A") windspeed = current.get("windspeed", "N/A") weathercode = current.get("weathercode", 0) # WMO天气代码 # 更新显示 temp_label.text = f"Temp: {temperature} °C" wind_label.text = f"Wind: {windspeed} km/h" update_label.text = f"Last: {time.localtime()[3]:02d}:{time.localtime()[4]:02d}" # 根据天气代码简单改变标题颜色 (例如,下雨为蓝色,晴天为黄色) if weathercode > 80: # 阴天、下雨等 title_label.color = 0x4444FF else: title_label.color = 0xFFFF00 print(f"Weather updated: {temperature}C, {windspeed}km/h") return True except Exception as e: print("Failed to fetch weather:", e) update_label.text = "Update Failed" update_label.color = 0xFF0000 return False # --- 主循环 --- connect_wifi() last_fetch = time.monotonic() - 3600 # 设置为1小时前,促使立即获取一次 while True: now = time.monotonic() # 每3600秒(1小时)获取一次天气 if now - last_fetch > 3600: if fetch_weather(): last_fetch = now else: # 如果失败,5分钟后重试 time.sleep(300) # 短暂休眠以降低功耗,同时保持响应 time.sleep(0.1)项目要点与优化方向:
- API选择:我们使用了无需API密钥的Open-Meteo。对于生产项目,你可能需要考虑请求频率限制、数据的准确性,或者使用其他提供更丰富数据的API(如OpenWeatherMap,需要注册获取API key并妥善存放在
settings.toml)。 - 错误处理:网络请求可能因各种原因失败。代码中使用了
try...except来捕获异常,并在失败时更新界面提示,而不是让程序崩溃。 - 功耗考虑:主循环中的
time.sleep(0.1)让CPU大部分时间在休眠,降低了功耗。对于电池供电的设备,可以进一步使用ESP32的深度睡眠模式,每小时唤醒一次获取数据,这样功耗可以做到极低。 - 显示优化:这里使用了内置的
terminalio.FONT,它是一种点阵字体。你可以将更漂亮的.pcf或.bdf字体文件放入CIRCUITPY根目录或fonts/文件夹,并使用adafruit_bitmap_font加载,让界面更美观。 - 扩展功能:你可以很容易地添加更多信息显示,如湿度、天气图标(用
displayio绘制简单图形)、未来几小时预报(解析API中hourly数据)等。
这个项目虽然小,但涵盖了CircuitPython开发的典型流程:硬件初始化、网络连接、数据获取、解析、用户交互和错误处理。你可以以此为骨架,扩展出无限可能。