8月12日(2)
例程环境:Windows 11、Visual Studio Code、IDF_V5.2.1、LVGL_V8.3.11、HelloBug ESP32 Pilot开发板
源码获取:https://item.taobao.com/item.htm?ft=t&id=652537645861 向商家索取对应源码
SNTP (Simple Network Time Protocol) 是一种简化版本的 NTP (Network Time Protocol),用于在网络中的设备之间同步时间。SNTP 提供了一种简单而有效的方法来同步计算机或设备的时间,以确保网络上的所有设备都运行在同一时间线上。这对于需要时间同步的应用程序和服务至关重要,例如日志记录、安全审计和网络通信。
一、SNTP简介
1. SNTP 的工作原理
SNTP 是基于 UDP 协议的,它使用客户端/服务器模型来同步时间。客户端会定期向 SNTP 服务器发送时间请求,服务器则会响应客户端的请求,提供其自己的时间戳。客户端根据接收到的时间信息调整自己的系统时钟。
2. 时间同步过程
- 客户端 发送请求到 SNTP 服务器,请求包含客户端发出请求的时间戳(称为 T1)。
- 服务器 收到请求后记录接收到的时间戳(T2),然后立即回送响应,其中包含自己的系统时间。
- 客户端 接收到响应后记录接收时间戳(T3)。
- 根据这些时间戳,客户端可以计算出服务器的时间偏差,并据此调整自己的时钟。
3. SNTP 和 NTP 的区别
- 复杂度:SNTP 相比 NTP 更加简化,通常不包括复杂的 NTP 特性,如选择最佳时间源的能力。
- 精度:SNTP 的时间精度略低于 NTP,但仍然足够用于大多数应用。
- 协议格式:SNTP 和 NTP 使用相同的报文格式,但 SNTP 对报文的处理更为简单。
4. SNTP 的应用场景
- 网络设备:路由器、交换机和其他网络设备使用 SNTP 来保持时间同步。
- 服务器和工作站:企业网络中的服务器和工作站通常使用 SNTP 保持时间同步。
- 嵌入式系统:小型嵌入式设备,如智能家电或物联网设备,也可能使用 SNTP 来同步时间。
5. SNTP 的配置
SNTP 服务器可以配置为使用 GPS 信号或其他高精度的时间源作为参考时钟。客户端可以通过配置文件或操作系统设置来指定 SNTP 服务器的地址。
6. 安全性
SNTP 本身并不提供加密或其他安全特性,但在敏感环境中,可以使用扩展如 NTPsec 来增强安全性。
7. SNTP 的优点
- 简单性:SNTP 的实现较为简单,易于部署和维护。
- 兼容性:SNTP 与 NTP 兼容,可以轻松集成到现有的 NTP 基础设施中。
- 资源消耗低:SNTP 占用较少的网络带宽和计算资源。
8. SNTP 的局限性
- 精确度限制:虽然足够用于大多数应用,但 SNTP 的精确度通常不如 NTP 高。
- 安全性问题:SNTP 不提供内置的安全机制,容易受到中间人攻击。
二、ESP32 SNTP介绍
在ESP32中使用SNTP(Simple Network Time Protocol)来同步网络时间是一个常见的需求,特别是在需要精确时间戳的应用场景中。以下是使用ESP32进行SNTP时间同步的一般步骤和示例代码。
1、步骤概述
- 配置Wi-Fi:确保ESP32连接到Wi-Fi网络。
- 初始化SNTP客户端:设置SNTP客户端,包括时间服务器地址、更新频率等。
- 启动SNTP客户端:启动SNTP客户端以开始同步时间。
- 处理时间更新:当时间更新成功后,可以通过API获取时间信息
2、示例函数解释
要使用ESP32的SNTP功能,需要以下几块功能函数
1、基本功能flash初始化
esp_err_t nvs_flash_init(void);
nvs_flash_init
是 ESP-IDF (Espressif IoT Development Framework) 中的一个函数,用于初始化非易失性存储空间 (NVS)。NVS 是一种非易失性存储区域,用于存储小块的数据,如配置参数等。这个函数确保 NVS 分区被正确初始化,并准备好供应用程序使用。
参数
nvs_flash_init
函数没有参数。
返回值
nvs_flash_init
返回一个 esp_err_t
类型的值,表示函数的执行结果:
ESP_OK
: 成功初始化 NVS。ESP_ERR_NVS_NO_FREE_PAGES
: NVS 分区没有足够的空闲页面。ESP_ERR_NVS_NEW_VERSION_FOUND
: 发现了新的 NVS 版本。ESP_ERR_NVS_INVALID_SIZE
: NVS 分区大小无效。ESP_ERR_NVS_NOT_INITIALIZED
: NVS 还未初始化。ESP_ERR_NVS_INVALID_STATE
: NVS 处于无效状态。ESP_ERR_NVS_NO_PAGE_HEADER
: NVS 分区中没有页头。ESP_ERR_NVS_NOT_ENOUGH_SPACE
: NVS 分区中没有足够的空间。ESP_ERR_NVS_CRC_CHECK_FAILED
: CRC 校验失败。ESP_ERR_NVS_INVALID_HANDLE
: 无效的 NVS 句柄。
功能
-
初始化 NVS 分区:
- 确保 NVS 分区可用。
- 如果 NVS 分区尚未初始化,此函数将尝试对其进行初始化。
- 如果分区已损坏,此函数将尝试恢复数据。
-
检查和修复分区:
- 如果发现 NVS 分区已损坏,
nvs_flash_init
会尝试恢复分区数据。 - 如果分区数据不可恢复,函数将尝试重新格式化 NVS 分区。
- 如果发现 NVS 分区已损坏,
-
设置 NVS 状态:
- 设置 NVS 的状态,使其准备好供应用程序使用。
2、网络初始化与连接
esp_err_t esp_netif_init(void);
esp_netif_init
是 ESP-IDF (Espressif IoT Development Framework) 中的一个函数,用于初始化网络接口。这个函数是 ESP-IDF 中网络栈的核心部分之一。
参数
esp_netif_init
没有任何参数。
返回值
esp_netif_init
返回一个 esp_err_t
类型的值,表示函数的执行结果:
ESP_OK
: 初始化成功。ESP_ERR_NO_MEM
: 内存分配失败。ESP_ERR_INVALID_STATE
: 网络接口已经初始化过了。
功能
-
初始化网络接口:
- 创建并初始化一个或多个网络接口(
esp_netif_t
结构体)。 - 网络接口用于管理和控制网络连接。
- 创建并初始化一个或多个网络接口(
-
配置网络事件处理:
- 设置网络事件处理机制,以便在连接状态改变时通知应用程序。
-
初始化网络栈:
- 准备好网络栈以供后续使用。
-
注册网络事件回调:
- 注册网络事件的回调函数,以便应用程序能够接收网络事件的通知。
esp_err_t esp_event_loop_create_default(void);
esp_event_loop_create_default()
是 ESP-IDF (Espressif IoT Development Framework) 中的一个函数,用于创建一个默认的事件循环。这个事件循环用于管理事件和回调函数,使得 ESP32 芯片上的应用程序可以响应来自不同组件的事件。
参数
esp_event_loop_create_default
没有任何参数。
返回值
esp_event_loop_create_default
返回一个 esp_err_t
类型的值,表示函数的执行结果:
ESP_OK
: 事件循环创建成功。ESP_ERR_NO_MEM
: 内存分配失败。ESP_ERR_INVALID_STATE
: 事件循环已经创建过。
功能
-
创建事件循环:
- 创建一个默认的事件循环实例,该实例用于处理来自不同来源的事件。
- 事件循环是 ESP-IDF 中事件处理的核心。
-
初始化事件队列:
- 初始化一个或多个事件队列,用于存放待处理的事件。
-
注册事件处理函数:
- 允许应用程序注册事件处理函数,以便在特定事件发生时调用这些函数。
-
事件分发:
- 事件循环负责检测事件队列中的事件,并将事件分发给相应的事件处理函数。
static esp_err_t example_connect(void);
example_connect()
是 ESP-IDF (Espressif IoT Development Framework) 中的一个辅助函数,用于在示例代码中简化 Wi-Fi 或以太网连接的过程。这个函数通常用于演示目的,而不是生产环境中的实际应用。该函数在examples/common_components/protocol_examples_common/include/protocol_examples_common.h
中实现,并具有非常简单的行为:阻塞直到连接建立并获取到 IP 地址后返回。此函数被用来减少样板代码的数量,并使示例代码专注于展示特定的协议或库。注意不要用于实际生产环境。在开发实际应用时,需要替换此辅助函数为完整的 Wi-Fi / 以太网连接处理代码。这样的代码可以在 examples/wifi/getting_started/
和 examples/ethernet/basic/
示例中找到。
配置示例
为了配置示例使用 Wi-Fi、以太网或同时使用两者连接,请打开项目配置菜单 (idf.py menuconfig
) 并导航至 “"Example Connection Configuration"” 菜单。在 “Connect using” 选项中选择 “Wi-Fi” 或 “Ethernet” 或者两者。
当使用 Wi-Fi 连接时,在相应的配置中输入您的 Wi-Fi 接入点的 SSID 和密码。如果连接到开放的 Wi-Fi 网络,则保持密码字段为空。
禁用 IPv6
默认情况下,example_connect()
函数会等待 Wi-Fi 或以太网连接建立,并获取 IPv4 地址和 IPv6 链路本地地址。在网络环境中无法获得 IPv6 链路本地地址的情况下,在 “Example Connection Configuration” 菜单中禁用 “Obtain IPv6 link-local address” 选项。
参数
example_connect
没有任何参数。
返回值
example_connect
返回一个 esp_err_t
类型的值,表示函数的执行结果:
ESP_OK
: 成功连接到 Wi-Fi 或以太网。ESP_FAIL
: 连接失败。
功能
-
连接到 Wi-Fi:
- 如果配置了 Wi-Fi 连接,此函数会尝试连接到指定的 Wi-Fi 网络。
- 如果 Wi-Fi 网络配置正确,此函数会等待直到连接成功。
-
连接到以太网:
- 如果配置了以太网连接,此函数会尝试建立以太网连接。
- 如果以太网配置正确,此函数会等待直到连接成功。
-
阻塞等待:
- 此函数会阻塞执行线程,直到连接成功或失败。
- 一旦连接成功,函数会返回
ESP_OK
;如果连接失败,则返回ESP_FAIL
。
-
获取 IP 地址:
- 连接成功后,此函数会等待直到获取到有效的 IP 地址。
3、SNTP功能及时间操作函数
esp_err_t esp_netif_sntp_init(const esp_sntp_config_t *config);
参数
config
:const esp_sntp_config_t *
类型的指针,指向一个包含 SNTP 配置信息的结构体。
这里详细说明一下它的配置参数
/**
* @brief SNTP 配置结构体
*/
typedef struct esp_sntp_config {
bool smooth_sync; ///< 如果设置为 true,则启用平滑同步
bool server_from_dhcp; ///< 如果设置为 true,则从 DHCP 请求 NTP 服务器配置
bool wait_for_sync; ///< 如果设置为 true,则创建一个信号量来指示时间同步事件
bool start; ///< 如果设置为 true,则自动启动 SNTP 服务
esp_sntp_time_cb_t sync_cb; ///< 可选地设置时间同步事件的回调函数
bool renew_servers_after_new_IP; ///< 如果设置为 true,则在获取新的 IP 地址时刷新服务器列表(如果 NTP 由 DHCP 提供)
ip_event_t ip_event_to_renew; ///< 设置刷新服务器列表所需的 IP 事件 ID(如果 renew_servers_after_new_IP=true)
size_t index_of_first_server; ///< 刷新服务器列表时的起始服务器索引(如果 renew_servers_after_new_IP=true)
size_t num_of_servers; ///< 预配置的 NTP 服务器数量
const char* servers[CONFIG_LWIP_SNTP_MAX_SERVERS]; ///< 服务器列表
} esp_sntp_config_t;
这里主要用到两个配置,
SNTP服务器地址列表:用于获取时间
同步事件的回调:用于通知用户同步成功后做一些其它动作
返回值
esp_netif_sntp_init
返回一个 esp_err_t
类型的值,表示函数的执行结果:
ESP_OK
: 初始化成功。
功能
使用提供的配置结构初始化 SNTP。
使用示例
void time_sync_notification_cb(struct timeval *tv)
{
ESP_LOGI(TAG, "Notification of a time synchronization event");
}
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(1,ESP_SNTP_SERVER_LIST("pool.ntp.org" ) );
config.sync_cb = time_sync_notification_cb;
esp_netif_sntp_init(&config);
esp_err_t esp_netif_sntp_sync_wait(TickType_t tout);
esp_netif_sntp_sync_wait
是 ESP-IDF 中的一个函数,用于等待 SNTP 时间同步事件的发生。此函数允许您在一段时间内等待 SNTP 完成时间同步。
参数
tout
:TickType_t
类型的值,表示等待时间同步事件的超时时间,单位为 RTOS ticks。
返回值
esp_netif_sntp_sync_wait
返回一个 esp_err_t
类型的值,表示函数的执行结果:
ESP_OK
: 时间同步完成。ESP_TIMEOUT
: 如果在超时时间内没有收到时间同步事件。ESP_ERR_NOT_FINISHED
: 如果时间同步事件到来但仍在平滑更新模式中进行(SNTP_SYNC_STATUS_IN_PROGRESS)。
功能
- 等待时间同步事件:
- 此函数会阻塞当前线程,直到时间同步事件发生或超时为止。
- 如果时间同步成功完成,函数返回
ESP_OK
。 - 如果在指定的超时时间内没有完成时间同步,则返回
ESP_TIMEOUT
。 - 如果时间同步事件到来但仍在平滑更新模式中进行,则返回
ESP_ERR_NOT_FINISHED
。
注意事项
- 在调用
esp_netif_sntp_sync_wait
之前,确保已经正确初始化了 SNTP 服务。 - 设置合适的超时时间,过短的超时时间可能导致函数提前返回
ESP_TIMEOUT
。 - 如果在平滑更新模式中使用此函数,需要注意函数可能返回
ESP_ERR_NOT_FINISHED
,这表示时间同步正在进行中。
time_t time(time_t *timer);
在C语言中,time
函数是用于获取当前时间的一个重要函数。它定义在<time.h>
头文件中,并且是处理日期和时间的核心部分。
参数
timer
: 指向time_t
类型的指针。如果传递NULL
,则函数会直接返回当前时间的值,而不会修改任何变量。
返回值
- 如果成功,则返回从1970年1月1日 00:00:00 UTC到当前时间的秒数(称为纪元时间或Unix时间戳)。这个值是一个
time_t
类型,通常是一个长整型数。 - 如果出错,
time
函数会返回(time_t)(-1)
。
注意事项
time_t
类型的具体实现依赖于平台,通常是一个长整型数。在某些系统上,它可能是一个64位整数以支持更远的未来时间。time
函数返回的是UTC时间,如果需要转换为本地时间,可以使用localtime
或localtime_r
函数。- 由于
time
函数返回的是秒数,对于毫秒级的精度需求,可以考虑使用其他库如POSIX或者Windows API中的相关函数。 - 时间戳是从1970年1月1日午夜开始计算的,这被称为纪元时间。
struct tm *localtime_r(const time_t *timep, struct tm *result);
localtime_r
函数是C语言标准库中的一个函数,它定义在<time.h>
头文件中,用于将time_t
类型的纪元时间转换为本地时间。与localtime
函数不同的是,localtime_r
是线程安全的版本,因为它不使用任何共享数据。
参数
timep
: 指向time_t
类型的指针,表示要转换的时间值。result
: 指向struct tm
类型的指针,该结构体用于存储转换后的本地时间信息。
返回值
- 如果成功,
localtime_r
返回指向result
的指针。 - 如果
timep
指向的时间值表示的时间点不在有效的本地时间范围内(例如,如果它早于本地时间的纪元),则返回NULL
。
注意事项
localtime_r
函数与localtime
的主要区别在于它使用了用户提供的struct tm
结构体来存储结果,而不是内部的临时缓冲区,因此它是线程安全的。struct tm
结构体包含了一系列用于表示时间的字段,比如年、月、日等。- 由于
localtime_r
不使用任何内部缓冲区,因此不会发生缓存竞争问题,这使得它适用于多线程环境。 - 如果
timep
指向的时间值表示的时间点早于本地时间的纪元(通常是1970年1月1日午夜),那么localtime_r
可能会返回NULL
。
void esp_netif_sntp_deinit(void);
esp_netif_sntp_deinit
是ESP-IDF (Espressif's IoT Development Framework) 中的一个函数,用于在ESP32或ESP8266等微控制器上解除初始化SNTP (Simple Network Time Protocol) 模块。这个函数是ESP-IDF网络接口库的一部分,用于管理SNTP相关的功能。
参数
此函数没有参数。
功能描述
esp_netif_sntp_deinit
函数用于释放与SNTP相关的资源并停止SNTP服务。当不再需要SNTP功能时,调用此函数可以确保所有SNTP资源被正确地清理和释放。
三、示例代码
1、代码结构
components文件夹主要放自己写的一些组件,如此示例中只有液晶和触摸组件
main\assets文件夹存放程序用到的各种显示资源,这里使用第三方工具将资源PNG图片转换成lvgl特定的数组资源,使用lv_img组件进行加载显示
main\lv_message_box文件夹是自己写的一个简单的lvgl消息框组件,它可以随时更改消息标题和消息内容,用于状态的显示更新,动画等待界面,根据结果动画变换颜色并显示对应图标,目前只写了两种结果界面,后期会增加更多功能,更炫动画过程。
main\watch_clock_app文件夹是一个指针表盘的手表app,也是lvgl编写的,定时读取系统时间并动态显示,下方文字显示当前日期和时间信息
main\app_main.c 文件用于系统各种初始化,如ESP32的基础初始化,液晶、触摸初始化,LVGL初始化。全部放在这个文件的好处是其它示例直接复制即可,基本是不更改的。
main\lvgl_app.c 文件为例程APP的主体部分
2、代码说明
按程序流程,例程主要分为以下几个部分
1、初始化基本运行环境
ESP_LOGI("\r\n\r\nHello Bug LittlevGL Demo", "APP Start!~");
uint8_t MAC[6];
// 打印芯片信息
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
ESP_LOGI(TAG, "ESP32 Chip Cores Count: %d",chip_info.cores);
if(chip_info.model == 1){
ESP_LOGI(TAG, "ESP32 Chip Model is: ESP32");
}else if(chip_info.model == 2){
ESP_LOGI(TAG, "ESP32 Chip Model is: ESP32-S2");
}else if(chip_info.model == 9){
ESP_LOGI(TAG, "ESP32 Chip Model is: ESP32-S3");
}else if(chip_info.model == 5){
ESP_LOGI(TAG, "ESP32 Chip Model is: ESP32-C3");
}else if(chip_info.model == 6){
ESP_LOGI(TAG, "ESP32 Chip Model is: ESP32-H2");
}else if(chip_info.model == 12){
ESP_LOGI(TAG, "ESP32 Chip Model is: ESP32-C2");
}else{
ESP_LOGI(TAG, "ESP32 Chip Model is: Unknown Model");
}
ESP_LOGI(TAG, "ESP32 Chip Features is: %ld",chip_info.features);
ESP_LOGI(TAG, "ESP32 Chip Revision is: %d",chip_info.revision);
ESP_LOGI(TAG, "ESP32 Chip, WiFi%s%s, ",
(chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
(chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
uint32_t size_flash_chip;
esp_flash_get_size(NULL, &size_flash_chip);
ESP_LOGI(TAG, "SPI Flash Chip Size: %lu MByte %s Flash", size_flash_chip / (1024 * 1024),
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "Embedded" : "External");
ESP_LOGI(TAG, "Free Heap Size is: %ld Byte",esp_get_free_heap_size());
ESP_LOGI(TAG, "Free Internal Heap Size is: %ld Byte",esp_get_free_internal_heap_size());
ESP_LOGI(TAG, "Free minimum Heap Size is: %ld Byte",esp_get_minimum_free_heap_size());
efuse_hal_get_mac(MAC);
//ESP_LOGI(TAG, "MAC Address:");
//ESP_LOG_BUFFER_HEX(TAG, MAC,6);
ESP_LOGI(TAG, "Base MAC Addr : %02X.%02X.%02X.%02X.%02X.%02X",MAC[0],MAC[1],MAC[2],MAC[3],MAC[4],MAC[5]);
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
vTaskDelay(100 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "LVGL Version: %d.%d.%d \r\n",LVGL_VERSION_MAJOR,LVGL_VERSION_MINOR,LVGL_VERSION_PATCH);
这段代码是程序的最开始,它打印了主控的基本配置信息(芯片型号、版本、存储信息、内存信息、网络信息)、nvs_flash初始化、打印当前LVGL版本号
2、初始化硬件、LVGL部分
ESP_LOGI(TAG, "Initialize LVGL library");
lv_init();
lv_port_disp_init(); // LVGL初始化并注册显示设备
lv_port_indev_init(); // LVGL初始化并注册输入设备
ESP_LOGI(TAG, "Install LVGL tick timer");
// 为 LVGL 提供的滴答接口(使用 esp_timer 生成 2 毫秒周期事件)
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &lvgl_tick_callback,// 回调函数
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));
lvgl_mux = xSemaphoreCreateRecursiveMutex();
assert(lvgl_mux);
ESP_LOGI(TAG, "Create LVGL task");
xTaskCreate(lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);
这段代码初始化了lvgl及所需要的硬件(显示、触摸),这里主要讲三个函数,其中lv_port_disp_init()、
lv_port_indev_init();两个函数非lvgl库函数,会展开说明。
lv_init()函数用于初始化LittlevGL GUI库。它执行以下操作:初始化内部数据结构和资源、设置默认的配置选项、准备GUI库以供使用。
lv_port_disp_init()函数的主要目的是设置显示驱动器,并将其注册到LVGL中,以便LVGL可以使用该驱动器来控制屏幕上的内容。这个函数主要分为三部分:
1、初始化显示相关的硬件资源:这可能包括配置SPI接口总线、初始化液晶等。
// 初始化显示设备
ESP_LOGI(TAG,"Display hor size: %d, ver size: %d", LV_HOR_RES_MAX, LV_VER_RES_MAX);
ESP_LOGI(TAG,"Initializing SPI master for display");
spi_bus_config_t buscfg = {
.miso_io_num = DISP_SPI_MISO,
.mosi_io_num = DISP_SPI_MOSI,
.sclk_io_num = DISP_SPI_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = SPI_BUS_MAX_TRANSFER_SZ
};
esp_err_t ret = spi_bus_initialize(TFT_SPI_HOST, &buscfg, 1);
assert(ret == ESP_OK);
disp_spi_add_device(TFT_SPI_HOST);
ili9341_init();
2、创建显示缓冲区和初始化初始化显示缓冲区,这里使用的是双缓冲区: LVGL 会将显示设备的内容绘制到其中一个缓冲区,并将他写入显示设备。 需要使用 DMA 将要显示在显示设备的内容写入缓冲区。当数据从第一个缓冲区发送时,它将使 LVGL 能够将屏幕的下一部分绘制到另一个缓冲区。这样使得渲染和刷新可以并行执行。
static lv_disp_draw_buf_t disp_buf; // 绘图显示缓冲区
// 使用双缓冲
lv_color_t *buf1 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 60 * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf1);
lv_color_t *buf2 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 60 * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf2);
// 初始化显示缓冲区
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, EXAMPLE_LCD_H_RES * 60);
ESP_LOGI(TAG, "Register display driver to LVGL");
3、注册显示驱动器:通过调用lv_disp_drv_register()函数将初始化好的显示驱动器注册给LVGL。这涉及到初始化一个lv_disp_drv_t类型的结构体,并填充必要的回调函数,例如lvgl_flush_cb用于将缓冲区的内容刷新到屏幕上。
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = EXAMPLE_LCD_H_RES;
disp_drv.ver_res = EXAMPLE_LCD_V_RES;
disp_drv.flush_cb = lvgl_flush_cb;
disp_drv.draw_buf = &disp_buf;
disp_drv.user_data = 0;
lv_disp = lv_disp_drv_register(&disp_drv);
lv_port_indev_init()函数是LittlevGL (LVGL)图形用户界面库的一部分,用于初始化输入设备驱动。这个函数通常包含在LVGL的端口化文件中,这些文件是为了适应特定硬件平台而编写的。这个示例中此函数分为三个部分
1、初始化触摸用到的ESP 硬件IIC总线
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = -1,
.scl_io_num = CONFIG_DEV_IIC_SCL,
.sda_io_num = CONFIG_DEV_IIC_SDA,
.glitch_ignore_cnt = 7,
};
i2c_master_bus_handle_t i2c_bus_handle = NULL;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle));
if (NULL == i2c_bus_handle) {
ESP_LOGE(TAG, "Failed create I2C bus");
return ESP_FAIL;
}
2、初始化液晶触摸芯片
NS2009_init(i2c_bus_handle);
3、将触摸芯片注册为lvgll输入设备,这里也有一个回调函数touch_driver_read,用于LVGL获取当前触摸的坐标信息。
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.disp = lv_disp;
indev_drv.read_cb = touch_driver_read;
lv_indev_drv_register(&indev_drv);
3、初始化显示等待消息框,并为每个步骤动态的显示消息
lv_message_box_initialize(3);// 关闭等待时间3秒
这个函数是自己写的一个简单的lvgl消息框组件,它可以随时更改消息标题和消息内容,用于状态的显示更新,动画等待界面,根据结果动画变换颜色并显示对应图标
3、初始化网络,连接网络
lv_message_box_show("WIFI Connecting","Wait for the connection");
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default() );
ESP_ERROR_CHECK(example_connect());
4、初始化SNTP,等待时间同步
lv_message_box_set_title_message("SNTP Get Time","Waiting system time set...");
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG_MULTIPLE(1,ESP_SNTP_SERVER_LIST("pool.ntp.org" ) );
config.sync_cb = time_sync_notification_cb;
esp_netif_sntp_init(&config);
print_servers();
// wait for time to be set
time_t now = 0;
struct tm timeinfo = { 0 };
int retry = 0;
const int retry_count = 15;
while (1) {
ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
if(esp_netif_sntp_sync_wait(2000 / portTICK_PERIOD_MS) == ESP_OK){
lv_message_box_close_succeed("SNTP Get Time","system time set ok");
break;
}
++retry;
if(retry>=retry_count){
lv_message_box_close_failed("SNTP Get Time","SNTP Sync Timeout");
break;
}
}
while(1)
{
vTaskDelay(200 / portTICK_PERIOD_MS);
if(!lv_message_box_get_show_status()){
break;
}
}
time(&now);
localtime_r(&now, &timeinfo);
ESP_ERROR_CHECK(example_disconnect());
esp_netif_sntp_deinit();
5、时间同步完成显示手表APP
void watch_clock_app(void)
{
font_14 = LV_FONT_DEFAULT;
font_14 = &lv_font_montserrat_14;
lv_obj_t* obj_watch_clock_panel = lv_obj_create(lv_scr_act());
lv_obj_set_size(obj_watch_clock_panel, 320, 240);
lv_obj_set_pos(obj_watch_clock_panel, 60, 0);
lv_obj_align(obj_watch_clock_panel, LV_ALIGN_TOP_LEFT, 0, 0);
label_date_time = lv_label_create(obj_watch_clock_panel);
lv_label_set_text(label_date_time, "2024-08-11 Sunday 10:22:33");
lv_obj_set_style_text_color(label_date_time, lv_color_hex(0xff862b), LV_PART_MAIN);
lv_obj_set_style_text_font(label_date_time, font_14, LV_PART_MAIN);
lv_obj_align(label_date_time, LV_ALIGN_BOTTOM_MID, 0, 5);
lv_obj_t * img_watch_bg = lv_img_create(obj_watch_clock_panel);
lv_img_set_src(img_watch_bg, &watch_bg);
lv_obj_set_size(img_watch_bg, 200, 200);
lv_obj_align(img_watch_bg, LV_ALIGN_TOP_MID, 0, 0);
img_watch_hour_pointer = lv_img_create(obj_watch_clock_panel);
lv_img_set_src(img_watch_hour_pointer, &hour);
lv_obj_align(img_watch_hour_pointer,LV_ALIGN_CENTER, 0, 0);
uint16_t h = Hour * 300 + Minute / 12 % 12 * 60;
lv_img_set_angle(img_watch_hour_pointer, h);
img_watch_minute_pointer = lv_img_create(obj_watch_clock_panel);
lv_img_set_src(img_watch_minute_pointer, &minute);
lv_obj_align(img_watch_minute_pointer,LV_ALIGN_CENTER, 0, 0);
lv_img_set_angle(img_watch_hour_pointer, Minute*1200);
img_watch_second_pointer = lv_img_create(obj_watch_clock_panel);
lv_img_set_src(img_watch_second_pointer, &second);
lv_obj_align(img_watch_second_pointer,LV_ALIGN_CENTER, 0, 0);
lv_img_set_angle(img_watch_second_pointer, Second*1000);
timer_update_clock = lv_timer_create(timer_update_clock_cb, 1000, img_watch_second_pointer);
}