Технологии прошлого и настоящего: как я сделал Nixie Clock с самодельным драйвером на ESP8266

от автора

Радиолампы, словно артефакты из прошлого, олицетворяют нечто большее, чем просто технологию. Они несут в себе определенную магию, отражающую уникальное сочетание технического мастерства и эстетики. Не удивительно, что часы на неоновых индикаторах занимают довольно уникальную нишу в мире дизайна и интерьера. Они представляют собой не просто инструмент для отображения времени, но и элемент декора, который может значительно изменить атмосферу помещения. Этой статье я расскажу о своем опыте создания Nixie Clock на базе драйвера собственной разработки.

С чего всё началось

Однажды, на предприятии где я работал, на складе обнаружилось много неликвидного материала, который хранился там ещё с советских времен.
Неликвид состоял из электронных компонентов, которые нам отдали безвозмездно для использования в личных целях, чтобы не тратить средства на утилизацию. На самом деле, там было очень много ценных компонентов, среди которых оказались неоновые индикаторные лампы марки ИН-12. В итоге я их забрал себе. С радиолампами знаком еще с детства, увлекаясь радиоконструированием, я часто собирал различные схемы, в том числе и на лампах. А тут такой флешбэк.

Разработка часов

По состоянию на 2016 год, было много различных схем часов на лампах, но мне не нравилась их схемотехника, она казалась мне избыточной и не эффективной. Хотелось реализовать что-то простое, питающееся от стандартного USB порта, без использования модуля RTC и светодиодной подсветки, которая, по моему мнению, только портит всю эстетику ламп. На тот момент большинство схем работало на Arduino и микроконтроллерах от компании Atmel. Годом ранее, компания Espressif Systems выпустила на рынок свой микроконтроллер ESP8266, который произвел революцию. Так как на тот момент, широкополосный интернет уже был достаточно распространен, в том числе и домашние сети Wi-Fi, я решил отказаться от применения RTC модуля в своей схеме часов и использовать NTP серверы для синхронизации времени. Как вы могли догадаться, в своей схеме я применил модуль ESP8266. Далее я поделился в Twitter своим опытом применения нового модуля ESP8266 в своем проекте. Мой твит вызвал интерес, и мне предложили написать статью на Hackaday.io. Я последовал совету и опубликовал свою статью там.

Но в этой статье я хочу описать реализацию часов с применением шести индикаторов ИН-14 с использованием улучшенного драйвера. Как выглядят эти лампы, вы можете увидеть ниже.

Давайте приступим

Ниже изображена схема драйвера часов:

Схема подключения ламп:

Согласно документации, индикаторная лампа работает от напряжения в 170В (напряжение возникновения разряда), для стабильной работы нам потребуется напряжение в 200В. Как вы можете видеть из схемы, для повышения напряжения до 200В применен set-up преобразователь на базе ШИМ контроллера МАХ1771 в связке с L2, D1 и Q1. Так как нам недостаточно выводов ESP8266 для управления лампами, то будем «размножать» пины управления с помощью дешифраторов CD4028BM96. Данный модифицированный драйвер позволяет управлять десятью газоразрядными индикаторными лампами. Выше описанный драйвер имеет динамический метод управления индикацией, то есть в определенный момент времени загорается только одна лампа, но переключение выполняется настолько быстро, что человеческий глаз практически не воспринимает переключение ламп и кажется что все лампы горят одновременно. Данный режим переводит работу ламп в импульсный режим, что положительно сказывается на их срок службы.

Разработка платы

Разработка платы велась в Sprint-Layout 5.0, так как мне это было удобнее для изготовления платы в домашних условиях.

Плата драйвера:

Плата для установки ламп:

Изготовление печатной платы выполнялось с применение фотошаблона и фоторезиста:

Засветка фоторезиста платы драйвера:

Засветка фоторезиста платы крепления ламп:

Травление платы драйвера:

Пайка компонентов:

Плата драйвера в собранном виде:

Монтаж ламп на плату управления:

Тест работы схемы часов с небольшой отладкой:

Для управления высоким напряжением используются оптроны TLP627 от компании TOSHIBA.

TLP627 — высоковольтный транзисторный оптрон со схемой Дарлингтона на выходе.

Корпус часов

Корпус часов не предполагает какой либо сложной конструкции, разработка выполнялась во FreeCAD:

Далее корпус был распечатан на 3D принтере, с использованием HIPS пластика. Данный пластик при печати создает структуру стенки, которая чем-то похоже на дерево и не обладает глянцевым эффектом как другие виды пластика типа PLA, ABS и т. п.

Монтаж электроники

После изготовления корпуса, необходимо смонтировать все компоненты. Ниже показан монтаж платы драйвера с применением, всеми любимого, термоклея. 🙂

В итоге мы получаем следующее:

Часы в работе:

Часы в данный момент находятся на моём на рабочем столе, естественно, в живую они выглядят гораздо красивее:

Давайте поговорим о прошивке часов

Для разработки прошивки часов, я использовал среду разработки Arduino IDE. Ниже представлен код прошивки:

Код прошивки

#include <TimeLib.h> #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <ESP8266mDNS.h> #include <ESP8266HTTPUpdateServer.h> #include <WiFiUdp.h> #include <EEPROM.h> #include <ESP8266SSDP.h>  const char* host = "retroclock"; const char *ap_ssid = "NixieClock"; const char *ap_password = "EsPnEtWoRk"; int statusCode; String st; String content;  // NTP Servers: String ntpServerName2; int timeZone = 0;     // Time zone  ESP8266WebServer httpServer(80); ESP8266HTTPUpdateServer httpUpdater;  WiFiUDP Udp; unsigned int localPort = 8888;  // local port to listen for UDP packets  time_t getNtpTime(); void digitalClockDisplay(); void printDigits(int digits); void sendNTPpacket(IPAddress &address); /* const int D_A0 = 16; const int D_A1 = 12; const int D_A2 = 13; const int D_A3 = 14; */ const int D_A0 = 13; //A const int D_A1 = 16; //B const int D_A2 = 14; //C const int D_A3 = 12; //D //digits multiplex const int D_M1 = 5; //A const int D_M2 = 2; //B const int D_M3 = 0; //C const int D_M4 = 4; //D const int LED1 = 1; //used Tx pin 1 const int LED2 = 3; //used Rx pin 3 int counter  = 0; int counter2 = 0;  uint32_t ms,  ms1 = 0; //for timer uint32_t ms2,  ms3 = 0;  uint32_t ms4,  ms5 = 0;  uint32_t ms6,  ms7 = 0;   void setup() { EEPROM.begin(512);  timeZone =  read_EEPROM(32,96).toInt(); ntpServerName2 =  read_EEPROM(0,32);  pinMode(D_A0, OUTPUT); //A0 pinMode(D_A1, OUTPUT); //A1 pinMode(D_A2, OUTPUT); //A2 pinMode(D_A3, OUTPUT); //A3 pinMode(D_M1, OUTPUT); //1 pinMode(D_M2, OUTPUT); //2 pinMode(D_M3, OUTPUT); //3 pinMode(D_M4, OUTPUT); //4 pinMode(LED1, OUTPUT); //LED1 pinMode(LED2, OUTPUT); //LED2   httpServer.on("/",   rootPageHandler); httpServer.on("/wlan_config", wlanPageHandler); httpServer.on("/setting", setting); httpServer.on("/time.html", timess); httpServer.on("/times.html", testpage); httpServer.onNotFound(handleNotFound);    WiFi.mode(WIFI_STA);   WiFi.begin();   for (int x = 0; x < 100; x ++){     if (WiFi.status() == WL_CONNECTED){        break;     }     delay(500);    }   if(WiFi.status() != WL_CONNECTED) {     delay(500);       WiFi.mode(WIFI_AP_STA);       WiFi.softAP(ap_ssid, ap_password);          }   WiFi.hostname("IoT Nixie Clock IN-14");   MDNS.begin(host);   httpUpdater.setup(&httpServer);   //httpServer.begin();    MDNS.addService("http", "tcp", 80);   delay(2000);         HTTP_init();    //настраиваем HTTP интерфейс         SSDP_init();    //запускаем SSDP сервис   Udp.begin(localPort); setSyncProvider(getNtpTime); setSyncInterval(300); }   time_t prevDisplay = 0;     void loop() {  httpServer.handleClient();  ms4 = micros();  if((ms4-ms5) >= 800 ) {       counter++;       ms5 = ms4;  }   if(counter > 30){     counter = 0;   }   if (millis() > 20000) {       switchs();    }   else {        demo();   } }     void displayig(){  if (counter == 1 or counter == 5 or counter == 10 or counter == 15 or counter == 20 or counter == 25) {           multiplex(7);  }   if (counter == 3 or counter == 4 or counter == 2){ // 1-я цифра //секунды десятые          int times = second() /10;         digitfunction(times);         multiplex(1);   }   if (counter == 8 or counter == 9 or counter == 7){ // 2-я цифра /скунды ед          int times = second() %10;         digitfunction(times);         multiplex(2);    }    if (counter == 13 or counter == 14 or counter == 12 ){ // 1-я цифра //минуты десятые          int times = minute() /10;         digitfunction(times);         multiplex(4);    }     if (counter == 18 or counter == 19 or counter == 17){ // 2-я цифра //минуты ед         int times = minute() % 10;        digitfunction(times);        multiplex(3);    }    if (counter == 23 or counter == 24 or counter == 22){ // 3-я цифра         int times = hour()% 10;;        digitfunction(times);        multiplex(5);    }    if (counter == 28 or counter == 29 or counter == 27){ // 4-я цифра            int times = hour()/10;       digitfunction(times);       multiplex(6);   } }   void multiplex_sw(int M1, int M2, int M3, int M4) {         digitalWrite(D_M1, M1); digitalWrite(D_M2, M2); digitalWrite(D_M3, M3); digitalWrite(D_M4, M4);            }       void multiplex(int chanel){    switch (chanel) {     case 1:       multiplex_sw(HIGH, LOW, HIGH, LOW);       break;     case 2:       multiplex_sw(HIGH, LOW, LOW, HIGH);          break;     case 3:      multiplex_sw(LOW, HIGH, HIGH, LOW);       break;     case 4:       multiplex_sw(HIGH, HIGH, LOW, LOW);        break;     case 5:         multiplex_sw(HIGH, LOW, LOW, LOW);          break;     case 6:      multiplex_sw(LOW, LOW, LOW, HIGH);       break;     case 7:      multiplex_sw(LOW, LOW, LOW, LOW);        break;         } }   void changeMux(int AO, int A1, int A2, int A3) {         digitalWrite(D_A0, AO); digitalWrite(D_A1, A1); digitalWrite(D_A2, A2); digitalWrite(D_A3, A3);          }   void digitfunction(int times){    switch (times) {     case 0:         changeMux(LOW, LOW, LOW, LOW);         break;     case 1:         changeMux(HIGH, LOW, LOW, LOW);         break;     case 2:          changeMux(LOW, HIGH, LOW, LOW);         break;     case 3:         changeMux(HIGH, HIGH, LOW, LOW);         break;     case 4:         changeMux(LOW, LOW, HIGH, LOW);         break;     case 5:            changeMux(HIGH, LOW, HIGH, LOW);         break;     case 6:         changeMux(LOW, HIGH, HIGH, LOW);         break;     case 7:         changeMux(HIGH, HIGH, HIGH, LOW);         break;         case 8:         changeMux(LOW, LOW, LOW, HIGH);         break;           case 9:         changeMux(HIGH, LOW, LOW, HIGH);         break;         }   }   void switchs() {   ms2 = second();      if(( ms2 ) >= 0 && ( ms2 ) <= 44){       displayig();       int pww = 1024;       ms = millis();          if(( ms - ms1 ) >= 0 && ( ms - ms1 ) <= 500){analogWrite(LED1, pww);}       if(( ms - ms1 ) >= 250 && ( ms - ms1 ) <= 500){analogWrite(LED2, pww);}       if(( ms - ms1 ) >= 500){analogWrite(LED1, 0); analogWrite(LED2, 0);}       if(( ms - ms1 ) >= 1000){ ms1 = ms;}              }     if(( ms2 ) >= 45 && ( ms2  ) <= 60){       displayig();       int pww = 1024;       ms = millis();          if(( ms - ms1 ) >= 0 && ( ms - ms1 ) <= 500){analogWrite(LED1, pww);        analogWrite(LED2, pww);}       //if(( ms - ms1 ) >= 250 && ( ms - ms1 ) <= 500){analogWrite(LED2, pww);}       if(( ms - ms1 ) >= 500){analogWrite(LED1, 0); analogWrite(LED2, 0);}       if(( ms - ms1 ) >= 1000){ ms1 = ms;}       } }    /*-------- 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          char char_var_resp[ntpServerName2.length()];          ntpServerName2.toCharArray(char_var_resp, ntpServerName2.length()+1);    while (Udp.parsePacket() > 0) ; // discard any previously received packets    WiFi.hostByName(char_var_resp, ntpServerIP);   sendNTPpacket(ntpServerIP);   uint32_t beginWait = millis();   while (millis() - beginWait < 1500) {     int size = Udp.parsePacket();     if (size >= NTP_PACKET_SIZE) {       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;     }   }   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(); }  void demo(){   ms6 = millis();   if ((ms6-ms7) >= 2) {   counter2++;   ms7 = ms6;  }   if (counter2 > 0 && counter2 < 1000){ // 4-я цифра      multiplex(2); if(counter2 == 100) { digitfunction(9);} if(counter2 == 200) { digitfunction(8);} if(counter2 == 300) { digitfunction(7);} if(counter2 == 400) { digitfunction(6);} if(counter2 == 500) { digitfunction(5);} if(counter2 == 600) { digitfunction(4);} if(counter2 == 700) { digitfunction(3);} if(counter2 == 800) { digitfunction(2);} if(counter2 == 900) { digitfunction(1);} if(counter2 == 1000) { digitfunction(0);}   }    if (counter2 >1100 && counter2 < 2100){ // 3-я цифра    multiplex(1);     if(counter2 == 1100) { digitfunction(9);}     if(counter2 == 1200) { digitfunction(8);}     if(counter2 == 1300) { digitfunction(7);}     if(counter2 == 1400) { digitfunction(6);}     if(counter2 == 1500) { digitfunction(5);}     if(counter2 == 1600) { digitfunction(4);}     if(counter2 == 1700) { digitfunction(3);}     if(counter2 == 1800) { digitfunction(2);}     if(counter2 == 1900) { digitfunction(1);}     if(counter2 == 2000) { digitfunction(0);}   }    if (counter2 >2100 && counter2 < 3100){ // 2-я цифра     multiplex(3);     if(counter2 == 2100) { digitfunction(9);}     if(counter2 == 2200) { digitfunction(8);}     if(counter2 == 2300) { digitfunction(7);}     if(counter2 == 2400) { digitfunction(6);}     if(counter2 == 2500) { digitfunction(5);}     if(counter2 == 2600) { digitfunction(4);}     if(counter2 == 2700) { digitfunction(3);}     if(counter2 == 2800) { digitfunction(2);}     if(counter2 == 2900) { digitfunction(1);}     if(counter2 == 3000) { digitfunction(0);}   }    if (counter2 >3100 && counter2 < 4100){ // 4-я цифра     multiplex(4);      if(counter2 == 3100) { digitfunction(9);}     if(counter2 == 3200) { digitfunction(8);}     if(counter2 == 3300) { digitfunction(7);}     if(counter2 == 3400) { digitfunction(6);}     if(counter2 == 3500) { digitfunction(5);}     if(counter2 == 3600) { digitfunction(4);}     if(counter2 == 3700) { digitfunction(3);}     if(counter2 == 3800) { digitfunction(2);}     if(counter2 == 3900) { digitfunction(1);}     if(counter2 == 4000) { digitfunction(0);}   }   if (counter2 >4100 && counter2 < 5100){ // 5-я цифра     multiplex(5);     if(counter2 == 4100) { digitfunction(9);}    if(counter2 == 4200) { digitfunction(8);}    if(counter2 == 4300) { digitfunction(7);}    if(counter2 == 4400) { digitfunction(6);}    if(counter2 == 4500) { digitfunction(5);}    if(counter2 == 4600) { digitfunction(4);}    if(counter2 == 4700) { digitfunction(3);}    if(counter2 == 4800) { digitfunction(2);}    if(counter2 == 4900) { digitfunction(1);}    if(counter2 == 5000) { digitfunction(0);}   }   if (counter2 >5100 && counter2 < 6100){ // 6-я цифра     multiplex(6);     if(counter2 == 5100) { digitfunction(9);}    if(counter2 == 5200) { digitfunction(8);}    if(counter2 == 5300) { digitfunction(7);}    if(counter2 == 5400) { digitfunction(6);}    if(counter2 == 5500) { digitfunction(5);}    if(counter2 == 5600) { digitfunction(4);}    if(counter2 == 5700) { digitfunction(3);}    if(counter2 == 5800) { digitfunction(2);}    if(counter2 == 5900) { digitfunction(1);}    if(counter2 == 6000) { digitfunction(0);}   } }   //web interface/////////====================================== /* WLAN page allows users to set the WiFi credentials */ String twoDigits(int digits){   if(digits < 10) {     String i = '0'+String(digits);     return i;   }   else {     return String(digits);   } }  void wlanPageHandler(){   if (httpServer.hasArg("ssid")){         if (httpServer.hasArg("password")){       WiFi.begin(httpServer.arg("ssid").c_str(), httpServer.arg("password").c_str());     }else{       WiFi.begin(httpServer.arg("ssid").c_str());     }          while (WiFi.status() != WL_CONNECTED){       delay(500);      }            delay(500);   }      String response_message = "";   response_message +="<head>";   response_message +="<title>Wi-Fi конфигурация</title>";   response_message += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8;\" />";   response_message += "<style type=\"text/css\">body{background-color: #7D8EE2;color:#FFF;}a {color:#73B9FF;}.blockk {border:solid 1px #2d2d2d;text-align:center;background:#0059B3;padding:10px 10px 10px 10px;-moz-border-radius: 5px;-webkit-border-radius: 5px;border-radius: 5px;}";   response_message += ".blockk{border:double 2px #000000;-moz-border-radius: 5px;-webkit-border-radius: 5px;border-radius: 5px;}";   response_message +="</style><style type=\"text/css\" media=\'(min-width: 810px)\'>body{font-size:18px;}.blockk {width: 400px;}</style>";   response_message +="<style type=\"text/css\" media=\'(max-width: 800px) and (orientation:landscape)\'>body{font-size:8px;}</style></head>";   response_message += "<body><center><div class=\"blockk\">";   response_message += "Настройка беспроводного соединения<br><hr>";      if (WiFi.status() == WL_CONNECTED){     IPAddress ip = WiFi.localIP();     String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);     response_message += "Статус: Модуль подключен к сети "+String(WiFi.SSID())+"<br><hr>";     response_message += "Уровень сигнала: "+String(WiFi.RSSI())+" dBi <br><hr>";     response_message += "IP адрес подключения: "+String(ipStr)+"<br><hr>";   }else{     response_message += "Статус: Модуль отключен от сети<br><hr>";   }      response_message += "<p>Для подключения к  WiFi сети, пожалуйста выберите сеть...</p><br><hr>";    // Get number of visible access points   int ap_count = WiFi.scanNetworks();      if (ap_count == 0){     response_message += "Не найдено ниодной беспроводной сети.<br><hr>";   }else{     response_message += "<form method=\"get\">";     // Show access points     for (uint8_t ap_idx = 0; ap_idx < ap_count; ap_idx++){       response_message += "<input type=\"radio\" name=\"ssid\" value=\"" + String(WiFi.SSID(ap_idx)) + "\">";       response_message += String(WiFi.SSID(ap_idx)) + " [Уровень сигнала: " + WiFi.RSSI(ap_idx) +" dBi]";       (WiFi.encryptionType(ap_idx) == ENC_TYPE_NONE) ? response_message += " " : response_message += "[защищена]";       response_message += "<br><br>";     }          response_message += "WiFi пароль доступа (если сеть защищена):<br>";     response_message += "<input type=\"text\" name=\"password\"><br><hr>";     response_message += "<input type=\"submit\" value=\"Подключиться\">";     response_message += "</form>";   }     response_message += "</body></html>";   response_message += "<a href=\"/\">Вернуться назад</a><br><hr>";   httpServer.send(200, "text/html", response_message); } /* Called if requested page is not found */ void handleNotFound(){   String message = "Файл не найден\n\n";   message += "URI: ";   message += httpServer.uri();   message += "\nMethod: ";   message += (httpServer.method() == HTTP_GET)?"GET":"POST";   message += "\nArguments: ";   message += httpServer.args();   message += "\n";      for (uint8_t i = 0; i < httpServer.args(); i++){     message += " " + httpServer.argName(i) + ": " + httpServer.arg(i) + "\n";   }      httpServer.send(404, "text/plain", message); } /* Root page for the webserver */  //================================================================================  void rootPageHandler() {   String response_message = "<html>";   response_message +="<head>";   response_message +="<title>Интерфейс неоновых часов</title>";   response_message += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8;\" />";   response_message += "<style type=\"text/css\">body{background-color: #7D8EE2;color:#FFF;}a {color:#73B9FF;}.blockk {border:solid 1px #2d2d2d;text-align:center;background:#0059B3;padding:10px 10px 10px 10px;-moz-border-radius: 5px;-webkit-border-radius: 5px;border-radius: 5px;}";   response_message += ".blockk{border:double 2px #000000;-moz-border-radius: 5px;-webkit-border-radius: 5px;border-radius: 5px;}";   response_message +="</style><style type=\"text/css\" media=\'(min-width: 810px)\'>body{font-size:18px;}.blockk {width: 400px;}</style>";   response_message +="<style type=\"text/css\" media=\'(max-width: 800px) and (orientation:landscape)\'>body{font-size:8px;}</style>";   response_message +="<script>\n";   response_message +="setInterval(server_time,1000);\n";   response_message +="function server_time(){\n";   response_message +="var req = new XMLHttpRequest();\n";   response_message +="req.open(\"GET\",\"times.html\",true);\n";   response_message +="req.onreadystatechange = function(){\n";   response_message +="document.getElementById(\"xz\").innerHTML = req.responseText;\n";   response_message +=" }\n";   response_message +=" req.send();\n";   response_message +="}\n";   response_message +="</script>";   response_message +="<script>\n";   response_message +="setInterval(server_time1,1000);\n";   response_message +="function server_time1(){\n";   response_message +="var req1 = new XMLHttpRequest();\n";   response_message +="req1.open(\"GET\",\"time.html\",true);\n";   response_message +="req1.onreadystatechange = function(){\n";   response_message +="document.getElementById(\"xzy\").innerHTML = req1.responseText;\n";   response_message +=" }\n";   response_message +=" req1.send();\n";   response_message +="}\n";   response_message +="</script>";   response_message +="</head>";   response_message += "<body><center><div class=\"blockk\">";   response_message += "Интерфейс неоновых часов <br><hr>";   //time display   response_message += "<b>Идентификатор устройства: "+String(ESP.getChipId())+" </b><hr>";   //time   int times =(millis()/1000);   int timehour =(((times)  % 86400L) / 3600);     if ( ((times % 3600) / 60) < 10 ) {       // In the first 10 minutes of each hour, we'll want a leading '0'       int timehour = 0;                }     int timeminuts=((times  % 3600) / 60); // print the minute (3600 equals secs per minute)      if ( (times % 60) < 10 ) {       // In the first 10 seconds of each minute, we'll want a leading '0'       int timeminuts = 0;         }     int timeseconds=(times % 60); // print the second         response_message += "<div id=\"content3\">Время работы модуля: <br>";       response_message += "<body>";   response_message += "<div id=\"xz\"></div>";   response_message += "</body>";   response_message += "<center>NTP сервер: "+String(ntpServerName2)+" <br></center>";   response_message += "<center>Часовой пояс: </center>";   response_message += "<center>";  if(timeZone==5){ response_message += "UTC/GMT+5 (Екатеринбург)"; }  if(timeZone==3){ response_message += "UTC/GMT+3 (Москва)"; }  if(timeZone==4){ response_message += "UTC/GMT+4 (Самара, Ижевск)"; }  if(timeZone==6){ response_message += "UTC/GMT+6 (Омск)"; }  if(timeZone==7){ response_message += "UTC/GMT+7 (Красноярск)"; }  if(timeZone==8){ response_message += "UTC/GMT+8 (Иркутск)"; }  if(timeZone==9){ response_message += "UTC/GMT+9 (Якутск)"; }  if(timeZone==10){ response_message += "UTC/GMT+10 (Владивосток)"; }  if(timeZone==11){ response_message += "UTC/GMT+11 (Камчатка)"; }  if(timeZone==1){ response_message += "UTC/GMT+1(Центральная Европа)"; }  if(timeZone==-2){ response_message += "UTC/GMT-2 (Среднеатлантическое время)"; }  if(timeZone==-3){ response_message += "UTC/GMT-3 (Аргентина)"; }  if(timeZone==-4){ response_message += "UTC/GMT-4 (Канада)"; }  if(timeZone==-5){ response_message += "UTC/GMT-5 (Нью-Йорк)"; }  if(timeZone==-6){ response_message += "UTC/GMT-6 (Чикаго)"; }  if(timeZone==-7){ response_message += "UTC/GMT-7 (Денвер)"; }  if(timeZone==-8){ response_message += "UTC/GMT-8 (Лос-Анджелес)"; }  response_message += "</center>";  String timenow = String(hour())+":"+twoDigits(minute())+":"+twoDigits(second());  response_message += "<hr>Время сети: <div id=\"xzy\">"+String(timenow)+"</div>";   if (WiFi.status() == WL_CONNECTED){     IPAddress ip = WiFi.localIP();     String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);     response_message += "<hr>Статус: подключен к сети "+String(WiFi.SSID())+"<br>";     response_message += "Уровень сигнала: "+String(WiFi.RSSI())+" dBi <br><hr>";     response_message += "IP адрес подключения: "+String(ipStr)+"<br><hr>";    }else{     response_message += "<br><hr>WLAN статус: Отключено<br><hr>";   }         response_message += "</p><form method='get' action='setting'><label>NTP сервер: <br></label><input name='ssid' length=32><br><label>Часовой пояс</label><br>";         response_message += "<select name='pass'>";         response_message += "<option disabled>Выберите часовой пояс</option>";         response_message += "<option selected value = '5'>UTC/GMT+5 (Екатеринбург)</option>";         response_message += "<option value = '3'>UTC/GMT+3 (Москва)</option>";         response_message += "<option value = '4'> UTC/GMT+4 (Самара, Ижевск)</option>";         response_message += "<option value = '6'> UTC/GMT+6 (Омск)</option>";         response_message += "<option value = '7'> UTC/GMT+7 (Красноярск)</option>";         response_message += "<option value = '8'> UTC/GMT+8 (Иркутск)</option>";         response_message += "<option value = '9'> UTC/GMT+9 (Якутск)</option>";         response_message += "<option value = '10'> UTC/GMT+10 (Владивосток)</option>";         response_message += "<option value = '11'> UTC/GMT+11 (Камчатка)</option>";         response_message += "<option value = '1'> UTC/GMT+1(Центральная Европа)</option>";         response_message += "<option value = '0'> UTC/GMT-0 (Гринвич)</option>";         response_message += "<option value = '-2'> UTC/GMT-2 (Среднеатлантическое время)</option>";         response_message += "<option value = '-3'> UTC/GMT-3 (Аргентина)</option>";         response_message += "<option value = '-4'> UTC/GMT-4 (Канада)</option>";         response_message += "<option value = '-5'> UTC/GMT-5 (Нью-Йорк)</option>";         response_message += "<option value = '-6'> UTC/GMT-6 (Чикаго)</option>";         response_message += "<option value = '-7'> UTC/GMT-7 (Денвер)</option>";         response_message += "<option value = '-8'> UTC/GMT-8 (Лос-Анджелес)</option>";         response_message += "</select>";         response_message += "<br><br><input type='submit'></form>";         response_message += "<a href=\"/wlan_config\">Настройки беспроводного соединения</a><br><hr>";         response_message += "<a href=\"/update\">Обновление прошивки (OTA)</a><br><hr>";         response_message += "</div></center></body></html>";      httpServer.send(200, "text/html", response_message); } ///====================================================  void setting() {   String response_message = "<html>";   response_message +="<head>";   response_message +="<title>Интерфейс неоновых часов</title>";   response_message += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8;\" />";   response_message += "<style type=\"text/css\">body{background-color: #7D8EE2;color:#FFF;}a {color:#73B9FF;}.blockk {border:solid 1px #2d2d2d;text-align:center;background:#0059B3;padding:10px 10px 10px 10px;-moz-border-radius: 5px;-webkit-border-radius: 5px;border-radius: 5px;}";   response_message += ".blockk{border:double 2px #000000;-moz-border-radius: 5px;-webkit-border-radius: 5px;border-radius: 5px;}";   response_message +="</style><style type=\"text/css\" media=\'(min-width: 810px)\'>body{font-size:18px;}.blockk {width: 400px;}</style>";   response_message +="<style type=\"text/css\" media=\'(max-width: 800px) and (orientation:landscape)\'>body{font-size:8px;}</style></head>";   response_message += "<body><center><div class=\"blockk\">";   response_message += "Интерфейс Smart Power Switch <br><hr>";   //time display   response_message += "<b>Идентификатор устройства: "+String(ESP.getChipId())+" </b><hr>";   //time   int times =(millis()/1000);   int timehour =(((times)  % 86400L) / 3600);     if ( ((times % 3600) / 60) < 10 ) {       int timehour = 0;           }     int timeminuts=((times  % 3600) / 60); // print the minute (3600 equals secs per minute)      if ( (times % 60) < 10 ) {       int timeminuts = 0;         }     int timeseconds=(times % 60); // print the second           response_message += "<div id=\"content3\">"+String(timehour)+":"+String(timeminuts)+":"+String(timeseconds)+"</div>";          if (WiFi.status() == WL_CONNECTED){     IPAddress ip = WiFi.localIP();     String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);     response_message += "<br><hr>Статус: подключен к сети "+String(WiFi.SSID())+"<br>";     response_message += "Уровень сигнала: "+String(WiFi.RSSI())+" dBi <br><hr>";     response_message += "IP адрес подключения: "+String(ipStr)+"<br><hr>";        }else{     response_message += "<br><hr>WLAN статус: Отключено<br><hr>";   }   ///==== String qsid =  httpServer.arg("ssid");         String qpass =  httpServer.arg("pass");         if (qsid.length() > 0 && qpass.length() > 0) {           timeZone = qpass.toInt();           for (int i = 0; i < 96; ++i) { EEPROM.write(i, 0); }            for (int i = 0; i < qsid.length(); ++i)             {               EEPROM.write(i, qsid[i]);             }                    for (int i = 0; i < qpass.length(); ++i)             {               EEPROM.write(32+i, qpass[i]);              }               EEPROM.commit();           timeZone = qpass.toInt();           ntpServerName2 = string_to_char(qsid);           response_message += "Выполнено! Сохранено в памяти... перезагрузите устройство для активации изменений <br>";           statusCode = 200;         } else {           response_message += "Ошибка! Отсутствуют данныее <br>";           statusCode = 404;                    }              ////===      response_message += "<a href=\"/wlan_config\">Настройки беспроводного соединения</a><br><hr>";   response_message += "<a href=\"/update\">Обновление прошивки (OTA)</a><br><hr>";   response_message += "<a href=\"/\">Главная</a><br><hr>";   response_message += "</div></center></body></html>";      httpServer.send(200, "text/html", response_message); } void timess(){   String response_message = "";   String timenow = String(hour())+":"+twoDigits(minute())+":"+twoDigits(second());  response_message += String(timenow);  httpServer.send(200, "text/html", response_message); } void testpage(){   String response_message = "";  int times =(millis()/1000);   int timehour =(((times)  % 86400L) / 3600);     if ( ((times % 3600) / 60) < 10 ) {       // In the first 10 minutes of each hour, we'll want a leading '0'       int timehour = 0;                }     int timeminuts=((times  % 3600) / 60); //     if ( (times % 60) < 10 ) {       int timeminuts = 0;         }     int timeseconds=(times % 60); //         String timenow = String(timehour)+":"+twoDigits(timeminuts)+":"+twoDigits(timeseconds);   response_message += String(timenow);  httpServer.send(200, "text/html", response_message); }  void SSDP_init(){               SSDP.setName("V.G.C. Smart Device Network");               SSDP.setSchemaURL("description.xml");               SSDP.setHTTPPort(80);               SSDP.setName("NixieClock IN-14");               SSDP.setSerialNumber(String(ESP.getChipId()));               SSDP.setURL("index.html");               SSDP.setModelName("NixieClock IN-14");               SSDP.setModelNumber("1.1.5");               SSDP.setModelURL("https://cyberex.online");               SSDP.setManufacturer("V.G.C. Smart Electronics");               SSDP.setManufacturerURL("https://cyberex.online");               SSDP.begin();            }                    void HTTP_init(){               httpServer.on("/index.html", HTTP_GET, [](){               httpServer.send(200, "text/plain", "NixieClock IN-14");              });               httpServer.on("/description.xml", HTTP_GET, [](){               SSDP.schema(httpServer.client());              });               httpServer.begin();          }     char* string_to_char(String char_var){ //преобразуем в char          char char_var_resp[char_var.length()];          char_var.toCharArray(char_var_resp, char_var.length()+1);          return char_var_resp;          }           // Чтение данных их ячейки    String read_EEPROM(int star_t, int end_t){             String data;             for(int i = star_t; i < end_t; ++i){             int bu = EEPROM.read(i);               if(bu > 31 && bu < 241){                  data += char(bu);               }               }             return data;          }     // запись данных в ячейки         String save_EEPROM (String data, int cell_start, int cell_end){                     for (int i = cell_start; i < cell_end; ++i) //стираем данные перед записью            {              EEPROM.write(i, 0);              }           for (int i = 0; i < data.length(); ++i) //записываем данные в ячейки             {             EEPROM.write(cell_start+i, data[i]);             }                 EEPROM.commit();                     return data;    }              

После удачной прошивки и первом включении, часы создадут Wi-Fi точку доступа. Для конфигурации часов необходимо подключиться к созданной точке доступа (пароль сети указан в прошивке) и перейдя по IP адресу 192.168.4.1 в браузере вашего устройства, выполнить не сложную настройку часов. Ниже представлен скриншот интерфейса устройства:

Для настройки часов, вам необходимо будет подключиться к вашей Wi-Fi сети, указать NTP сервер и ваш часовой пояс. Затем перезагрузить часы. Всё, часы готовы к использованию.

Что в итоге?

В итоге у нас получились простые в реализации часы на ламповых индикаторах, где не требуется применять антикварные микросхемы типа К155ИД1, вся схема выполнена на современной элементной базе. Часы не нуждаются в ручной настройке времени, синхронизация времени выполняется автоматически с удаленного NTP сервера, что гарантирует постоянную точность времени. Разработанный драйвер показал хорошие результаты надежности, работая уже более пяти лет.

Есть желание собрать часы на базе этого драйвера с применением ламп ИН-18, но пока стоимость ламп меня пугает).

Спасибо, что дочитали до конца! Если статья понравилась, то вы знаете что делать. И как всегда, вопросы, пожелания, осуждение? 🙂 — добро пожаловать в комментарии. До встречи в новых статьях!

Небольшой бонус, фото из архива

Ссылки к статье:


Возможно, захочется почитать и это:


ссылка на оригинал статьи https://habr.com/ru/articles/777136/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *