Arduino筆記(48):NodeMCU使用MAX7219控制 8x32 LED 顯示日期時間

上一篇 Arduino筆記(47): 對網絡時間協定(NTP) 校時後顯示時間,將顯示在 16x2 的 LCD上的時間透過 NTP 的協定進行校時,可以免去按鍵調整時間的不便。由於 16x2 的 LCD螢幕較小,也不美觀,想說改成 4 個 8x8 的 LED 顯示時間,字體會比較大,又是紅色的字,會比較漂亮。也挑了 4 個還不錯的字型在程式中,供有意要製作的朋友使用。
另外,除了顯示時間外,每遇到 0秒跟 30秒時,自動顯示 5 秒鐘的日期,但由於 32x8 的 LED 長度不夠,無法顯示日/月/年,故本實作僅顯示月日。

[安裝LEDMatrixDriver Library]

LEDMatrixDriver 函式庫提供與NTP伺服主機的連線,並回傳日期時間,可以用 getFormattedTime()函數取得字串,字串格式為 2018-05-28T16:00:13Z。或是使用 getEpochTime() 取得自 1970/01/01起算的妙數,這個函式主要是用來更新 RTC 時間模組的 。

先到 Github 下載 bartoszbielawski/LEDMatrixDriver,按右邊綠色選項「Clone or download」,再選「Download ZIP」。

• 將下載的壓縮檔解壓縮,放在 Arduino 主程式下的 libraries目錄內,以我的電腦來說,Arduino安裝在  C:\User\[登入的帳號]\Documents\Arduino,點進目錄有一個 libraries的子目錄,下載後解壓縮的目錄,整個放進  libraries 目錄,重新啟動 Arduino即可。

[材料]

  • NodeMCU V3 x 1
  • 4 x 8 x 8 LED 顯示模組 x 1
  • 連接線 x 5

[線路圖]

NodeMCUI2C 8x8x4 LED
VINVCC
GNDGND
D5CLK
D7DIN
D8CS


[程式]

主程式如下:
#include <LEDMatrixDriver.hpp>
#include <ESP8266WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

// 儲存日期及時間的變數
String formattedDate;
String dayStamp;
String timeStamp;

// 設定無線基地台SSID跟密碼
const char* ssid     = "MyHome";
const char* password = "12345678";

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);

// 設定 LCD的欄和列數
// LiquidCrystal_I2C lcd(0x27,16,2);

// Dot Matrix
const uint8_t LEDMATRIX_CS_PIN = D8;
const int LEDMATRIX_WIDTH = 31;
const int LEDMATRIX_HEIGHT = 7;
const int LEDMATRIX_SEGMENTS = 4;

unsigned long lastShowColon = 0;
bool showColon = false;
String lastTimeStr;

LEDMatrixDriver lmd(LEDMATRIX_SEGMENTS, LEDMATRIX_CS_PIN);
extern void drawTime(String strTime, int offset = 0) ;

void setup() {
  
  Serial.begin(115200);
  lmd.setEnabled(true);
  lmd.setIntensity(2);
  
  // 連接無線基地台
  WiFi.begin(ssid, password);
  Serial.print("\n\r \n\rWorking to connect");

  // 等待連線,並從 Console顯示 IP
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  timeClient.begin();

  // 設定時區 *60分 * 60秒,例如:
  // GMT +1 = 3600
  // GMT +8 = 28800  台灣時區
  // GMT 0 = 0
  timeClient.setTimeOffset(28800);
}

void loop() {

  while(!timeClient.update()) {
    timeClient.forceUpdate();
  }
  // formattedDate函式取得的日期格式為: 2018-05-28T16:00:13Z
  formattedDate = timeClient.getFormattedDate();
  Serial.println(formattedDate);

  // 取得日期
  int splitT = formattedDate.indexOf("T");
  dayStamp = formattedDate.substring(0, splitT);
  Serial.print("DATE: ");
  Serial.println(dayStamp);

  // 取得時間
  timeStamp = formattedDate.substring(splitT+1, formattedDate.length()-1);
  Serial.print("TIME:");
  Serial.println(timeStamp);

  if ((millis() - lastShowColon) >= 500) {
    lastShowColon = millis();
    showColon = !showColon;
  }

  // 讓時間的冒號閃跳
  if (timeStamp != lastTimeStr) {
    timeStamp.replace(":",showColon ? ":" : " ");

    // 自第4欄的燈開始顯示
    drawTime(timeStamp, 3);
    lastTimeStr = timeStamp;
  }
    if ((timeStamp.substring(6) == "00") or (timeStamp.substring(6) == "30")) {
        // 字體太大,32x8 無法顯示年月日,僅顯示月日,用空白隔開
        // String showdate = dayStamp.substring(2, 4)+ dayStamp.substring(5, 7)+ dayStamp.substring(8, 10);
        String showdate = dayStamp.substring(5, 7)+" "+ dayStamp.substring(8, 10);

        //清除畫面 
        lmd.clear();
        // 自第4個燈開始顯示
        drawTime(showdate, 3); 
        delay(5000);            
     }
       
   delay(10);
}

主程式會參考兩個副程式,一是將字型呈現的Draw.ino,以下是 Draw.ino 的程式內容:
extern byte font_number[10][5] ;
extern byte font_colon[3] ;

void drawTime(String strTime, int offset) {
  int pos_x = offset;
  for (int i=0;i<strTime.length();i++) {
    pos_x += drawChar(strTime.charAt(i), pos_x);
  }
  lmd.display();
}

int drawChar(char c, int offset_x) {
  int width;
  byte dataWrite[5];
  if (c ≥ '0' && c ≤ '9') {
    width = 6;
    memcpy(dataWrite, font_number[c - '0'], 5);
  } else if (c == ':') {
    width = 4;
    memcpy(dataWrite, font_colon, 3);
  } else {
    width = 4;

    for (int i=0;i<4;i++) dataWrite[i] = 0;
  }
  dataWrite[width-1] = 0;

  for (int x=0;x<width;x++) {
    for (int y=0;y<8;y++) {
      lmd.setPixel(x + offset_x, 7 - y, dataWrite[x]&(0x01<<y));
    }
  }
  
  return width;
}
另一個副程式是存放表示數字位元的陣列 font.ino,以下有好幾個檔案,字型不同,可依據喜好挑選喜歡的字型:第一組字型程式跟顯示結果如下:
byte font_number[10][5] = {
  { 0x3E, 0x41, 0x41, 0x41, 0x3E }, // 0
  { 0x11, 0x21, 0x7F, 0x01, 0x01 }, // 1
  { 0x21, 0x43, 0x45, 0x49, 0x31 }, // 2
  { 0x49, 0x49, 0x49, 0x49, 0x36 }, // 3
  { 0x0C, 0x14, 0x24, 0x7F, 0x04 }, // 4
  { 0x79, 0x49, 0x49, 0x49, 0x4F }, // 5
  { 0x3E, 0x49, 0x49, 0x49, 0x0E }, // 6
  { 0x40, 0x47, 0x48, 0x50, 0x60 }, // 7
  { 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8
  { 0x39, 0x49, 0x49, 0x49, 0x3E }, // 9
};
byte font_colon[3] { 0x0, 0x12, 0x0 };

第二組字型程式跟顯示結果如下:
byte font_number[10][5] = {
  {0x3e, 0x45, 0x49, 0x51, 0x3e},  // 0 (pretty)
  {0x00, 0x10, 0x20, 0x7f, 0x00},
  {0x47, 0x49, 0x49, 0x49, 0x31},
  {0x42, 0x49, 0x59, 0x69, 0x46},
  {0x08, 0x18, 0x28, 0x7f, 0x08},
  {0x71, 0x49, 0x49, 0x49, 0x46},
  {0x3e, 0x49, 0x49, 0x49, 0x06},
  {0x40, 0x47, 0x48, 0x50, 0x60},
  {0x36, 0x49, 0x49, 0x49, 0x36},
  {0x30, 0x49, 0x49, 0x49, 0x3e}, // 9
};

byte font_colon[3] { 0x0, 0x12, 0x0 };

第三組字型程式跟顯示結果如下:
byte font_number[10][5] = {
  {0x7f, 0x41, 0x41, 0x41, 0x7f},  // 0 (block)
  {0x00, 0x00, 0x00, 0x00, 0x7f},
  {0x4f, 0x49, 0x49, 0x49, 0x79},
  {0x49, 0x49, 0x49, 0x49, 0x7f},
  {0x78, 0x08, 0x08, 0x08, 0x7f},
  {0x79, 0x49, 0x49, 0x49, 0x4f},
  {0x7f, 0x49, 0x49, 0x49, 0x4f},
  {0x40, 0x40, 0x40, 0x40, 0x7f},
  {0x7f, 0x49, 0x49, 0x49, 0x7f},  // 8
  {0x79, 0x49, 0x49, 0x49, 0x7f},
};

byte font_colon[3] { 0x0, 0x12, 0x0 };

第四組字型程式跟顯示結果如下:

byte font_number[10][5] = {
  {0x7c, 0x8a, 0x92, 0xa2, 0x7c}, // 0
  {0x00, 0x42, 0xfe, 0x02, 0x00}, // 1
  {0x42, 0x86, 0x8a, 0x92, 0x62}, // 2
  {0x84, 0x82, 0xa2, 0xd2, 0x8c}, // 3
  {0x18, 0x28, 0x48, 0xfe, 0x08}, // 4
  {0xe4, 0xa2, 0xa2, 0xa2, 0x9c}, // 5
  {0x3c, 0x52, 0x92, 0x92, 0x0c}, // 6
  {0x80, 0x8e, 0x90, 0xa0, 0xc0}, // 7
  {0x6c, 0x92, 0x92, 0x92, 0x6c}, // 8
  {0x60, 0x92, 0x92, 0x94, 0x78}, // 9
};

byte font_colon[3] { 0x0, 0x12, 0x0 };

[實作結果]

除了上述顯示的時間照片外,以下影片在每0秒跟30秒時,會出現5秒鐘的日期。

[參考資料]


 字型參考:

Post a Comment

較新的 較舊