230 views|2 replies

35

Posts

4

Resources
The OP
 

[2024 DigiKey Creative Competition] ESP32-S3-LCD-EV-BOARD lights up LCD, connects to the Internet, and uses SNTP to synchronize time [Copy link]

 

Hello everyone, I am Zheng Gong, a little engineer who is lost in this world.

I wanted to challenge myself in this competition, and also wanted to write a post on how to get started with esp idf development, so I decided to use esp idf to develop applications in this competition (it was really a big job for me T_T)

After many days of debugging, I finally got a good grasp of the WiFi networking and SNTP functions. Now I would like to share some of my experiences with you, hoping that it will be helpful to you.

1. Turn on the LCD screen

Since we are using the official development board from Espressif, it is actually very simple to light up the screen. We can just use the official routine. The GitHub address is as follows:

链接已隐藏,如需查看请登录或者注册

The factory program uses 86box_smart_panel. I have read the code and found that there is a lot of logic and interface written in it, which is not convenient for us to learn from scratch, so I decided to use the lvgl_demo example.

They directly helped us to transplant lvgl, which saved us a lot of development work. There are a lot of documents and videos about lvgl on esp32s3 on the Internet. Generally speaking, it is not difficult. You just need to download the official lvgl library and then connect the LCD interface and touch interface. I won't introduce it in detail here.

Then we can start to develop the interface. Here I learned some code to write the interface. It feels the same as developing the interface with tkinter. Each control needs to be created, resized, styled and laid out. There is no simulation, and the download speed of esp32s3 is not fast. After several adjustments, an hour has passed. So in the end I chose to use squareline studio to develop the interface.

In this way, you only need to drag and adjust the properties, and the images will be automatically transcoded, which can really save you a lot of effort.

2. Networking

How can you use Espressif's chip without using the networking function? The following is a simple networking test code

static EventGroupHandle_t wifi_event_group;

#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT     BIT1

static void event_handler(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data){
    static uint32_t wifi_retry_cnt = 0;
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    }else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if( wifi_retry_cnt < 10){
            ESP_LOGI(TAG, "WiFi disconnected, retrying...");
            esp_wifi_connect();
            wifi_retry_cnt++;
        }else {
            ESP_LOGE(TAG, "WiFi disconnected, retrying failed");
            xEventGroupSetBits(wifi_event_group, WIFI_FAIL_BIT);
        }
        
    }else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "Got IP address: %s", ip4addr_ntoa(&event->ip_info.ip));
        wifi_retry_cnt = 0;
        xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void){

    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);

    wifi_event_group = xEventGroupCreate();

    // tcpip_adapter_init();

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "QC",
            .password = "Qaz123456",
            .scan_method = WIFI_FAST_SCAN,
            .sort_method = WIFI_CONNECT_AP_BY_SIGNAL,
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );

    ESP_LOGI(TAG, "wifi_init finished.");

    EventBits_t bits = xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "connected to ap");
    }else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "fail to connected to ap");
    }else {
        ESP_LOGE(TAG, "WIFI_EVENT_STA_DISCONNECTED");
    }
    
    ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler));
    ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler));
    vEventGroupDelete(wifi_event_group);
}

In short, there are the following steps to connect esp32s3 to the Internet:

  1. Initialize network stack : Initialize network interfaces and protocol stack.

    ESP_ERROR_CHECK(esp_netif_init());
  2. Create a default Wi-Fi interface : Create a default Wi-Fi Station (STA) interface.

    esp_netif_create_default_wifi_sta();
  3. Configure Wi-Fi interface : Set the Wi-Fi mode and configuration parameters.

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
  4. Set Wi-Fi mode : Set ESP32-S3 to Wi-Fi Station mode.

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
  5. Configure Wi-Fi credentials : Set up the Wi-Fi SSID and password.

    wifi_config_t wifi_config;
    memset(&wifi_config, 0, sizeof(wifi_config_t));
    strncpy((char *)wifi_config.sta.ssid, "your_ssid", sizeof(wifi_config.sta.ssid));
    strncpy((char *)wifi_config.sta.password, "your_password", sizeof(wifi_config.sta.password));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
  6. Enable Wi-Fi : Enable the Wi-Fi interface.

    ESP_ERROR_CHECK(esp_wifi_start());
  7. Connect to a Wi-Fi network : Use esp_wifi_connect()the function to connect to a Wi-Fi network.

    ESP_ERROR_CHECK(esp_wifi_connect());
  8. Waiting for the connection to complete : Usually you need to wait for the Wi-Fi connection to complete, which can be achieved by polling or registering an event callback function.

    while (!esp_netif_is_connected()) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
  9. Get IP address : After the connection is successful, get the IP address assigned to ESP32-S3.

    ip_info_t ip;
    esp_netif_get_ip_info(esp_netif_get_handle(ESP_IF_WIFI_STA), &ip);
  10. Using the network : At this point, ESP32-S3 has been connected to the Wi-Fi network and can communicate over the network.

Then the function needs to add the following business code to determine the network status and automatically reconnect.

3. SNTP Time Synchronization

I have made two programs for this topic, one is to get the time based on the network time service API used in the previous follow me task, and the other is to use SNTP (simple network time portocol) provided by ESP-IDF. I will post the following code to explain the following.

Network APIs

#include "cJSON.h"
#include "esp_http_client.h"
struct tm timeinfo;


void parse_json_time(const char *json_str) {
    // 解析 JSON 字符串
    cJSON *json = cJSON_Parse(json_str);
    if (json == NULL) {
        const char *error_ptr = cJSON_GetErrorPtr();
        if (error_ptr != NULL) {
            ESP_LOGE(TAG, "Error before: %s", error_ptr);
        }
        return;
    }

    // 提取 server_time 字段
    cJSON *server_time_item = cJSON_GetObjectItemCaseSensitive(json, "server_time");
    if (cJSON_IsNumber(server_time_item)) {
        // 获取时间戳
        long server_time = server_time_item->valuedouble /1000;
        // 将时间戳转换为本地时间
        
        time_t l_time = (time_t)server_time;
        struct tm *utc_time = gmtime(&l_time);
        utc_time->tm_hour += 8;		//东八区
		if(utc_time->tm_hour > 23)		//防止过界
			utc_time->tm_hour -= 24;
        timeinfo = *utc_time;
        // 格式化时间并打印
        char time_str[32];
        printf("TIME: %02d:%02d:%02d\n",utc_time->tm_hour, utc_time->tm_min, utc_time->tm_sec);
        strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", utc_time);
        ESP_LOGI(TAG, "Server time: %s", time_str);
    } else {
        ESP_LOGE(TAG, "server_time is not a number");
    }

    // 清理 cJSON 对象
    cJSON_Delete(json);

}


void http_test_task(void *pvParameters)
{
    char output_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};   //用于接收通过http协议返回的数据
    int content_length = 0;  //http协议头的长度
    struct tm* l_time = get_time();
    
    //02-2 配置http结构体
   
   //定义http配置结构体,并且进行清零
    esp_http_client_config_t config ;
    memset(&config,0,sizeof(config));

    //向配置结构体内部写入url
    
   
    static const char *URL = "http://api.pinduoduo.com/api/server/_stm";
    config.url = URL;

    //初始化结构体
    esp_http_client_handle_t client = esp_http_client_init(&config);	//初始化http连接

    //设置发送请求 
    esp_http_client_set_method(client, HTTP_METHOD_GET);

    //02-3 循环通讯

    while(1)
    {


        // 与目标主机创建连接,并且声明写入内容长度为0
        esp_err_t err = esp_http_client_open(client, 0);

        //如果连接失败
        if (err != ESP_OK) {
            ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
        } 
        //如果连接成功
        else {

            //读取目标主机的返回内容的协议头
            content_length = esp_http_client_fetch_headers(client);

            //如果协议头长度小于0,说明没有成功读取到
            if (content_length < 0) {
                ESP_LOGE(TAG, "HTTP client fetch headers failed");
            } 

            //如果成功读取到了协议头
            else {

                //读取目标主机通过http的响应内容
                int data_read = esp_http_client_read_response(client, output_buffer, 					MAX_HTTP_OUTPUT_BUFFER);
                if (data_read >= 0) {

                    //打印响应内容,包括响应状态,响应体长度及其内容
                    ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
                    esp_http_client_get_status_code(client),				//获取响应状态信息
                    esp_http_client_get_content_length(client));			//获取响应信息长度
                    // printf("data:%s\n", output_buffer);

                    parse_json_time(output_buffer);

                } 
                //如果不成功
                else {
                    ESP_LOGE(TAG, "Failed to read response");
                }
            }
        }
        //关闭连接
        esp_http_client_close(client);
        //延时
        vTaskDelay(pdMS_TO_TICKS(1000));
    }

}

The test code can add tasks via xTaskCreate(&http_test_task, "http_test_task", 8192, NULL, 5, NULL);.

The code mainly accesses http://api.pinduoduo.com/api/server/_stm Pinduoduo Time API through network request to obtain the timestamp. The format of the returned data is as follows:

{"server_time":1729698004538}

The timestamp is then parsed to obtain the value corresponding to "server_time", and then the timestamp is converted to standard time through the gmtin function. It should be noted that

1. The unit of the timestamp range is ms, and the unit of gmtime processing time is seconds, so the timestamp needs to be divided by 1000 before passing it into the function.

2. The timestamp obtained is the time on the prime meridian. Beijing time needs to be processed by adding 8.

This method is difficult to achieve accurate time display to the second. Perhaps you can request it once, then create a time maintenance internally, and then calibrate the time regularly. Otherwise, as in my example, requesting once every second will waste a lot of network resources, and the request return also takes time, and the second jump often occurs, the implementation effect is not ideal

SNTP Time Server

The second method is to use the SNTP time server provided by ESP-IDF. It is easy to use, does not occupy much system resources, and does not require system time maintenance. The code is as follows:

static void initialize_sntp(void);
static void obtain_time(void);


static void time_sync_notification_cb(struct timeval *tv)
{
    ESP_LOGI(TAG, "Notification of a time synchronization event, sec=%lu", tv->tv_sec);
    settimeofday(tv, NULL);
}

void app_sntp_init(void)
{
    setenv("TZ", "CST-8", 1);
    tzset();
    obtain_time();
}

static void obtain_time(void)
{
    initialize_sntp();
    int retry = 0;
    const int retry_count = 10;
    while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) {
        ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
        vTaskDelay(2000 / portTICK_PERIOD_MS);
    }

    if (retry == retry_count) {
        ESP_LOGI(TAG, "Could not obtain time after %d attempts", retry);
    }else {
        ESP_LOGI(TAG, "Time synchronized");
    }
}

static void initialize_sntp(void)
{
    ESP_LOGI(TAG, "Initializing SNTP");
    esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
    //设置3个时间服务器
    esp_sntp_setservername(0, "ntp.aliyun.com");
    esp_sntp_setservername(1, "time.asia.apple.com");
    esp_sntp_setservername(2, "pool.ntp.org");

    esp_sntp_set_time_sync_notification_cb(time_sync_notification_cb);
    esp_sntp_init();
}

As you can see, the code is very simple. Basically, once executed, you can get the local time through the time function. The method of getting the time is also very simple. You only need to call two functions.

    // 获取当前时间
    time_t unix_time = time(NULL);
    // 将时间转换为本地时间,这是非线程安全的方法,只有一个参数,所以不能在多线程中使用
    struct tm *time_info = localtime(&unix_time);

The time update interval can be set by opening the system settings using idf.py menuconfig, and setting Request interval to update time (ms) under Component config -> LWIP -> SNTP. I set it to calibrate once every 12 hours, which is usually sufficient.

It should be noted that it is best not to call the obtain_time function in any callback function or interrupt processing, otherwise it may be stuck. Combined with the above networking content, you can recalibrate the time after disconnecting and reconnecting.

Weather functions will be added later. At that time, you can use the network API method to register your personal business of Xinzhi Weather.

View your API key | Weather Documentation (seniverse.com)

This post is from DigiKey Technology Zone

Latest reply

The unit of the timestamp range is ms, and the unit of gmtime processing time is seconds, so you need to divide the timestamp by 1000 before passing it into the function. This is a trick   Details Published on 2024-10-25 07:24
 
 

6593

Posts

0

Resources
2
 

The unit of the timestamp range is ms, and the unit of gmtime processing time is seconds, so you need to divide the timestamp by 1000 before passing it into the function. This is a trick

This post is from DigiKey Technology Zone

Comments

Yes, I didn't look carefully at first, and all I got were the maximum values, all fixed times, and I thought there was a problem and I didn't get the time.  Details Published on 2024-10-30 11:44
 
 
 

35

Posts

4

Resources
3
 
Jacktang posted on 2024-10-25 07:24 The unit of the timestamp range is ms, and the unit of gmtime processing time is seconds, so you need to divide the timestamp by 1000 before passing it into the function. This is a trick

Yes, I didn't look carefully at first, and all I got were the maximum values, all fixed times, and I thought there was a problem and I didn't get the time.

This post is from DigiKey Technology Zone
 
 
 

Just looking around
Find a datasheet?

EEWorld Datasheet Technical Support

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号
快速回复 返回顶部 Return list