【DIY教程】作一個永遠準時的WiFi時鐘

新手請勿跳過的瑣碎簡介

  系統時間是單片機系統中常常用到的要素,通常來講,採用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

材料清單

  1. ESP8266-01S模塊 × 1
  2. ESP8266下載器(基於CP2104)× 1
  3. STM32F103C8T6核心板 × 1
  4. 0.96‘ OLED屏(IIC接口)× 1

    總的材料花費在30左右

如何獲取網絡時間

  準確的網絡時間通常經過NTP(Network Time Protocol)來獲取,具體的實現流程可能稍顯複雜,並且這些流程每每是相對固定的,沒什麼意思(畢竟能修改的東西纔好玩嘛),爲此,筆者決定選用比較簡單的方式來給你們作個示範。

  咱們鏈接WiFi是經過ESP8266實現的,一般你們給ESP8266寫程序的方式有這些:

  1. 採用官方SDK進行開發(基於C/C++)
  2. 燒錄micropython固件之後寫micropython
  3. 使用Arduino IDE

  從設計意圖就能知道,在咱們想偷懶的時候該選擇哪個——SDK面向的主要是是使用其產品進行深度開發的工程師;micropython主要是爲了給軟件開發者玩硬件提供便利;而Arduino是爲了給非專業人士提供控制硬件進行交互的傻瓜化方法,因此就選它了。

  首先去下載Arduino,官網選個最新的版本便可,連接在這:

https://www.arduino.cc/en/Mai...

  標有「ZIP file for non admin install」字樣的是解壓後直接能夠運行的版本。

  下載後打開,而後從菜單欄依次選擇「文件 -> 首選項 」,在「附加開發板管理網址」中填入http://arduino.esp8266.com/st...,確認。

  而後從菜單欄依次選擇「工具 -> 開發板 -> 開發板管理器」,稍稍等待一會,而後在搜索框中輸入esp8266,選擇對應的庫進行安裝,下面是安裝好的效果:

P2.jpg

  爲了使用NTP,咱們須要再額外安裝一些庫(站在大神的肩膀上XD)。打開「工具 -> 管理庫」,輸入NTPClient,安裝名字徹底匹配的那個庫。以後,以相同方式安裝「ArduinoJson」這個庫。另外,爲了使用WiFi功能,還須要ESP8266WiFi和WiFiUdp這兩個庫,不過這些是軟件自帶的直接使用就行。

鏈接WiFi

  要鏈接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 ( "." );
  }

經過NTP獲取GMT時間

  學過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屏。

STM32接收JSON字符串

安裝Jansson庫

  在比較正經的單片機開發中,單片機接收字符串通常經過cjson來實現,不過須要本身移植一下,在Keil下進行開發時,可使用官方提供的Jansson這個庫來對Json進行處理。

  使用庫前得先去官網把pack下下來,地址:http://www2.keil.com/mdk5/par...

  下載完雙擊安裝便可。

  以後打開一個Keil的工程,在如圖位置單擊打開運行時環境管理窗口

P1.jpg

  而後勾選下圖位置:

P3.jpg

  以後點擊確認便可。

Jansson庫API簡單說明

  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版本庫函數)

相關文章
相關標籤/搜索