系統時間是單片機系統中常常用到的要素,通常來講,採用RTC時鐘能夠獲取較準確的時間。可是,你們若是有過使用街邊買的便宜電子錶的經驗,就會知道,若是不進行對時,電子錶用着用着就不許了,一年產生的偏差在十幾秒到幾十秒之間。這是由於電子錶的精度依賴於所使用的晶振,通常低端的電子產品裏使用的晶振,其精度多在20ppm、10ppm(百萬分之一)這兩檔上。對於20ppm的晶振,理論上一年的最大偏差爲31S,同時,受到溫度變化、電容是否匹配等因素的影響,實際偏差可能大於這個值。同理,單片機RTC時鐘的精度依賴於RTC晶振,使用時間長了以後,精度大機率不會讓人滿意。html
那麼,有沒有啥解決辦法呢?python
有一種你們都很容易想到的辦法是——氪金。畢竟,氪金帶來力量是廣泛規律,這一點在電子設計領域表現得格外突出。既然偏差源於晶振,那提升晶振精度不就行了嘛。的確,若是願意花錢,那麼你可使用高精度的溫補晶振——顧名思義,這種晶振具備溫度補償功能。並且,做爲具有補償功能的高端產品,自己的精度通常也很不錯,0.1ppm的一抓一大把,若是使用這樣的晶振,理論最大年偏差爲1.55S,這還要啥自行車啊。git
不過,0.1ppm的溫補晶振,價格通常在50RMB以上。編程
因此咱們來看下一個方案吧。json
這幾年物聯網之類的概念仍是炒的挺熱的,相關產品也出貨很多,其中樂鑫的ESP8266這款芯片能夠說是個劃時代的東西(指價格),將單片機系統接入網絡的成本一會兒降到了7RMB之內,接入網絡能夠給單片機系統帶來許多強大的功能,好比獲取網絡時間、獲取本身帳戶的B站粉絲數、獲取天氣信息甚至在線播放badapple等等。網絡
因此,第二個爲單片機系統提供準確時間的方案就是,經過ESP系列或相似的具備WiFi功能的芯片,獲取網絡時間,而後發送給主控單片機進行顯示。app
若是有電子設計領域的熟手路過,看到這可能會笑出聲來。由於,其實ESP系列的芯片自己就是一塊單片機,並且其性能在常見32位單片機裏算是至關不錯的,引腳數量也很多,電子愛好者我的輕度使用徹底足夠。函數
可是,外掛芯片方案也是有着本身的優點的——WiFi模塊和主控MCU相對分離,在程序設計上能夠較爲容易地處理系統的層次結構。並且,畢竟有不少人只熟悉5一、STM32啥的。工具
瑣碎的話就說到這裏,立刻開始動手作吧!oop
總的材料花費在30左右
準確的網絡時間通常經過NTP(Network Time Protocol)來獲取,具體的實現流程可能稍顯複雜,並且這些流程每每是相對固定的,沒什麼意思(畢竟能修改的東西纔好玩嘛),爲此,筆者決定選用比較簡單的方式來給你們作個示範。
咱們鏈接WiFi是經過ESP8266實現的,一般你們給ESP8266寫程序的方式有這些:
從設計意圖就能知道,在咱們想偷懶的時候該選擇哪個——SDK面向的主要是是使用其產品進行深度開發的工程師;micropython主要是爲了給軟件開發者玩硬件提供便利;而Arduino是爲了給非專業人士提供控制硬件進行交互的傻瓜化方法,因此就選它了。
首先去下載Arduino,官網選個最新的版本便可,連接在這:
https://www.arduino.cc/en/Mai...
標有「ZIP file for non admin install」字樣的是解壓後直接能夠運行的版本。
下載後打開,而後從菜單欄依次選擇「文件 -> 首選項 」,在「附加開發板管理網址」中填入http://arduino.esp8266.com/st...,確認。
而後從菜單欄依次選擇「工具 -> 開發板 -> 開發板管理器」,稍稍等待一會,而後在搜索框中輸入esp8266,選擇對應的庫進行安裝,下面是安裝好的效果:
爲了使用NTP,咱們須要再額外安裝一些庫(站在大神的肩膀上XD)。打開「工具 -> 管理庫」,輸入NTPClient,安裝名字徹底匹配的那個庫。以後,以相同方式安裝「ArduinoJson」這個庫。另外,爲了使用WiFi功能,還須要ESP8266WiFi和WiFiUdp這兩個庫,不過這些是軟件自帶的直接使用就行。
要鏈接WiFi,最簡單的方式是在程序裏預先配置好SSID和密碼後直接鏈接,具體以下:
const char *ssid = "your SSID"; const char *password = "your password";
另外順便說一下,Arduino的程序結構基本就是setup和loop這兩個函數,setup相似於咱們日常使用的初始化函數,loop相似於單片機編程經常使用的while(1)循環。初始化操做通常放在setup函數裏面(好比鏈接WiFi),以下:
WiFi.begin(ssid, password); while ( WiFi.status() != WL_CONNECTED ) { delay ( 500 ); Serial.print ( "." ); }
學過C語言的同窗應該都知道「Unix時間」,用time函數就能夠獲取,其數值表示自1970年01月01日 0:00:00至當前所通過的秒數,時間標準爲GMT時間。咱們經過NTP獲取的就是這樣的數值(藉助NTPClient庫)。
timeClient.update(); epoch_time = timeClient.getEpochTime(); //epoch_time爲預先定義的32位無符號數
直接獲取的時間數據不太適合人類理解,通常人應該不能一眼從「1585144726」這樣的數據解讀出當前的時間吧?因此,把它轉換成通常的時間格式是頗有必要的。
使用Arduino的時候就該偷懶嘛,此次仍是用現成代碼解決問題,筆者使用了一位網友的代碼來進行時間格式的轉換,地址:https://blog.csdn.net/mill_li...
數據轉換結果將被存入以下的結構體:
typedef struct { unsigned short nYear; unsigned char nMonth; unsigned char nDay; unsigned char nHour; unsigned char nMin; unsigned char nSec; unsigned char DayIndex; /* 0 = Sunday */ } mytime_struct;
若是咱們僅僅是想作個能顯示時間的時鐘的話,只要發送一個時間數據,而後在主控單片機那邊解析出時分秒什麼的就能夠了。可是,若是考慮到要方便後期添加各類諸如天氣顯示之類的功能的話,就有必要選用一種靈活的數據交換格式了。
筆者在這裏選擇選擇json。
JSON是一種簡潔、輕量的、基於文本的數據交換格式,使用json交換數據時,能夠避免考慮一些大端小端之類的問題。關於json的格式,能夠看這裏:https://www.cnblogs.com/mcgra...
咱們以前下載的ArduinoJson庫能夠幫助咱們完成打包Json字符串和經過串口發送它的工做。部分代碼以下:
StaticJsonDocument<200> doc; //建立Json對象 //添加關鍵字和對應的值 doc["year"] = 0; doc["mon"] = 0; ···
//每次接收完網絡時間並完成格式轉換後,更新Json對象中的數據 doc["year"] = my_time.nYear; doc["mon"] = my_time.nMonth; doc["day"] = my_time.nDay; ··· serializeJson(doc, Serial);//經過串口發送根據Json對象製造的Json字符串
發送完數據之後就沒ESP8266什麼事了,這部分的完整代碼在這裏:https://gitee.com/multicolore...
接下來,咱們能夠在主控單片機上接收Json字符串、解析數據而且進行顯示了,筆者這裏選用STM3二、Keil,顯示用0.96寸、IIC接口的OLED屏。
在比較正經的單片機開發中,單片機接收字符串通常經過cjson來實現,不過須要本身移植一下,在Keil下進行開發時,可使用官方提供的Jansson這個庫來對Json進行處理。
使用庫前得先去官網把pack下下來,地址:http://www2.keil.com/mdk5/par...
下載完雙擊安裝便可。
以後打開一個Keil的工程,在如圖位置單擊打開運行時環境管理窗口
而後勾選下圖位置:
以後點擊確認便可。
Jansson這個庫提供了一下用於處理Json的API,這裏咱們用到如下幾個:
//從Json字符串建立Json對象 json_t *json_loads(const char *input, size_t flags, json_error_t *error) //解析Json對象,獲取數據 int json_unpack(json_t *root, const char *fmt, ...) //刪除Json對象 json_delete(json_t *object)
json_delete的使用最爲簡單,調用的時候把json對象的名稱傳入就行。
json_loads的第一個參數是指定的字符串首地址(也就是它的名稱),第二個參數經常使用「JSON_ENCODE_ANY」,第三個是要求預先建立的一個json錯誤對象,用於一些錯誤提示信息的存儲。
json_unpack使用的時候稍稍複雜些,須要填入一個相似於{s:i,s:i,s:i}
這樣的列表,這裏s表明字符串,i表明int型變量,同時,能夠指定解析獲得的數據的存儲位置,以下:
json_unpack(epoch_time_raw,"{s:i,s:i,s:i,s:i,s:i,s:i,s:i}", \ "year",&year,"mon",&mon,"day",&day,"day_index",&day_index, \ "hour",&hour,"min",&min,"sec",&sec);
串口接收的程序是基於原子的代碼改的,沒有作什麼工做,並且相關的講解也比較多,這裏就不贅述了。奉上上粗糙的代碼一份,招待不週,還請原諒。
地址:https://gitee.com/multicolore...
(測試代碼基於STM32F103C8T6,V3.5版本庫函數)