Пробовал делать на DS3231, работают точно! Ну и решил сделать большие и точные часы, которые было бы видно одним полуоткрытым глазом

В наличии были модули на ESP8266 типа "бутерброд" и NodeMCU v3 Lua, остановился на втором. А поскольку эти модули просто созданы для подключения к Wi-Fi, то появилась задумка синхронизировать время с NTP серверами через интернет. Потом и вовсе решил отказаться от использования RTC модуля на DS3231.
В качестве дисплея выбрал светодиодные матрицы 8х8 с драйвером MAX7219, на алиекспрессе они имеются в виде годовых/полуготовых модулей. Долго думал, из скольки модулей собирать, в сети много проектов 1х3, 1х4 модулей (8х24, 8х32 точек), маловато! Нашёл проект 2х4 (16х32 точек) (найду - дам ссылку), размер уже боле-менее!
В наличии была одна матрица из набора Arduino без драйвера, всё, на что она сгодилась - прикинуть размер экрана

Порылся на Алиекспрессе, если заказывать сразу десяток штук - выходит дешевле, чем поштучно 8 штук оО Ну и сразу обрисовался размер дисплея! 2х5 (16х40 точек)

Что получилось - смотрите на видео!
По Wi-Fi конектится к роутеру, получает с интернета время (раз в минуту, можно настроить) и погоду.
Десять одинарных модулей (Микросхему нужно паять самостоятельно
Десять одинарных модулей (микросхемы распаяны, но немного подороже)
CH340 nodemcu V3 Lua WI-FI
Наверно зимой доделаю часики, в планах нарисовать одностороннюю плату (кое-чего накидал в SprintLayout ещё прошлой зимой), впаять туда все модули-матрицы, со стороны фольги впаять отдельный (голый) модуль ESP8266 (ESP-12F).
Но что то задумался... Есть матричные модули зелёного цвета, сразу 10 модулей по 4 шт Может спаять большой экран 10х4 из зелёных матриц, а те 10 красных, что есть - поставить одной строкой снизу. Но это придётся делать большую плату, да и корпус на 3Д принтере печатать придётся из частей. Пока думаю...
Код писал в Arduino IDE. За основу был взят код из проекта 16х32 с samopal.pro
Код: Выделить всё
//#include <Time.h>
#include <TimeLib.h>
#include <arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266HTTPClient.h>
#include <WiFiUdp.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
MDNSResponder mdns;
ESP8266WebServer server(80);
String webPage = "";
#include <SPI.h>
#include <Adafruit_GFX.h> // https://github.com/adafruit/Adafruit-GFX-Library
#include <Max72xxPanel.h> // https://github.com/markruys/arduino-Max72xxPanel
#include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson
const unsigned char wifi_icon [] PROGMEM = {0x07, 0xfb, 0xfd, 0x1e, 0xee, 0xf6, 0x36, 0xb6 };
const unsigned char digit0 [] PROGMEM = { 0x83,0x01,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x39,0x01,0x83 };
const unsigned char digit1 [] PROGMEM = { 0xF7,0xE7,0xC7,0x87,0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,0x81 };
const unsigned char digit2 [] PROGMEM = { 0x83,0x01,0x39,0x39,0xF9,0xF9,0xF3,0xE3,0xC7,0x8F,0x1F,0x3F,0x01,0x01 };
const unsigned char digit3 [] PROGMEM = { 0x83,0x01,0x39,0xF9,0xF9,0xF9,0xE3,0xE3,0xF9,0xF9,0xF9,0x39,0x01,0x83 };
const unsigned char digit4 [] PROGMEM = { 0xF3,0xE3,0xC3,0xC3,0x93,0x93,0x33,0x31,0x01,0x01,0xF3,0xF3,0xF3,0xF3 };
const unsigned char digit5 [] PROGMEM = { 0x01,0x01,0x3F,0x3F,0x3F,0x03,0x01,0xF9,0xF9,0xF9,0xF9,0x39,0x01,0x83 };
const unsigned char digit6 [] PROGMEM = { 0x83,0x01,0x39,0x3F,0x3F,0x03,0x01,0x39,0x39,0x39,0x39,0x39,0x01,0x83};
const unsigned char digit7 [] PROGMEM = { 0x01,0x01,0xF9,0xF9,0xF9,0xF1,0xF3,0xE3,0xE7,0xCF,0xCF,0x9F,0x9F,0x9F };
const unsigned char digit8 [] PROGMEM = { 0x83,0x01,0x39,0x39,0x39,0x83,0x83,0x39,0x39,0x39,0x39,0x39,0x01,0x83 };
const unsigned char digit9 [] PROGMEM = { 0x83,0x01,0x39,0x39,0x39,0x39,0x39,0x01,0x81,0xF9,0xF9,0x39,0x01,0x83 };
const unsigned char m_digit0 [] PROGMEM = { B01100000, B10010000, B10010000, B10010000, B10010000, B10010000, B01100000};
const unsigned char m_digit1 [] PROGMEM = { B00100000, B01100000, B10100000, B00100000, B00100000, B00100000, B11110000};
const unsigned char m_digit2 [] PROGMEM = { B01100000, B10010000, B00010000, B00100000, B01000000, B10000000, B11110000};
const unsigned char m_digit_sep [] PROGMEM = { B00000000, B11100000, B00000000, B00000000, B11100000};
const unsigned char ms_digit0 [] PROGMEM = { B01100000, B10010000, B10010000, B10010000, B01100000};
const unsigned char ms_digit1 [] PROGMEM = { B00100000, B01100000, B00100000, B00100000, B01110000};
const unsigned char ms_digit2 [] PROGMEM = { B01100000, B10010000, B00100000, B01000000, B11110000};
const unsigned char ms_digit3 [] PROGMEM = { B01100000, B00010000, B00100000, B00010000, B01100000};
const unsigned char ms_digit4 [] PROGMEM = { B00100000, B01100000, B10100000, B11110000, B00100000};
const unsigned char ms_digit5 [] PROGMEM = { B11110000, B10000000, B11100000, B00010000, B11100000};
const unsigned char ms_digit6 [] PROGMEM = { B01100000, B10000000, B11100000, B10010000, B01100000};
const unsigned char ms_digit7 [] PROGMEM = { B11110000, B00010000, B00100000, B01000000, B01000000};
const unsigned char ms_digit8 [] PROGMEM = { B01100000, B10010000, B01100000, B10010000, B01100000};
const unsigned char ms_digit9 [] PROGMEM = { B01100000, B10010000, B01110000, B00010000, B01100000};
// Параметры доступа к WiFi
const char W_SSID[] = "Имя точки доступа";
const char W_PASS[] = "пароль к точке доступа";
// Параметры погодного сервера
String W_URL = "http://api.openweathermap.org/data/2.5/weather";
String W_API = "8у3c8537ed0c00eа62f83с880b423376"; //IPPID. Получить бесплатно: http://openweathermap.org/appid#get
// Код города на сервере openweathermap.org
// Москва = 524901, Челябинск = 1508291
// Остальные города можно посмотреть http://bulk.openweathermap.org/sample/city.list.json.gz
String W_ID = "1508291";
String W_NAME = "В Челябинске ";
// Параметры NTP сервера
WiFiUDP udp;
const int NTP_PACKET_SIZE = 48;
byte packetBuffer[ NTP_PACKET_SIZE];
const char NTP_SERVER[] = "0.ru.pool.ntp.org";
int TZ = 5;//Таймзона для Челябинска
uint32_t NTP_TIMEOUT = 600000; //10 минут
// Подключение экрана
// Если голый модуль: DIN -> GPIO_13 (MOSI), CLK -> GPIO_14 (SCK), CS -> GPIO_0 (задан параметром pinCS)
// Для модуля NodeMCU V3: DIN -> D7pin (MOSI), CLK -> D5pin (SCK), CS -> D3pin
int pinCS = 0; // Attach CS to this pin, DIN to MOSI and CLK to SCK (cf http://arduino.cc/en/Reference/SPI )
int numberOfHorizontalDisplays = 5;
int numberOfVerticalDisplays = 2;
Max72xxPanel matrix = Max72xxPanel(pinCS, numberOfHorizontalDisplays, numberOfVerticalDisplays);
// Настройка бегущей строки
String tape_weather = "";
String tape_date = "";
byte wait = 40; // Задержка бегущей строки в мс (меньше число - больше скорость)
byte spacer = 1;// Разделитель между буквами в 1 пиксел
byte width = 5 + spacer; // The font width is 5 pixels
uint32_t i_ms0 = 0;
uint32_t ms, ms0=0, ms1=0, ms2=0, ms3=0, ms4=0, ms_mode=0;
uint32_t tm = 0;
uint32_t t_cur = 0;
long t_correct = 0;
bool pp = false; // Флаг разделителя часы/минуты (1Гц)
byte mode = 0;
bool clear_flag = false;
void setup() {
// Просто тестовая HTML страничка, доступная в браузере по IP адресу устройства.
webPage += "<!DOCTYPE html>\n<html>\n<head>\n<title>ESP8266 LED Matrix Clock</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />";
webPage += "\n</head>\n<body>";
webPage += "<h1>ESP8266 LED Matrix Clock</h1><p>Socket #1 <a href=\"socket1On\"><button>ON</button></a> <a href=\"socket1Off\"><button>OFF</button></a></p>";
webPage += "<p>Socket #2 <a href=\"socket2On\"><button>ON</button></a> <a href=\"socket2Off\"><button>OFF</button></a></p>";
webPage += "\n</body>\n</html>";
Serial.begin(115200);
// Настройка дисплея
matrix.setIntensity(0); // Яркость экрана (от 0 до 15)
// Порядок матриц (порядковый номер матрицы, позиция модуля в экране по Х, позиция модуля в экране по Y)
matrix.setPosition(0, 5, 0);
matrix.setPosition(1, 6, 0);
matrix.setPosition(2, 7, 0);
matrix.setPosition(3, 8, 0);
matrix.setPosition(4, 9, 0);
matrix.setPosition(5, 0, 1);
matrix.setPosition(6, 1, 1);
matrix.setPosition(7, 2, 1);
matrix.setPosition(8, 3, 1);
matrix.setPosition(9, 4, 1);
// Ориентация матриц (порядковый номер матрицы, количество поворотов на 90 градусов)
matrix.setRotation(0, 1);
matrix.setRotation(1, 1);
matrix.setRotation(2, 1);
matrix.setRotation(3, 1);
matrix.setRotation(4, 1);
matrix.setRotation(5, 1);
matrix.setRotation(6, 1);
matrix.setRotation(7, 1);
matrix.setRotation(8, 1);
matrix.setRotation(9, 1);
matrix.fillScreen(LOW); // Очищаем экран
delay(500); // Ждем пол секунды
// Содиняемся с WiFi
Serial.print("\nConnecting to ");
Serial.println(W_SSID); // Выводим в монитор порта имя точки доступа
WiFi.mode(WIFI_STA);
WiFi.begin(W_SSID, W_PASS);
for (int i=0;WiFi.status() != WL_CONNECTED&&i<150; i++) {
Serial.print(".");
// Мигаем значком WiFi
matrix.drawBitmap(20, 4, wifi_icon, 8, 8, 0, 1);
matrix.write();
delay(250);
matrix.fillRect(20, 4, 8, 8, LOW);
matrix.write();
delay(250);
}
//Выдаем значек WiFi
matrix.drawBitmap(20, 4, wifi_icon, 8, 8, 0, 1);
matrix.write();
Serial.println("\nWiFi connected\nIP address: ");
Serial.println(WiFi.localIP()); // Выводим в монитор порта IP адрес подключения
Serial.println();
if (mdns.begin("ESP_Matrix_Clock", WiFi.localIP())) {
Serial.println("MDNS responder started");
// "Запущен MDNSresponder"
}
server.on("/", [](){
server.send(200, "text/html", webPage);
});
server.begin();
Serial.println("HTTP server started");
ms_mode = millis();
//Cоединение для NTP
udp.begin(2390);
} // END SETUP
void loop() {
server.handleClient();
ms = millis();
//Serial.println(mode);
// Цикл бегущей строки
if( ms0 == 0 || ms < ms0 || (ms - ms0)>wait ){
ms0 = ms;
switch(mode){
case 0: break;
case 1: break;
case 2: break;
case 3: break;
case 4: break;
case 5: break;
case 6: if(clear_flag){ clear_flag = !clear_flag; matrix.fillScreen(LOW); matrix.write(); }; PrintTicker(tape_date); break;
case 7: PrintTicker(tape_weather); break;
}
}
if( ms1 == 0 || ms < ms1 || (ms - ms1)>300000 ){ // Получаем погоду раз в 5 минуты
ms1 = ms;
GetWeather();
}
if( ms4 == 0 || ms < ms4 || (ms - ms4)>1000 ){
ms4 = ms;
GetDate();
}
// Показ часов
if( ms2 == 0 || ms < ms2 || (ms - ms2)>500 ){
ms2 = ms;
t_cur = ms/1000;
tm = t_cur + t_correct;
switch(mode){
case 0: PrintBigTime(); break;
case 1: PrintBigTime(); break;
case 2: PrintBigTime(); break;
case 3: PrintBigTime(); break;
case 4: PrintBigTime(); break;
case 5: PrintBigTime(); break;
case 6: PrintTime(); break;
case 7: PrintTime(); break;
}
}
// Опрос NTP сервера
if( ms3 == 0 || ms < ms3 || (ms - ms3)>NTP_TIMEOUT ){
uint32_t t = GetNTP();
if( t!=0 ){ ms3 = ms; t_correct = t - t_cur; }
}
}
// Печать времени
void PrintTime(){
int time_sec = second();
int time_min = minute();
int time_hour = hour();
char s[10];
matrix.fillRect(0, 0, 40, 8, LOW);
sprintf(s, "%02d", time_hour);
matrix.setCursor(2, 0);
matrix.print(s);
if( pp ) matrix.drawBitmap(14, 1, m_digit_sep, 1, 5, 1, 0);
else matrix.drawBitmap(14, 1, m_digit_sep, 1, 5, 0, 0);
sprintf(s, "%02d", time_min);
matrix.setCursor(16, 0);
matrix.print(s);
PrintMiniSecondDigit(29,2,time_sec/10);
PrintMiniSecondDigit(34,2,time_sec%10);
pp = !pp;
//ms_mode = ms;
}
// Печать времени большими цифрами
void PrintBigTime(){
if(!clear_flag) clear_flag = !clear_flag;
char s[20];
matrix.fillScreen(LOW);
int h = (int)( tm/3600 )%24;
int m = (int)( tm/60 )%60;
PrintBigDigit(1,1,h/10);
PrintBigDigit(10,1,h%10);
PrintBigDigit(23,1,m/10);
PrintBigDigit(32,1,m%10);
if( pp ){
matrix.fillRect(19, 4, 2, 2, HIGH);
matrix.fillRect(19, 9, 2, 2, HIGH);
}
pp = !pp;
matrix.write();
if( ms - ms_mode > 5000){
mode++;
if( mode >= 8 ) mode = 0;
ms_mode = ms;
}
}
// Печать большой цифры
void PrintBigDigit(int x, int y, int dig){
switch(dig){
case 0 : matrix.drawBitmap(x, y, digit0, 7, 14, 0, 1); break;
case 1 : matrix.drawBitmap(x, y, digit1, 7, 14, 0, 1); break;
case 2 : matrix.drawBitmap(x, y, digit2, 7, 14, 0, 1); break;
case 3 : matrix.drawBitmap(x, y, digit3, 7, 14, 0, 1); break;
case 4 : matrix.drawBitmap(x, y, digit4, 7, 14, 0, 1); break;
case 5 : matrix.drawBitmap(x, y, digit5, 7, 14, 0, 1); break;
case 6 : matrix.drawBitmap(x, y, digit6, 7, 14, 0, 1); break;
case 7 : matrix.drawBitmap(x, y, digit7, 7, 14, 0, 1); break;
case 8 : matrix.drawBitmap(x, y, digit8, 7, 14, 0, 1); break;
case 9 : matrix.drawBitmap(x, y, digit9, 7, 14, 0, 1); break;
}
}
// Печать маленькой цифры для секунд
void PrintMiniSecondDigit(int x, int y, int dig){
switch(dig){
case 0 : matrix.drawBitmap(x, y, ms_digit0, 4, 5, 1, 0); break;
case 1 : matrix.drawBitmap(x, y, ms_digit1, 4, 5, 1, 0); break;
case 2 : matrix.drawBitmap(x, y, ms_digit2, 4, 5, 1, 0); break;
case 3 : matrix.drawBitmap(x, y, ms_digit3, 4, 5, 1, 0); break;
case 4 : matrix.drawBitmap(x, y, ms_digit4, 4, 5, 1, 0); break;
case 5 : matrix.drawBitmap(x, y, ms_digit5, 4, 5, 1, 0); break;
case 6 : matrix.drawBitmap(x, y, ms_digit6, 4, 5, 1, 0); break;
case 7 : matrix.drawBitmap(x, y, ms_digit7, 4, 5, 1, 0); break;
case 8 : matrix.drawBitmap(x, y, ms_digit8, 4, 5, 1, 0); break;
case 9 : matrix.drawBitmap(x, y, ms_digit9, 4, 5, 1, 0); break;
}
}
void GetDate(){
int date_month_num = month();
String date_month = "";
switch(date_month_num){
case 1 : date_month = "Января"; break;
case 2 : date_month = "Февраля"; break;
case 3 : date_month = "Марта"; break;
case 4 : date_month = "Апреля"; break;
case 5 : date_month = "Мая"; break;
case 6 : date_month = "Июня"; break;
case 7 : date_month = "Июля"; break;
case 8 : date_month = "Августа"; break;
case 9 : date_month = "Сентября"; break;
case 10 : date_month = "Октября"; break;
case 11 : date_month = "Ноября"; break;
case 12 : date_month = "Декабря"; break;
}
int date_weekday_num = weekday();
String date_weekday = "";
switch(date_weekday_num){
case 1 : date_weekday = "Воскресенье"; break;
case 2 : date_weekday = "Понедельник"; break;
case 3 : date_weekday = "Вторник"; break;
case 4 : date_weekday = "Среда"; break;
case 5 : date_weekday = "Четверг"; break;
case 6 : date_weekday = "Пятница"; break;
case 7 : date_weekday = "Суббота"; break;
}
tape_date = "Сегодня ";
tape_date += date_weekday;
tape_date += " ";
tape_date += day();
tape_date += " ";
tape_date += date_month;
tape_date += ", ";
tape_date += year();
tape_date += " года.";
tape_date = utf8rus(tape_date);
}
// Один такт бегущей строки
void PrintTicker(String tape){
if( i_ms0 >= width * tape.length() + matrix.width() - 1 - spacer ){
i_ms0 = 0;
mode++;
ms_mode = ms;
if( mode >= 8 ) mode=0;
}
matrix.fillRect(0, 8, 32, 8, LOW);
int letter = i_ms0 / width;
int x = (matrix.width() - 1) - i_ms0 % width;
int y = 8; // center the text vertically
while ( x + width - spacer >= 0 && letter >= 0 ) {
if ( letter < tape.length() ){matrix.drawChar(x, y, tape[letter], HIGH, LOW, 1);}
letter--;
x -= width;
}
matrix.write(); // Send bitmap to display
i_ms0++;
}
// Перекодировка UTF8 в Windows-1251
String utf8rus(String source)
{
int i,k;
String target;
unsigned char n;
char m[2] = { '0', '\0' };
k = source.length(); i = 0;
while (i < k) {
n = source[i]; i++;
if (n >= 0xC0) {
switch (n) {
case 0xD0: {
n = source[i]; i++;
if (n == 0x81) { n = 0xA8; break; }
// if (n >= 0x90 && n <= 0xBF) n = n + 0x30;
if (n >= 0x90 && n <= 0xBF) n = n + 0x2F;
break;
}
case 0xD1: {
n = source[i]; i++;
if (n == 0x91) { n = 0xB8; break; }
// if (n >= 0x80 && n <= 0x8F) n = n + 0x70;
if (n >= 0x80 && n <= 0x8F) n = n + 0x6F;
break;
}
}
}
m[0] = n; target = target + String(m);
}
return target;
}
// Получаем погоду с погодного сервера
void GetWeather(){
HTTPClient client;
String url = W_URL;
url += "?id=";
url += W_ID;
url += "&APPID=";
url += W_API;
url += "&units=metric&lang=ru";
//Serial.println(url);
client.begin(url);
int httpCode = client.GET();
if( httpCode == HTTP_CODE_OK ){
String httpString = client.getString();
//Serial.println(httpString);
ParseWeather(httpString);
}
client.end();
}
// Парсим погоду
void ParseWeather(String s){
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(s);
if (!root.success()) {
Serial.println("Json parsing failed!");
return;
}
// Погода
tape_weather = W_NAME;
tape_weather += root["weather"][0]["description"].as<String>();
// Температура
tape_weather += ", Температура ";
int t = root["main"]["temp"].as<int>();
tape_weather += String(t);
tape_weather += (char)128;
// Влажность
tape_weather += "С, Влажность ";
tape_weather += root["main"]["humidity"].as<String>();
// Давление
tape_weather += "%, Давление ";
double p = root["main"]["pressure"].as<double>()/1.33322;
tape_weather += String((int)p);
// Ветер
tape_weather += "мм. Ветер ";
double deg = root["wind"]["deg"];
if( deg >22.5 && deg <=67.5 )tape_weather += "северо-восточный ";
else if( deg >67.5 && deg <=112.5 )tape_weather += "восточный ";
else if( deg >112.5 && deg <=157.5 )tape_weather += "юго-восточный ";
else if( deg >157.5 && deg <=202.5 )tape_weather += "южный ";
else if( deg >202.5 && deg <=247.5 )tape_weather += "юго-западный ";
else if( deg >247.5 && deg <=292.5 )tape_weather += "западный ";
else if( deg >292.5 && deg <=337.5 )tape_weather += "северо-западный ";
else tape_weather += "северный ";
tape_weather += root["wind"]["speed"].as<String>();
tape_weather += " м/с. ";
// Перекодируем из UNICODE
tape_weather = utf8rus(tape_weather);
s = "";
}
// Посылаем и парсим запрос к NTP серверу
time_t GetNTP(void) {
IPAddress ntpIP;
time_t tm = 0;
WiFi.hostByName(NTP_SERVER, ntpIP);
sendNTPpacket(ntpIP);
delay(1000); // это надо убрать, переделав на millis(), чтоб не вызывало подтормаживания.
int cb = udp.parsePacket();
if (!cb) {
return tm;
} else {
// Читаем пакет в буфер
udp.read(packetBuffer, NTP_PACKET_SIZE);
// 4 байта начиная с 40-го сождержат таймштамп времени - число секунд
// от 01.01.1900
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// Конвертируем два слова в переменную long
unsigned long secsSince1900 = highWord << 16 | lowWord;
// Конвертируем в UNIX-таймстамп (число секунд от 01.01.1970
const unsigned long seventyYears = 2208988800UL;
unsigned long epoch = secsSince1900 - seventyYears;
// Делаем поправку на местную тайм-зону
tm = epoch + TZ*3600;
setTime(tm);
}
return tm;
}
//Посылаем запрос NTP серверу на заданный адрес
unsigned long sendNTPpacket(IPAddress& address)
{
// Очистка буфера в 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Формируем строку зыпроса NTP сервера
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;
// Посылаем запрос на NTP сервер (123 порт)
udp.beginPacket(address, 123);
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
}
Пост пока на стадии написания, позже буду дополнять...