ESP32 SNTP 网络校时 钟表显示

news2024/11/25 10:42:03

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、步骤概述

  1. 配置Wi-Fi:确保ESP32连接到Wi-Fi网络。
  2. 初始化SNTP客户端:设置SNTP客户端,包括时间服务器地址、更新频率等。
  3. 启动SNTP客户端:启动SNTP客户端以开始同步时间。
  4. 处理时间更新:当时间更新成功后,可以通过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 句柄。

功能

  1. 初始化 NVS 分区:

    • 确保 NVS 分区可用。
    • 如果 NVS 分区尚未初始化,此函数将尝试对其进行初始化。
    • 如果分区已损坏,此函数将尝试恢复数据。
  2. 检查和修复分区:

    • 如果发现 NVS 分区已损坏,nvs_flash_init 会尝试恢复分区数据。
    • 如果分区数据不可恢复,函数将尝试重新格式化 NVS 分区。
  3. 设置 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: 网络接口已经初始化过了。

功能

  1. 初始化网络接口:

    • 创建并初始化一个或多个网络接口(esp_netif_t 结构体)。
    • 网络接口用于管理和控制网络连接。
  2. 配置网络事件处理:

    • 设置网络事件处理机制,以便在连接状态改变时通知应用程序。
  3. 初始化网络栈:

    • 准备好网络栈以供后续使用。
  4. 注册网络事件回调:

    • 注册网络事件的回调函数,以便应用程序能够接收网络事件的通知。
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: 事件循环已经创建过。

功能

  1. 创建事件循环:

    • 创建一个默认的事件循环实例,该实例用于处理来自不同来源的事件。
    • 事件循环是 ESP-IDF 中事件处理的核心。
  2. 初始化事件队列:

    • 初始化一个或多个事件队列,用于存放待处理的事件。
  3. 注册事件处理函数:

    • 允许应用程序注册事件处理函数,以便在特定事件发生时调用这些函数。
  4. 事件分发:

    • 事件循环负责检测事件队列中的事件,并将事件分发给相应的事件处理函数。
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: 连接失败。

功能

  1. 连接到 Wi-Fi:

    • 如果配置了 Wi-Fi 连接,此函数会尝试连接到指定的 Wi-Fi 网络。
    • 如果 Wi-Fi 网络配置正确,此函数会等待直到连接成功。
  2. 连接到以太网:

    • 如果配置了以太网连接,此函数会尝试建立以太网连接。
    • 如果以太网配置正确,此函数会等待直到连接成功。
  3. 阻塞等待:

    • 此函数会阻塞执行线程,直到连接成功或失败。
    • 一旦连接成功,函数会返回 ESP_OK;如果连接失败,则返回 ESP_FAIL
  4. 获取 IP 地址:

    • 连接成功后,此函数会等待直到获取到有效的 IP 地址。

3、SNTP功能及时间操作函数

esp_err_t esp_netif_sntp_init(const esp_sntp_config_t *config);

参数

  • configconst 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 完成时间同步。

参数

  • toutTickType_t 类型的值,表示等待时间同步事件的超时时间,单位为 RTOS ticks。

返回值

esp_netif_sntp_sync_wait 返回一个 esp_err_t 类型的值,表示函数的执行结果:

  • ESP_OK: 时间同步完成。
  • ESP_TIMEOUT: 如果在超时时间内没有收到时间同步事件。
  • ESP_ERR_NOT_FINISHED: 如果时间同步事件到来但仍在平滑更新模式中进行(SNTP_SYNC_STATUS_IN_PROGRESS)。

功能

  1. 等待时间同步事件:
    • 此函数会阻塞当前线程,直到时间同步事件发生或超时为止。
    • 如果时间同步成功完成,函数返回 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时间,如果需要转换为本地时间,可以使用localtimelocaltime_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);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2035906.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中科亿海微SoM模组——电机驱动板

电机驱动板 电机驱动板作为驱动电机的重要组成部分&#xff0c;被广泛应用于工业自动化、消费电子、汽车、家用电器等应用领域。在工业自动化中&#xff0c;电机驱动板主要用于控制机器人、数控机床、输送带等设备&#xff0c;确保其高效、精准地运行。在消费电子和家用电器中…

【ARM Coresight Debug 工具系列 -- Trace32 | ARM-DS5 | OpenOCD JLINK 关系与差】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 常用debug工具差异介绍Trace32ARM DS-5OpenOCDJ-Link 关系与差异差异 示例比较使用 Trace32 进行实时跟踪使用 ARM DS-5 进行高级调试使用 OpenOCD 进行开源调试 Summary 常用debug工具差异介绍 在嵌入式系统开发和…

阿里淘天校招校招开始啦,欢迎投递~

淘天校招&校招开始啦&#xff0c;欢迎投递~ 后续继续推出技术类面试资料&#xff0c;有问题也可咨询哦&#xff01; 校招内推码&#xff08;25年10月前均有效&#xff09; 社招内推码&#xff08;长期有效&#xff09;

Tarjan(五)vDCC缩点

Tarjan(五) vDCC点双联通分量&#xff1a; 需要之前的前置知识&#xff0c;需要搞懂什么是割点。在tarjan(2)中有介绍到。 点双连通分量是指在一个无向图中&#xff0c;如果一个子图是点双连通的&#xff08;即去掉该子图中的任意一个节点后&#xff0c;剩余的图仍然是连通的&a…

电商平台产品ID|CDN与预渲染|前端边缘计算

技术实现 都是通过ID拿到属性&#xff0c;进行预渲染html&#xff0c;通过 oss 分发出去 详情页这种基本都是通过 ssr 渲染出来&#xff0c;然后上缓存 CDN 分发到边缘节点来处理&#xff0c;具体逻辑可以参考 淘宝——EdgeRoutine边缘计算&#xff08;CDNServerless 边缘计算…

深度解析HAProxy:构建高可用负载均衡的终极指南

目录 haproxy配置文件组成 实验环境 haproxy安装 haproxy的配置文件说明 全局配置段global 多进程和多线程配置 代理配置段proxies server配置说明 实验相关配置 测试效果&#xff1a; haproxy的状态页 socat命令 socat命令的一些常用示例 HAProxy的调度算法 静…

Oracle事务是怎么练成的

什么是事务 事务是数据库管理系统执行过程的一个逻辑单位&#xff0c;由一系列有限的数据库操作序列构成&#xff0c;事务必须满足‌ACID属性。ACID理论是数据库中最重要的概念之一&#xff0c;分别代表原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consisten…

人工智能GPU算力评估分析

GPU算力评估 一、 关于训练GPU的带宽 大模型训练算力需求&#xff1a;总算力(Tlops)6倍模型参数量训练数据token量&#xff0c;精准高效满足大规模训练需求。 需要把那么计算量和通信量的比例是多少&#xff1f; 3&#xff1a;指的是一次正向两次反向&#xff0c;反向是梯度…

程序员职场升级攻略:学AI技能,稳步迈向月薪破万之路

在人工智能高速发展的今天&#xff0c;AI技术已经成为职场人士提升收入的有力武器。许多人通过学习AI技能&#xff0c;成功跻身高收入行业&#xff0c;实现了月薪破万的目标。本文将揭秘高收入行业与城市&#xff0c;并提供一条清晰的学习路线&#xff0c;助你成为AI领域的一员…

ubuntu:更新阿里云apt源

前言 我用vmware也搭建了ubuntu服务器&#xff0c;并同样发现apt几乎完全用不了&#xff08;系统默认用的是清华源&#xff0c;可能较老了&#xff09; 更新阿里云apt源 1、去阿里云官网找系统对应的apt源配置 阿里云镜像&#xff1a;阿里巴巴开源镜像站-OPSX镜像站-阿里云开发…

Unity教程(九)角色攻击的改进

Unity开发2D类银河恶魔城游戏学习笔记 Unity教程&#xff08;零&#xff09;Unity和VS的使用相关内容 Unity教程&#xff08;一&#xff09;开始学习状态机 Unity教程&#xff08;二&#xff09;角色移动的实现 Unity教程&#xff08;三&#xff09;角色跳跃的实现 Unity教程&…

WindowsAPI 查阅笔记:进程间管道通信

进程间有名管道的通信&#xff1a; 1.1 重叠I/O&#xff08;Overlapped I/O&#xff09; 重叠I/O&#xff08;Overlapped I/O&#xff09;是Windows编程中的一种异步 I / O 处理方式&#xff0c;它允许程序在发出I/O请求后继续执行其他任务&#xff0c;而不必等待I/O操作完成…

萌啦定价工具,萌啦数据ozon定价工具

在电商行业日益竞争激烈的今天&#xff0c;精准定价成为了商家们获取市场竞争优势的关键一环。尤其是对于在Ozon平台上耕耘的卖家而言&#xff0c;无论是本土卖家还是跨境商家&#xff0c;如何快速、准确地制定出既符合市场需求又能保障利润的价格策略&#xff0c;成为了亟待解…

高防服务器的机制和原理

高防服务器是一种具备强大防御能力的服务器&#xff0c;旨在保护网站免受各种网络攻击&#xff0c;如DDoS&#xff08;分布式拒绝服务&#xff09;攻击、CC&#xff08;ChallengeCollapsar&#xff09;攻击等。今天小编将从流量过滤与清洗、负载均衡与反向代理、实时监控与报警…

圈内水刊“三巨头”之首实至名归?发文量飙升至9000+,硕博小白照样发1区TOP!

【SciencePub学术】昨天&#xff0c;小编给大家介绍了环境水刊“三巨头”之一的《Journal of Hazardous Materials》&#xff0c;本期&#xff0c;给大家带来的是位于环境水刊“三巨头”之首的《Science of the Total Environment》&#xff0c;属于JCR1区中科院1区TOP&#xf…

冷数据归档(历史库),成本与性能如何兼得?| OceanBase应用实践

随着数据量的迅猛增长&#xff0c;企业和组织在数据库管理方面遭遇的挑战愈发凸显。数据库性能逐渐下滑、存储成本节节攀升&#xff0c;以及数据运维复杂性的增加&#xff0c;这些挑战使得DBA和开发者在数据管理上面临更大的压力。 为了应对这些挑战&#xff0c;对数据生命周期…

vulnstack-5

环境搭建 靶场虚拟机共用两个&#xff0c;一个外网一个内网&#xff0c;用来练习红队相关内容和方向&#xff0c;主要包括常规信息收集、Web攻防、代码审计、漏洞利用、内网渗透以及域渗透等相关内容学习。 虚拟机密码 win7 sun\heart 123.com sun\Administrator dc123.com # …

华为软件测试笔试真题,赶快收藏

软件测试工程师笔试题目 一&#xff0e;填空 1、 系统测试使用&#xff08; C &#xff09;技术, 主要测试被测应用的高级互操作性需求, 而无需考虑被测试应用的内部结构。 A、 单元测试 B、 集成测试 C、 黑盒测试 D、白盒测试 2、单元测试主要的测试技术不包括&#xff08;…

【nvidia-smi】Failed to initialize NVML: Driver/library version mismatch

服务器更新后&#xff0c;输入nvidia-smi出现如下报错&#xff1a; 解决方法参考&#xff1a; 已解决【nvidia-smi】Failed to initialize NVML: Driver/library version mismatch解决方法-腾讯云开发者社区-腾讯云 (tencent.com) 输入命令查看nvidia驱动的版本号&#xff1a…

Linux软件包yum

目录 Linux软件包管理器 yum关于rzsz注意事项查看软件包如何安装软件卸载命令 Linux开发工具Linux编辑器-vim使用1. vim的基本概念2. vim的基本操作3. vim正常模式命令集4. vim末行模式命令集5. vim操作总结 小彩蛋 Linux软件包管理器 yum 软件包 在Linux下安装软件&#xff…