本文做者:默數組
前段時間我在網上看到了一款頗有意思的點陣時鐘,它能夠播報天氣,查看 YouTube 的訂閱數,還有好看的時間動畫。你能夠把它當作普通鬧鐘,也能夠鏈接藍牙把它當作音箱來使用。它的許多功能都頗有意思,其中我最喜歡的是它的時間顯示動畫效果,然而它一千多的價格讓我望而卻步,放棄了入手的打算。不過既然身爲創客,我爲何不製做一個屬於本身獨一無二的創意網絡時鐘呢?服務器
說幹就幹,因而我就作了一個創意點陣時鐘,先來看一下演示視頻吧(點擊連接跳轉B站查看演示視頻):網絡
https://www.bilibili.com/video/BV12v411z7sJ/ide
電路鏈接關係以下圖所示:模塊化
將 USB 數據線按下圖所示方向由外殼背部插入開發板,使用熱熔膠將開發板固定到木板上,保持穩定直到熱熔膠凝固,主要熱熔膠不要碰到數據線。函數
將外殼前部與點陣屏按下圖所示方式放置入面板凹槽,使用熱熔膠固定點陣,保持穩定直到熱熔膠凝固。oop
而後使用杜邦線按原理圖正確鏈接電路,並拼接外殼底部與左右兩側,最後將外殼頂部進行封頂。字體
剪切合適大小的櫟木滑面仿木紋貼紙,粘貼至外殼表面。注意留出點陣位置,能夠適當使用刻刀雕刻出 USB 下載接口,以便進行供電及程序下載或更新。動畫
下面開始詳細講解程序設計過程。ui
咱們使用 Aduino 軟件來編寫本項目的程序,開發板選擇 ESP8266 類型。至於如何在 Arduino 中配置 ESP8266 的開發環境,不在本文的介紹範圍,請自行查閱相關資料。
爲了達到咱們的預期目標,咱們先繪製功能的思惟導圖,再根據思惟導圖逐步實現創意點陣時鐘的程序設計。
下面咱們將具體討論創意點陣時鐘各個子功能是如何實現的。
做爲一個時鐘,最重要的功能固然是顯示時間啦。那麼該如何從網絡獲取時間呢?
下面的例子演示瞭如何獲取網絡時間並將時間保存在變量中,其中 ESP8266WiFi.h
庫的功能是鏈接網絡,NtpClientLib.h
庫的功能是獲取 NTP 服務器的網絡時間,SimpleTimer.h
庫是用來設置定時器每秒刷新一次時間。該例子並無串口打印當前時間,你能夠添加串口打印相關代碼用來調試程序。
#include <ESP8266WiFi.h> #include <NtpClientLib.h> #include <TimeLib.h> #include <SimpleTimer.h> SimpleTimer timer; const PROGMEM char *ntpServer = "ntp1.aliyun.com"; int8_t timeZone = 8; volatile int hour_variable; volatile int minute_variable; volatile int second_variable; void Simple_timer() { hour_variable = NTP.getTimeHour24(); minute_variable = NTP.getTimeMinute(); second_variable = NTP.getTimeSecond(); } void setup() { Serial.begin(9600); WiFi.begin("ssid", "password"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("Local IP:"); Serial.print(WiFi.localIP()); NTP.setInterval(600); NTP.setNTPTimeout(1500); NTP.begin(ntpServer, timeZone, false); timer.setInterval(1000L, Simple_timer); } void loop() { timer.run(); }
MD_Parola
是 MAX7219 點陣屏的模塊化滾動文本顯示庫,其主要特色以下:
下面的例子簡單演示瞭如何利用 MD_Parola 滾動顯示字符串,其中 MD_Parola 對象有 4 個參數:分別爲 SPI 管腳 DIN、CLK、CS 及點陣數目。下面咱們所作的創意點陣時鐘的顯示功能均由此庫開發。
#include <MD_Parola.h> #include <MD_MAX72xx.h> #include <SPI.h> MD_Parola P = MD_Parola(13,14,12,4); //DIN(D7) CLK(D5) CS(D6) MD_MAX72XX mx = MD_MAX72XX(13,14,12,4); //DIN(D7) CLK(D5) CS(D6) void setup() { mx.begin(); P.begin(); } void loop() { if (P.displayAnimate()) { P.displayScroll("Mixly", PA_LEFT, PA_SCROLL_LEFT, 50); } }
要在點陣屏中顯示圖片,首先須要設計點陣圖案(位圖),而後對圖案進行取模操做。點陣取模使用 PCtoLCD2002 取模軟件,取模設置以下:
取模方式爲陰碼、逆向、逐列式,輸出方式爲 16 進制,注意格式設置爲 C51 格式,其他參數按照默認取模方式設置便可。
這裏咱們取模的數據格式爲 uint8_t 數組,咱們有自定義字體 0~9 和時間分隔符「:」,再加上一些自定義的圖像,這就致使咱們有大量的位圖。爲了方便的管理這些位圖,咱們使用指針數組 bitmap_data[]
去管理咱們的位圖。爲了顯示方便,咱們定義了函數 display_bitmap()
,該函數須要 3 個參數,分別爲顯示橫座標 abscissa、位圖寬度 width 及指針數組 bitmap_data[]
中的位置 bitmap_number。須要注意的是咱們這裏並無指定位圖的高度,由於咱們用到的 MAX7219 點陣屏分辨率爲 8×32,因此這裏咱們默認位圖高度爲 8。
#include <MD_Parola.h> #include <MD_MAX72xx.h> #include <SPI.h> MD_Parola P = MD_Parola(13,14,12,4); //DIN(D7) CLK(D5) CS(D6) MD_MAX72XX mx = MD_MAX72XX(13,14,12,4); uint8_t bitmap_data1[] = {0x3e, 0x2a, 0x3e}; uint8_t bitmap_data2[] = {0x2e, 0x2a, 0x3e}; uint8_t * bitmap_data[] = { bitmap_data1 bitmap_data2 …… }; void display_bitmap(int abscissa, int width, int bitmap_number) { mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF); mx.setBuffer(abscissa, width, bitmap_data[bitmap_number]); mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON); }
MD_Parola 庫中,因爲字體過大並且不美觀,致使顯示的時間過長,因此咱們須要自定義字體。自定義字體以下圖所示,值得注意的是 0~9 的位圖寬度是 3,分割符「:」的寬度是 1。
自定義字體取模數據以下所示:
uint8_t Small_font_0[] = {0x3e, 0x22, 0x3e}; uint8_t Small_font_1[] = {0x24, 0x3e, 0x20}; uint8_t Small_font_2[] = {0x3a, 0x2a, 0x2e}; uint8_t Small_font_3[] = {0x2a, 0x2a, 0x3e}; uint8_t Small_font_4[] = {0x0e, 0x08, 0x3e}; uint8_t Small_font_5[] = {0x2e, 0x2a, 0x3a}; uint8_t Small_font_6[] = {0x3e, 0x2a, 0x3a}; uint8_t Small_font_7[] = {0x02, 0x02, 0x3e}; uint8_t Small_font_8[] = {0x3e, 0x2a, 0x3e}; uint8_t Small_font_9[] = {0x2e, 0x2a, 0x3e}; uint8_t Small_font_10[] = {0x14};
下面咱們分析如何顯示時間,這裏咱們只顯示小時和分鐘。
這裏咱們有一個小技巧,咱們能夠把 0~9 的位圖放到指針數組 bitmap_data[]
的 0~9 的位置上,時間分隔符「:」放置在數組序號 10 的位置上。因爲前面咱們定義了一個顯示位圖的函數 display_bitmap()
,這樣咱們不須要經過任何映射就能夠顯示數字了,例如 display_bitmap(22, 3, 0)
就顯示 0;display_bitmap(22, 3, 1)
就顯示 1,這樣是否是很方便呢?
爲了分別獲取小時和分鐘的十位及個位,咱們須要對其進行除法和取餘操做,例如對小時 9 除 10 獲得十位 0(爲何不是0.9?這是由於咱們時間變量定義爲整數,一個整數除以另外一個整數結果只能爲整數。仍是不懂?那你就該補一下C語言基礎知識了。),9 除 10 取餘獲得個位 9。由分析咱們在合適的位置顯示時間獲得了下面的時間顯示函數。
最後,爲了顯示更加美觀,若是小時或分鐘只有一位數,咱們就須要進行補零操做,將 1:1 補零變成 01:01。顯示時間的代碼以下:
display_bitmap(22, 3, hour_variable / 10); display_bitmap(18, 3, hour_variable % 10); display_bitmap(14, 1, 10); display_bitmap(12, 3, minute_variable / 10); display_bitmap(8, 3, minute_variable % 10);
時間在流逝,可是咱們上面並無顯示秒鐘,那咱們怎樣感知時間的進度呢?爲了解決這個問題,咱們定義了下面的一系列位圖,注意這裏定義位圖的寬度是 5 不是 8,咱們每隔一秒切換一次下面的位圖,看起來是否是像秒針在走動呢?
使用取模軟件分別對上述點陣圖案取模:
uint8_t clock_0[] = {0x1c, 0x22, 0x2e, 0x22, 0x1c}; uint8_t clock_1[] = {0x1c, 0x22, 0x2a, 0x26, 0x1c}; uint8_t clock_2[] = {0x1c, 0x22, 0x2a, 0x2a, 0x1c}; uint8_t clock_3[] = {0x1c, 0x22, 0x2a, 0x32, 0x1c}; uint8_t clock_4[] = {0x1c, 0x22, 0x3a, 0x22, 0x1c}; uint8_t clock_5[] = {0x1c, 0x32, 0x2a, 0x22, 0x1c}; uint8_t clock_6[] = {0x1c, 0x2a, 0x2a, 0x22, 0x1c}; uint8_t clock_7[] = {0x1c, 0x26, 0x2a, 0x22, 0x1c};
前面咱們指針數組 bitmap_data[]
的 0~10 位置都用來放置數字了,咱們這裏有 8 幅位圖,因此放入指針數組 bitmap_data[]
的 11~18 位置,咱們定義一個靜態局部變量Clock_variable
,設置其初始值爲 11,每隔一秒 Clock_variable
變量的值增長 1,並顯示對應序號的位圖,當 Clock_variable
的值爲 19 時,將它從新賦值爲 11,這樣咱們就實現了秒錶動畫的設計。程序以下:
static int Clock_variable = 11; display_bitmap(4, 5, Clock_variable); Clock_variable = Clock_variable + 1; if (Clock_variable == 19) { Clock_variable = 11; }
上面咱們設計了秒錶動畫,可是還有一個問題,因爲點陣屏空間限制,咱們沒辦法用數字顯示精確的秒數,那怎麼辦呢?咱們觀察到,在點陣屏的底部還空了 2 個像素點的高度,因此咱們能夠在最後一行經過點數顯示精確到秒數。
如上圖所示,最後一行前面有 5 個點,後面有 9 個點,所以秒數爲 59 秒。顯示秒數的代碼以下:
if (second_variable / 10) { mx.drawLine(7, 22, 7, (23 - second_variable / 10), true); } if (second_variable % 10) { mx.drawLine(7, 14, 7, (15 - second_variable % 10), true); }
其中 mx.drawLine()
爲繪製線段的函數,它有 4個參數,分別爲:線段起點橫座標、起點縱座標、終點橫座標、終點縱座標,以及顯示狀態(true 點亮線段;false 熄滅線段)。根據咱們使用的 4 和 1 點陣座標定義,其中橫座標最大爲 7,縱座標最大爲 31。
當秒數的個位爲 0 的時候將線段清除,重複顯示線段便可顯示當前秒數了。這裏我不對顯示線段的位置、長度與秒數的關係進行分析,留給你們當作思考題活動一下大腦了。
爲了感知一天時間的變化,咱們但願不一樣時間段用不一樣的圖標進行提示。咱們定義了太陽和月亮兩個圖標,它們的寬度都是 8,樣式以下圖所示。
使用取模軟件取模數據以下:
uint8_t sun[] = {0x24, 0x00, 0xbd, 0x3c, 0x3c, 0xbd, 0x00, 0x24}; uint8_t moon[] = {0x38, 0x7c, 0xe2, 0xc0, 0xc4, 0x4e, 0x24, 0x00};
繼續將太陽和月亮的取模數據添加到指針數組 bitmap_data[]
的位置 19 和 20。這裏咱們定義在 6 點到 18 點之間,在橫座標爲 31 處顯示太陽,其餘時間顯示月亮,程序以下:
if ((hour_variable >= 6) && (hour_variable <= 18)) { display_bitmap(31, 8, 19); } else { display_bitmap(31, 8, 20); }
若是咱們在程序裏固定 WiFi 信息,那麼當網絡環境變化時,時鐘將不可用,此時你須要從新修改網絡信息並上傳程序,這無疑是很麻煩的。爲此咱們須要一種動態修改網絡信息的辦法,這裏咱們使用了 WiFiManager
庫,該庫支持經過網頁對 WiFi 鏈接進行配置。下面是一個網絡配置的簡單示例,該例子上傳成功後,將啓用一個名爲 ESP8266 的 WiFi 熱點,使用手機鏈接此熱點便可按提示對網絡進行配置。這裏你也可使用其餘熱點名稱,例如你的做品名稱而不是 ESP8266。須要注意的是,ESP8266 僅支持 2.4G 頻段的 WiFi 網絡,不支持 5G 頻段的 WiFi 網絡。
#include <ESP8266WiFi.h> #include <DNSServer.h> #include <ESP8266WebServer.h> #include <WiFiManager.h> WiFiServer server(80); void setup(){ WiFiManager wifiManager; wifiManager.autoConnect("ESP8266"); server.begin(); } void loop(){ }
當網絡環境發生變化時,咱們可能須要對網絡從新進行配置,爲此咱們定義了下面的位圖用於斷網提示。該位圖的寬度爲 19,看上去像是 WiFi 被外星人劫持了,是否是很生動形象!
使用取模軟件取模數據以下:
uint8_t wifi[] = {0x04, 0x06, 0x13, 0xDB, 0xDB, 0x13, 0x06, 0x04, 0x00, 0x70, 0x18, 0x7d, 0xb6, 0x3c, 0x3c, 0xb6, 0x7d, 0x18, 0x70};
這裏咱們使用 !(WiFi.status() != WL_CONNECTED)
語句來判斷網絡鏈接是否斷開。當 WiFi 鏈接成功時,!(WiFi.status() != WL_CONNECTED)
返回真,這是咱們能夠同步時間;當 WiFi 斷開時,!(WiFi.status() != WL_CONNECTED)
返回假,咱們在點陣屏上顯示 WiFi 斷開鏈接提示,而後使用配網函數對網絡進行配置,配網成功後再次顯示正常的時間便可。代碼以下:
if (!(WiFi.status() != WL_CONNECTED)) { hour_variable = NTP.getTimeHour24(); minute_variable = NTP.getTimeMinute(); second_variable = NTP.getTimeSecond(); } else { mx.clear(); display_bitmap(25, 19, 21); WiFiManager wifiManager; wifiManager.autoConnect("ESP8266"); server.begin(); mx.clear(); }
爲了時鐘富有動態感,咱們這裏爲時鐘添加一個小狗的動畫效果,該動畫由兩個寬度爲 8 的動畫幀構成,首先咱們先使用取模軟件繪製出這兩幀圖像,再點擊水平鏡像按鈕獲得鏡像後的圖像,最後生成字模便可。
使用取模軟件取模數據以下:
uint8_t PROGMEM dog[] = {0x8C, 0x4C, 0xFE, 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x0C, 0x0C, 0xFE, 0x30, 0x30, 0x30, 0xF8, 0x00,};
下面的例子演示將點陣劃分爲兩個區域,區域 0 和區域 1。P.setZone()
函數將點陣劃分爲不一樣的顯示區域,它有 3 個參數:分別爲區域編號、起始點陣及終止點陣。P.begin()
指定區域數量,參數爲空默認一個區域,這裏咱們有兩個顯示區域,故參數爲 2,其中點陣編號與區域的對應關係以下圖所示:
P.setSpriteData()
函數爲精靈動畫的初始化函數,該函數接受 7 個參數:分別爲初始化區域、動畫開始精靈數據、動畫開始精靈寬度、動畫開始精靈幀數、動畫結束精靈數據、動畫結束精靈寬度、動畫結束精靈幀數。
P.displayAnimate()
函數有兩個做用,分別爲反饋顯示狀態和動畫執行函數。看成爲反饋狀態時,動畫顯示完成返回 1,未完成返回 0。看成爲動畫執行函數時,經過不斷調用該函數實現動畫的流暢運行,所以程序須要不斷的調用 P.displayAnimate()
函數。
P.getZoneStatus()
函數做用相似 P.displayAnimate()
函數,不一樣的是它僅返回區域的顯示狀態。
P.displayZoneText()
函數爲字符串的動畫顯示函數,該函數接受 7 個參數,分別爲:顯示區域、顯示字符串、對齊方式、動畫速度、文本顯示時間、動畫進入效果、動畫退出效果。下面的代碼演示瞭如何在區域顯示精靈動畫。這裏咱們顯示字符串爲空、顯示時間爲0,顯示字符串爲空保證了咱們僅有小狗動畫沒有文字,顯示時間爲 0 保證了小狗動畫的連貫性。
void setup() { P.begin(2); mx.begin(); P.setZone(0, 0, 2); P.setZone(1, 3, 3); P.setSpriteData(1, dog, 8, 2, dog, 8, 2); } void loop() { P.displayAnimate(); if (P.getZoneStatus(1)) { P.displayZoneText(1, "", PA_CENTER, 100, 0, PA_SPRITE, PA_SPRITE); } }
當咱們睡覺之後咱們是不會看時間的,此時下降點陣顯示的亮度有助於節能環保,所以咱們須要根據時間段自動調節點陣顯示的亮度。下面的代碼在晚上 0~6 點亮度設置爲 1,其餘時間亮度設置爲 10。P.setIntensity()
函數爲區域亮度設置函數,其有兩個參數,分別是:顯示區域和亮度值,其中亮度值範圍爲 0~15。
if ((hour_variable >= 0) && (hour_variable < 6)) { P.setIntensity(0, 1); P.setIntensity(1, 1); } else { P.setIntensity(0, 10); P.setIntensity(1, 10); }
最後,按照上述功能之間的邏輯關係,將代碼組合在一塊兒便可。因爲篇幅限制,這裏就不放完整的代碼了。
首先鏈接電源,時鐘進行初始化,同時出現以下界面提示配網,此時開發板會自動開啓名爲 ESP8266 的無密碼 WiFi 熱點。
打開手機,鏈接這個網絡,配網步驟以下圖所示:
配網說明(以安卓手機爲例):