Lập trình đồng hồ trong arduino dùng ma trận MAX7219 với esp8266 cập nhật thời gian từ Internet

Cách thực hiện là sử dụng các mã nguồn có sẵn trên mạng, chỉnh sửa lại cho phù hợp và sử dụng.

Trong clip, Tôi cũng hướng dẫn cách quản lý mạng wifi trên module ESP khi cần thiết lập lại mạng wifi, các bạn không phải nạp lại code từ đầu.

LINH KIỆN CẦN CÓ:

  1. Led ma trận MAX7219
  2. Module ESP8266. Trong bài hướng dẫn, tôi sử dụng module ESP07 vì giá thành hợp lý. Nếu sử dụng ESP07 thì cần phải mua thêm đế để hàn module này lên.
  3. Module nạp chương trình CP1202 hoặc các mạch điện chuyển đổi USB thành cổng COM tương ứng.
  4. Module chuyển nguồn sang 3.3V

TẢI CÁC THƯ VIỆN

Các đường dẫn thư viện, bạn có thể lấy trong phần mô tả video hoặc hoặc trong bài viết này trên blog DanDienTu.com

Tải thư viện để cập nhật thời gian từ Internet theo đường link sau. 

https://github.com/PaulStoffregen/Time

Tải thư viện và demo cho MAX2719 gồm có 2 file thư viện và 1 file demo trên blog theo đường dẫn dưới đây:

https://hackaday.io/project/160337-nodemcu-wifi-sync-precision-dot-matrix-clock

Sau khi tải về 3 file từ blog hackaday.io như link ở mục 2. Khi bạn mở file ESP_LEDMatrix_clock.ino  bằng Arduino IDE, chương trình sẽ hỏi bạn tạo thư mục cho file này, bạn đồng ý với đề xuất. Sau đó chép 2 file thư viện của nó cho chung vào thư mục với file ESP_LEDMatrix_clock.ino.

CẤU HÌNH DÙNG ARDUINO IDE LẬP TRÌNH CHO ESP07

Nếu bạn chưa biết cách dùng Arduino để lập trình cho ESP thì có thể tham khảo bài viết sau đây. Nếu bạn đã biết có thể bỏ qua bước này.

Cài đặt thư viện của ESP8266 ESP-12E NodeMCU để lập trình trong Arduino

http://dandientu.com/lap-trinh-nhung/lap-trinh-esp8266-esp-12e-nodemcu-su-dung-arduino.html

Cài đặt thư viện của ESP32 để lập trình trong Arduino

http://dandientu.com/lap-trinh-nhung/cai-dat-thu-vien-cua-esp32-de-lap-trinh-trong-arduino.html

BẮT ĐẦU

Cài thư viện thời gian vào trong Arduino

Mở file ví dụ mẫu TimeNTP_ESP8266WiFi trong thư viện C:\Users\<tên máy tính của bạn>\Documents\Arduino\libraries\Time\examples\TimeNTP_ESP8266WiFi

Nhập tên mạng wifi nhà bạn vào 2 dòng code sau:

const char ssid[] = "*************";  //  your network SSID (name)
const char pass[] = "********";       // your network password

Thay thế các dấu hoa thị (*) bằng tên tài khoản mạng wifi nhà bạn.

Đổi server lấy dữ liệu thời gian từ Mỹ qua Châu Á

Sửa dòng:

static const char ntpServerName[] = "us.pool.ntp.org";

Thành :

static const char ntpServerName[] = "asia.pool.ntp.org";

Đổi múi giờ:

Sửa:

const int timeZone = 1;     // Central European Time

Thành:

const int timeZone = 7;     // VN

Sau đó nạp chương trình vào module ESP để kiểm tra. Mở Serial Monitor để xem kết quả trả về có giống với đồng hồ việt nam hay không.

Sau khi kiểm tra mọi thứ đã OK.  Trong file chương trình, bổ sung các biến như sau:

int sc, mn, h, d, m, y, wd;

Mở file ESP_LEDMatrix_clock.ino để copy và dán một số dòng code từ file này qua file bạn đang chỉnh sửa.

Copy các dòng từ ESP_LEDMatrix_clock.ino

#define NUM_MAX 4 //sử dụng module có 4 ic điều khiển

// for NodeMCU    Vcc Red   Gnd Brown

#define CS_PIN  0  // D3 Yellow

#define CLK_PIN 14  // D5 Green

#define DIN_PIN 13  // D7 Orange

#include "max7219.h"

#include "fonts.h" //font hiển thị số và chữ

Sửa lại tên chân các chân SPI tương ứng với bo mạch ESP07 theo sơ đồ chân sau:

#define CS_PIN  15  

#define CLK_PIN 14  

#define DIN_PIN 13  

Trở lại file file ESP_LEDMatrix_clock.ino, copy tiếp các dòng sau và đặt vào hàm setup của chương trình đang chỉnh sửa:

initMAX7219();

sendCmdAll(CMD_SHUTDOWN,1);

sendCmdAll(CMD_INTENSITY,5);

Copy tiếp 2 dòng code sau từ file ESP_LEDMatrix_clock.ino. 2 dòng code này sẽ thông báo cho bạn biết địa chỉ IP của module ESP.

printStringWithShift((String("  IP: ") + WiFi.localIP().toString()).c_str(), 15);

delay(1500);

Copy tiếp các dòng khai báo biến từ ESP_LEDMatrix_clock.ino

#define MAX_DIGITS 20

byte dig[MAX_DIGITS]={0};

byte digold[MAX_DIGITS]={0};

byte digtrans[MAX_DIGITS]={0};

int updCnt = 0;

int dots = 0;

long dotTime = 0;

long clkTime = 0;

int dx=0;

int dy=0;

byte del=0;

Copy hết tất cả các dòng code trong hàm loop() từ ESP_LEDMatrix_clock.ino đưa qua chương trình đang chỉnh sửa

Copy tất cả các hàm trong file ESP_LEDMatrix_clock.ino từ sau hàm loop()

void showAnimClock()

void showDigit(char ch, int col, const uint8_t *data)

void setCol(int col, byte v)

int showChar(char ch, const uint8_t *data)

int dualChar = 0;

unsigned char convertPolish(unsigned char _c)

void printCharWithShift(unsigned char c, int shiftDelay)

void printStringWithShift(const char* s, int shiftDelay)

Sau khi copy qua hết các hàm liên quan, chúng ta chuyển sang chương trình đang mở trong Arduino để tiếp tục chỉnh sửa

Viết hàm getTime để cập nhật thời gian

void getTime()

{

  h = hour();

  mn = minute();

  sc = second();

  mm = month();

  yy = year();

  d = day();

  wd = weekday() ;

}

Khai báo thêm biến

byte count5min= 0;

biến này dùng để đếm cứ sau 5 phút sẽ hiển thị ngày

Bổ sung dòng code

count5min--;

Khai báo thêm dòng hiển thị thứ trong tuần

char daysOfTheWeek[7][3] = {"CN", "T2", "T3", "T4", "T5", "T6", "T7"};

Bổ sung thêm các dòng sau:

if(count5min <=0){

    count5min=5;

    printStringWithShift(String(" TODAY:" + String(daysOfTheWeek[wd]) ).c_str(), 50);

    delay(2000);

    printStringWithShift(String(" " + String(d) + "/" + String(mm) + "/" + String(yy) ).c_str(), 50);

    delay(2000);

  }

Cuối cùng là cắm dây cho module gồm module max7219, module CP2012 theo sơ đồ chân, và bắt đầu nạp chương trình.

Sơ đồ chân ESP07

Sơ đồ cấp nguồn và đấu dây để nạp chương trình

Lưu ý, toàn bộ mạch điện chỉ sử dụng điện áp 3.3V, không được cấp nguồn cao hơn. Khi sử dụng, cần bổ sung thêm module nguồn từ 5V sang 3V3

Nguồn sử dụng bạn có thể lấy trực tiếp từ CP1202 để cấp và thử nghiệm.

CODE HOÀN CHỈNH

/*
 * TimeNTP_ESP8266WiFi.ino
 * Example showing time sync to NTP time source
 *
 * This sketch uses the ESP8266WiFi library
 */
#include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino

//needed for library
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>         //https://github.com/tzapu/WiFiManager

#include <TimeLib.h>
#include <WiFiUdp.h>

// NTP Servers:
static const char ntpServerName[] = "asia.pool.ntp.org";
//static const char ntpServerName[] = "time.nist.gov";
//static const char ntpServerName[] = "time-a.timefreq.bldrdoc.gov";
//static const char ntpServerName[] = "time-b.timefreq.bldrdoc.gov";
//static const char ntpServerName[] = "time-c.timefreq.bldrdoc.gov";

const int timeZone = 7;     // VN
//const int timeZone = -5;  // Eastern Standard Time (USA)
//const int timeZone = -4;  // Eastern Daylight Time (USA)
//const int timeZone = -8;  // Pacific Standard Time (USA)
//const int timeZone = -7;  // Pacific Daylight Time (USA)


WiFiUDP Udp;
unsigned int localPort = 1337;  // local port to listen for UDP packets

int sc, mn, h, d, wd, mm, yy;

#define NUM_MAX 4

#define CS_PIN  15  // D3 Yellow
#define CLK_PIN 14  // D5 Green
#define DIN_PIN 13  // D7 Orange

#include "max7219.h"
#include "fonts.h"

#define MAX_DIGITS 10
byte dig[MAX_DIGITS] = {0};
byte digold[MAX_DIGITS] = {0};
byte digtrans[MAX_DIGITS] = {0};
int updCnt = 0;
int dots = 0;
long dotTime = 0;
byte count5min= 0;
long clkTime = 0;
int dx = 0;
int dy = 0;
byte del = 0;

char daysOfTheWeek[7][3] = {"CN", "T2", "T3", "T4", "T5", "T6", "T7"};

time_t getNtpTime();
void sendNTPpacket(IPAddress &address);

void setup()
{
  initMAX7219();
  
  Serial.begin(9600);
  //WiFiManager
    //Local intialization. Once its business is done, there is no need to keep it around
    WiFiManager wifiManager;
    //reset saved settings
    //wifiManager.resetSettings();
    
    //set custom ip for portal
    //wifiManager.setAPStaticIPConfig(IPAddress(10,0,1,1), IPAddress(10,0,1,1), IPAddress(255,255,255,0));

    //fetches ssid and pass from eeprom and tries to connect
    //if it does not connect it starts an access point with the specified name
    //here  "AutoConnectAP"
    //and goes into a blocking loop awaiting configuration
    wifiManager.autoConnect("AutoConnectAP");

  Serial.print("IP number assigned by DHCP is ");
  Serial.println(WiFi.localIP());
  Serial.println("Starting UDP");
  Udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(Udp.localPort());
  Serial.println("waiting for sync");
  setSyncProvider(getNtpTime);
  setSyncInterval(300);

  printStringWithShift((String("  IP: ") + WiFi.localIP().toString()).c_str(), 50);
  getTime(); // cập nhật ngày giờ trước khi vào hàm loop
  delay(1500);
}

//time_t prevDisplay = 0; // when the digital clock was displayed

void loop()
{
  if(count5min <=0){
    count5min=5;
    
    printStringWithShift(String(" TODAY:" + String(daysOfTheWeek[wd]) ).c_str(), 50);
    delay(2000);
    
    printStringWithShift(String(" " + String(d) + "/" + String(mm) + "/" + String(yy) ).c_str(), 50);
    delay(2000);
  }
  
  /*if (timeStatus() != timeNotSet) {
    if (now() != prevDisplay) { //update the display only if time has changed
      prevDisplay = now();
      digitalClockDisplay();
    }
  }*/

  if (updCnt <= 0) { // every 10 scrolls, ~450s=7.5m
      updCnt = 60;
      count5min--;
      getTime();
      Serial.println("Data loaded");
      clkTime = millis();
  }

  if (millis() - clkTime > 1000 && !del && dots) { // clock for 60s, then scrolls for about 30s
    updCnt--;
  }

  if (millis() - dotTime > 500) {
    dotTime = millis();
    dots = !dots;
  }
  showAnimClock();
}

void showAnimClock()
{
  byte digPos[6] = {0, 8, 17, 25, 34, 42};
  int digHt = 12;
  int num = 6;
  int i;
  if (del == 0) {
    del = digHt;
    for (i = 0; i < num; i++) digold[i] = dig[i];
    dig[0] = h / 10 ? h / 10 : 10;
    dig[1] = h % 10;
    dig[2] = mn / 10;
    dig[3] = mn % 10;
    dig[4] = sc / 10;
    dig[5] = sc % 10;
    for (i = 0; i < num; i++)  digtrans[i] = (dig[i] == digold[i]) ? 0 : digHt;
  } else
    del--;

  clr();
  for (i = 0; i < num; i++) {
    if (digtrans[i] == 0) {
      dy = 0;
      showDigit(dig[i], digPos[i], dig6x8);
    } else {
      dy = digHt - digtrans[i];
      showDigit(digold[i], digPos[i], dig6x8);
      dy = -digtrans[i];
      showDigit(dig[i], digPos[i], dig6x8);
      digtrans[i]--;
    }
  }
  dy = 0;
  setCol(15, dots ? B00100100 : 0);
  setCol(32, dots ? B00100100 : 0);
  refreshAll();
  delay(30);
}

void showDigit(char ch, int col, const uint8_t *data)
{
  if (dy < -8 | dy > 8) return;
  int len = pgm_read_byte(data);
  int w = pgm_read_byte(data + 1 + ch * len);
  col += dx;
  for (int i = 0; i < w; i++)
    if (col + i >= 0 && col + i < 8 * NUM_MAX) {
      byte v = pgm_read_byte(data + 1 + ch * len + 1 + i);
      if (!dy) scr[col + i] = v; else scr[col + i] |= dy > 0 ? v >> dy : v << -dy;
    }
}

void setCol(int col, byte v)
{
  if (dy < -8 | dy > 8) return;
  col += dx;
  if (col >= 0 && col < 8 * NUM_MAX)
      if (!dy) scr[col] = v; else scr[col] |= dy > 0 ? v >> dy : v << -dy;
}

int showChar(char ch, const uint8_t *data)
{
  int len = pgm_read_byte(data);
  int i, w = pgm_read_byte(data + 1 + ch * len);
  for (i = 0; i < w; i++)
    scr[NUM_MAX * 8 + i] = pgm_read_byte(data + 1 + ch * len + 1 + i);
  scr[NUM_MAX * 8 + i] = 0;
  return w;
}

int dualChar = 0;

unsigned char convertPolish(unsigned char _c)
{
  unsigned char c = _c;
  if (c == 196 || c == 197 || c == 195) {
    dualChar = c;
    return 0;
  }
  if (dualChar) {
    switch (_c) {
      case 133: c = 1 + '~'; break; // 'ą'
      case 135: c = 2 + '~'; break; // 'ć'
      case 153: c = 3 + '~'; break; // 'ę'
      case 130: c = 4 + '~'; break; // 'ł'
      case 132: c = dualChar == 197 ? 5 + '~' : 10 + '~'; break; // 'ń' and 'Ą'
      case 179: c = 6 + '~'; break; // 'ó'
      case 155: c = 7 + '~'; break; // 'ś'
      case 186: c = 8 + '~'; break; // 'ź'
      case 188: c = 9 + '~'; break; // 'ż'
      //case 132: c = 10+'~'; break; // 'Ą'
      case 134: c = 11 + '~'; break; // 'Ć'
      case 152: c = 12 + '~'; break; // 'Ę'
      case 129: c = 13 + '~'; break; // 'Ł'
      case 131: c = 14 + '~'; break; // 'Ń'
      case 147: c = 15 + '~'; break; // 'Ó'
      case 154: c = 16 + '~'; break; // 'Ś'
      case 185: c = 17 + '~'; break; // 'Ź'
      case 187: c = 18 + '~'; break; // 'Ż'
      default:  break;
    }
    dualChar = 0;
    return c;
  }
  switch (_c) {
    case 185: c = 1 + '~'; break;
    case 230: c = 2 + '~'; break;
    case 234: c = 3 + '~'; break;
    case 179: c = 4 + '~'; break;
    case 241: c = 5 + '~'; break;
    case 243: c = 6 + '~'; break;
    case 156: c = 7 + '~'; break;
    case 159: c = 8 + '~'; break;
    case 191: c = 9 + '~'; break;
    case 165: c = 10 + '~'; break;
    case 198: c = 11 + '~'; break;
    case 202: c = 12 + '~'; break;
    case 163: c = 13 + '~'; break;
    case 209: c = 14 + '~'; break;
    case 211: c = 15 + '~'; break;
    case 140: c = 16 + '~'; break;
    case 143: c = 17 + '~'; break;
    case 175: c = 18 + '~'; break;
    default:  break;
  }
  return c;
}

void printCharWithShift(unsigned char c, int shiftDelay) {
  c = convertPolish(c);
  if (c < ' ' || c > '~' + 25) return;
  c -= 32;
  int w = showChar(c, font);
  for (int i = 0; i < w + 1; i++) {
    delay(shiftDelay);
    scrollLeft();
    refreshAll();
  }
}

void printStringWithShift(const char* s, int shiftDelay) {
  while (*s) {
    printCharWithShift(*s, shiftDelay);
    s++;
  }
}

void getTime()
{
  h = hour();
  mn = minute();
  sc = second();
  mm = month();
  yy = year();
  d = day();
  wd = weekday() - 1; // giảm trừ cho 1, để tính chủ nhật là số 0
}
/*
void printDigits(int digits)
{
  // utility for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}*/

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  Serial.print(ntpServerName);
  Serial.print(": ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

Nguồn: Dân Điện Tử / dandientu.com

Trả lời

Email của bạn sẽ không được hiển thị công khai.