手把手教你製做ESP8266物聯網創意點陣時鐘,女友看了都想要!

本文做者:默數組

前段時間我在網上看到了一款頗有意思的點陣時鐘,它能夠播報天氣,查看 YouTube 的訂閱數,還有好看的時間動畫。你能夠把它當作普通鬧鐘,也能夠鏈接藍牙把它當作音箱來使用。它的許多功能都頗有意思,其中我最喜歡的是它的時間顯示動畫效果,然而它一千多的價格讓我望而卻步,放棄了入手的打算。不過既然身爲創客,我爲何不製做一個屬於本身獨一無二的創意網絡時鐘呢?服務器

說幹就幹,因而我就作了一個創意點陣時鐘,先來看一下演示視頻吧(點擊連接跳轉B站查看演示視頻):網絡

https://www.bilibili.com/video/BV12v411z7sJ/ide

預期目標及功能

  • 網絡自動校準時間
  • 無網絡鏈接時及時反饋
  • 一鍵配置時鐘網絡
  • 自定義精美時間顯示字體
  • 時間顯示動畫
  • 亮度自動調節
  • 時段提示

材料清單

  • ESP8266 Wemos mini 開發板 1 塊;
  • 杜邦線若干;
  • 4 合 1 點陣模塊;
  • 激光切割外殼;
  • 櫟木滑面仿木紋貼紙。

電路原理圖

電路鏈接關係以下圖所示:模塊化

結構拼裝

將 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

MD_Parola 是 MAX7219 點陣屏的模塊化滾動文本顯示庫,其主要特色以下:

  • 支持點陣屏顯示文本時左對齊、右對齊或居中對齊;
  • 具備文字滾動,進入和退出效果;
  • 可以控制顯示參數和動畫速度;
  • 支持硬件 SPI 接口;
  • 能夠在點陣屏虛擬多個顯示區域;
  • 用戶定義的字體和/或單個字符替換;
  • 支持雙高顯示;
  • 支持在混合顯示文本和圖形。

下面的例子簡單演示瞭如何利用 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 格式,其他參數按照默認取模方式設置便可。

位圖顯示函數:display_bitmap()

這裏咱們取模的數據格式爲 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);
}

一鍵配網:WiFiManager

若是咱們在程序裏固定 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(){

}

WiFi 鏈接反饋

當網絡環境發生變化時,咱們可能須要對網絡從新進行配置,爲此咱們定義了下面的位圖用於斷網提示。該位圖的寬度爲 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 熱點。

打開手機,鏈接這個網絡,配網步驟以下圖所示:

配網說明(以安卓手機爲例):

  1. 打開手機設置選擇 WiFi 設置打開 WiFi;
  2. 鏈接時鐘熱點 ESP8266(熱點名由程序設置,可更改成其餘名稱);
  3. 選擇登陸進入網絡配置頁面;
  4. 點擊配置 WiFi 進入圖示頁面點擊掃描,掃描附近熱點;
  5. 選擇 WiFi 輸入 WiFi 密碼;
  6. 點擊保存等待配網成功。

效果展現

相關文章
相關標籤/搜索