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

本文作者:默 & 鐵熊

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

說幹就幹,於是我就做了一個創意點陣時鐘,先來看一下演示視頻吧:

[video(video-aynePODb-1591445136040)(type-bilibili)(url-https://player.bilibili.com/player.html?aid=243386172)(image-https://ss.csdn.net/p?http://i2.hdslb.com/bfs/archive/878bc2ad7fda3aaef08cae694955dbe722b718d6.jpg)(title-)]

預期目標及功能

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

材料清單

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

電路原理圖

電路連接關係如下圖所示:

結構拼裝

將 USB 數據線按下圖所示方向由外殼背部插入開發板,使用熱熔膠將開發板固定到木板上,保持穩定直到熱熔膠凝固,主要熱熔膠不要碰到數據線。

將外殼前部與點陣屏按下圖所示方式放置入面板凹槽,使用熱熔膠固定點陣,保持穩定直到熱熔膠凝固。

然後使用杜邦線按原理圖正確連接電路,並拼接外殼底部與左右兩側,最後將外殼頂部進行封頂。

剪切合適大小的櫟木滑面仿木紋貼紙,粘貼至外殼表面。注意留出點陣位置,可以適當使用刻刀雕刻出 USB 下載接口,以便進行供電及程序下載或更新。

程序設計

下面開始詳細講解程序設計過程。

開發環境

我們使用 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. 點擊保存等待配網成功。

效果展示

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章