ESP8266學習進階協議類(2)——SNTP服務

SNTP服務有啥用:可校時獲取時間等等哈

ESP8266-RTOS-SDK中有提供LwIP的這個組件:LwIP是Light Weight (輕型)IP協議,有無操作系統的支持都可以運行。LwIP實現的重點是在保持TCP協議主要功能的基礎上減少對RAM 的佔用,它只需十幾KB的RAM和40K左右的ROM就可以運行,這使LwIP協議棧適合在低端的嵌入式系統中使用。(摘自百度百科)

LwIP組件下有提供SNTP服務,SNTP(Simple Network Time protocol)直譯即意思:簡單網絡時間協議,用於獲取和實現網絡時間同步的協議,它是基於NTP((Network Time Protocol)一 種用於計算機時鐘同步的協議)簡化後的產物;

雖然SDK有提供對應的SNTP接口,可以很方便的使用,但是也得先了解一下SNTP的工作原理,纔不至於變成一個純搬磚的工程師嘛。

簡述工作原理

SNTP協議可以使用單播、廣播或多播模式進行工作。

  • 單播模式是指一個客戶發送請求到預先指定的一個服務器地址,然後從服務器獲得準確的時間、來回時延和與服務器時間的偏差。
  • 廣播模式是指一個廣播服務器週期地向指定廣播地址發送時間信息,在這組地址內的服務器偵聽廣播並且不發送請求。
  • 多播模式是對廣播模式的一種擴展,它設計的目的是對地址未知的一組服務器進行協調。在這種模式下,多播客戶發送一個普通的NTP請求給指定的廣播地址,多個多播服務器在此地址上進行偵聽。一旦收到一個請求信息,一個多播服務器就對客戶返回一個普通的NTP服務器應答,然後客戶依此對廣播地址內剩下的所有服務器作同樣的操作,最後利用NTP遷移算法篩選出最好的三臺服務器使用。

本文使用單播模式;

靈魂畫師的示意圖見諒,工作原理很簡單,大概場景:就是客戶端(client)在 t1時刻(Originate Timestamp) 爲了調整自己的時鐘,向服務端(server)發送了一個校時請求,服務端在 t2時刻(Receive Timestamp) 收到了客戶端發來的校時請求後,在 t3時刻(Transmit Timestamp) 將通過來自GPS信號或自帶的原子鐘的標準時間回發給客戶端,客戶端在 t4時刻(Destination Timestamp) 收到該校時數據,校時數據中也會包含t2和t3的時間哈,據此,客戶端通過t1、t2、t3、t4計算出時差去調整本地時鐘,大概流程就這樣,更加詳細的可以自行百度查閱;

在這裏插入圖片描述

在8266中實現

官方提供的SDK還是很貼心的,幾乎都提供好了API接口;

SNTP頭文件

#include "lwip/apps/sntp.h"

開啓SNTP需要使用到的API不多,下面針對關鍵的幾個進行分析:

設置SNTP的工作模式

前面有介紹到SNTP有三種工作模式,單播、廣播、組播這些都是需要通過API進行設置的,但是沒有提供組播這種模式;

void sntp_setoperatingmode(u8_t operating_mode)
宏定義 解析
#define SNTP_OPMODE_POLL 0 單播模式
#define SNTP_OPMODE_LISTENONLY 1 廣播模式

設置SNTP的服務器地址

設置完SNTP的工作模式後,需要配置SNTP的服務器地址,用來發送校時請求,通過IP地址初始化一個NTP服務器;

void sntp_setserver(u8_t idx, const ip_addr_t *server)
參數 解析
idx 設置SNTP服務器的數量(這裏不可大於SNTP_MAX_SERVERS),可以填0
server 要設置的NTP服務器的服務器IP地址

初始化這個模塊

void sntp_init(void)

就以上三步,就算是配置並對SNTP進行了初始化同時向服務器發出校時請求並修改本地時間。


本文以通過以SNTP獲取最新時間,並通過OLED顯示時鐘,具體實現代碼及解析如下:

初始化SNTP
pool.ntp.org 是一個以時間服務器的大虛擬部署爲上百萬的客戶端提供可靠的易用的網絡時間協議(NTP)服務的項目

static void initialize_sntp(void)               //初始化SNTP
{
    ESP_LOGI(TAG, "Initializing SNTP");
    sntp_setoperatingmode(SNTP_OPMODE_POLL);    //設置爲單播模式    
    sntp_setservername(0, "pool.ntp.org");      //設置SNTP服務器
    sntp_init();                                //初始化,並向SNTP服務器發出校時請求
}

獲取時間

static void obtain_time(void)
{
    xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
                        false, true, portMAX_DELAY);    //等待WIFI連接成功
    initialize_sntp();      //wifi連接成功後進行SNTP校時                         

    time_t now = 0;
    struct tm timeinfo = { 0 };
    int retry = 0;
    const int retry_count = 10;
    
    //8266上電沒校時的話,時鐘是1970年開始的,這裏的while設置了20s超時處理
    while (timeinfo.tm_year < (2016 - 1900) && ++retry < retry_count) { 
        ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
        vTaskDelay(2000 / portTICK_PERIOD_MS);     
        time(&now);
        localtime_r(&now, &timeinfo);       //獲取系統時間,如果系統時間被成功修改後,今年時2020年,timeinfo.tm_year應該爲120 > 116
    }
}

顯示

static void showtime_task(void *arg)
{
    time_t now;
    struct tm timeinfo;
    
    char strftime_datebuf[32];  //保存日期
    char strftime_timebuf[32];  //保存時間
    
    OLED_Init();    //OLED初始化
    OLED_Clear();   //OLED清屏幕
    
    time(&now);     
    localtime_r(&now, &timeinfo);           //獲取本地時間
    if (timeinfo.tm_year < (2016 - 1900)) { //判斷是否爲修改過
        ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
        obtain_time();      //獲取時間
    }

    setenv("TZ", "CST-8", 1);           //設置爲中國區時間
    tzset();                            //設置時間環境變量
    
    while(1)
    {
        time(&now);
        localtime_r(&now, &timeinfo);
        
        if (timeinfo.tm_year < (2016 - 1900)) {
            ESP_LOGE(TAG, "The current date/time error");
        } else {
            strftime(strftime_datebuf, sizeof(strftime_datebuf), "%F", &timeinfo);//取出日期/年月日
            strftime(strftime_timebuf, sizeof(strftime_timebuf), "%T", &timeinfo);//取出時間/時分秒
            //ESP_LOGI(TAG, "The current date in Shanghai is: %s", strftime_datebuf);
            //ESP_LOGI(TAG, "The current time in Shanghai is: %s", strftime_timebuf);
            OLED_ShowString(24, 0, (uint8_t *)strftime_datebuf, 16); 
            OLED_ShowString(32, 2, (uint8_t *)strftime_timebuf, 16); 
        }

        vTaskDelay(1000 / portTICK_RATE_MS);
    }
}

附加說明:strftime()這個函數可以將 struct tm 格式的時間信息結構體轉成我們想要的格式,並保存到指定的內存去,可參考(https://www.runoob.com/cprogramming/c-function-strftime.html)

實物展示

在這裏插入圖片描述
在這裏插入圖片描述
具體完整代碼,由於文中沒有很好的優化,可能會出現跳秒的情況,可自行優化!有幫助請點個贊,謝謝~


我的GITHUB

我的個人博客

CSDN

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章