Радиолампы, словно артефакты из прошлого, олицетворяют нечто большее, чем просто технологию. Они несут в себе определенную магию, отражающую уникальное сочетание технического мастерства и эстетики. Не удивительно, что часы на неоновых индикаторах занимают довольно уникальную нишу в мире дизайна и интерьера. Они представляют собой не просто инструмент для отображения времени, но и элемент декора, который может значительно изменить атмосферу помещения. Этой статье я расскажу о своем опыте создания 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, но пока стоимость ламп меня пугает).

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

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



















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



Моё мобильное приложение для быстрого поиска и доступа к моим(и не только) самодельным устройствам.



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


Комментарии (55)


  1. kalapanga
    08.12.2023 08:23
    +3

    Раз 3Д-печать уже используется, я бы напечатал для индикаторов посадочные панельки - шайбы с отверстиями, чтобы индикаторы не висели в воздухе. Паять легче и результат аккуратнее. На фото, где часы на столе, видно что индикаторы гуляют кто куда.


    1. CyberexTech Автор
      08.12.2023 08:23
      +2

      На самом деле, индикаторы довольно жестко стоят на плате.


      1. kalapanga
        08.12.2023 08:23
        +5

        Жестко, но криво.


        1. CyberexTech Автор
          08.12.2023 08:23

          Рекомендую к прочтению: https://habr.com/ru/articles/779228/


          1. vvzvlad
            08.12.2023 08:23
            +3

            Вас никто не обесценивал


            1. CyberexTech Автор
              08.12.2023 08:23

              Спасибо, я ценю это.


  1. vvbob
    08.12.2023 08:23
    +4

    Классно!

    "На тягу не влияет", конечно, но я бы сделал в платках отверстия и поставил их на стойки, или смоделировал стойки из пластика самого корпуса. Так как-то аккуратнее чем термоклеем клеить. Но в принципе это так, перфекционизм, не особо важно, конечно, внутри корпуса не видно.


    1. CyberexTech Автор
      08.12.2023 08:23
      +3

      Спасибо!


  1. REPISOT
    08.12.2023 08:23
    +3

    Я бы высоковольтную часть таки разместил на плате ламп. Или хотя бы на расстоянии от цифровой части на ESP8266. Места-то у вас хватает.


    1. CyberexTech Автор
      08.12.2023 08:23
      +3

      Я не вижу каких-либо проблем в текущем расположении высоковольтного генератора. Схема показала свою надежность, работая непрерывно уже почти шесть лет. Но в своей реализации вы можете переместить генератор куда угодно :)


      1. REPISOT
        08.12.2023 08:23
        +5

        Это ошибка выжившего.

        Нормы безопасности не просто так придуманы. Для напряжений 171-250V (не сетевое, там вообще 8 мм) зазор должен быть не менее 1.25 мм на внешних слоях. А у вас?


        1. CyberexTech Автор
          08.12.2023 08:23

          Можно ссылки на эти нормы скинуть?



          1. REPISOT
            08.12.2023 08:23
            +1

            IPC-2221B


            1. CyberexTech Автор
              08.12.2023 08:23
              -1

              И что конкретно я там должен увидеть?


  1. Jury_78
    08.12.2023 08:23
    +2

    то будем «размножать» пины управления с помощью дешифраторов CD4028BM96

    Почему не PCF8574 ? Экономнее по управлению - I2C.


    1. CyberexTech Автор
      08.12.2023 08:23
      +1

      Всё банально и просто :) Собирал из того, что было у меня в наличии. Да и применение CD4028BM96, с точки зрения экономики, выгоднее.


  1. DarkWolf13
    08.12.2023 08:23
    +3

    все прекрасно, но логичнее все-таки сервер времени держать внутри домашней сети. У сея я эту задачу запустил на роутере, который синхронизируется с сетевым сервером и/или спутниками. Таким образом в домашней сети свой сервер времени 2/3 уровня. и не надо будет менять настройки сервера времени если он перестанет быть доступен во внешнем интернете.


    1. CyberexTech Автор
      08.12.2023 08:23
      +2

      Да, безусловно. Но пока проблем с NTP сервером не возникало. Можно сделать модуль на базе esp8266|esp32 c GPS приемником и использовать его в качестве локального NTP сервера.


      1. Harwest
        08.12.2023 08:23
        +1

        А подключить DS3231 с литиевой "таблеткой" к ESP?


        1. CyberexTech Автор
          08.12.2023 08:23
          +1

          Для чего усложнять схему? Если esp8266 уже имеет на борту модуль Wi-Fi, что обеспечивает возможность использования данных точного времени с NTP серверов? Я понимаю если бы применялся микроконтроллер без сетевых интерфейсов, то да RTC DS3231 имел бы смысл использовать. Системы должны быть просты и не перегружены лишними элементами.


          1. Harwest
            08.12.2023 08:23
            +1

            То есть модуль esp32 c gps - это не 'переусложнять'? :))

            А если us.pool.ntp.org возьмут и роскомпозорнут?


            1. Winnie_The_Pooh
              08.12.2023 08:23
              +1

              NTP серверов, включая Российские - сколько угодно.


      1. Winnie_The_Pooh
        08.12.2023 08:23
        +1

        GPS приемник в комнате далеко не везде работает. Например у меня в комнате сигналы времени принимаются только около окон. Делать часы с выносным блоком неудобно.


  1. kekoz
    08.12.2023 08:23
    +3

    О, именно самодельные цифровые часы с газоразрядными индикаторами в 1978 и стали моей дорогой в цифровую электронику. Только конструировать их пришлось на элементарной ТТЛ (К133). И хотя у нас была “Служба точного времени”, никто бы меня подключиться к ней не пустил, конечно. Слишком уж военная она была :)


    1. CyberexTech Автор
      08.12.2023 08:23
      +1

      О, да. Мой путь в цифровую эру тоже начинался с устройств на жёсткой логике. Это были интересные времена.


  1. da-nie
    08.12.2023 08:23
    +1

    Данный режим переводит работу ламп в импульсный режим, что положительно сказывается на их срок службы.

    Что-то я в этом сомневаюсь. Дело в том, что импульсный ток лампы в этом случае должен быть сильно больше, чем в статике (так, чтобы средний ток соответствовал току в статическом режиме).


    1. sim2q
      08.12.2023 08:23
      +2

      Если не ошибаюсь, на радиокоте этому холивару были посвящены тысячные треды :)
      но я бы тоже делал динамику, впрочем для мною используемых индикаторов по другому и не получится


      1. CyberexTech Автор
        08.12.2023 08:23

        Да, реализаций полно, но мне хотелось сделать что-то простое и на современной элементной базе.


    1. xSVPx
      08.12.2023 08:23

      Есть ощущение, что это не обязательно в связи с строением глаза в том числе. Т.е. при очень низком постоянном токе лампа не будет гореть, а при импульсном глаз просто интегрирует это все и вы будете видеть чуть менее яркое свечение.


      1. CyberexTech Автор
        08.12.2023 08:23

        Тут суть в том, что ГРИ подвержены катодному отравлению, особенно на малых токах. Динамическая индикация конечно может снижать яркость свечения при использовании рекомендованного тока электрода, но динамическая индикации переводит лампу на импульсный режим и чтобы повысить яркость свечения лампы в этом режиме, нам нужно завышать ток. А одним из методом борьбы с катодным отравлением, является питание лампы повышенным током.


        1. da-nie
          08.12.2023 08:23
          +1

          в случае редкого включения отдельных индикаторных катодов и активности
          других, частицы металла, распыляемого работающими катодами, оседают на
          редко используемых, что способствует их «отравлению».

          Когда вы подаёте повышенный ток, увеличивается количество распыляемых частиц с электрода. Эти частицы оседают на других электродах. Метод восстановления как раз связан с увеличением распыления с дефектного электрода (на все остальные). В каком случае идикатор выйдет из строя быстрее, когда катоды медленно пылят или когда пылят много (ток больше)? В импульсном режиме средний ток совпадает со статическим. Но вот распыление от тока линейно или нет? В зависимости от ответа на этот вопрос, выйдет ответ больше среднее ли испарение с катодов в импульсном режиме по сравнению со статическим.


          1. CyberexTech Автор
            08.12.2023 08:23

            Ок


    1. CyberexTech Автор
      08.12.2023 08:23
      -1

      Отбросьте всякие сомнения...


      1. da-nie
        08.12.2023 08:23

        Что в руки взять нельзя - того для вас и нет,

        С чем не согласны вы - то ложь одна и бред,

        Что вы не взвесили - за вздор считать должны,

        Что не чеканили - в том будто нет цены.


        1. CyberexTech Автор
          08.12.2023 08:23
          -3

          Рекомендую к прочтению https://habr.com/ru/articles/779228/


          1. da-nie
            08.12.2023 08:23
            +1

            Не рекомендуйте. Вам это не идёт. Но раз в дело пошли наезды с "рекомендациями", стало быть, аргументов нет. А раз вас так покоробило от цитаты из Фауста, стало быть, вы явно не в себе (вечная обиженка?) и любые замечания пытаетесь перевести на оппонента (выше уже был такой случай).


  1. vesowoma
    08.12.2023 08:23
    +2

    Интересный подход, хотя и местами спорный, во многом соглашусь с предыдущими комментариями, но все равно плюсик. По поводу "панелек" - без них у меня в тишине слышно когда лампа работает - какое-то жужжание, правда и длина выводов была около 25 мм, после жесткой посадки на суперклей, жужжание заметно стихло. Вибрации? Индикация динамическая, возможно из-за этого.

    Вот https://habr.com/ru/articles/170551/ можно сравнить со статьей 10-летней давности про подобные часы. По той статье даже сделал несколько часов, но с иной схемой до 155ИД1


    1. CyberexTech Автор
      08.12.2023 08:23

      Отличная статья


  1. VT100
    08.12.2023 08:23
    +3

    Нравится.

    Но, не смотря на отсутствие светодиодной "досветки" - сделать замечания надо:

    Хоть какую-то защиту от пропадания НТП предусмотреть полезно;

    Вместо оптронов - вполне можно обойтись 300 В транзисторами (MMBT/MPS/PZT)A42 (нижний ключ и предварительный каскад верхнего) и A92 (верхний ключ). Если нижний ключ сделать по схеме с общей базой - будет бесплатная стабилизация тока сегментов.


    1. CyberexTech Автор
      08.12.2023 08:23

      Подсветку сделать элементарно, просто добавив адресные светодиоды типа ws2812b. Транзисторы я раньше использовал, но применение оптронов значительно упростило схему, плюс имеется развязка с высоковольтной цепью. Что касается NTP, то проблем не возникало, даже если сервер не ответит, время не собьётся, а при следующей синхронизации обновится.


      1. VT100
        08.12.2023 08:23
        +1

        Постарался запутать слова, наверное, - я всецело одобряю отсутствие подсветки баллонов.

        Развязки в текущей схеме - нет.


        1. CyberexTech Автор
          08.12.2023 08:23

          Развязки в текущей схеме - нет.

          Странно это читать. Посмотрите схему подключения ламп.


          1. zatim
            08.12.2023 08:23
            +2

            Ее действительно нет. У источника 200 в и источника питания логики - общий gnd. Поэтому использование оптронов бессмысленно, действительно можно было бы обойтись более дешевыми транзисторами.


            1. CyberexTech Автор
              08.12.2023 08:23

              Удачи с выживанием вашего микроконтроллера при пробое транзистора.


              1. VT100
                08.12.2023 08:23
                +1

                Вероятность пробоя транзистора с напряжением пробоя 300 В при коммутации 200 (без индуктивной составляющей) - достаточно мала.

                Более того, добавив 2 детали - можно защитить от этой вероятности все выходы на индикаторы.


      1. Winnie_The_Pooh
        08.12.2023 08:23
        +1

        Раз в шесть часов 8266 ходит за временем. Но и без синхронизации все работает достаточно точно.


  1. N1Tron1X
    08.12.2023 08:23
    +1

    Исходными файлами самой платы не поделитесь? Как раз в долгом ящике лежала идея разработки таких часов с синхронизацией через интернет (правда рассматривал Ардуино за основу)


    1. CyberexTech Автор
      08.12.2023 08:23

      Гербер файлы устроят? Но в современных реалиях я бы рекомендовал использовать микроконтроллер eps32, там достаточно выводов для управления лампами, что снимает необходимость использования дешифраторов для размножения контактов. Плата получится компактной и простой (если использовать динамический тип индикации).


      1. N1Tron1X
        08.12.2023 08:23
        +1

        Для первого опыта - более чем устроят) планировал собрать наиболее простую рабочую версию, а уже в дальнейшем постепенно заниматься доработкой по мере необходимости


        1. CyberexTech Автор
          08.12.2023 08:23
          +1

          Добавил ссылку в конце статьи на GitHub с проектом платы и исходным кодом прошивки.


  1. krapivinmaksim
    08.12.2023 08:23
    +3

    Hidden text
    Yet another nixie clock
    Yet another nixie clock

    Не так давно тоже решил сделать еще одни часы с ламповыми индикаторами. Основной концепцией стало совмещение прошлого и настоящего. Поэтому в качестве духа современности решил сделать платы с smd монтажом, как сердце проекта использовать мк RPI pico и адресные светодиоды ws2812b. Ну а чтоб уж совсем злободневно стало - код писал на micropython.


    1. CyberexTech Автор
      08.12.2023 08:23

      Отлично получилось! Почему бы вам не написать статью о этом проекте? Было бы интересно почитать.


      1. Harwest
        08.12.2023 08:23
        +2

        Hidden text

        Недавно сделал современные часы-информер на восьми матрицах 8х8. Основной контроллер - esp8266 с самосборной прошивкой Tasmota, доп. платы - BH1750 и DS3231.

        BH1750 отслеживает освещенность в кухонной зоне, уровень в lux отправляется на сервер Homeassistant (НА). Яркость часов меняется либо с сервера НА по mqtt, либо локальным скриптом модуля esp, все это в соответствии с освещенностью. Над разделочной зоной - линейная LED подсветка с диммированием, ее 'стартовая' яркость также зависит от уровня освещенности.

        Плюс к этому с 7ч утра и до 23ч эти часы работают информером: НА один раз в минуту отправляет в mqtt топик часов текущую температуру и влажность на улице, бегущей строкой.

        Если дома никого нет - индикация часов полностью отключается экономя ЭЭ.


        1. CyberexTech Автор
          08.12.2023 08:23

          Красиво!