ESP32桌面信息牌,作品定位桌面创意小摆件,工作之余发发呆、摸摸鱼,盯着屏幕上可爱的动画也别是一般滋味。主要显示的内容及功能如下:时间显示(支持农历)、天气显示(含实时天气 + 未来三天天气 + 空气质量 + 实时气象预警 + 生活指数建议等)、热点新闻、室外温湿度显示等、采用Web配置参数(含wifi信息修改和手动修改城市),交互简单,作品技术路线:ESP32+WIFI+HTTP请求+JSON数据解析+LVGL UI设计
开始制作
1、根据BOM表采购元件,如图1所示
图1
交互式BOM表,对于DIY爱好者非常的友好,鼠标选中元件PCB中即可自动定位到该元件的坐在位置(传送门-一键直达体验),效果如图2所示
图2
2、PCB电路板打样和3D打印外观(资料下载),这一步一般会选择第三方服务,如PCB打样则使用广为人知的嘉立创,外观打印推荐使用光敏树脂材料,如果使用普通PLA材料打印失败率较高且精细度不够导致作品不美观。焊接过程中有条件的,推荐使用加热平台配合钢网,阻容封装采用0603以及还有TYPE-C、40P FPC座后两者引脚间距较密,焊接难度大,使用推荐方法可以大大减小焊接难度,提高焊接体验
3、焊接过程很简单,大致流程:固定PCB板、刷锡膏、手工贴元件、上加热台加热、后焊处理,参考图3-7
图3-7
固件烧录
准备烧录前一定要检查焊接后是否有虚焊和连锡,并使用万用表检测电源端是否有短路,上述问题检查无误一般就可以进入固件烧录环节了,烧录步骤如图8所示,设置bin文件目录及烧录地址,选择COM编号后点击START开始下载,待下方绿色进度头走完提示完成即可断电重启,不出意外,接上屏幕后即能看到显示画面,如图9所示
如插上数据线后无端口,可能是你没有安装ch340的串口驱动或TYPC-C接口和CH340C芯片可能出现虚焊
图8
图9
最后,将屏幕和PCB装入3D打印的外壳中,底板使用B7000胶水密封即可,过程如图10-11所示
图10-11
Web参数配置
固件烧录成功后,屏幕显示配置模式和一大段文字说明,文字很多,其实就是告诉你怎么使用,千万不要偷懒,一定要慢慢看完。
这个时候呢,打开手机的WIFI连接界面,在已搜索到的WIFI列表中找到ESP32桌面信息牌,然后点击连接(无密码),连接成功后不出意外会自动打开Web配置页面(这里采用强制用户认证机制),显示页面内容如图12所示,主要输入WIFI名称和密码即可,城市会根据所在地自动定位,如定位不准可以手动输入,另外授权码目前最新版的固件已经内置,这里留空即可(作者正在进行信息牌点亮中国地图的行动(目前除西北地区外,其他城市基本均已点亮),因此已经内置)
图12
信息输入无误,点击保存并连接,此时信息牌会自动重启,开始连接,并进入显示内容画面。到此,制作过程结束,流程还是比较简单。
电路原理
电源部分(图13),采用DCDC国产方案,DC方案相比LDO效率高、发热低
图13
程序自动下载匹配电路(图14),USB转串口芯片采用国产CH340C,通过2N7002双MOS管配合,巧妙运用流控接口(DTR/RTS)可以实现程序自动下载
图14
主控部分(图15),选用ESP32-WROOM-32模组,内置4M Flash,简化电路设计,只需电源滤波和上电复位电路即可正常工作
图15
外部按钮及光感电路(图16),外部按钮的作用主要用于人机交互,包含双击按钮手动刷新数据,长按按钮设备重启进入配置模式,在配置模式中根据屏幕提示可以更换主屏显示动画(内置6种)以及进入重新配网模式(如需更换WIFI的情况下使用);光感部分主要为自动控制屏幕背光,外部光线增强则屏幕背光亮度增加,外部光线减弱则屏幕背光亮度减小,在全黑环境下屏幕背光保持微微亮,并非全黑。
图16
3.5寸屏幕背光驱动及屏幕接口电路(图17-18),屏幕驱动芯片采用ILI9488,分辨率320*480,通信接口为8位并口
图17-18
程序思路
程序编译环境为VSCode + Platformio,使用Arduino框架,程序总行数超过2000,由于篇幅有限,故而介绍部分主要功能
1.Web配网,ESP32自建热点,手机或PC连接该热点,打开指定HTML页面,配置完成后,将WIFI信息存储在NVS区域中
#include <WiFi.h>
#include <DNSServer.h>
#include <WebServer.h>
const char* AP_NAME = "ESP32桌面信息牌";//Web配网模式下的AP-wifi名字
//暂时存储wifi账号密码、城市代码、NewsKEY
char sta_ssid[32] = {0};
char sta_password[64] = {0};
char sta_citycode[32] = {0};
char sta_NewsKEY[256] = {0};
char blynkTimes[8] = {0};
//配网页面代码
String page_html = R"(
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
<title>ESP32桌面小电视</title>
<style type="text/css">
* { margin: 0; padding: 0; }
html { height: 100%; }
h2 {text-align: center;color: #fff;line-height: 2.2;}
body { height: 100%; background-color: #1F6F4A; 50% 50% no-repeat; background-size: cover;}
.dowebok { position: absolute; left: 50%; top: 30%; width: 380px; height: 440px; margin: -200px 0 0 -200px; border: 3px solid #fff; border-radius: 10px; overflow: hidden;}
.form-item { position: relative; width: 360px; margin: 0 auto; padding-bottom: 20px;}
.form-item input { width: 288px; height: 48px; padding-left: 10px; border: 1px solid #fff; border-radius: 25px; font-size: 18px; color: #fff; background-color: transparent; outline: none;}
.send_button { width: 360px; height: 50px; border: 0; border-radius: 25px; font-size: 18px; color: #1f6f4a; outline: none; cursor: pointer; background-color: #fff; }
.tip { display: none; position: absolute; left: 20px; top: 52px; font-size: 14px; color: #f50; }
.reg-bar { width: 360px; margin: 20px auto 0; font-size: 14px; overflow: hidden;}
.reg-bar a { color: #fff; text-decoration: none; }
.reg-bar a:hover { text-decoration: underline; }
.reg-bar .reg { float: left; }
.reg-bar .forget { float: right; }
.dowebok ::-webkit-input-placeholder { font-size: 18px; line-height: 1.4; color: #fff;}
.dowebok :-moz-placeholder { font-size: 18px; line-height: 1.4; color: #fff;}
.dowebok ::-moz-placeholder { font-size: 18px; line-height: 1.4; color: #fff;}
.dowebok :-ms-input-placeholder { font-size: 18px; line-height: 1.4; color: #fff;}
@media screen and (max-width: 500px) {
* { box-sizing: border-box; }
.dowebok { position: static; width: auto; height: auto; margin: 0 30px; border: 0; border-radius: 0; }
.logo { margin: 50px auto; }
.form-item { width: auto; }
.form-item input, .form-item button, .reg-bar { width: 100%; }
}
</style>
</head>
<body>
<div class="dowebok">
<h2>参 数 配 置</h2>
<form style='text-align: center;padding-top: 20px' name='input' action='/' method='POST'>
<div class="form-item">
<input id="username" type="text" name='ssid' autocomplete="off" placeholder="WiFi名称">
</div>
<div class="form-item">
<input id="password" type="password" name='password' autocomplete="off" placeholder="WiFi密码">
</div>
<div class="form-item">
<input id="citycode" type="citycode" name='citycode' autocomplete="off" placeholder="城市代码,留空则定位获取">
</div>
<div class="form-item">
<input id="NewsKEY" type="NewsKEY" name='NewsKEY' autocomplete="off" placeholder="授权码,系统已内置">
</div>
<div class="form-item">
<div id="">
<input id="send_button" type='submit' value='保存并连接'>
</div>
</div>
</form>
</div>
</body>
</html>
)";
const byte DNS_PORT = 53;//DNS端口号
IPAddress apIP(192, 168, 4, 1);//esp32-AP-IP地址
DNSServer dnsServer;//创建dnsServer实例
WebServer server(80);//创建WebServer
unsigned long getChipId() {
unsigned long mcuchipId = 0;
for(int i=0; i<31; i=i+8) {
mcuchipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
return mcuchipId;
}
void handleRoot() {//访问主页回调函数
long chipid = getChipId();
server.send(200, "text/html", page_html);
}
void handleRootPost() {//Post回调函数
Serial.println("handleRootPost");
if (server.hasArg("ssid")) {//判断是否有账号参数
Serial.print("got ssid:");
strcpy(sta_ssid, server.arg("ssid").c_str());//将账号参数拷贝到sta_ssid中
Serial.println(sta_ssid);
} else {//没有参数
Serial.println("error, not found ssid");
server.send(200, "text/html", "<meta charset='UTF-8'>提示:请输入WiFi名称");//返回错误页面
return;
}
//密码与账号同理
if (server.hasArg("password")) {
Serial.print("got password:");
strcpy(sta_password, server.arg("password").c_str());
Serial.println(sta_password);
} else {
Serial.println("error, not found password");
server.send(200, "text/html", "<meta charset='UTF-8'>提示:请输入WiFi密码");
return;
}
if (server.hasArg("citycode")) {
Serial.print("got citycode:");
strcpy(sta_citycode, server.arg("citycode").c_str());
Serial.println(sta_citycode);
} else {
//Serial.println("error, not found citycode");
//server.send(200, "text/html", "<meta charset='UTF-8'>提示:请输入城市代码");
return;
}
if (server.hasArg("NewsKEY")) {
Serial.print("got NewsKEY:");
strcpy(sta_NewsKEY, server.arg("NewsKEY").c_str());
Serial.println(sta_NewsKEY);
} else {
Serial.println("error, not found NewsKEY");
server.send(200, "text/html", "<meta charset='UTF-8'>提示:请输入授权码");
return;
}
preferences.begin("wifi", false);
preferences.putString( "ssid" , sta_ssid);
preferences.putString( "password", sta_password);
preferences.putString( "citycode", sta_citycode);
String nvs_sta_NewsKEY = String(sta_NewsKEY);
if(nvs_sta_NewsKEY.length() >= 30) { //如果授权码有更新,则做修改,否则无,默认使用上次的记录
preferences.putString( "NewsKEY", sta_NewsKEY);
}
preferences.end();
server.send(200, "text/html", "<meta charset='UTF-8'><h1>保存成功,ESP32桌面小电视重启中...</h1>");//返回保存成功页面
delay(2000);
//连接wifi
//connectNewWifi();
ESP.restart(); //重启ESP32
}
void initBasic(void){//初始化基础
//Serial.begin(115200);
//WiFi.hostname("Smart-ESP32");//设置ESP32设备名
}
void initSoftAP(void){//初始化AP模式
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
if(WiFi.softAP(AP_NAME)){
Serial.println("热点已开:ESP32桌面信息牌");
}
}
void initWebServer(void){//初始化WebServer
//server.on("/",handleRoot);
//上面那行必须以下面这种格式去写否则无法强制门户
server.on("/", HTTP_GET, handleRoot);//设置主页回调函数
server.onNotFound(handleRoot);//设置无法响应的http请求的回调函数
server.on("/", HTTP_POST, handleRootPost);//设置Post请求回调函数
server.begin();//启动WebServer
Serial.println("Web服务已启动");
}
void initDNS(void){//初始化DNS服务器
if(dnsServer.start(DNS_PORT, "*", apIP)){//判断将所有地址映射到esp32的ip上是否成功
Serial.println("DNS服务成功");
}
else Serial.println("DNS服务失败");
}
void connectNewWifi(void){
WiFi.mode(WIFI_STA);//切换为STA模式
// WiFi.setAutoConnect(true);//设置自动连接
WiFi.begin(PrefSSID.c_str(), PrefPassword.c_str());//连接上一次连接成功的wifi
Serial.println("");
Serial.print("Connect to wifi");
int count = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
count++;
if(count > 20){//如果10秒内没有连上,就开启Web配网 可适当调整这个时间
initSoftAP();
initWebServer();
initDNS();
break;//跳出 防止无限初始化
}
Serial.print(".");
}
Serial.println("");
if(WiFi.status() == WL_CONNECTED){
Serial.println("WIFI Connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.stop();
}
}
上述代码为主要函数,具体使用中调用函数setWiFi即可
//强制门户Web配网
bool setWiFi_Flag = false;
void setWiFi()
{
//配网页面显示
LV_IMG_DECLARE(Page_Setting);
lv_obj_t *img_WIFISetting = lv_img_create(lv_scr_act());
lv_img_set_src(img_WIFISetting, &Page_Setting);
lv_obj_set_pos(img_WIFISetting, 2, 0);
for (int i = 0; i <= 100; i++)
{
lv_timer_handler();
delay(5);
}
initBasic();
initSoftAP();
initWebServer();
initDNS();
while (setWiFi_Flag == false)
{
server.handleClient();
dnsServer.processNextRequest();
if (WiFi.status() == WL_CONNECTED)
{
server.stop();
setWiFi_Flag = true;
}
}
}
2.获取新闻数据,使用天行数据的api接口,发起api请求后,接收返回的JSON格式数据,并使用ArduinoJSON库做数据解析,解析后通过LVGL中的lv_label_set_text_fmt函数,将数据推送到屏幕显示,其他的天气数据的数据请求和数据解析与新闻数据的方法一致,由于内容较多就不一一介绍了
void getNews()
{ //获取今日简报
HTTPClient httpClient;
//启动内置天行数据APIKEY
if (TianXingAPIKEY_Flag == true)
{
APIKEY = "9307ac0871681b03f6d0ed892caaea83";
}
String URL = "http://api.tianapi.com/bulletin/index?key=" + APIKEY;
//创建 HTTPClient 对象
httpClient.begin(URL);
//设置请求头中的User-Agent
httpClient.setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1");
httpClient.addHeader("Referer", "http://api.tianapi.com/");
//启动连接并发送HTTP请求
int httpCode = httpClient.GET();
Serial.println("正在获取今日简报");
// Serial.println(URL);
//如果服务器响应OK则从服务器获取响应体信息并通过串口输出
if (httpCode == HTTP_CODE_OK)
{
String Neswstr = httpClient.getString();
// Serial.println(Neswstr);
Serial.println("获取今日简报成功");
DynamicJsonDocument NeswData(4096);
deserializeJson(NeswData, Neswstr); //传入指定JSON数据
const char *Newslist_00_title_1 = NeswData["newslist"][0]["title"].as<char *>();
const char *Newslist_01_title_1 = NeswData["newslist"][1]["title"].as<char *>();
const char *Newslist_02_title_1 = NeswData["newslist"][2]["title"].as<char *>();
lv_label_set_text_fmt(label_message_data,
"1、%s\n2、%s\n3、%s",
Newslist_00_title_1, Newslist_01_title_1, Newslist_02_title_1);
}
httpClient.end();
}
3.上电后首先运行的程序,主要以初始化操作为主(屏幕初始化、人机交互、WIFI联网、LVGL界面),以及配置模式均在该函数中运行,其中还有设备激活部分,该部分目前还在调试阶段
void setup()
{
Serial.begin(115200);
pinMode(button, INPUT);
pinMode(35, INPUT);
randomSeed(analogRead(35));
ledcSetup(0, 5000, 8);
ledcAttachPin(22, 0);
ledcWrite(0, 150);
lv_init();
timer.setInterval(1000L, Simple_timer_disp_Time);
tft.begin();
tft.setRotation(0);
lv_disp_draw_buf_init(&draw_buf, buf, buf2, screenWidth * 10);
/*Initialize the display*/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
/*Change the following line to your display resolution*/
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
lv_disp_drv_register(&disp_drv);
/*Initialize the (dummy) input device driver*/
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
lv_indev_drv_register(&indev_drv);
// LV_FONT_DECLARE(LED_Font);
// vTaskDelay(2000);
unsigned long enterButtonTime = 0;
lv_obj_t *setlabel = lv_label_create(lv_scr_act());
lv_label_set_long_mode(setlabel, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_set_size(setlabel, 300, 28);
lv_obj_set_style_text_align(setlabel, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(setlabel, LV_ALIGN_DEFAULT, 0, 0);
lv_obj_center(setlabel);
lv_label_set_text(setlabel, " ");
bool setFlag = false;
while (digitalRead(button) == HIGH)
{ //上电后,按下按钮自动进入配置页面
enterButtonTime = millis();
// Serial.println(enterButtonTime);
// setWiFi();
LV_IMG_DECLARE(fengche_00);
LV_IMG_DECLARE(finsh_00);
LV_IMG_DECLARE(huojian_00);
LV_IMG_DECLARE(longmao_00);
LV_IMG_DECLARE(love_00);
LV_IMG_DECLARE(taikongren_00);
LV_IMG_DECLARE(setting_00);
LV_IMG_DECLARE(wain_00);
lv_obj_t *img_start = lv_img_create(lv_scr_act());
lv_obj_set_pos(img_start, 115, 115);
if (enterButtonTime > 500 && enterButtonTime < 1500)
{
lv_img_set_src(img_start, &finsh_00);
lv_label_set_text(setlabel, "动画:鲤鱼跃龙门");
setModeValue = 1;
}
else if (enterButtonTime > 1500 && enterButtonTime < 2500)
{
lv_img_set_src(img_start, &longmao_00);
lv_label_set_text(setlabel, "动画:龙猫");
setModeValue = 2;
}
else if (enterButtonTime > 2500 && enterButtonTime < 3500)
{
lv_img_set_src(img_start, &love_00);
lv_label_set_text(setlabel, "动画:情人专属版");
setModeValue = 3;
}
else if (enterButtonTime > 3500 && enterButtonTime < 4500)
{
lv_img_set_src(img_start, &taikongren_00);
lv_label_set_text(setlabel, "动画:旋转太空人");
setModeValue = 4;
}
else if (enterButtonTime > 4500 && enterButtonTime < 5500)
{
lv_img_set_src(img_start, &huojian_00);
lv_label_set_text(setlabel, "动画:火箭");
setModeValue = 5;
}
else if (enterButtonTime > 5500 && enterButtonTime < 6500)
{
lv_img_set_src(img_start, &fengche_00);
lv_label_set_text(setlabel, "动画:大风车,转呀转");
setModeValue = 6;
}
else if (enterButtonTime > 6500 && enterButtonTime < 7500)
{
lv_img_set_src(img_start, &setting_00);
lv_label_set_text(setlabel, "进入配置模式");
setModeValue = 7;
}
else if (enterButtonTime > 7500 && enterButtonTime < 8500)
{
lv_img_set_src(img_start, &wain_00);
lv_label_set_text(setlabel, "到头了,快放开我吧!");
setModeValue = 8;
}
lv_timer_handler();
setFlag = true;
}
if (setFlag)
{
if (setModeValue == 1 || setModeValue == 2 || setModeValue == 3 || setModeValue == 4 || setModeValue == 5 || setModeValue == 6)
{
preferences.begin("wifi", false);
preferences.putInt("GIFNUM", setModeValue);
preferences.end();
vTaskDelay(10);
ESP.restart();
}
}
if (setModeValue == 7)
{
setWiFi();
} //进入配网模式
if(setModeValue == 8) {
vTaskDelay(500);
ESP.restart();
}
lv_obj_del(setlabel);
//首次使用自动进入配网模式,读取NVS存储空间内的ssid、password和citycode
preferences.begin("wifi", false);
PrefSSID = preferences.getString("ssid", "none");
PrefPassword = preferences.getString("password", "none");
cityCode = preferences.getString("citycode", "none");
APIKEY = preferences.getString("NewsKEY", "none");
GIFNUM = preferences.getInt("GIFNUM", 0);
preferences.end();
if (PrefSSID == "none")
{
setWiFi();
}
if (GIFNUM <= 0)
{
GIFNUM = 1;
} //首次使用直接显示鲤鱼跃龙门动画
static lv_style_t style_bg;
static lv_style_t style_indic;
lv_style_init(&style_bg);
lv_style_set_border_color(&style_bg, lv_palette_main(LV_PALETTE_BLUE));
lv_style_set_border_width(&style_bg, 2);
lv_style_set_pad_all(&style_bg, 6);
lv_style_set_radius(&style_bg, 6);
lv_style_set_anim_time(&style_bg, 1000);
lv_style_init(&style_indic);
lv_style_set_bg_opa(&style_indic, LV_OPA_COVER);
lv_style_set_bg_color(&style_indic, lv_palette_main(LV_PALETTE_BLUE));
lv_style_set_radius(&style_indic, 3);
lv_obj_t *bar = lv_bar_create(lv_scr_act());
lv_obj_remove_style_all(bar);
lv_obj_add_style(bar, &style_bg, 0);
lv_obj_add_style(bar, &style_indic, LV_PART_INDICATOR);
lv_obj_set_size(bar, 280, 20);
lv_bar_set_range(bar, 0, 100);
lv_obj_set_pos(bar, 20, 300);
lv_bar_set_value(bar, 0, LV_ANIM_ON);
lv_obj_t *connectText = lv_label_create(lv_scr_act());
lv_label_set_long_mode(connectText, LV_LABEL_LONG_SCROLL_CIRCULAR);
// lv_label_set_text(connectText,"星期三");
lv_obj_set_width(connectText, 220);
lv_obj_set_height(connectText, 28);
lv_obj_set_style_text_align(connectText, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(connectText, LV_ALIGN_DEFAULT, 0, 0);
lv_obj_set_pos(connectText, 50, 330);
//版本号
lv_obj_t *versionNumberText = lv_label_create(lv_scr_act());
lv_label_set_long_mode(versionNumberText, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_label_set_text_fmt(versionNumberText, "版本号:%s", versionNumber);
lv_obj_set_width(versionNumberText, 170);
lv_obj_set_height(versionNumberText, 28);
lv_obj_set_style_text_align(versionNumberText, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_set_style_text_color(versionNumberText, lv_color_hex(0xD3D3D3), 0);
lv_obj_align(versionNumberText, LV_ALIGN_DEFAULT, 0, 0);
lv_obj_set_pos(versionNumberText, 175, 458);
//联网时的动画显示
lv_obj_t *connect_lv_gif = lv_gif_create_from_data(lv_scr_act(), connect_wifi_map);
lv_obj_set_pos(connect_lv_gif, 120, 135);
WiFi.begin(PrefSSID.c_str(), PrefPassword.c_str());
// WiFi.begin("1302","dxzc1302");
const char *wifiText = PrefSSID.c_str();
int32_t bar_value = 0;
// while (WiFi.status() != WL_CONNECTED) {
// lv_bar_set_value(bar, bar_value, LV_ANIM_ON);
lv_label_set_text_fmt(connectText, "正在连接:%s...", wifiText);
while (WiFi.status() != WL_CONNECTED)
{
bar_value++;
// Serial.println(bar_value);
lv_bar_set_value(bar, bar_value, LV_ANIM_ON);
lv_timer_handler();
vTaskDelay(40);
if (bar_value >= 101)
{ //如果连接超时,自动重启再连接
// setWiFi();
ESP.restart(); //重启ESP32
}
if (WiFi.status() == WL_CONNECTED)
{ // wifi连接成功后,快速走完进度条
lv_label_set_text(connectText, "连接成功,正在同步数据...");
for (int i = bar_value; i <= 100; i++)
{
lv_bar_set_value(bar, i, LV_ANIM_ON);
lv_timer_handler();
vTaskDelay(5);
}
}
}
Serial.print("本地IP:");
Serial.println(WiFi.localIP());
/*执行激活函数*/
// EquipmentActivation(true);
Udp.begin(localPort);
setSyncProvider(getNtpTime);
// setSyncInterval(60*60); //NTP网络同步频率,单位秒。
setSyncInterval(600);
lv_obj_del(connect_lv_gif);
lv_obj_del(bar);
lv_obj_del(connectText); //删除进度条和文字提示
lv_obj_del(versionNumberText);
/* Create simple demo */
lv_draw_line();
lv_disp_img();
lv_top_text();
//lv_COVID_19();
if (cityCode.length() >= 8)
{
// Serial.println("手动设置cityCode");
getCityWeater(); //获取天气数据
}
else
{
// Serial.println("自动设置cityCode");
getCityCode(); //获取城市代码
}
getNews(); //获取新闻数据
//getxinguan(); //获取疫情数据
getlunar();
button18.attachDuringLongPress(attachDuringLongPress18); //长按
button18.attachDoubleClick(attachDoubleClick18); //双击
button18.attachClick(attachClick18); //短按
}
4.主loop函数中运行的内容不多,主要以LVGL内容刷新为主
unsigned long oldTime = 0;
void loop()
{
timer.run(); //定时刷新时间任务
lv_timer_handler(); //LVGL界面刷新
vTaskDelay(5);
/*定时设定时间联网刷新一次数据*/
if (millis() - oldTime >= (updateTime * 60000) && time_Refresh_Flag())
{
oldTime = millis();
getCityWeater();
getNews();
//getxinguan();
getlunar();
Serial.println("定时更新数据中...");
}
/*屏幕背光自动控制*/
Filter_Value = Filter();
ledcWrite(0, map(Filter_Value, 0, 4095, 1, 255));
// Serial.println(Filter_Value);
button18.tick(); //按键事件 短按、双击、长按
}
结束语
这个作品难度不大,也是作为自学lvgl的第一个实战作品,程序还有很大的优化空间,比如引入FreeRTOS提高执行效率;断网、无网络情况的处理;作品定位也非常明确,就是桌面小摆件,且全程均在WIFI环境下使用;TFT屏幕和ESP32功耗较大,功耗层面做优化意义不大,因此不会考虑增加电池。另外这是一个桌面信息牌,其实是可以融入或嵌入很多的桌面设备中,比如笔筒、台灯等,还可以在此基础上重新设计外壳,增加无线充电单元,PCB预留的接口中增加RGB灯效。
xxoo到此一游