NTP 時鐘 – 產品思維 (二) & Debug

當我自豪的覺得應該已經沒問題了,NTP 時鐘看起來有模有樣,殊不知前去向 Bird 闡述我目前的所做的 “功能” 時,Bird 只回了我,你覺得這樣就夠了嗎?

頓時不知道該怎麼反應,Bird 立即又問了我,(原話如下) 你現在將 NTP server 寫死在裡面,如果 NTP server 壞掉了呢,抓不到時間這個時鐘不就沒用了? 你的顏色可不可以變?你用的是 256 * 256 * 256 種顏色的 colorduino LED matrix,你卻只讓他顯示三種顏色…,會不會太浪費?

功能這種東西不要我告訴你你該做什麼,你應該要自己想清楚,這些可以玩的東西太多了,如果你不會動腦想這些附加價值的東西,那就跟一般的所謂碼農沒兩樣。

不知道是我對時鐘沒有想像力,還是我現階段真的沒有辦法把這種問題思考的很明確,不管是哪種情況,這件事我想我會牢記在心,只為了不再犯此類似的錯誤,也為了不要成為沒有創造力的 progrmmer。

anyway 接下來就是分享上面 Bird 提到的兩個功能,先做完後再看看有沒有其他的可能性。

NTP server 的多重選擇

在上一篇 NTP 時鐘 – 程式碼彈性 & 產品思維 提到我在 ESP8266 的 web server 裡寫了一個 webpage 做為使用者的 interface,讓 user 可以利用此 interface 去和時鐘做互動。而多重的 NTP server 的功能,我也是利用 webpage 在上面加了三個 button 讓 user 在其中一個 NTP server 抓不到資料的時候,還有另外兩個 NTP server 可以選擇 (如果三個都掛掉的機率應該是不高…)

用程式碼來看,其實並沒有很難,只要在把 NTP server 的來源地址新增成三種來源,接著把 server 端的程式碼增加三個 button clicked 後送出的 HTTP GET 來的 request,只要這兩項搞定,這項功能也就解決了,以下是部份程式碼:

//there are three option
char *ntpServerName = "tick.stdtime.gov.tw";
char *ntpServerName2 = "clock.stdtime.gov.tw";
char *ntpServerName3 = "time.stdtime.gov.tw";
void setup()
{
//.skip this part
//.
//.
server.on("/NTP", []()
{
ntpServer(ntpServerName);
hours = ((epoch % 86400L) / 3600) + 8;
minutes = (epoch % 3600) / 60;
seconds = epoch % 60;
show();
server.send(1000, "text/html", page);
});
server.on("/NTP2", []()
{
ntpServer(ntpServerName2);
hours = ((epoch % 86400L) / 3600) + 8;
minutes = (epoch % 3600) / 60;
seconds = epoch % 60;
show();
server.send(1000, "text/html", page);
});
server.on("/NTP3", []()
{
ntpServer(ntpServerName3);
hours = ((epoch % 86400L) / 3600) + 8;
minutes = (epoch % 3600) / 60;
seconds = epoch % 60;
show();
server.send(1000, "text/html", page);
});
}

另外,程式碼中沒有提到的是 ntpServer 這個 function 我將裡面原先的 argument 換掉,不把送進去的東西寫死,而是可以吃不同來源的 NTP server 網址。

經過一番努力後確定這個 function 是可以 work 地非常好,接著下一個功能。

Color-picker 變換時鐘數字顏色

這的 part 是我在最近嘗試得比較久也比較多東西可以分享的部分。

想要變換時鐘顏色首先要先釐清的事情有幾個,我要在哪裡選擇且如何選擇我想要時鐘變換的顏色,還有現在的工作都是在 master 做好再丟 data 過去給 slave 端顯示,那就要先知道我要丟什麼東西過去給 slave,再來就是要知道 slave 端接收東西後要做什麼樣的事情才能做到正確顯示我給的顏色 data。

這件事其實沒有很容易,讓我來一一說明。

要讓 user 去選擇自己想要改的顏色的方式在經過我查了一些資料後決定用 color-picker 的方式讓 user 自己去選定想要時鐘顯示的顏色。

當然,這裡還是要站在巨人的肩膀上,一方面我目前 focus 的點不在網頁前端 (說真的我也沒有很喜歡) 重點在軟硬整合,另一方面真的有很多現成的 open source 可以使用,那我當然就直接把別人寫好的東西改成我要的形式即可。

不過過程中其實發生了很多意想不到的問題,畢竟我對前端也是略知一二而已,總之最後我將 color-picker 的東西成功整合進我的 webpage,讓使用者可以在網頁上調整顏色,我還順便把 webpage 背景色經由 user 調整顏色時,一起連動更改 (只是實現心裡小小的想法)。

接著第一個重頭戲來了,color-picker 在前端被 user 調整後要怎麼讓 web server 知道這件事情,並且讓 web server 去做出送出 data 給 slave 去改變顏色的動作。

先前在寫 ESP8266 server 端的程式時都是利用 HTTP GET 的形式,用 button clicked 的方法向 server 送出 request 並做出相對應的行為。而這次也不例外,也打算用相同的形式來做到我想達到的目標,但在實踐的過程中卻有些小不同。

當初讓 user 在 textbox 內設定值,當按下 button 時會將三個 textbox (分別為時分秒) 的值一起送到 web server 做 parse 的動作,不過現在 colorpicker 的清況是,我要抓出 bar 拉到 slider 某處後,那個 location RGB 分別的數字,也是利用 button clicked 的方法把值送出 request 給 web server 做相對應的行為。

這件事在我沒有很了解 jQuery 的情況下對我來說是難的,在詢問朋友還有自己找資料後才發現原來 form tag 裡面,的 submit button 一按下去的時候,會把有 name 的 tag 的資料一起送出去 (即向 web server 做 request 的動作),而 value 的部分在 js 裡面用 jQuery 就搞定了 (這也表示我一開始在做設定時間的功能的時候並不是非常了解其中運作的方式,當時純粹只是找到網路上的資料卻沒有很了解他真正的意義,不過幸好現在懂了)

螢幕快照 2017-07-30 上午2.31.56.png

所以現在的狀況是,我已經可以將 user 在 webpage 上調整到的顏色,利用 button clicked 的方式將 RGB 值送出 request 給 server 做動作。

接下來是第二個重頭戲的部分。可以送值到 server 是我想做的,但送進來的東西有 RGB 三個值,但很遺憾的是,如果先前的文章有 follow 到就會知道,我現在的每個顯示元素,不論是動畫,數字,還是 AppWorks 幾個大字,都是用二維陣列產生 0 或某個特定的數字 (1 是紅色,2 是黃色,3 是橘色),去決定在 colorduino 上面顯示的顏色。

那要如何解決這個問題,其實我當下真的沒有想出比較好的解法 (我一開始的想像甚至是,由於 user 是透過同個 AP 去連到 ESP8266 web server,我希望 user 可以在邊拉 color-picker 的 bar 時,數字的顏色就能跟著動,但以我現在手上現有的資源實際上不太可能),在和 Bird 討論此事後決定不要產生 16 個 million 多的顏色其實也無妨,一方面牽涉的軟體面太多不是此專案的重點 (就是我上面提到原先想要的形式,而且應該是用 python 做,小的不才目前還沒有涉略 python 的機會),另一方面人眼其實也沒有辦法辨識出這麼多種顏色。

所以不顯示 16 million 多的顏色,只顯示 256 種顏色是我的目標,而實作的方式會是將吃進來的值 (即 RGB 三個各別的值) 轉換成一個數字,比如說,R : 255, G : 215, B : 100,那我就 R 取前三個 bits,G 取中間 3 個 bits,B 取尾末 2 個 bits,用這樣的方式組成一個新的 byte 也就是一個全新的數字,即 244。

組成全新的數字後,就將這個值從 master 端傳給 slave 端,請 slave 端再做一次處理,使得能有效地顯示 RGB 的顏色。作法雷同只是會有問題,244 這個數字傳到 slave 後要再拆開一次,才會有 RGB 三個分別不同的值,而 R 因為是前 3 個 bits,因此是用 244 & 224 (即 11100000),取 R 的值,G 是中間 3 個 bits,因此是用 244 & 28 (即 00011100),B 最慘是尾沒 2 個 bits,因此是用 244 & 3 (即 00000011),這樣會產生的問題其實很明顯的可以知道 R 的顏色永遠比較強烈,而 B 的顏色就無法被辨識。

為了解決以上的問題,就又牽扯到演算法的部分,目前不想著重此部分,最後我只是將 G 吃進來的值 * 9 (即和 & 28 做完運作後的值再乘上 9),B 吃進來的值在 * 85,效果呈現的確好了非常非常多。

不過還是有問題存在,如果我當初在 color-picker 上調到的綠色好死不死就是 242 那我進 server 處理融合在拆開原先 242 是個頗大的數值,但在 slave 端卻會是 0,這是非常非常大的誤差,但說真的,用這種融合又拆解的方式,這基本上無解 (如果有人有解法麻煩告訴我,我會很感謝你)。

總之,現在時鐘可以讓 user 利用 webpage 設定顏色,雖然綠色的部分真的不太明顯,不過基本上很多顏色是可以清楚辨別的,算是一個小成功吧!

做完了這兩個 function 終於也過了 Bird 的認可,不過難的卻還在後頭 – Debug

Bird 要我先將時鐘放在 AppWorks 的會議室兩個星期,倘若能夠正常運作沒出 trouble,就可以順利裝盒,當然,我從沒這麼幸運,我放了兩天就出問題……。待續……。

 

NTP 時鐘 – 程式碼彈性 & 產品思維

在上一篇 NTP 時鐘 – RTC & NTP & master and slave 整合 的最後提到了三個問題,分別是 “秒數的變動不規律”、“時間不準 (70 多分鐘慢了 1 分多鐘)”,以及 “Colorduino 上顯示錯誤 pattern”。

如圖:這就是所謂的 Colorduino 上顯示錯誤的 pattern (終於讓我找到照片) (最左邊的 pattern 壞掉了)

19866904_1438091146283930_87774249_o.jpg

嚴格來說只有兩個問題,“秒數的變動不規律” 和 “時間不準” 這兩件事情是有關係的,因為秒數的變動不規律,造成時間快慢不一,進而影響時間的不準確。

我還記得當時我以為 “秒數的變動不規律” 是件合理的事情,因為我仔細看了 Mac 上面的數字時間,他的秒數變動也不是每秒都相同,還為此合理化自己造成的問題,結果 Mac 上面的秒數變動不規律只是因為我開太多 applicatioin,導致 CPU 被搶去 support 那些應用程式,也就是說 Mac 的時鐘的 priority 不是很高,所以才有這樣的現象發生

上一篇說去找 Bird 時被小ㄉㄧㄤ了一下,其實在那之前我已經解決秒數變動不規律和時間不準確的問題。經過 Bird 解釋,瞭解 ESP8266 的精度可以到 50ppm 後,我就確定是我的軟體面產生了問題,因此我去看了我的程式碼,並且將 Software RTC 的部分 (我懷疑是這裡) 寫了另外一個版本,最後終於解決問題 (當時這裡我不應該就這樣算了,應該去釐清原本的寫法為什麼會出問題)。

總之,解決完這兩個問題 (Colorduino 顯示錯誤這個問題我打算放到後面再分享,其實這個問題我很早就發現也有問過 Bird,當時 Bird 懷疑可能是因為當初我不是一秒送一次 data 去 Colorduino 顯示因此造成錯亂,後來發現另有隱情),我以為自己 “差不多” 就把時鐘做完了,結果去找 Bird 後瞬間被打槍。

Bird 只問了我兩個問題:1. 你的時鐘功能是什麼?有哪些功能? 2. 你現在程式碼裡怎麼跑的?

我當時真的很天真地說了顯示時間,後來才發現 Bird 之前要我想的 spec 原來就是這裡說的功能,是面對使用者去設想、假設情境的功能,不單單只是單純硬體面的 spec。Anyway,我到底做了哪些功能我在接下來的文章會分享,重點是第二個問題,我從來沒有想過程式怎麼跑這件事,但這卻非常非常重要

上面之所以先提到功能就是因為程式碼怎麼跑會影響到程式碼的彈性。舉例來說,我去找 Bird 時,我的程式碼是在 main loop 裡面不斷不斷在執行,看起來很合理,但如果宏觀一點來看,做時間的計算、顯示對於這個時鐘是最基本的事情,我竟然就這麼佔用了整個 main loop,那當我接下來要加其他功能的時候就變得非常不好處理

Bird 傳輸這個觀念給我的同時,我就想到我常常因為沒有做好事先規劃,想怎麼寫就怎麼寫,結果到頭來要和其他功能做合併的時候,就變成我原先寫好的程式碼又要再做大幅度的更動,非常不聰明也沒效率,這也就是我在上篇文章不斷提到說我的程式碼不夠彈性的原因。(但有時候的確無法避免大幅度調整程式碼,畢竟能規劃的就盡量規劃,有些東西事前還是無法預知)

講完觀念,就要來分享那要怎麼解決現在程式碼霸佔 main loop 的問題 – ISR。利用一段固定時間的 timeout (我可以自己設定),去執行 interrupt 的 function,而在這裡就是把計算時間和顯示這件事丟去 interrupt function 做,這樣 main loop 就清淨多了

而 ISR 對我來說並不陌生,先前在沒有使用 library 的時候就自己親手實做過了 interrupt 的 function,以下分享部分程式碼:

extern "C"
{
#include "user_interface.h"
}
os_timer_t myTimer;
void user_init(void) // the function will be set in setup to trigger the ISR function
{
os_timer_setfn(&myTimer, timerCallback, NULL);
os_timer_arm(&myTimer, 1, true);//call interrupt function every 1 millisec
}
void setup()
{
//skip the part above
user_init();
}
void loop()
{
//release main loop to let it be blank
}
void timerCallback(void *pArg)
{
timeNow++;
if (timeNow - timeLast == 1000)//go in the if statement every 1 sec
{
timeLast = timeNow;
seconds++;
if (initialSecond)
{
seconds = initialSecond;
initialSecond = 0;
}
//-------the section for "seconds"--------
if (seconds == 60)
{
seconds = 0;
minutes = minutes + 1;
}
//-------the section for "minutes"--------
if (minutes == 60)
{
minutes = 0;
hours = hours + 1;
}
//-------the section for "hours"--------
if (hours == 24)
{
hours = 0;
days = days + 1;
}
time_first = seconds % 10;
time_second = seconds / 10;
time_third = minutes % 10;
time_forth = minutes / 10;
time_fifth = hours % 10;
time_sixth = hours / 10;
sendDataToColorduino();// function to send data to show on those colorduino board
calibrateTime();// function to calibrate Time through getting ntp server time again at every specific time i set
}
}

為了實現其他我要做的功能,釋放 main loop 是必要的。上面的程式碼,我是先以 1 millisec 為 timeout 去執行一次 interrupt function,實際的操作是 “每次進 interrupt funciton timeNow 就會加一,而加到 1000 millisec (即一秒) 時,進去 if statement 計算時間並顯示”。想法沒問題,讓他 upload 去跑,成功顯示但卻怪怪的。

怪怪的其實就是他跑得異常慢,我記得大概 20 秒就慢了一秒了吧,表示這一定有問題,不過我張時真的不知道為什麼。我想了好一陣子終於有點頭緒 (其實也是 Bird 提示我時間維度),事情是這樣的,main loop 在每 1 個 millisec 就被 interrupt function 綁架一次,意思是說,等到真的 timeNow-timeLast 等於 1000 時,進去 if statement run 完一整個也要在 1 個 millisec 內完成,這樣事情才會完整完美,但光是用看的就知道不可能 1 個 millisec 跑完,那也就當然不可能會 work。

而不只是要知道 interrupt function 1 個 millisec 跑不完,更是要去了解他到底跑多久跑完,因此我去測量如果有進 if statement 時的 interrupt function 跑完一次要花的時間 – 73 millisec,可想而知,上面的狀況是如果 timeNow-timeLast == 1000,進去 if statement 跑完要 73 millisec,那在回去 main loop 的時候 timeNow 早就錯過了好幾次的計算,因此當然時間會慢很多

那這裡的解法其實也沒有很困難,既然 1 個 millisec 不夠跑,反正我的目的就是要一秒計算一次時間並且顯示 (即進去 if statement 時的 interrupt function),時距一樣都是 1000 millisecs,那我不要 1 millisec call 一次 interrupt,我 200 millisecs call 一次就好,所以 timeNow-timeLast == 5 就是一秒了 (200*5),這樣總該夠 interrupt function 跑了吧,果然結局是好的,不但成功顯示在 Colorduino 上,而且還超級準。

改造 NTP 時鐘 & 產品思維 (粗淺)

完成上面釋放 main loop 的 task 後,接著我沒有去找 Bird,我自己想了一些如果我是使用者我會想要的功能 – 設定時間 (如果 NTP server 抓不到的情況下 user 可以自己使用 webpage 的介面去設定當然的時間,讓時鐘的功能繼續運行)。

在分享下去前我先提個外話,由於我上篇文章 lost 掉一個 part,在做完整合後我就著手把那些惱人的跳線還有 level shift 和 NodeMCU 利用我在成為 “硬漢" 後的技能,將他們用 ok 線焊接,也整合起來,讓他看起來更像一個接近成品的樣子。如圖:

20108035_1443181732441538_467312827_o.jpg

回歸正題,既然我要做設定時間這項功能,那我勢必要使用到 ESP8266 內建的 web server,讓我可以透過 WiFi 連上 web server 裡面的 webpage,這樣 user 才能在上面做設定時間的動作。

再來,要設定時間的情境是因為 user 抓不到 NTP server 的 data,接下來 user 會想要得到一組 IP 讓他們可以在同網域的 WiFi 底下,透過在 browser 輸入這組 IP 進入 ESP8266 裡的 webpage 去設定時間,那 IP 如何得知?user 又不可能有 console 可以用,因此我設計了一個 button 作為 trigger,讓 user 想要連上 webpage 時 (NTP server 的 data 抓不下來),press button 後 web server 的 IP 位置就會顯示在 Colorduino 上面。如圖:

20132782_1443181759108202_574567124_o.jpg

我的設計是 IP 位置顯示完,Colorduino 的畫面會變成 00:00:00,等待 user 輸入時間並且按下 setting time 的 button (在 web page 上),而在此同時 Colorduino 上的數字就會根據 user 想要顯示的時間做變化,最後在按一次 button,時鐘就會開始運作 (其他 demo 的圖我會再補)。

這裡的重點不只如此,前面提到我為了 “功能” 這件事情,釋放 main loop 把計算時間和顯示的 work 都放到背景 (即 interrupt function) 處理,而這裡 “設定時間” 的這個 “功能”,我就是放在 main loop 裡面處理的 (用了條件式),試想如果我到此時都沒有將計算和顯示時間的 work 丟去背景處理,現在的功能就不會這麼順利地被我 build 出來了。

終於,NTP 時鐘有點樣子了,自信滿滿地去找了 Bird 給他看成果,結果又是一次打槍…待續…(但真的又是一次句句有道理到我真的連 upset 的反應都來不及,下回分享!)

NTP 時鐘 – RTC & NTP & master and slave 整合

繼上一篇文章文末分享到在 Colorduino 上面以 NodeMCU ESP8266 為 master 實作數字顯示、動畫以及顏色的變換,接下來要分享的東西是如何將這些我一一完成的 “積木" 成功的把他們組裝起來,並且順利運作。

bq32000 (RTC module)

在我可以正確的從 NodeMCU ESP8266 (master) 發送 data 給 Colorduino (slave),並且成功顯示在 Colorduino 上後 (真的是經過了一番折騰),我開始要將 NTP 時鐘主要的功能一個一個加上去。

首先要做的事情是讓 master 可以正確的一秒改變一次數字。畢竟我不可能無時無刻都在抓 NTP server 的資料 (如果 WiFi 掛了時鐘就掛了,不過這只是個小原因,後面會分享主要是為什麼),那假設我設定每隔 5 seconds 向 NTP server 抓時間資訊回來我本機端,master 只做這件事而且自己不數秒的話,在 Colorduino 上面就不會看到像電子鐘那樣一秒一秒地改變顯示,而是看到每隔 5 秒的狀況 (意思是秒數的個位數字只會顯示 5 或是 0)。

為了避免這麼蠢的事情發生 (我想也沒有時鐘長這樣吧),又不可能無時無刻都在向遠端抓 NTP server 的資料,那勢必需要一個 “東西" 讓 master 自己能數秒,並且正確顯示在 Colorduino 上,而這個東西就是接下來要分享的 RTC (real-time clock) module – bq32000。

先附上 bq32000 的 datasheet。這裡沒有要細說 bq32000 是如何操作 (如何操作其實看 datasheet 就會懂了,雖然說我有些也看得沒有很懂),我比較想要著重在『如何和 master 合作』這個部分。

由於 bq32000 也是採用 I2C protocol 和其他元件做溝通,我的 NodeMCU ESP8266 是為 master,因此 bq32000 會是這個 master 之下的其中一個 slave,而知道了主從關係後,要關心的重點就在於 write 和 read。

RTC 的應用就是要把 RTC module (slave) 跑的時間 write 給 master,master read data 後把東西再傳給另外一邊的 slave 也就是 Colorduino,不過事情當然沒有這麼簡單,如果我希望 user 可以自己設定時間的話,勢必是在 master 獲得 user 設定的時間,也就是說 master 必須也要 write data (user 設定的時間的 data) 給 RTC module (這個部分其實容易,因為就跟我先前做的 master (ESP8266) write data 給 slave (Colorduino) 一樣)。

想好怎麼做後,也大致瞭解 bq32000 datasheet 的內容後,便向 Bird 尋求 bq32000 的元件坐第一次的嘗試。不過當時 Bird 手邊只有這種形式的元件:

BQ32000.jpg

因此 Bird 先要我用軟體實現 RTC,也就是在不用外接 RTC module 的情況下,用 software 寫出等同於 RTC module 的 function,除此之外,Bird 直接要我嘗試將很早之前做好 (當兵前) NTP 的 function 和即將要寫的 software RTC 結合,讓他們可以合力運作。

Software RTC & NTP server function

Software RTC

由於無法使用 bq32000 RTC module,得先跟他暫別。原先想說用 software 寫 RTC 也未免太困難,不過冷靜想想後發現完全不是件難事。其實原理很簡單,要秒我給你秒,要分我給你分,要時我給你時,意思是 60 秒為 1 分鐘,60 分鐘為一小時,也就是只要我有一個不斷在計算時間的東西我就可以利用換算的方式達到同等於 real-time clock 功能的效果

而這個東西讓我馬上想到了在做之前 project 的時候用到了 Arduino 內建的一個 function – millis()。這個 function 簡單來說就是從 MCU 開始 run 的那刻,millis() 這個 function 便會開始計算 MCU run-time 的時間,詳細的說明我附上 Arduino 官方文件:millis

以下是部分程式碼:

unsigned long timeNow = 0;
unsigned long timeLast = 0;
int seconds = 0;
int minutes = 0;
int hours = 0;
int days = 0;
timeNow = millis()/1000;// millis is the function i have mentioned above
seconds = timeNow - timeLast;
if (seconds == 60)
{
timeLast = timeNow;
minutes += 1;
}
if (minutes == 60){
minutes = 0;
hours += 1;
}
if (hours == 24){
hours = 0;
days += 1;
}

以上簡單的程式碼就可以實現 software RTC 了 (意外的簡單),不過做到後來發現 software RTC 這樣寫的彈性不夠好,後續文章會說明為什麼 (其實接下來就會遇到所謂的彈性到底指的是什麼,我將一併解釋)。

NTP server function

做完 software RTC ,確定他可以成功顯示在 Colorduino 上,以『計時器』的方式在跑時間 (因為我設定時分秒的 initial value 都是 0),我還很無聊的用我手機碼錶的功能跟他較勁,目前 “看起來" 一切運作順利。

接著就是將 NTP server 這個原先寫好的 function 加進我 master 端的 code 裡面。再往下分享整合部分之前,由於我發現我講了這麼久的 『NTP 時鐘』,在之前的文章竟然都沒有分享到 NTP server 這件事情實在有點說不過去,所以我在這裡大致分享一些 material 和這個 function 的實作內容。

關於 NTP (network time protocol) server 的由來以及故事,我是看 鳥哥的Linux 私房菜– NTP 時間伺服器。各種有關於 GMT (Greenwich Mean Time),UTC (Coordinated Universal Time),Unix time 的說明以及之間的比較都寫的都很詳盡。

而 NTP server 這個 function 就是藉由實作 protocol 去抓取我需要的資料,比如說 NTP 這個 protocol 是以 port123 為連接的埠口 (想像自己問別人問題 <相當於送出 request>,被問的那個人要接收你的訊息,而他打算用右耳接收 <特定的連接埠口>),以 UDP 封包的形式傳送時間的資訊。

再者,NTP server 其實是一種階層的改念,就是一般的 server/client 主從架構,而最多可以到 15 個階層,不過 anyway 這不是重點,有興趣的人可以點選以上連結看更詳細的說明。

以下是部份程式碼:

IPAddress timeServerIP; // time.nist.gov NTP server address
const char *ntpServerName = "tick.stdtime.gov.tw";// this is one of the NTP server
const int NTP_PACKET_SIZE = 48;
byte packetBuffer[NTP_PACKET_SIZE];
WiFiUDP udp;
unsigned long sendNTPpacket(IPAddress &address)
{
Serial.println("sending NTP packet...");
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udp.beginPacket(address, 123); //NTP requests are to port 123
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
}
void ntpServer(char *NTP)
{
//get a random server from the pool
WiFi.hostByName(NTP, timeServerIP);
sendNTPpacket(timeServerIP); // send an NTP packet to a time server
delay(1000);
int alreadyConnected = udp.parsePacket();//return true or false
if (!alreadyConnected) {
Serial.println("no packet yet");
}
else {
Serial.print("packet received, length= ");
Serial.println(alreadyConnected);
// We've received a packet, read the data from it
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
//the timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, esxtract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900 = " );
Serial.println(secsSince1900);
// now convert NTP time into everyday time:
Serial.print("Unix time = ");
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
epoch = secsSince1900 - seventyYears;
// print Unix time:
Serial.println(epoch);
// print the hour, minute and second:
Serial.print("The UTC time is ");
Serial.print(((epoch % 86400L) / 3600) + 8); //hour (GMT+8)
Serial.print(':');
if ( ((epoch % 3600) / 60) < 10 ) {
Serial.print('0');
}
Serial.print((epoch % 3600) / 60);//minute
Serial.print(':');
if ( (epoch % 60) < 10 ) {
Serial.print('0');
}
Serial.println(epoch % 60); //second
}
}
view raw NTP_server.cpp hosted with ❤ by GitHub

由上方的程式碼可以看得出來,想必在 console 裡面一定看得到現在的標準時間,比如說 11:35:27 (當然是要在成功抓到 NTP server 資料的情況下),而接下來我要做的就是把顯示在 console 裡的資訊從 master 透過 I2C 傳給 slave,即顯示在 Colorduino 上方。

在上上篇文章 NTP 時鐘 – I2C 應用 (master v.s slave) (二) & NodeMCU ESP8266 as master 有提到 master 是如何傳 data 給 slave (Colorduino) 使其能夠跨板子顯示各種 pattern,所以我的做法就是把 NTP server 抓來的時間資料,總共 6 個數字 (時 x 2,分 x 2,秒 x 2) 每個數字分割成兩個部份,每部份 16 bytes (一個數字 32 bytes),傳送到 slave 端顯示。

搞定後再加上原先動畫和改變顏色的部分 (改變顏色上次有提到就是我每個 byte 送的資料不同,比如說我要紅色我就送 1,黃色 2,橘色 3 以此類推,但做到後來也會發現這樣寫很沒有彈性,往後的文章會解釋)。

19749549_1431652130261165_1118607472_o.jpg

成功把上面我要達成的目標完成後,目前 NTP 時鐘長這個樣子。我記得當時開心了半小時就發現了新的問題……,我又很無聊的把它跟我在 google 上查到的 NTP 時間做比較,發現了驚人的現象 – 秒數的變動很不規律,意思是 1 到 2 秒可能是 a 這個速度,但 3 到 4 秒時卻是 b,然後 a != b,這絕對不正常。

不僅如此,我放在旁邊不管他,看他過了一兩個小時後會不會變快或變慢,結果很慘的他慢了 1 分多鐘 (我當時設定兩個小時重新抓一次 NTP server 的資料),這也很不合理,因為 ESP8266 本身在計算 MCU 運算時間方面,精度可以到 50 ppm (也就是說每一百萬秒才會有 50 秒的誤差,大概是 11 12 天的時間才會差 50 秒),但我現在卻是兩個小時就差了 70 幾秒。

還有一個問題是 Colorduino 顯示會出現奇怪的狀況,所謂奇怪就是顯示在不該顯示的地方,比如說 ab (16 bytes) 應該要顯示在 column 1 和 2 (一個 column 8 個 bytes),但過了一段時間 ab 卻顯示在 column 7 和 8,使得 Colorduino 顯示的東西不是我要的結果,而這個原因待查。

以上三個問題在我前去問 Bird 後,整個被大ㄉㄧㄤ 了一番,不過 Bird 交給我的這些東西經過我思考消化後發現真的是在這條路上一輩子都受用無窮的道理,有礙於篇幅,讓我們下篇文章再繼續分享!