不過因為我們有使用WiFi模組接上網際網路,若能透過網路上的時間伺服器抓取時間來使用,不僅可以省下RTC,就使用上也比較方便!
要如何取得時間資訊呢?只要透過SNTP封包(Simple Network Time Protocol)對時間伺服器要求傳送時間封包,時間伺服器就會回傳時間資料回來,資料封包格式如下:
傳輸與接收端均以相同的資料封包做傳輸,在這邊可以看到一堆的資料如Root Delay,可做為封包傳輸時延遲的時間修正,但是在這邊並不追求過於精細的時間資訊,只做時間的接收與換算。
若需要對時間伺服器要求傳送封包,只需要對VN與Mode下定義就好,定義可以參考RFC2030,節錄如下:
VN為NTP的版本編號,最新版本為第四版,所以VN就設定為100
為了接收時間,所以Mode要設定成Client接收Server傳過來的時間。
待傳送結束後,就可以準備接收時間伺服器回傳的封包,時間的區塊有四個區塊,分別為如下:
根據說明,Transmit Timestamp這個區塊是時間伺服器傳輸封包給客戶端的時間,故將以此區塊作為參考時間。
接下來將說明如何實作,完整程式碼可以參考:Github
1. 設定UDP客戶端發送接收封包,先用gethostbyname取得時間伺服器的IP位址。
char ntp_server[] = "time.stdtime.gov.tw"; ret = gethostbyname((char*)ntp_server, strlen(ntp_server), &(tar_ip.ulip));
2. 設定方式如下:
// Get socket from spider L3. ntp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if(ntp_socket < 0){ ntp_socket = 0xFFFFFFFF; return -2; } // Setting BSD socket timeout. ntp_timeout = 1000; ret = setsockopt(ntp_socket, SOL_SOCKET, SOCKOPT_RECV_TIMEOUT, &ntp_timeout, sizeof(ntp_timeout)); if(ret < 0){ closesocket(ntp_socket); ntp_socket = 0xFFFFFFFF; return -3; } // Setting BSD like socket setting. host_addr.sa_family = AF_INET; tar_port.usport = ntp_port; host_addr.sa_data[0] = tar_port.ucport[1]; host_addr.sa_data[1] = tar_port.ucport[0]; host_addr.sa_data[2] = tar_ip.ucip[3]; host_addr.sa_data[3] = tar_ip.ucip[2]; host_addr.sa_data[4] = tar_ip.ucip[1]; host_addr.sa_data[5] = tar_ip.ucip[0];
3. 傳送封包要求時間伺服器傳送時間資訊
// NTP time stamp request header, version 3, mode : 3 as client // Reference => http://tools.ietf.org/html/rfc2030 msg[0] = 0b00100011; sendto(ntp_socket, msg, sizeof(msg), 0, &host_addr, sizeof(sockaddr)); memset(&recv_addr, 0, sizeof(sockaddr)); recv_addr_l = 0; recvfrom(ntp_socket, msg, sizeof(msg), 0, &recv_addr, &recv_addr_l);
4. 接收時間資訊封包
memset(&recv_addr, 0, sizeof(sockaddr)); recv_addr_l = 0; recvfrom(ntp_socket, msg, sizeof(msg), 0, &recv_addr, &recv_addr_l);
5. TimeStamp換算,計算出1970/01/01的Timestamp
gmt_offset = (MY_GMT) * 60 * 60; // Caculate timestamp rightnow timestamp_now = ((unsigned long)msg[40] << 24) | ((unsigned long)msg[41] << 16) | ((unsigned long)msg[42] << 8) | ((unsigned long)msg[43]); // Dec 70 years in second. timestamp_now -= 2208988800UL; timestamp_now += gmt_offset;
6. 計算出秒分時
/* counting reference : http://maumaubug.blogspot.tw/2013/05/sntp-ntp-02-epoch-time.html */ *second = timestamp_now % 60; *minute = (timestamp_now / 60) % 60; *hour = (timestamp_now / 3600) % 24;
7. 利用閏年的公式計算出現在的年份
// Count years. *year = 1970; while(counter > 365){ if(((*year % 4 == 0) && (*year % 100 != 0)) || (*year % 400 == 0)){ counter-= 366; } else{ counter -=365; } *year = *year + 1; }
8. 運用查表法算出月份與日期,先確認是否為閏年
unsigned char m_d_table_normal[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; unsigned char m_d_table_leap[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; unsigned char *m_d_ptr = 0; // Count month and days. if(((*year % 4 == 0) && (*year % 100 != 0)) || (*year % 400 == 0)){ m_d_ptr = m_d_table_leap; } else{ m_d_ptr = m_d_table_normal; }
9. 查表計算
*month = 0; for(ret = 0; ret < 12; ret++){ *month = *month + 1; if(counter > m_d_ptr[ret]){ counter -= m_d_ptr[ret]; } else{ *day = counter; break; } }
10. 印出結果
unsigned int n_year = 0; unsigned char n_month = 0; unsigned int n_day = 0; unsigned char n_hour = 0; unsigned char n_minute = 0; unsigned char n_second = 0; get_time(&n_year, &n_month, &n_day, &n_hour, &n_minute, &n_second); Serial.print("Time :"); Serial.print(n_year, DEC); Serial.print("-"); Serial.print(n_month, DEC); Serial.print("-"); Serial.print(n_day, DEC); Serial.print(" "); Serial.print(n_hour, DEC); Serial.print(":"); Serial.print(n_minute, DEC); Serial.print(":"); Serial.print(n_second, DEC); Serial.println();
11. 成功轉換出時間與日期
透過這些流程,只要WiFi模組連在網路上,就可以隨時取得網路時鐘伺服器的時間。我有另外把程式整理包成NTP.c與NTP.h,放在Spider L3S Library的資料夾下,這樣在開發APP時就可以很簡單的取得時間了。
沒有留言:
張貼留言