尝鲜DFRobot ESP32-C5开发板,玩转LVGL Micropython
本帖最后由 PY学习笔记 于 2025-8-26 12:07 编辑近期,DFRobot 推出了全新开发板 FireBeetle 2 ESP32-C5。这块开发板搭载了 ESP32-C5 主控,集成 支持5GHz的Wi-Fi6(意思为可以连接5G网络),凭借强劲性能,令人眼前一亮。很荣幸能抢先体验这块开发板!1.开发板介绍FireBeetle 2 ESP32-C5有很多外设:
[*]Type-C:USB接口
[*]Charge:充电指示灯
[*]熄灭:未接入电源或已充满
[*]常亮: 充电中
[*]15/D13:板载LED引脚
[*]RST:复位按键
[*]28/BOOT:IO28引脚/BOOT按键
[*]BAT:锂电池接口,支持3.7~4.2V
[*]lO1:电池电压检测引脚
[*]3V3_C:IO0控制3.3V电源输出,默认关闭,可高电平开启。
[*]GDI:GDI显示屏接口
[*]ESP32-C5:型号为ESP32-C5-WROOM-1模组
2.micropython编译由于 MicroPython 官方尚未支持 ESP32-C5,支持尚需时日,我直接采用 GitHub PR 中某位大佬的半成品,稍作修改后,再集成到lvgl_micropython项目中,所以过程复杂,而且很多人并不想自行编译,所以这里,只放固件了(固件后期DF工作人员也会会放在文档中)。3.烧录建议使用esptool进行烧录,官方的工具现在无法擦除,所以用一下代码进行烧录(最后的固件名字自行修改):esptool --chip esp32c5 -p COM15 -b 460800 --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size 4MB --flash_freq 80m --erase-all 0x2000 firmware_eco1.bin
4.正式体验注意:因为扩展板有问题,在测试之前请把扩展板拔下来。1.普通MicroPython部分LED测试:from machine import Pin
import time
led=Pin(15,Pin.OUT)
while True:
led.on()
time.sleep(0.5)
led.off()
time.sleep(0.5)
联网测试:import network,time
SSID = 'SSID'
PWD = 'PWD'
def connect():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('esp32c5正在联网',end="")
wlan.connect(SSID, PWD)
while not wlan.isconnected():
print(".",end="")
time.sleep(1)
print('\n网络信息为: ', wlan.ifconfig())
connect()
联网扩展测试(串口与AI对话):import network
import time
import urequests
import ujson,micropython,select,sys
from machine import reset
# ====== 配置部分 ======
SSID = 'SSID'
PASSWORD = 'PWD'
API_KEY = "API-KEY"
API_URL = "https://api.siliconflow.cn/v1/chat/completions"
def connect_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('esp32c5正在联网',end="")
wlan.connect(SSID, PASSWORD)
while not wlan.isconnected():
print(".",end="")
time.sleep(1)
print('网络信息为: ', wlan.ifconfig())
print("连接成功!")
return wlan
def api_request():
headers = {
"Authorization": "Bearer sk-fzvjpoldzvziuvdrvrjiadercddliahbfyohdrnfnnaedjmt",
"Content-Type": "application/json"
}
micropython.kbd_intr(-1)
print("您:",end="")
while True:
while sys.stdin in select.select(,[],[],0):
#print("您:",end="")
sp= sys.stdin.readline().replace("\n", "").replace(" ", "")
if sp != "":
#sp = str(input("您:"))
#print("您:"+sp)
payload = {
"model": "Qwen/Qwen3-8B",
"messages": [# 必须包含消息内容
{"role": "system", "content": "你是我的AI助手qwen,你必须用中文回答且字数不超过85个,思考时间不超过1秒"},
{"role": "user", "content": sp}
],
"enable_thinking":False,
}
print("AI正在思考中...")
#print("Payload:", payload)
start_time = time.time()
response = urequests.post(
API_URL,
headers=headers,
data=ujson.dumps(payload).encode('utf-8'),
#timeout=20
)
response_time = time.time() - start_time
print(f"AI思考了{response_time:.1f}s")
#print(f"\n Status: {response.status_code}")
if response.status_code == 200:
json_resp = response.json()
print("AI回复:", json_resp['choices']['message']['content'].replace("\n", ""))
else:
print("Error Response:", response.text)
response.close()
print("您:",end="")
print("=== 启动系统 ===")
connect_wifi()
api_request()
2.LVGL+MicroPython部分先把屏幕的驱动下载进板子里(命名为screen.py),这里以ILI9488+GT911为主,其它的屏幕DF工作人员正在撰写对应文档,如果更新了即可下载:import lcd_bus
from micropython import const
import machine
import lcd_utils
# 创建SPI总线对象
spi_bus = machine.SPI.Bus(
host=1,
mosi=24,
miso=25,
sck=23,
)
# 创建显示屏的SPI通信对象
display_bus = lcd_bus.SPIBus(
spi_bus=spi_bus,
freq=40000000,
dc=8,
cs=27,
)
import ili9488
import lvgl as lv
import task_handler
# 创建显示屏对象
display = ili9488.ILI9488(
data_bus=display_bus,
display_width=320,
display_height=480,
backlight_pin=15,
reset_pin=26,
reset_state=ili9488.STATE_LOW,
color_space=lv.COLOR_FORMAT.RGB888,
color_byte_order=ili9488.BYTE_ORDER_RGB,
rgb565_byte_swap=True,
)
import i2c
import task_handler
import gt911
# 初始化显示屏
display.init()
# 定义触摸屏的I2C通信参数
i2c_bus = i2c.I2C.Bus(host=0, scl=10, sda=9, freq=400000, use_locks=False)
print(i2c_bus.scan())
touch_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=0x5D, reg_bits=gt911.BITS)
# 创建触摸屏设备对象
indev = gt911.GT911(touch_dev)
# 启用输入优先级
indev.enable_input_priority()
# 旋转显示
display.set_rotation(lv.DISPLAY_ROTATION._270)
# 打开屏幕背光
display.set_backlight(1)
th = task_handler.TaskHandler()
滑块测试:import screen
import lvgl as lv
scrn = lv.screen_active()# 获取当前激活的屏幕对象
scrn.set_style_bg_color(lv.color_hex(0x000000), 0)# 设置屏幕的背景颜色为黑色
slider = lv.slider(scrn)# 创建一个滑块
slider.set_size(200, 50)# 设置滑块的大小为宽度300,高50
slider.center()# 将滑块居中显示
label = lv.label(scrn)# 创建一个标签
label.set_text('HELLO LVGL_MICROPYTHON!')# 标签内容
label.align(lv.ALIGN.CENTER, 0, -50)# 将标签对齐到屏幕中心,并向上偏移50
打砖块测试:import screen
import lvgl as lv
scrn = lv.screen_active()# 获取当前激活的屏幕对象
scrn.set_style_bg_color(lv.color_hex(0x000000), 0)# 设置屏幕的背景颜色为黑色
slider = lv.slider(scrn)# 创建一个滑块
slider.set_size(200, 50)# 设置滑块的大小为宽度300,高50
slider.center()# 将滑块居中显示
label = lv.label(scrn)# 创建一个标签
label.set_text('HELLO LVGL_MICROPYTHON!')# 标签内容
label.align(lv.ALIGN.CENTER, 0, -50)# 将标签对齐到屏幕中心,并向上偏移50import screen, math, gc
import lvgl as lv
SCREEN_W = 480
SCREEN_H = 320
on_pad = True # 球是否还在板上
# ---------------- 物理参数 ----------------
BALL_R = 8 # 小球半径
MAT_W = 80
MAT_H = 15 # 薄板
VEL_Y0 = -6 # 初始向上速度
# ---------------- 全局对象 ----------------
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x000000), 0)
# 容器(避免坐标系混乱)
root = lv.obj(scrn)
root.remove_style_all()
root.set_size(SCREEN_W, SCREEN_H)
root.center()
# 弹力板
mat = lv.obj(root)
mat.set_size(MAT_W, MAT_H)
mat.set_style_bg_color(lv.palette_main(lv.PALETTE.GREEN), 0)
mat.set_y(SCREEN_H - MAT_H - 5) # 固定高度
# 小球
ball = lv.obj(root) # 用 obj 比 led 更易控制
ball.set_size(BALL_R*2, BALL_R*2)
ball.set_style_radius(BALL_R, 0)
ball.set_style_bg_color(lv.palette_main(lv.PALETTE.ORANGE), 0)
# 砖块
BRICK_W = 31
BRICK_H = 15
BRICK_GAP = 2
BRICK_COLS = SCREEN_W // (BRICK_W + BRICK_GAP)
BRICK_ROWS = 5
bricks = []
for r in range(BRICK_ROWS):
for c in range(BRICK_COLS):
b = lv.obj(root)
b.set_size(BRICK_W, BRICK_H)
b.set_style_bg_color(
lv.palette_main(lv.PALETTE.BLUE if r % 2 else lv.PALETTE.RED), 0)
b.set_pos(c*(BRICK_W+BRICK_GAP),
5 + r*(BRICK_H+BRICK_GAP))
bricks.append(b)
# ---------------- 物理量 ----------------
ball_x= SCREEN_W // 2
ball_y= SCREEN_H - 50
vx = 0
vy = VEL_Y0
game_over = False
def reset_ball():
global ball_x, ball_y, vx, vy, game_over, on_pad
ball_x = mat.get_x() + MAT_W // 2 # 直接按板子位置算
ball_y = mat.get_y() - BALL_R
vx = 0
vy = VEL_Y0
game_over = False
on_pad = True # 复位后球在板上
ball.set_pos(int(ball_x - BALL_R), int(ball_y - BALL_R))
# ---------------- 定时器 ----------------
def update_timer(t):
global ball_x, ball_y, vx, vy, game_over, on_pad
if game_over:
return
# 如果球还在板上,先让球水平跟着板子
if on_pad:
ball_x = mat.get_x() + MAT_W // 2
ball_y = mat.get_y() - BALL_R
ball.set_pos(int(ball_x - BALL_R), int(ball_y - BALL_R))
return
else:
# 原来的物理运动
ball_x += vx
ball_y += vy
# 边界反弹
if ball_x - BALL_R < 0 or ball_x + BALL_R > SCREEN_W:
vx = -vx
ball_x = max(BALL_R, min(SCREEN_W - BALL_R, ball_x))
if ball_y - BALL_R < 0:
vy = -vy
ball_y = BALL_R
# 掉出底部
if ball_y + BALL_R > SCREEN_H:
reset_ball()
return
# 与板碰撞
mat_left= mat.get_x()
mat_right = mat_left + MAT_W
if (ball_y + BALL_R >= mat.get_y() and
ball_y - BALL_R <= mat.get_y() + MAT_H and
ball_x + BALL_R >= mat_left and
ball_x - BALL_R <= mat_right and
vy > 0): # 必须正在下落才反弹
hit_pos = (ball_x - mat_left) / MAT_W
vx = (hit_pos - 0.5) * 10
vy = -abs(vy)
ball_y = mat.get_y() - BALL_R
# ---------------- 碰撞检测 ----------------
to_delete = [] # 1. 先收集要删的砖块
for brick in bricks:
bx, by = brick.get_x(), brick.get_y()
bw, bh = BRICK_W, BRICK_H
if (ball_x + BALL_R > bx and
ball_x - BALL_R < bx + bw and
ball_y + BALL_R > by and
ball_y - BALL_R < by + bh):
# 计算最小重叠方向
overlap_l = (ball_x + BALL_R) - bx
overlap_r = (bx + bw) - (ball_x - BALL_R)
overlap_t = (ball_y + BALL_R) - by
overlap_b = (by + bh) - (ball_y - BALL_R)
min_overlap = min(overlap_l, overlap_r, overlap_t, overlap_b)
if min_overlap == overlap_t or min_overlap == overlap_b:
vy = -vy
else:
vx = -vx
to_delete.append(brick) # 2. 只记录,不立即删
# 3. 统一删除并回收
for brick in to_delete:
brick.delete()
bricks.remove(brick)
gc.collect() # 立即释放已删对象
# 4. 统一刷新显示
ball.set_pos(int(ball_x - BALL_R), int(ball_y - BALL_R))
# ---------------- 触摸回调 ----------------
def touch_cb(e):
global on_pad, vx, vy, ball_x, ball_y
code = e.get_code()
point = lv.point_t()
screen.indev.get_point(point)
# 板子移动逻辑(所有触摸状态都处理)
new_x = point.x - MAT_W // 2
new_x = max(0, min(SCREEN_W - MAT_W, new_x))
mat.set_x(new_x)
# 球在板上时的同步处理
if code == lv.EVENT.PRESSING:
if on_pad == 1:
ball_x = new_x + MAT_W // 2
ball_y = mat.get_y() - BALL_R
else:
ball_x += vx
ball_y += vy
# 边界反弹
if ball_x - BALL_R < 0 or ball_x + BALL_R > SCREEN_W:
vx = -vx
ball_x = max(BALL_R, min(SCREEN_W - BALL_R, ball_x))
if ball_y - BALL_R < 0:
vy = -vy
ball_y = BALL_R
# 掉出底部
if ball_y + BALL_R > SCREEN_H:
reset_ball()
return
# 与板碰撞
mat_left= mat.get_x()
mat_right = mat_left + MAT_W
if (ball_y + BALL_R >= mat.get_y() and
ball_y - BALL_R <= mat.get_y() + MAT_H and
ball_x + BALL_R >= mat_left and
ball_x - BALL_R <= mat_right and
vy > 0): # 必须正在下落才反弹
hit_pos = (ball_x - mat_left) / MAT_W
vx = (hit_pos - 0.5) * 10
vy = -abs(vy)
ball_y = mat.get_y() - BALL_R
# ---------------- 碰撞检测 ----------------
to_delete = [] # 1. 先收集要删的砖块
for brick in bricks:
bx, by = brick.get_x(), brick.get_y()
bw, bh = BRICK_W, BRICK_H
if (ball_x + BALL_R > bx and
ball_x - BALL_R < bx + bw and
ball_y + BALL_R > by and
ball_y - BALL_R < by + bh):
# 计算最小重叠方向
overlap_l = (ball_x + BALL_R) - bx
overlap_r = (bx + bw) - (ball_x - BALL_R)
overlap_t = (ball_y + BALL_R) - by
overlap_b = (by + bh) - (ball_y - BALL_R)
min_overlap = min(overlap_l, overlap_r, overlap_t, overlap_b)
if min_overlap == overlap_t or min_overlap == overlap_b:
vy = -vy
else:
vx = -vx
to_delete.append(brick) # 2. 只记录,不立即删
# 3. 统一删除并回收
for brick in to_delete:
brick.delete()
bricks.remove(brick)
gc.collect() # 立即释放已删对象
ball.set_pos(int(ball_x - BALL_R), int(ball_y - BALL_R))
# 仅PRESSED事件触发发射
elif code == lv.EVENT.PRESSED and on_pad == 1:
hit_pos = (ball_x - mat.get_x()) / MAT_W
vx = (hit_pos - 0.5) * 10
vy = VEL_Y0
on_pad = False
# 发射后立即更新一次位置
ball.set_pos(int(ball_x - BALL_R), int(ball_y - BALL_R))
# 启动定时器
lv.timer_create(update_timer, 50, None)
root.add_event_cb(touch_cb, lv.EVENT.PRESSED, None)
root.add_event_cb(touch_cb, lv.EVENT.PRESSING, None)
reset_ball()
时间表盘测试:import screen
import time,gc
import lvgl as lv
from machine import Pin, Timer
import math
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x000000), 0)
class AnalogClock:
def __init__(self, parent):
self.scale = None
self.second_hand = None
self.minute_hand = None
self.hour_hand = None
# 获取当前时间
now = time.localtime()
self.hour = now % 12# 转换为12小时制
self.minute = now
self.second = now
self.create_clock(parent)
self.start_timer()
def create_clock(self, parent):
"""创建模拟时钟组件"""
# 创建表盘主体(保持120x120大小)
self.scale = lv.scale(parent)
self.scale.set_size(200, 200)
self.scale.set_mode(lv.scale.MODE.ROUND_INNER)
# 设置表盘样式(保持不变)
self.scale.set_style_bg_opa(lv.OPA._60, 0)
self.scale.set_style_bg_color(lv.color_hex(0x222222), 0)
self.scale.set_style_radius(lv.RADIUS_CIRCLE, 0)
self.scale.set_style_clip_corner(True, 0)
self.scale.center()
# 配置刻度系统(保持不变)
self.scale.set_label_show(True)
hour_labels = ["12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", None]
self.scale.set_text_src(hour_labels)
self.scale.set_style_text_font(lv.font_montserrat_12, 0)
self.scale.set_total_tick_count(61)
self.scale.set_major_tick_every(5)
# 主刻度样式(保持不变)
style_indicator = lv.style_t()
style_indicator.init()
style_indicator.set_text_color(lv.color_hex(0xFFFFFF))
style_indicator.set_line_color(lv.color_hex(0xFFFFFF))
style_indicator.set_length(5)
style_indicator.set_line_width(2)
self.scale.add_style(style_indicator, lv.PART.INDICATOR)
# 次刻度样式(保持不变)
style_minor = lv.style_t()
style_minor.init()
style_minor.set_line_color(lv.color_hex(0xAAAAAA))
style_minor.set_length(3)
style_minor.set_line_width(1)
self.scale.add_style(style_minor, lv.PART.ITEMS)
# 表盘边框样式(保持不变)
style_main = lv.style_t()
style_main.init()
style_main.set_arc_color(lv.color_hex(0x222222))
style_main.set_arc_width(3)
self.scale.add_style(style_main, lv.PART.MAIN)
# 设置量程和角度(保持不变)
self.scale.set_range(0, 60)
self.scale.set_angle_range(360)
self.scale.set_rotation(270)
# 创建秒针(红色,长度90px,细线)
self.second_hand = lv.line(self.scale)
self.second_hand.set_style_line_width(1, 0)# 更细的线宽
self.second_hand.set_style_line_rounded(True, 0)
self.second_hand.set_style_line_color(lv.color_hex(0xFFFFFF), 0)
# 创建分钟指针(蓝色,长度75px)
self.minute_hand = lv.line(self.scale)
self.minute_hand.set_style_line_width(3, 0)
self.minute_hand.set_style_line_rounded(True, 0)
self.minute_hand.set_style_line_color(lv.color_hex(0x00BFFF), 0)
# 创建小时指针(橙色,长度60px)
self.hour_hand = lv.line(self.scale)
self.hour_hand.set_style_line_width(5, 0)
self.hour_hand.set_style_line_rounded(True, 0)
self.hour_hand.set_style_line_color(lv.color_hex(0xFFA500), 0)
# 添加中心点(保持不变)
center = lv.obj(self.scale)
center.set_size(8, 8)# 稍微减小中心点大小
center.center()
center.set_style_radius(lv.RADIUS_CIRCLE, 0)
center.set_style_bg_color(lv.color_hex(0xFFD700), 0)
center.set_style_bg_opa(lv.OPA.COVER, 0)
self.update_hands()
def update_hands(self):
"""更新所有指针位置"""
# 秒针(90px长度)
lv.scale.set_line_needle_value(self.scale, self.second_hand, 90, self.second)
# 分钟指针(75px长度)
lv.scale.set_line_needle_value(self.scale, self.minute_hand, 75, self.minute)
# 小时指针(60px长度),考虑分钟偏移
hour_value = self.hour * 5 + (self.minute // 12)
lv.scale.set_line_needle_value(self.scale, self.hour_hand, 60, hour_value)
def timer_callback(self, timer):
"""定时器回调(每秒更新)"""
# 获取当前时间
now = time.localtime()
self.hour = now % 12
self.minute = now
self.second = now
self.update_hands()
gc.collect()
def start_timer(self):
"""启动硬件定时器(每秒触发)"""
self.timer = Timer(1)
self.timer.init(period=1000, mode=Timer.PERIODIC, callback=self.timer_callback)
# 创建时钟实例
clock = AnalogClock(scrn)
5.效果https://www.bilibili.com/video/BV1WdeRzAESB
https://www.bilibili.com/video/BV1sdeRzAEJR
https://www.bilibili.com/video/BV1WdeRzAEXF
6.问题解答
以上的问题是由于设备已经初始化一次了,不能重复初始化,将板子重启再次运行即可
页:
[1]