【演示视频】
【项目总结】
1、前言
非常感谢EEWORLD和得捷电子组织本次follow me活动,本次follow me活动主要围绕规定的几个任务展开,完成了任务1~任务4的必做任务。
期间遇到不少困难,好在论坛大佬多资料不少,倒是勉强能完成本次活动。
项目中主要遇到困难有:
1.字库制作方法少且制作复杂,py库引用的文件字体里不能有小数点的像素
2.wifi连接报错,在环境复杂的情况下,可能找不到目标wifi,强行连接会报错。
2、介绍
这一次的板卡是Adafruit ESP32-S3 TFT Feather
Adafruit ESP32-S3 TFT Feather是由开源硬件行业知名公司Adafruit出品的一款富有特色的开源硬件,开发板使用乐鑫ESP32-S3芯片,支持WiFi和蓝牙能,自带高清TFT彩色显示屏。
特性
• 搭载 Xtensa® 32 位 LX7 双核处理器,主频高达 240 MHz,内置 512 KB SRAM (TCM),具有 45 个可编程 GPIO 管脚和丰富的通信接口,集成 2.4 GHz Wi-Fi 和 Bluetooth 5 (LE),配备原生 USB,可用作键盘/鼠标、MIDI 设备、磁盘驱动器等;
• 获得FCC / CE认证,带有4 MB闪存和2 MB PSRAM;
• 彩色 1.14“ IPS TFT,240x135 像素——采用 ST7789 芯片组的高清彩色显示屏,可从任何角度观看;
• 电源选项 - USB C 型或锂聚合物电池,通过 USB-C 供电时可为内置电池充电;
• LiPoly 电池监控器 - LC709203 芯片主动监控电池的电压和充电状态/通过 I2C 报告电池电量百分比;
• 重置和DFU(BOOT0)按钮以进入ROM引导加载程序;
• 内置原生USB串行端口,不需要单独的电缆,串行调试输出引脚(可选,用于检查硬件串行调试控制台);
• 用于 I2C 设备的 STEMMA QT 连接器,具有可切换电源,可进入低功耗模式;
• 板载 NeoPixel 具有引脚控制电源,可实现低功耗,用于多种状态显示;
• 在深度睡眠模式下,可从Lipoly连接获得80~100uA的电流消耗;
• 可与Arduino或CircuitPython一起使用。
3.任务一:控制屏幕显示中文
CircuitPython显示文本可以使用官方右做好的的lib,但是没有提供中文字库,所以想要显示中文,额外要做的就提供中文的字库文件。
自己制作或直接从网上下载都可以,详细制作方法可以参考官方指导生成DBF(字体像素大小尽量使用整数,不然可能不能使用):
https://learn.adafruit.com/custom-fonts-for-pyportal-circuitpython-display
但是用BDF刷新速度会慢些,如果使用PCF文件会快很多。这里网站可以在线将BDF转PCF:https://adafruit.github.io/web-bdftopcf/
第一步,使用import导入相关的库
第二步,创建一个显示对象display
第三步,创建一个文本对象
第四步,创建一个图像对象(可选)
第五步,指定中文字库文件,文本内容,颜色,显示坐标灯相关参数
第六步,调用display.show接口进行显示即可
# 导入board内置库
import board
# 图形
import displayio
import adafruit_imageload
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text.label import Label
def font_show():
font25 = bitmap_font.load_font("/font/SourceHanSansCN-Bold-25.pcf")
font50 = bitmap_font.load_font("/font/SourceHanSansCN-Bold-50.pcf")
def image_init(path):
image, palette = adafruit_imageload.load(path)
return displayio.TileGrid(image, pixel_shader=palette )
def lable_init(x, y, text="", color=0, font=font25 ):
return Label(x=x, y=y, text=text, color=color, font=font)
week_day = {0:"周一", 1:"周二", 2:"周三", 3:"周四", 4:"周五", 5:"周六", 6:"周日", }
font25 = bitmap_font.load_font("/font/SourceHanSansCN-Bold-25.pcf")
font50 = bitmap_font.load_font("/font/SourceHanSansCN-Bold-50.pcf")
time_lable = lable_init(0, 90, font= font50, text="%02d:%02d" % ( 19, 23) )
data_lable = lable_init(0, 50, text="%02d月%02d日" % (1, 2) )
week_lable = lable_init(0, 20, text=week_day[0] )
str_temp,st_weather = "80","晴"
weather_lable = lable_init(55, week_lable.y, text= st_weather)
temp_lable = lable_init(110, weather_lable.y, text="%s°" % str_temp)
# 加入画板
group = displayio.Group()
grid=image_init("/pic/bg.png")
group.append(grid)
group.append(time_lable)
group.append(week_lable)
group.append(data_lable)
group.append(weather_lable)
group.append(temp_lable)
board.DISPLAY.show(group)
while True:
pass
font_show()
4、任务二:网络功能使用
网络功能主要实现:1、显示可连接WIFI;2、连接到路由器WIFI(如果连接不上会重连);3、设置为AP模式,供其它设备连接;
在wifi连接的过程中,会输出一些重要的信息到控制台。
注意,如果附件wifi信号太强,有可能找不到手机的热点wifi,导致连接不上,最好看esp可连接的wifi再进行连接。
这里我用VSCODE中的CircuitPython插件调试打印,也可以用浏览器调试打印。
# 导入board内置库
import wifi
# 网络
import time
def task_wifi():
WIFI_SSID = "123456789"# os.getenv("CIRCUITPY_WIFI_SSID")
WIFI_PASSWORD = "88888888"# os.getenv("CIRCUITPY_WIFI_PASSWORD")
AP_SSID = "AP_PMI"
AP_PASSWORD = "77777778"
def wifi_init(ssid=WIFI_SSID, password=WIFI_PASSWORD ):
# 开启热点
wifi.radio.start_ap(AP_SSID, AP_PASSWORD)
print("AP_SSID =", AP_SSID)
print("AP_PASSWORD =", AP_PASSWORD)
print("AP_IP:", wifi.radio.ipv4_address_ap)
# 连接目标WIFI
print("WIFI_SSID =", ssid)
print("WIFI_PASSWORD =", password)
print("WIFI_IP:", wifi.radio.ipv4_address)
for n in range(20):
if wifi.radio.ipv4_address is not None:
break
# 显示可连接网络
for net in wifi.radio.start_scanning_networks():
print("%d:%s\t\t\t\t\tRSSI:%d\tChannel:%d" %(n, str(net.ssid,"utf-8"), net.rssi, net.channel) )
# 连接符合目标
if net.ssid == ssid:
try :
print("wifi connect...",n)
wifi.radio.connect(ssid, password)
except ConnectionError as e:
print("wifi connect err:", e)
wifi.radio.stop_scanning_networks()
time.sleep(2)
if(wifi.radio.ipv4_address is None ) :
print("wifi unkonw")
else :
print("WIFI_IP:", wifi.radio.ipv4_address)
wifi_init()
while True:
pass
task_wifi()
1.连接目标wifi
2.连接ESP热点
5、任务三:控制WS2812B
本任务实现对WS2812的控制从而实现对颜色的控制。
本任务中基于官方代码稍作扩展,实现如下功能:
1、使用异步线程编程
2、使用异步线程控制Boot按键短按改变WS2812B等颜色的切换
3、使用异步线程控制board.D13的灯珠,用于板子心跳并打印log
板载有2个按键,其中Rst按键,无法当作用户按键使用,只有Boot按键可以当作用户按键使用,本次就直接使用Boot按键作为颜色切换的按键。
# 导入board内置库
import board
# 导入外置库
import asyncio
import neopixel
# 从digitalio内置库导入DigitalInOut, Direction, Pull
from digitalio import DigitalInOut, Direction, Pull
async def task_btn():
def WS2812B_init():
# 初始化对象且设置灯珠数量
pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
# 取消写入后马上刷新
pixel.auto_write = False
# 灯珠格式
pixel.pixel_order = neopixel.GRB
return pixel
def btn_init():
# 初始化btn按钮对象
btn = DigitalInOut(board.BOOT0)
# 设置btn引脚为输入
btn.direction = Direction.INPUT
# 设置btn引脚为上拉
btn.pull = Pull.UP
return btn
pixel = WS2812B_init()
btn = btn_init()
btn_state = 0
btn_state_old = btn_state
while True:
if not btn.value:
btn_state+=1
if( btn_state_old == btn_state ):
await asyncio.sleep(0.2)
continue
# 按键触发了
# 处理按键状态
if( btn_state > 4 ) :
btn_state = 1
btn_state_old = btn_state
print("btn val ",btn_state_old)
# 根据按键状态做出动作
if (btn_state_old == 1):
pixel.fill((255, 0, 0)) # 所有灯珠颜色改变
elif (btn_state_old == 2):
pixel[0] = (0, 255, 0) # 单个灯珠颜色改变
elif (btn_state_old == 3):
pixel.fill((0, 0, 255))
elif (btn_state_old == 4):
pixel.fill((0, 0, 0))
# 执行led闪烁动态效果
pixel.show()
if not btn.value:
await asyncio.sleep(0.2)
await asyncio.sleep(0.2)
async def task_tick():
def led_init():
# 初始化btn按钮对象
led = DigitalInOut(board.D13)
# 设置btn引脚为输入
led.direction = Direction.OUTPUT
return led
led = led_init()
cnt = 0
while True:
cnt+=1
print("tick",cnt)
led.value = not led.value
await asyncio.sleep(0.8)
async def main():
print("loop start")
btn_task = asyncio.create_task(task_btn())
tick_task = asyncio.create_task(task_tick())
# This will run forever, because no tasks ever finish.
await asyncio.gather(btn_task, tick_task)
asyncio.run(main())
6、任务4:日历&时钟
此任务用到了任务二的代码,完成一个可通过互联网更新的万年历时钟,并显示当地的天气信息
# 导入board内置库
import wifi
# 网络
import time
import rtc
import socketpool
# 网络
import adafruit_ntp
import ssl
import adafruit_requests
# 从digitalio内置库导入DigitalInOut, Direction, Pull
from digitalio import DigitalInOut, Direction, Pull
def get_weather():
if (wifi.radio.ipv4_address is None ) :
return "---","未知"
# 设置城市id https://www.cnblogs.com/trigger-cn/p/17463196.html
city = "441900"
#这个函教使用的是高德API,使用该API需要先去注册相关账户,中请key。
key = "8027d9b28a93e1fdc1bd9f8076041e0e"
#接天气链接url
getweather_url = "https://restapi.amap.com/v3/weather/weatherInfo?city=" + city + "&key=" + key
# 获取天气json微据
pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool,ssl.create_default_context() )
try :
response = requests.get(getweather_url)
except :
#关闭连接
response.close()
return "---","未知"
json_resp = response.json()
#关闭连接
response.close()
# 解析json微据,并返回温度和天气信息
print(json_resp)
for da in json_resp["lives"]:
return da["temperature"], da["weather"]
def task_wifi():
WIFI_SSID = "Luxshare-VIP"# os.getenv("CIRCUITPY_WIFI_SSID")
WIFI_PASSWORD = "Luxsh@re-1ct2o2100"# os.getenv("CIRCUITPY_WIFI_PASSWORD")
# AP_SSID = "AP_PMI"
# AP_PASSWORD = "77777778"
def wifi_init(ssid=WIFI_SSID, password=WIFI_PASSWORD ):
# # 开启热点
# wifi.radio.start_ap(AP_SSID, AP_PASSWORD)
# print("AP_SSID =", AP_SSID)
# print("AP_PASSWORD =", AP_PASSWORD)
# print("AP_IP:", wifi.radio.ipv4_address_ap)
# 连接目标WIFI
print("WIFI_SSID =", ssid)
print("WIFI_PASSWORD =", password)
print("WIFI_IP:", wifi.radio.ipv4_address)
for n in range(20):
if wifi.radio.ipv4_address is not None:
break
# 显示可连接网络
for net in wifi.radio.start_scanning_networks():
print("%d:%s\t\t\t\t\tRSSI:%d\tChannel:%d" %(n, str(net.ssid,"utf-8"), net.rssi, net.channel) )
# 连接符合目标
if net.ssid == ssid:
try :
print("wifi connect...",n)
wifi.radio.connect(ssid, password)
except ConnectionError as e:
print("wifi connect err:", e)
wifi.radio.stop_scanning_networks()
time.sleep(2)
if(wifi.radio.ipv4_address is None ) :
print("wifi unkonw")
else :
print("WIFI_IP:", wifi.radio.ipv4_address)
def ntc_init():
if (wifi.radio.ipv4_address is not None ) :
ntp_pool = socketpool.SocketPool(wifi.radio)
ntp = adafruit_ntp.NTP(ntp_pool, tz_offset= 8, server= "ntp.aliyun.com")
try :
rtc.RTC().datetime = ntp.datetime
return time.localtime
except ConnectionError as e:
print( "ntp init error" ,e)
pass
time.struct_time((2019, 5, 29, 15, 14, 15, 0, -1, -1))
print( "unkonw ntp" )
return time.localtime
week_day = {0:"周一", 1:"周二", 2:"周三", 3:"周四", 4:"周五", 5:"周六", 6:"周日", }
wifi_init()
rtc_time = ntc_init()
tim_text="%02d月%02d日" % ( rtc_time().tm_mon, rtc_time().tm_mday)
dat_text=week_day[rtc_time().tm_wday]
str_temp,str_weather = get_weather()
print(tim_text, dat_text, str_temp, str_weather)
while True:
pass
task_wifi()
效果展示:
7、综合应用:
结合前面的几个任务做了一个综合应用。
【源代码】