回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

【花雕学编程】Arduino动手做(238)---带 LVGL 的 ESP32 CYD:显示时间和日期的数字时钟 简单

头像 驴友花雕 2024.11.15 11 0

0.jpg

ESP32-CYD(2432S028)液晶2.8寸屏开发板使用ESP32-WROOM-32模块作为主控,主控是一款双核MCU,集成了Wi-Fi和蓝牙功能,主频可达240MHz,具有520KB的SRAM、448KB的ROM,闪存容量为4MB+4MB,显示分辨率为240x320,采用电阻式触控式屏幕。该模块包括LCD显示器、背光控制电路、触控式屏幕控制电路、扬声器驱动电路、光敏电路和RGB LED控制电路。支持TF卡界面、序列界面、温湿度感测器界面(DHT11界面)和保留的IO口界面,该模块支持在Arduino IDE、ESP IDE、MicroPython和Mixly中进行开发。

 

00.jpg

安装TFT_eSPI、XPT2046_Touchscreen、BasicLinearAlgebra和LVGL库
网址:
TFT_eSPI库的网址:https://github.com/Bodmer/TFT_eSPI
XPT2046_Touchscreen库的网址: https://github.com/PaulStoffregen/XPT2046_Touchscreen
BasicLinearAlgebra库:https://github.com/tomstewart89/BasicLinearAlgebra

要正确使用 TFT_eSPI 库,需要根据不同开发板与TFT屏幕正确配置User_Setup.h和lv_conf.h文件。

LVGL中文开发手册:https://lvgl.100ask.net/master/

 

02-4.jpg
02-5.jpg

04.jpg
04---.jpg

要获取您所在时区的准确日期和时间,我们将使用 WorldTimeAPI。要从 API 获取时间,ESP32 需要连接到互联网,因此您需要在周围环境中安装路由器,以便 ESP32 可以连接到它。

安装 ArduinoJson 库
对于此项目,您需要安装 ArduinoJSON 库,以便在向 WorldTimeAPI 发出请求时处理 JSON 响应。

在 Arduino IDE 中,转到 Sketch > Include Library > Manage Libraries。搜索 ArduinoJSON 并安装 Benoit Blanchon 提供的库。我们使用的是 7.0.4 版本。我们建议使用相同的版本。

 

05-11.jpg

  【Arduino】168种传感器模块系列实验(资料代码+仿真编程+图形编程)
 实验二百三十八:ESP32开发板WiFi蓝牙2.8寸240*320智能液晶显示屏带触摸屏TFT模块
 项目实验之二十四:带 LVGL 的 ESP32 CYD:显示时间和日期的数字时钟

实验开源代码

 

代码
/*
  【Arduino】168种传感器模块系列实验(资料代码+仿真编程+图形编程)
  实验二百三十八:ESP32开发板WiFi蓝牙2.8寸240*320智能液晶显示屏带触摸屏TFT模块
  项目实验之二十四:带 LVGL 的 ESP32 CYD:显示时间和日期的数字时钟
*/

#include <lvgl.h> // 引入LVGL图形库
#include <TFT_eSPI.h> // 引入TFT_eSPI库,用于驱动TFT显示屏
#include <WiFi.h> // 引入WiFi库,用于WiFi连接
#include <HTTPClient.h> // 引入HTTPClient库,用于发起HTTP请求
#include <ArduinoJson.h> // 引入ArduinoJson库,用于处理JSON数据

// 替换为您的网络凭据
const char* ssid = "zhz3";// WiFi名称
const char* password = "z156721";// WiFi密码

// 指定您想要获取时间的时区:https://worldtimeapi.org/api/timezone 
// 葡萄牙时区示例:"Europe/Lisbon"
const char* timezone = "Europe/Lisbon"; // 设置时区

// 存储日期和时间
String current_date; // 当前日期
String current_time; // 当前时间

// 存储小时、分钟、秒
static int32_t hour; // 小时
static int32_t minute; // 分钟
static int32_t second; // 秒
bool sync_time_date = false; // 是否同步日期和时间

#define SCREEN_WIDTH 240 // 屏幕宽度
#define SCREEN_HEIGHT 320 // 屏幕高度

#define DRAW_BUF_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8)) // 绘图缓冲区大小
uint32_t draw_buf[DRAW_BUF_SIZE / 4]; // 绘图缓冲区

// 如果启用了日志记录,它将告知用户库中发生了什么
void log_print(lv_log_level_t level, const char * buf) {
  LV_UNUSED(level); // 未使用的参数
  Serial.println(buf); // 打印日志
  Serial.flush(); // 清空串口缓冲区
}

String format_time(int time) { // 格式化时间为两位数
  return (time < 10) ? "0" + String(time) : String(time);
}

static lv_obj_t * text_label_time; // 时间标签
static lv_obj_t * text_label_date; // 日期标签

static void timer_cb(lv_timer_t * timer){ // 定时器回调函数
  LV_UNUSED(timer);
  second++;
  if(second > 59) { // 秒数超过59则进位
    second = 0;
    minute++;
    if(minute > 59) { // 分钟超过59则进位
      minute = 0;
      hour++;
      sync_time_date = true; // 设置同步日期和时间标志
      Serial.println(sync_time_date);
      Serial.println("\n\n\n\n\n\n\n\n");
      if(hour > 23) { // 小时超过23则归零
        hour = 0;
      }
    }
  }

  String hour_time_f = format_time(hour); // 格式化小时
  String minute_time_f = format_time(minute); // 格式化分钟
  String second_time_f = format_time(second); // 格式化秒

  String final_time_str = String(hour_time_f) + ":" + String(minute_time_f) + ":"  + String(second_time_f); // 组合成最终时间字符串
  //Serial.println(final_time_str);
  lv_label_set_text(text_label_time, final_time_str.c_str()); // 设置时间标签文本
  lv_label_set_text(text_label_date, current_date.c_str()); // 设置日期标签文本
}

void lv_create_main_gui(void) { // 创建主GUI界面
  // 从WorldTimeAPI获取时间和日期
  while(hour==0 && minute==0 && second==0) {
    get_date_and_time();
  }
  Serial.println("Current Time: " + current_time); // 打印当前时间
  Serial.println("Current Date: " + current_date); // 打印当前日期

  lv_timer_t * timer = lv_timer_create(timer_cb, 1000, NULL); // 创建定时器
  lv_timer_ready(timer); // 定时器立即执行

  // 创建居中对齐的时间文本标签
  text_label_time = lv_label_create(lv_screen_active()); // 创建标签
  lv_label_set_text(text_label_time, ""); // 设置标签文本为空
  lv_obj_align(text_label_time, LV_ALIGN_CENTER, 0, -30); // 设置标签位置
  // 设置字体类型和大小
  static lv_style_t style_text_label;
  lv_style_init(&style_text_label); // 初始化样式
  lv_style_set_text_font(&style_text_label, &lv_font_montserrat_48); // 设置字体为48号
  lv_obj_add_style(text_label_time, &style_text_label, 0); // 应用样式

  // 创建居中对齐的日期文本标签
  text_label_date = lv_label_create(lv_screen_active()); // 创建标签
  lv_label_set_text(text_label_date, current_date.c_str()); // 设置标签文本
  lv_obj_align(text_label_date, LV_ALIGN_CENTER, 0, 40); // 设置标签位置
  // 设置字体类型和大小
  static lv_style_t style_text_label2;
  lv_style_init(&style_text_label2); // 初始化样式
  lv_style_set_text_font(&style_text_label2, &lv_font_montserrat_30); // 设置字体为30号
  lv_obj_add_style(text_label_date, &style_text_label2, 0); // 应用样式
  lv_obj_set_style_text_color((lv_obj_t*) text_label_date, lv_palette_main(LV_PALETTE_GREY), 0); // 设置文本颜色为灰色
}

void get_date_and_time() { // 获取日期和时间
  if (WiFi.status() == WL_CONNECTED) { // 如果WiFi已连接
    HTTPClient http; // 创建HTTPClient对象

    // 构造API端点
    String url = String("http://worldtimeapi.org/api/timezone/") + timezone;
    http.begin(url); // 初始化HTTP请求
    int httpCode = http.GET(); // 发起GET请求

    if (httpCode > 0) { // 如果请求成功
      // 检查响应
      if (httpCode == HTTP_CODE_OK) { // 如果响应码为OK
        String payload = http.getString(); // 获取响应内容
        //Serial.println("Time information:");
        //Serial.println(payload);
        // 解析JSON以提取时间
        JsonDocument doc; // 创建JSON文档
        DeserializationError error = deserializeJson(doc, payload); // 反序列化JSON
        if (!error) { // 如果没有错误
          const char* datetime = doc["datetime"]; // 获取datetime字段
          // 将datetime分割为日期和时间
          String datetime_str = String(datetime);
          int splitIndex = datetime_str.indexOf('T');
          current_date = datetime_str.substring(0, splitIndex); // 提取日期部分
          current_time = datetime_str.substring(splitIndex + 1, splitIndex + 9); // 提取时间部分
          hour = current_time.substring(0, 2).toInt(); // 提取小时
          minute = current_time.substring(3, 5).toInt(); // 提取分钟
          second = current_time.substring(6, 8).toInt(); // 提取秒
        } else { // 如果反序列化失败
          Serial.print("deserializeJson() failed: ");
          Serial.println(error.c_str()); // 打印错误信息
        }
      }
    } else { // 如果请求失败
      Serial.printf("GET request failed, error: %s\n", http.errorToString(httpCode).c_str()); // 打印错误信息
      sync_time_date = true; // 设置同步日期和时间标志
    }
    http.end(); // 关闭连接
  } else { // 如果WiFi未连接
    Serial.println("Not connected to Wi-Fi"); // 打印未连接信息
  }
}

void setup() {
  String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch(); // LVGL库版本
  Serial.begin(115200); // 初始化串口通信
  Serial.println(LVGL_Arduino); // 打印LVGL库版本

  // 连接到WiFi
  WiFi.begin(ssid, password); // 初始化WiFi连接
  Serial.print("Connecting"); // 打印连接信息
  while (WiFi.status() != WL_CONNECTED) { // 等待WiFi连接
    delay(500); // 延时
    Serial.print("."); // 打印点以显示进度
  }
  Serial.print("\nConnected to Wi-Fi network with IP Address: "); // 打印连接成功的信息
  Serial.println(WiFi.localIP()); // 打印IP地址

  // 开始LVGL图形库的初始化
lv_init();
// 注册打印函数,用于调试
lv_log_register_print_cb(log_print);

// 创建一个显示对象
lv_display_t * disp;
// 使用TFT_eSPI库初始化TFT显示屏
disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf)); // 创建显示对象,设置屏幕宽高和绘图缓冲区
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270); // 设置屏幕旋转方向为270度

// 函数用于绘制GUI
lv_create_main_gui();
}

void loop() {
  // 从WorldTimeAPI获取日期和时间
  if(sync_time_date) { // 如果需要同步日期和时间
    sync_time_date = false; // 重置同步标志
    get_date_and_time(); // 获取日期和时间
    while(hour==0 && minute==0 && second==0) { // 如果获取的时间无效(即小时、分钟、秒都为0)
      get_date_and_time(); // 再次尝试获取日期和时间
    }
  }
  lv_task_handler();  // 让LVGL执行GUI任务
  lv_tick_inc(5);     // 告诉LVGL已经过去了5毫秒
  delay(5);           // 等待5毫秒
}

代码的简要说明:

1、引入库:

lvgl.h:用于图形界面的LVGL库。
TFT_eSPI.h:用于驱动TFT显示屏的TFT_eSPI库。
WiFi.h:用于WiFi连接的库。
HTTPClient.h:用于发起HTTP请求的库。
ArduinoJson.h:用于处理JSON数据的库。

2、WiFi设置:

ssid和password:用于连接到WiFi网络的凭据。

3、时区设置:

timezone:设置为葡萄牙的时区"Europe/Lisbon"。

4、日期和时间变量:

current_date和current_time:存储当前日期和时间的字符串。
hour、minute、second:存储小时、分钟、秒的整数变量。
sync_time_date:标志变量,用于指示是否需要同步日期和时间。

5、屏幕参数:

SCREEN_WIDTH和SCREEN_HEIGHT:屏幕的宽度和高度。
DRAW_BUF_SIZE和draw_buf:绘图缓冲区的大小和数组。

6、日志打印函数:

log_print:用于打印LVGL日志的函数。

7、时间格式化函数:

format_time:将时间格式化为两位数的字符串。

8、定时器回调函数:

timer_cb:每秒更新时间显示,并在需要时同步日期和时间。

9、主GUI创建函数:

lv_create_main_gui:初始化GUI界面,包括创建时间标签和日期标签,并设置它们的样式和位置。

10、日期和时间获取函数:

get_date_and_time:从WorldTimeAPI获取当前的日期和时间,并解析JSON数据。

11、setup函数:

初始化串口通信,连接到WiFi网络,初始化LVGL和显示屏,并创建主GUI。

12、loop函数:

周期性地检查是否需要更新时间,并处理LVGL的GUI任务,以及更新屏幕显示。

 

 

 

代码编译时,出现错误

 

05-12.jpg

错误信息

Sketch uses 1448233 bytes (110%) of program storage space. Maximum is 1310720 bytes.

Global variables use 63176 bytes (19%) of dynamic memory, leaving 264504 bytes for local variables. Maximum is 327680 bytes.

Sketch too big; see https://support.arduino.cc/hc/en-us/articles/360013825179 for tips on reducing it.

text section exceeds available space in board

 

Compilation error: text section exceeds available space in board


 

Sketch使用1448233字节(110%)的程序存储空间。最大值为1310720字节。

全局变量使用63176字节(19%)的动态内存,留下264504字节用于局部变量。最大为327680字节。

草图太大;看https://support.arduino.cc/hc/en-us/articles/360013825179以获取减少它的提示。

文本部分超出板上的可用空间

编译错误:文本部分超出板上的可用空间

 

 

没有想到,ESP32也会遇到程序存储空间不够的情况。

后来采取扩大存储,在 Arduino IDE 中转到工具 > 分区方案>选择任何超过 1.4MB APP 的内容,例如:“Huge APP (3MB No OTA/1MB SPIFFS”)。

 

05-13.jpg

重新上传代码,可以运行了,实验串口返回情况

 

05-14.jpg

检查API的服务状态
访问WorldTimeAPI网站(https://worldtimeapi.org/),才发现API服务不能正常运行。

 

 

 

没想到,过了一天,这个网站又可以正常访问了

 

05-14--.jpg

连接CYD,打开Arduono IDE,实验串口返回情况

 

 

05-15.jpg

实验场景图  动态图

 

00022.gif

实验场景图 

 

05-16.jpg

评论

user-avatar
icon 他的勋章
    展开更多