【得捷电子Follow me第2期】+ ESP32-S3 TFT 为智能物联赋能
[复制链接]
Part1 视频演示
Part2 任务/项目总结报告
任务1:控制屏幕显示中文
控制Adafruit ESP32-S3 TFT Feather的屏幕显示中文需要分两步走:
1.加载自定义的中文字库
首先,我们需要制作一个体积较小的pcf字体,选择一个包含常用中文字符的开源字体(.ttf)并下载到本地,在linux环境中,通过otf2bdf程序将.ttf字体转换为.bdf格式,执行以下命令输出字体尺寸为48px的.bdf字体:
otf2bdf ./num.ttf -p 48 -o 48pxFont.bdf
但是,经过实际测试.bdf字体加载速度很慢,考虑将其转换为.pcf格式,在以下网站中进行转换:
web-bdftopcf
之后将生成的字体移动到相应的目录中,CIRCUITPY:\src:
为了能在程序中使用字体,需要调用adafruit_bitmap_font外部库,以下代码完成调库并加载位图字体:
from adafruit_bitmap_font import bitmap_font
# 加载需要的字体
font_16 = bitmap_font.load_font("/src/16pxFont.pcf")
color = 0x333333
经过,以上操作,我们便可以在程序中使用自定义字体了。
2.控制屏幕显示中文
首先,我们需要调用内部库displayio,并生成一个屏幕对象,用于显示内容,代码如下:
import displayio
# 获取屏幕参数
display = board.DISPLAY
接下来,调用外部库adafruit_imageload、adafruit_display_text以实现生成一个Group对象,Group对象自身可以设置背景图,同时还可以通过append()方法向其中添加其他对象,以下代码就是通过向Group对象中添加,Label标签以达到显示中文的目的:
# 加载图片资源
image, palette = adafruit_imageload.load("/src/bg.png")
# 设为不透明
palette.make_transparent(21)
# 创建显示组
group = displayio.Group()
# 创建布局
grid = displayio.TileGrid(image, pixel_shader=palette)
# 布局应用于显示组
group.append(grid)
# 信息标签
info = label.Label(font_16, text="PWM Play", color=color)
info.x = 8
info.y = 16
group.append(info)
r_info = label.Label(font_16, color=color)
r_info.x = 8
r_info.y = 36
group.append(r_info)
display.show(group)
hz = 440
r_info.text = "频率 {}hz".format(hz)
最终的效果,是通过按键控制扬声器发出不同频率的声音,用到的硬件还用一同购置的放大器和扬声器,元器件连接如下:
为了发出不同频率的声音和检测按键中断,我们需要调用内部库pwmio、keypad,来指定某个引脚发出固定频率的pwm波,代码如下:
import pwmio
import keypad
# 获取板载按键引脚
btn_pin = board.BOOT0
pwm = pwmio.PWMOut(board.A0, duty_cycle=2 ** 15, frequency=hz, variable_frequency=True)
time.sleep(0.1)
with keypad.Keys((btn_pin,), value_when_pressed=False) as keys:
while True:
event = keys.events.get()
if event:
if event.pressed:
hz *= 2
pwm.frequency = hz
time.sleep(0.1)
检测到按键按下后,调整pwm输出频率,并在屏幕上反馈信息。
任务2:网络功能使用
网络功能使用这一步较为简单,分为两部分:
1.作为Station结点,连接WiFi
连接WiFi较为简单,只需要调用内部库wifi即可完成,值得注意的是,circuitpython的固件是复位自动连接settings.toml中指定参数的wifi:
CIRCUITPY_WIFI_SSID = "P30"
CIRCUITPY_WIFI_PASSWORD = "24865709"
修改后,按下rst按键,就会连接指定WiFi并,输出连接成功后获取到的IP地址。
当然,除此之外,我们还可以在程序中自主连接WiFi,调用wifi.radio对象的connect()方法即可:
import wifi
import os
# 获取WiFi信息、密码,并连接
ssid = os.getenv("CIRCUITPY_WIFI_SSID")
passwd = os.getenv("CIRCUITPY_WIFI_PASSWORD")
wifi.radio.connect(ssid, passwd)
2.作为AP结点,可以被连接
此外,此开发板还可以作为热点(AP),被其他设备连接,同样的也只需要调用内部库wifi即可,wifi.radio的start_ap()方法即可:
import wifi
wifi.radio.start_ap(ap_ssid, passwd)
最终实现的效果如下,在之前在屏幕上显示中文的代码的基础上,继续添加WiFi模块的代码,实现了通过按键中断来切换连接WiFi和作为AP被连接两种状态,部分代码如下:
cnt = 0
with keypad.Keys((btn_pin,), value_when_pressed=False) as keys:
while True:
event = keys.events.get()
if event:
if event.pressed:
if cnt % 2 == 0:
# 连接WiFi
connect_wifi(ssid, passwd)
else:
set_ap("Access_Point", "24865709")
cnt += 1
效果如图:
任务3:控制WS2812B
控制WS2812B灯珠,这部分内容需要用到两个外部库:adafruit_led_animation,neopixel,这些库包含多种炫酷的灯光效果,这里选择其中的Pulse效果作为演示。
除了以上库,还需要调用内部库digitalio和board,分别用于控制io口和获取板载io的信息,代码如下:
# 导入board、time、digitalio、countio内置库
import board
import time
from digitalio import DigitalInOut, Direction, Pull
import keypad
#导入neopixel、adafruit、adafruit_led_animation外部库
import asyncio
import neopixel
from adafruit_led_animation.animation.pulse import Pulse
from adafruit_led_animation.color import JADE, BLACK, ORANGE
# 获取板载led引脚、按键引脚
led_pin = board.NEOPIXEL
btn_pin = board.BOOT0
# led数量为1
led_num = 1
# 初始化像素灯
pixels = neopixel.NeoPixel(
led_pin, led_num, brightness=0.2, auto_write=False, pixel_order=neopixel.GRB
)
# 初始化pulse动态效果
pulse = Pulse(pixels, speed=0.3, color=BLACK, period=10)
经过以上代码初始化后,由于color参数为black,led灯并不会亮。
为了通过按键中断来控制灯光颜色,这里引入另一个外部库asyncio,这个库可以实现多线程,这样两个线程,一个负责检测中断,另一个负责led动效控制,代码如下:
# 按键中断检测并切换led颜色
async def catch_interrupt(pin):
""" 按下按键,切换灯光颜色 """
global pulse, pixels
push_cnt = 0
with keypad.Keys((pin,), value_when_pressed=False) as keys:
while True:
event = keys.events.get()
if event:
if event.pressed:
if push_cnt == 0:
pulse = Pulse(pixels, speed=0.3, color=JADE, period=10)
print("LED is JADE")
elif push_cnt == 1:
pulse = Pulse(pixels, speed=0.3, color=ORANGE, period=10)
print("LED is ORANGE")
elif push_cnt == 2:
pulse = Pulse(pixels, speed=0.3, color=BLACK, period=10)
print("LED is BLACK")
push_cnt = (push_cnt + 1) % 3
# 主动释放处理机
await asyncio.sleep(0)
# led动效执行任务
async def led_animate():
global pulse
while True:
time.sleep(0.2)
pulse.animate()
await asyncio.sleep(0)
async def main():
interrupt_task = asyncio.create_task(catch_interrupt(btn_pin))
animate_task = asyncio.create_task(led_animate())
await asyncio.gather(interrupt_task, animate_task)
asyncio.run(main())
最终实现效果:通过boot按键触发,每次在black,red,green三种颜色间切换,并执行Pulse(脉冲)动效,效果图如下:
任务4:分任务1:日历&时钟
天气时钟部分可以分为三个模块,负责时间更新的模块,负责天气更新的模块,负责屏幕显示的模块
1.屏幕显示模块
屏幕显示部分,在之前的基础上,我们还需要提前规划屏幕的UI布局,UI设计部分我们可以在Pixso网站中完成
Pixso
背景图设置为240x135px,并添加需要的信息字段,移动到合适的位置,最终设计出的效果如下图:
隐藏字体并导出画板为.png图像,将其作为屏幕的背景图
接下来,程序上分别设计显示时间,日期,温度,星期,天气状态的label,通过上面Pixso的UI布局以及字体的大小将UI设计的界面显示到屏幕上,这里我选用了两种大小的字体:16px、48px,代码下:
# 加载图片资源
image, palette = adafruit_imageload.load("/src/bg.png")
# 设为不透明
palette.make_transparent(21)
# 创建显示组
group = displayio.Group()
# 创建布局
grid = displayio.TileGrid(image, pixel_shader=palette)
# 布局应用于显示组
group.append(grid)
# 加载需要的字体
font_16 = bitmap_font.load_font("/src/16pxFont.pcf")
font_48 = bitmap_font.load_font("/src/48pxFont.pcf")
color = 0x333333
# 时间标签
t_label = label.Label(font_48, color=color)
t_label.x = 113-24
t_label.y = 57+32
group.append(t_label)
# 日期标签
d_label = label.Label(font_16, color=color)
d_label.x = 11
d_label.y = 77+16
group.append(d_label)
# 温度标签
tp_label = label.Label(font_48, color=color)
tp_label.x = 11
tp_label.y = 7+32
group.append(tp_label)
# 星期标签
w_label = label.Label(font_16, color=color)
w_label.x = 120-16
w_label.y = 23+8
group.append(w_label)
# 天气标签
we_label = label.Label(font_16, color=color)
we_label.x = 120+16*4
we_label.y = 23+8
group.append(we_label)
2.时间更新模块
时间更新和time模块相关,这里需要先对系统时间进行ntp校时,需要ssl、rtc、socketpool三个内部库,以及外部库adfruit_ntp,建立socket连接后,通过阿里云ntp服务器进行校时,注意,中国时区为东八区,代码如下:
import rtc
import socketpool
import wifi
import ssl
# 连接ntp服务器
pool = socketpool.SocketPool(wifi.radio)
ntp = adafruit_ntp.NTP(pool, tz_offset=8, server="ntp.aliyun.com")
# 校验本地时钟
rtc.RTC().datetime = ntp.datetime
之后,便可以通过调用time.localtime()方法获取本地时间信息,代码如下:
t = time.localtime()
if boot:
mon = t.tm_mon
mday = t.tm_mday
hour = t.tm_hour
min = t.tm_min
d_label.text = "{}月{}日".format(mon, mday)
t_label.text = "{:0>2d}:{:0>2d}".format(hour, min)
w_label.text = get_wday(t.tm_wday)
weather_event.set()
boot = False
elif t.tm_mday != mday:
mon = t.tm_mon
mday = t.tm_mday
d_label.text = "{}月{}日".format(mon, mday)
w_label.text = get_wday(t.tm_wday)
elif t.tm_min != min:
hour = t.tm_hour
min = t.tm_min
t_label.text = "{:0>2d}:{:0>2d}".format(hour, min)
# 每10min更新天气
if (min % 10) == 0:
weather_event.set()
3.天气更新模块
天气更新模块需要对用到adafruit_requests外部库,用来对提供天气信息的api发起get()请求,再将获取到的response转化为json格式,用操作字典(dict)的方式来获取其中的天气状态以及气温两个关键信息,用以更新天气状态,代码如下:
key = "SoTHYYaP1ty9qqdiP"
city = "yaan"
get_url = "https://api.seniverse.com/v3/weather/now.json?key={}&location={}".format(key, city)
while True:
await weather_event.wait()
res = requests.get(get_url)
json_res = res.json()
res.close()
we_label.text = json_res["results"][0].get("now").get("text")
tp_label.text = json_res["results"][0].get("now").get("temperature")
weather_event.clear()
将时间更新和天气更新的两个模块封装成函数后,再通过asyncio库引入多线程,以及其中的events机制引入信号量,用以完成两个进程之间的同步,这里设置的效果是:如果是首次启动则更新时间及天气信息;循环获取本地时间,如果获取到的时间信息和上次记录的不一致就更新相应的时间或日期等信息,同时每隔半小时调用event.set(),用以通知天气更新线程更新信息,代码如下:
boot = True
# 天气信号量,线程同步
weather_event = asyncio.Event()
async def time_update():
# 获取本地时间
global d_label,t_label,w_label, weather_event, boot
mon = ""
mday = ""
hour = ""
min = ""
while True:
t = time.localtime()
if boot:
mon = t.tm_mon
mday = t.tm_mday
hour = t.tm_hour
min = t.tm_min
d_label.text = "{}月{}日".format(mon, mday)
t_label.text = "{:0>2d}:{:0>2d}".format(hour, min)
w_label.text = get_wday(t.tm_wday)
weather_event.set()
boot = False
elif t.tm_mday != mday:
mon = t.tm_mon
mday = t.tm_mday
d_label.text = "{}月{}日".format(mon, mday)
w_label.text = get_wday(t.tm_wday)
elif t.tm_min != min:
hour = t.tm_hour
min = t.tm_min
t_label.text = "{:0>2d}:{:0>2d}".format(hour, min)
# 每10min更新天气
if (min % 10) == 0:
weather_event.set()
# 主动释放处理机
await asyncio.sleep(0)
async def weather_update():
global we_label, tp_label, requests, weather_event, update_event
key = "SoTHYYaP1ty9qqdiP"
city = "yaan"
get_url = "https://api.seniverse.com/v3/weather/now.json?key={}&location={}".format(key, city)
while True:
await weather_event.wait()
res = requests.get(get_url)
json_res = res.json()
res.close()
we_label.text = json_res["results"][0].get("now").get("text")
tp_label.text = json_res["results"][0].get("now").get("temperature")
weather_event.clear()
# 主动释放处理机
await asyncio.sleep(0)
async def main():
t_update_task = asyncio.create_task(time_update())
w_update_task = asyncio.create_task(weather_update())
await asyncio.gather(t_update_task, w_update_task)
asyncio.run(main())
效果如下图:
Part3 可编译下载的代码
源码
#### 心得体会
在控制Adafruit ESP32-S3 TFT Feather的屏幕显示中文时,我了解到需要分为两步进行操作。首先是加载自定义的中文字库,需要选择一个包含常用中文字符的开源字体并将其转换为.bdf或.pcf格式。在实际测试中,发现.bdf字体加载速度较慢,因此建议转换为.pcf格式。然后,将字体移动到相应目录,并使用adafruit_bitmap_font库调用位图字体。这样,我们便能在程序中使用中文字体了。
接下来是屏幕显示部分。首先需要调用displayio库生成一个屏幕对象,用于显示内容。然后,结合使用adafruit_imageload和adafruit_display_text库,我们可以创建一个Group对象,并向其中添加Label标签来显示中文。可以通过设置背景图和调整位置来设计UI布局,最后将界面显示在屏幕上。此外,控制网络功能和WS2812B灯珠也很简单,只需调用相应的内部和外部库即可实现连接WiFi和灯光控制。
通过学习和实践,我对控制Adafruit ESP32-S3 TFT Feather的屏幕显示中文有了更深入的了解。这个过程中,我发现选择适合的字体格式和进行UI布局设计非常重要,而且需要注意字体加载速度和位置调整。此外,对相关库的使用和调用也需要一定的了解。虽然初次接触可能有些困惑,但通过不断尝试和实践,我渐渐掌握了其中的要点和技巧。我相信,在今后的实际应用中,我能更加熟练地控制Adafruit ESP32-S3 TFT Feather的屏幕显示中文,并将其应用在更多有趣的项目中。
#### 建议
首先,可以组织一个创客竞赛,鼓励参与者使用Adafruit ESP32-S3 TFT Feather来开发创新项目。这将激发创造力和创新思维,同时宣传和推广该开发板的功能和特点。其次,可以提供实用的教程和指南,帮助参与者快速上手和理解开发板的使用方法。此外,还可以设置一个技术支持平台,提供问题解答和交流互动,以帮助参与者克服技术难题。最后,可以为参与者准备一份奖励,例如奖金或者Adafruit产品优惠券,以激励他们的参与和创新。通过这样的活动,能够促进技术共享、推动创新发展,同时扩大EEWorld的影响力。
|