很高兴这次有机会参加DigiKey的活动。虽然过程有点曲折,但还是顺利地拿到了Adafruit的ESP32-S3 TFT开发板。这个板子小巧精致,个人非常喜欢。当然,最喜欢的还是这个报销活动,哈哈。可惜下单的时候没处理好,只买了单板,手边也没有太多外设,展现不出这块板子真正的实力。
这个任务的要求是完成屏幕的控制,并且能显示中文。这里暂且将要求具体定为:在不同的行列上,用不同的颜色分别显示中文汉字。
首先需要说明的是,CircuitPython不支持直接在屏幕上显示中文,因此想要在屏幕上显示中文,需要使用额外的方式。
首先需要准备字体文件。出于之后的项目考虑,这里使用其他博主提供的思源黑体16号字体,该字体只包含了很少的字,主要用于显示时间相关的内容。 有了字体文件,还需要读取字体文件的方式。Adafruit提供了大量有用的CircuitPython额外库可供使用,使用其中的adafruit_bitmap_font就能读取字体文件。
读取字体文件还需要进行显示才行。同样的,显示过程也使用Adafruit的库adafruit_display_text来完成。 项目原理理清楚了之后,就可以参考官方例程编写代码了。
首先是导入库:
# 导入内置库
import board
import displayio
# 导入外部库
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
由于adafruit系列的库不是核心库,因此需要先从官网上下载后导入到板子内才可使用。由于板子默认的库查找文件夹是lib文件夹,因此将两个库文件放入磁盘的lib文件夹中,如下图所示:
导入完成后,定义相关变量:
display = board.DISPLAY # 使用固件上的屏幕,即TFT屏幕
group = displayio.Group() # 定义组
font = bitmap_font.load_font("/font/sytq_16.pcf") # 加载字体
变量font对应的文件路径是自己创建的,可根据需要进行修改。文件sytq_16.pcf就是思源黑体16号字体对应的文件。
读取字体后就能进行显示操作。Label对象能够按行在指定位置显示数据,参数font表示字体,text表示需要显示的字符串,color为显示颜色,x和y则表示显示的起始位置。
display = board.DISPLAY # 使用固件上的屏幕,即TFT屏幕
group = displayio.Group() # 定义组
font = bitmap_font.load_font("/font/sytq_16.pcf") # 加载字体
color = 0x00FFFF # 蓝色
sc = label.Label(font, text="年", color=color, x=0, y=30)
group.append(sc)
color = 0xFF0000 # 红色
sc = label.Label(font, text="月", color=color, x=60, y=60)
group.append(sc)
color = 0xFF9900 # 橙色
sc = label.Label(font, text="日", color=color, x=120, y=90)
group.append(sc)
color = 0x9900FF # 紫色
sc = label.Label(font, text="周", color=color, x=180, y=120)
group.append(sc)
display.show(group) # 显示
# 死循环使得显示结果得以保持
while True:
pass
参数color接受整数或十六进制的RGB颜色代码,颜色代码可从各种在线网页上进行选取。这块TFT屏幕的尺寸为240x135像素大小,对应坐标范围x:0~239、y:0~134。在设置的时候允许超出这个范围,不会报错,只是会不显示对应的图形。 最后使用死循环保持显示。代码保存后会自动运行,运行结果如下:
可以看到指定的四个汉字使用不同的颜色显示在了屏幕的不同位置上,满足要求。
在调试时可以很明显体会到CircuitPython的Web流调试方式的便利之处:网页终端可以打印汉字,这不受开发板的影响。在完成该任务时,可以利用这一特点来测试是否成功加载了对应的汉字库。
任务2:网络功能使用
这个任务的要求是完成网络功能的使用,能够创建热点和连接到WiFi。这里暂且将要求具体定为:让开发板能连接到正常WiFi上,再让开发板创建WiFi热点。
先说连接WiFi。由于本人使用的是网络工作流进行开发,自然就需要先让这块板子连接到网络上。
首先在官网上下载对应的固件库,这里给出下载网址:Feather ESP32-S3 TFT Download,选择其中uf2文件进行下载。
下载完成后,给开发板上电,连按两下Rst按键,进入到烧录模式。在电脑上打开开发板对应的磁盘,并将uf2文件复制到该磁盘中。当复制过程结束后,开发板会自动重启。
再次打开对应的磁盘,此时磁盘名变更为CIRCUITPY,打开后在其目录下有一个名为settings.toml的文件,使用编辑器打开并修改该文件,写入内容如下:
# 配置WiFi名称
CIRCUITPY_WIFI_SSID = "3ggg"
# 配置WiFi密码
CIRCUITPY_WIFI_PASSWORD = "qwer1234"
#所在行是注释,变量CIRCUITPY_WIFI_SSID和CIRCUITPY_WIFI_PASSWORD都是系统变量,分别对应着WiFi名称和WiFi密码,将引号内的内容按需修改之后,该开发板就能连接到WiFi了。如下图所示:
从显示屏左上角可以看到IP地址,说明连接WiFi成功,且在网页端也能看到该核心板能正常上网。
除了连接WiFi,该板子还可以创建WiFi热点。直接输入以下代码即可:
# 导入内置库
import wifi
print("Opening WiFi......")
wifi.radio.start_ap('esp32-s3', '12345678')
print("Success")
代码非常简单,要点就是调用核心库中的wifi库即可。从屏幕上的显示可以看出确实执行成功了:
同时这个WiFi也确实是可以连接的:
但需要注意的是,ESP32-S3并不支持同时连接WiFi和创建WiFi热点,因此创建WiFi成功后会自动断开当前连接的WiFi,即使WiFi的连接方式是通过配置settings.toml文件实现的。
任务3:控制WS2812B
这个任务的要求是使用按键控制板载Neopixel LED的显示和颜色切换。
首先是显示。CircuitPython核心库提供了neopixel_write库对Neopixel LED进行控制,但该控制太过初级,不够简便,因此使用Adafruit提供的neopixel来实现功能。
然后是控制。由于本人只有裸板,无法外接其他设备,因此只能使用板子上的按键进行控制。然而该板只有两个按键,其中一个还是复位按键,肯定不能拿来当作控制按键,因此只有唯一的选择:使用Boot按键当作控制按键。
项目原理理清楚了之后,就可以参考官方例程编写代码了。
首先是下载neopixel并放入lib文件夹中:
然后导入库:
# 导入内置库
import board
import time
import digitalio
# 导入外部库
import neopixel
导入完成后,定义并设置相关变量:
pixel_pin = board.NEOPIXEL # 使用板子上的Neopixel LED
neo = neopixel.NeoPixel(pixel_pin, 1, brightness=0.2, auto_write=False, pixel_order=neopixel.GRB) # 定义并设置Neopixel
button = digitalio.DigitalInOut(board.BOOT0) # 使用板子上的BOOT0按键作为按钮
button.direction = digitalio.Direction.INPUT # 将按键设置为输入
button.pull = digitalio.Pull.UP # 按键设置为上拉,默认为1
color_tuple = ((0,0,0), (255,0,0), (0,255,0), (0,0,255))
counter = 0
这里先定义一个含RGB数据的多维元组,用来确定Neopixel LED的显示样式,然后使用计数器对按下的次数进行计数,根据计数器的值确定显示哪个样式,如下面的代码所示:
while True:
# 如果按键按下
if not button.value:
time.sleep(0.2) # 去抖
color = color_tuple[counter % len(color_tuple)]
neo.fill(color)
neo.show()
print("current RGB=", color)
counter = counter + 1
由于设置的是按键上拉,因此button.value的默认值就是1,当按键按下时,button.value的值变为0,执行后面的语句。首先先根据当前计数器的值确定Neopixel LED的显示模式并进行显示,然后打印当前的RGB值,最后counter自增,实现按键控制Neopixel LED。
代码保存后会自动运行,运行结果如下:
从显示屏上的文字可以看出RGB值确实按顺序发生了变化,这是因为按下了多次按键。当前时刻R=255、G=0、B=0,对应着红色,Neopixel LED也确实发出了红光,满足要求。
任务4:日历&时钟
这个任务的要求是完成一个可通过互联网更新的万年历时钟,并显示当地的天气信息。
这个任务的主要问题在于时间和天气信息的获取,其他部分的代码可以参考实验1实现。
首先是时间的获取。可以通过NTP服务器获取互联网时间,然后利用互联网时间更新本地的实时时钟。这里也可以完全使用NTP服务器上的时间,但需要多次访问,尝试的时候有时候会有BUG,因此选择使用本地时间。CircuitPython核心库提供了rtc库用来调用本地时间,但没有和NTP相关的库,因此需要Adafruit提供的额外库。
然后是天气信息的获取。这里采用URL请求互联网公司提供的天气API来取得天气信息。这里选择高德开放平台作为API的来源。既然要使用URL,自然需要requests库,这个库也不是核心库,需要Adafruit提供。
获取完时间和天气信息,就可以参照实验1的方式进行显示了。项目原理清楚了之后,就可以参考官方例程编写代码。
首先是下载ntp和requests库并放入lib文件夹中:
然后导入库:
# 导入内置库
import board
import displayio
import rtc
import time
import wifi
import ssl
import socketpool
# 导入外部库
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
import adafruit_ntp
import adafruit_requests
导入完成后,定义并设置相关变量。因为应用Key涉及个人隐私,因此没有在此展示。这里完全可以自行更改:
# 定义城市代码
CITYCODE = "500108" # 南岸区
# 高德天气查询API服务地址
LBS_WEATHER_URL = "https://restapi.amap.com/v3/weather/weatherInfo?city="
# 高德控制台应用Key
LBS_APP_KEY = "xxx"
为了后续使用方便,定义两个简单的函数:
# 自定义函数
# wday2week: 将tm_wday对应的值转换为星期字符串
def wday2week(wday):
if(wday == 0):
return "周一"
elif(wday == 1):
return "周二"
elif(wday == 2):
return "周三"
elif(wday == 3):
return "周四"
elif(wday == 4):
return "周五"
elif(wday == 5):
return "周六"
elif(wday == 6):
return "周日"
else:
return "xxxx"
# getWeather: 获取当前天气信息
def getWeather(request, citycode):
full_url = LBS_WEATHER_URL + citycode + "&key=" + LBS_APP_KEY # 拼接完整的url
response = request.get(full_url) # 获取url返回数据
json = response.json() # 取出json数据
response.close() # 关闭连接
lives_list = json["lives"][0] # 解析数据
weather = lives_list["weather"]
return weather
然后就可以编写主代码了:
# 程序
pool = socketpool.SocketPool(wifi.radio) # 初始化ntp服务
ntp = adafruit_ntp.NTP(pool, tz_offset=8, server="ntp.aliyun.com")
rtc.RTC().datetime = ntp.datetime # 利用ntp时间更新系统时间
request = adafruit_requests.Session(pool, ssl.create_default_context()) # 初始化request
display = board.DISPLAY # 使用固件上的屏幕,即TFT屏幕
font = bitmap_font.load_font("/font/sytq_16.pcf") # 加载字体
# 死循环,每分钟更新一次信息
while True:
current_time = time.localtime() # 获取当前时间
if(current_time.tm_sec == 0):
group = displayio.Group() # 定义并清空组
# 获得年月日等信息
date = "%d年%d月%d日" % (current_time.tm_year, current_time.tm_mon, current_time.tm_mday) # 日期字符串
week = wday2week(current_time.tm_wday) # 星期字符串
present = "%d:%d" % (current_time.tm_hour, current_time.tm_min) # 时间字符串
weather = getWeather(request, CITYCODE) # 天气字符串
# 显示日期
color = 0xFF9900 # 橙色
sc = label.Label(font, text=date, color=color, x=0, y=10)
group.append(sc)
# 显示星期
color = 0xFF9900 # 橙色
sc = label.Label(font, text=week, color=color, x=190, y=10)
group.append(sc)
# 显示时间
color = 0x00ff00 # 绿色
sc = label.Label(font, text=present, color=color, x=70, y=40)
group.append(sc)
# 显示天气
color = 0x00ffff # 蓝色
sc = label.Label(font, text=weather, color=color, x=150, y=40)
group.append(sc)
# 显示
display.show(group)
这段代码首先创建了相关的对象并进行了初始化,然后利用NTP服务器上的时间更新本地时间以便后续使用。死循环中不断获取本地时间并进行判断,因为一分钟内只有一次tm_sec=0的时刻,因此判断语句之后的代码每分钟执行一次。后续代码的功能为:获取本地时间中的年月日信息、获取本地时间中的星期信息并转换为对应的中文、通过高德API获取当地的天气信息、将相关信息显示在TFT屏幕上。
代码保存后会自动运行,运行结果如下:
这里以同一时刻手机上的天气信息作为对比:
从这两张图相关信息的对比上看,两者完全一样,满足要求。代码整体难度不高,主要耗时部分在调试和API的申请上。
第三部分:代码链接
这里给出本人在任务中用到的所有代码,欢迎学习交流:得捷电子Follow me第2期可编译下载的代码。
结尾
当时知道会延迟发货的时候真的吓了一跳,还以为没法完成任务了,但主办单位迅速调整了活动时间,还给了退款渠道,得捷电子的相关负责人值得好评。任务总体来说不难,我这个第一次接触CircuitPython相关软硬件的人也能在一周内进行简单的开发,还是蛮有收获的。希望后续的几期活动越办越好!