2026 M-Design设计竞赛 - 基于XIAO ESP32 S3设计环境检测系统
该项目使用了XIAO ESP32 S3,实现了环境检测系统的设计,它的主要功能为:连接WiFi并通过HTTP服务器请求获取当地城市的天气信息,驱动温湿度传感器和紫外线传感器获取室内环境信息。
标签
嵌入式系统
CircuitPython
环境检测
ESP32-S3
波波l
更新2026-06-09
5

一、项目介绍

项目采用XIAO ESP32 S3 Plus板卡、SHT45温湿度传感器、LTR390紫外线传感器,设计实现了一个室内外环境检测系统。由ESP32 S3连接WiFi并通过HTTP服务器请求获取当地城市的天气信息(即室外环境信息);驱动温湿度传感器和紫外线传感器获取室内环境信息;并将室内外环境整合在ST7789的LCD屏幕上显示。


微信图片_20251220234534_280_52.jpg


二、硬件介绍

        主板:Seeed Studio XIAO ESP32 S3 Plus

        温湿度传感器:SHT45

        紫外线传感器:LTR390
        LCD屏幕:ST7789



Snipaste_2026-04-19_23-25-24.png


三、软件流程图及设计思路

该项目基于CircuitPython开发,由ESP32 S3连接WiFi并通过HTTP服务器请求获取当地城市的天气信息(即室外环境信息);通过I2C总线访问温湿度传感器和紫外线传感器获取室内环境信息;并将室内外环境整合通过SPI协议发送到ST7789的LCD屏幕上显示。


方案框图:

image.png


软件流程图:

220648b0kt0rhrkorv9rvs.png


四、关键代码介绍

主函数:

import board
import time

from module.network import wifi_connect, create_session, fetch_weather
from module.display import init_display, Text_Show
from module.sht40_device import Get_Sht4x_data
from module.ltr390_device import Get_ltr_data
#API 数据
city_encoded = "%E5%BA%93%E5%B0%94%E5%8B%92" # 这是"库尔勒"
key = "" #这里填写api接口的key
#WIFI 数据
wifi_ssid = "" #这里填写自己的WiFi名
wifi_password = "" #这里填写自己的WiFi密码

# 1. 初始化显示
display, display_root = init_display(board.SPI(), board.D7, board.D6)
weather_net = Text_Show(display_root, x=0, y=0)
weather_sht = Text_Show(display_root, x=120, y=0)

# 2. 连接 WiFi
wifi_radio = wifi_connect(wifi_ssid, wifi_password, 0.5)
session = create_session(wifi_radio)

# 3. 获取并显示天气
weather_data = fetch_weather(session, city_encoded, key)
#print(f"Temp: {weather_data['temperature']}°C\n"
# f"Hum: {weather_data['humidity']}%\n"
# f"Aqi: {weather_data['aqi']}")
weather_net.Show("Outdoor:\n"
f"Temp: {weather_data['temperature']}°C\n"
f"Hum: {weather_data['humidity']}%\n"
f"Aqi: {weather_data['aqi']}")

#weather_net.Show("Outdoor:\n"f"Temp: {-25}\n"f"Hum: {30}%")
#temperature, relative_humidity = Get_Sht4x_data(board.I2C())
#print(f"Temperature: {temperature:.1f} C \nHumidity: {relative_humidity:.1f} %")
while True:
temperature, relative_humidity = Get_Sht4x_data(board.I2C())
ltr = Get_ltr_data(board.I2C())
weather_sht.Show("Indoor:\n"
f"Temp: {temperature}\n"
f"Hum: {relative_humidity}%\n"
f"UV: {ltr.uvs}")
time.sleep(1)


WiFi驱动:

import time
import wifi
import ssl
import socketpool
import adafruit_requests

def wifi_connect(wifi_ssid, wifi_password, delay_time):
print(f"Connet {wifi_ssid}...")
radio = wifi.radio
if radio.connected:
return radio

while not radio.connected:
try:
radio.connect(wifi_ssid, wifi_password)
except Exception as e:
print(f"Connection retry... ({e})")
time.sleep(delay_time)

print(f"WiFi connect success! IP: {radio.ipv4_address}")
return radio

def create_session(radio):
"""创建 requests 会话"""
pool = socketpool.SocketPool(radio)
return adafruit_requests.Session(pool, ssl.create_default_context())

def fetch_weather(session, city_encoded, api_key):
# 手动拼接 URL,不使用 params 参数
url = f"http://apis.juhe.cn/simpleWeather/query?city={city_encoded}&key={api_key}"

print("Getting weather data...")

try:
# 发送 GET 请求 (直接传 URL)
response = session.get(url)
# 解析 JSON
data = response.json()
# 记得关闭连接,释放内存
response.close()
# 4. 打印结果
if data.get("error_code") != 0:
print("API error:", data.get("reason"))
return None

print("Get Weather Success!")
return data["result"]["realtime"]

except Exception as e:
print("Network error:", e)
return None

if __name__ == "__main__":
wifi_ssid = ""
wifi_password = ""
wifi_radio = wifi_connect(wifi_ssid, wifi_password, 2)
session = create_session(wifi_radio)

city_encoded = "" # 这是"库尔勒"
key = ""

# 获取并显示天气
weather_data = fetch_weather(session, city_encoded, key)
print(f"Temp: {weather_data['temperature']}°C\n"
f"Hum: {weather_data['humidity']}%\n"
f"Aqi: {weather_data['aqi']}")


显示驱动:

import displayio
import terminalio
from adafruit_display_text import label
from fourwire import FourWire
from adafruit_st7789 import ST7789

def init_display(SPI, DC, CS):
#释放当前正在使用的显示资源,确保可以重新初始化显示屏,避免资源冲突。
displayio.release_displays()

#创建SPI总线对象,使用指定的时钟和MOSI引脚。注意这里只指定了MOSI,因为ST7789通常只需要单向数据传输。
tft_spi = SPI
tft_dc = DC
tft_cs = CS
#创建四线显示总线接口,将SPI总线、命令引脚和芯片选择引脚组合在一起,为显示屏提供通信接口。
display_bus = FourWire(tft_spi, command=tft_dc, chip_select=tft_cs)#, reset=tft_rest)
#创建ST7789显示屏对象,配置参数:
display = ST7789(
display_bus,
rotation=270,
width=240,
height=135,
rowstart=40,
colstart=53,
)

root = displayio.Group()
display.root_group = root
return display, root

#"""简单文本显示"""
class Text_Show:
def __init__(self, root_group, scale=2, color=0x00FFFF, x=4, y=4):
self.label = label.Label(
terminalio.FONT,
text="",
color=color,
scale=scale,
)
self.label.anchor_point = (0, 0)
self.label.anchored_position = (x, y)
root_group.append(self.label)

def Show(self, data):
if data is None:
self.label.text = "Error: No data"
return
self.label.text = (data)

if __name__ == "__main__":
import board
display, display_root = init_display(board.SPI(), board.D7, board.D6)
weather_net = Text_Show(display_root, x=20, y=20)
weather_net.Show('° C')


SHT45驱动:

import adafruit_sht4x

_sht_cache = {} # 使用字典缓存不同I2C总线的传感器实例

def Get_Sht4x_data(I2C):
# 获取I2C总线的唯一标识符作为键
# 可以使用id(I2C)或实际的总线标识
i2c_key = f"{I2C.__class__.__name__}_{id(I2C)}"
#查询字典是否有该键
if i2c_key not in _sht_cache:
# 初始化新传感器
sht = adafruit_sht4x.SHT4x(I2C)
#打印序列号,sht.serial_number:获取传感器的唯一序列号
print(f"Found SHT4x with serial number {hex(sht.serial_number)} on {i2c_key}")
#设置传感器模式,NOHEAT:不使用内置加热器 HIGHPRECISION:高精度测量模式
sht.mode = adafruit_sht4x.Mode.NOHEAT_HIGHPRECISION
#将实例保存进字典
_sht_cache[i2c_key] = sht
#获取字典中的实列
sht = _sht_cache[i2c_key]
#读取测量数据,实际触发一次测量
temperature, relative_humidity = sht.measurements
return temperature, relative_humidity
'''
def Get_Sht4x_data(I2C):
sht = adafruit_sht4x.SHT4x(I2C)
print("Found SHT4x with serial number", hex(sht.serial_number))
sht.mode = adafruit_sht4x.Mode.NOHEAT_HIGHPRECISION
temperature, relative_humidity = sht.measurements
return temperature, relative_humidity
'''
if __name__ == "__main__":

import board
temperature, relative_humidity = Get_Sht4x_data(board.I2C())
print(f"Temperature: {temperature:.1f} C \nHumidity: {relative_humidity:.1f} %")


LTR390驱动:

import time
from adafruit_ltr390 import LTR390, Gain, MeasurementDelay, Resolution

def Get_ltr_data(I2C):

ltr = LTR390(I2C)
return ltr
# ltr.resolution = Resolution.RESOLUTION_16BIT
#print("Measurement resolution is", Resolution.string[ltr.resolution])

# ltr.gain = Gain.GAIN_1X
#print("Measurement gain is", Gain.string[ltr.gain])

# ltr.measurement_delay = MeasurementDelay.DELAY_100MS
#print("Measurement delay is", MeasurementDelay.string[ltr.measurement_delay])
#print("")
if __name__ == "__main__":
import board
while True:
ltr = Get_ltr_data(board.I2C())
print("UV:", ltr.uvs, "\t\tAmbient Light:", ltr.light)

# for shorter measurement delays you may need to make this sleep shorter to see a change
time.sleep(1.0)


五、功能展示

5.1室外温湿度


5.2紫外线检测


5.3室内温湿度

六、设计中遇到的难题和解决方法

6.1 固件烧录问题

CircuitPython的固件作用:

DOWNLOAD .BIN 文件用于直接向mcu烧录程序,esp32为按下boot按键然后按一下rest按键,然后松开boot按键,进入烧录程序的模式

DOWNLOAD .UF2 文件用于向mcu烧录引导程序,连按两下rest按键,进入U盘烧录模式。


为了方便快捷的更改固件,烧录引导程序是必不可少的。但是进入U盘烧录模式的物理按键,很小而且节奏可能难以掌握,可使用如下程序,强制进入 Bootloader 模式:

import microcontroller
import time
print("正在尝试进入 Bootloader 模式...")
time.sleep(1) # 给自己一点反应时间
# 设置下次复位模式为 UF2 Bootloader
microcontroller.on_next_reset(microcontroller.RunMode.UF2)
# 执行复位
microcontroller.reset()


6.2 CircuitPython的espcamera 模块不支持OV3660摄像头

我有Xiao ESP32-S3配套的摄像头OV3660,尝试驱动起来时发现了问题,CircuitPython 为 Seeed Xiao ESP32-S3 Plus 编译的这个10.0.3版本的固件,虽然 espcamera 模块存在,但是没有OV3660相应的例程,可能是在编译阶段没有开启 OV3660 的驱动支持,因此无法用该版本固件的espcamera 模块驱动OV3660。AI给我的建议是如果想要驱动起来OV3660建议使用Arduino或者ESP-IDF来编写程序

七、对本次竞赛的心得体会

通过参与2026 贸泽电子 M-Design 创意设计大赛,我深刻体会到CircuitPython为嵌入式开发带来的革命性变化,从最初点亮LED到最终完成室内外环境检测系统的过程,不仅让我掌握了ESP32S3的ADC、DAC、WiFi连接及传感器驱动等核心技术,更让我领略到CircuitPython简洁优雅的开发体验——其REPL交互式环境、丰富的库支持和直观的错误处理机制大大降低了开发门槛。建议活动未来可增加更多实际项目案例和社区交流环节,帮助初学者更快跨越理论到实践的鸿沟,同时考虑为完成进阶任务的参与者提供更多硬件资源支持,以激发更多创新应用的诞生。
        最后非常感谢贸泽电子与硬禾科技联合主办的这次 M-Design 创意设计大赛,希望今后能有更多这样自由发挥空间很大的活动!

附件下载
源码sound code.7z
工程源码
团队介绍
电子开发爱好者
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号