Ретро часы на ESP32 содержать четыре индикатора тлеющего разряда ИН-14 и два индикатора ИН-16. Индикаторы ИН-14 используются для индикации часов и минут, а ИН-16 для секунд. Возможно использование других индикаторов тлеющего разряда. Часы помимо индикаторов содержат стабилизатор 5 В (7805), повышающий преобразователь на 180 В (IRF840), шесть транзисторных оптопар (TLP627), дешифратор (К155ИД1) и плату ESP32. Сами часы работают от напряжения 12 В (можно от 9 В), стабилизатор на 5 В необходим для питания платы ESP32 и дешифратора.
Повышающий преобразователь работает на частоте 25 кГц и управляется микроконтроллером ESP32. С выхода D15 микроконтроллера сигнал ШИМ уровня 3,3 В подается на базу транзистора BC547 который работает в ключевом режиме. Далее с коллектора ШИМ (уровень сигнала 12 В) сигнал подается на затвор MOSFET транзистора IRF840.
MOSFET транзистор в схеме преобразователя представляет собой ключ, при открытом ключе через индуктивность L1 течет ток, который линейно растет. При этом, в связи с явлением самоиндукции, ток через индуктивность не может измениться моментально, так как в это время идёт постепенный запас энергии (ЭДС) в магнитном поле катушки. После закрытия ключа ток продолжает идти уже через открытый диод в нагрузку. Поскольку источник питания и катушка в этой цепи соединены последовательно, то их ЭДС складываются. Таким образом происходит повышение напряжения.
На выходе повышающего преобразователя установлен индикатор ИН-3, который сигнализирует о наличии высокого напряжения. Выходное напряжение преобразователя настраивается программно, путем изменения длительности импульса сигнала ШИМ.
Индикация часов динамическая, в ее состав входит К155ИД1, который представляет собой двоично-десятичный дешифpатоp с высоковольтным выходом. Выходы дешифратора напрямую управляют работой всех катодов индикаторных ламп. Управление анодами индикаторов осуществляется при помощи шести транзисторных оптопар.
Плата управления ESP32 DevKit v1 Wi-Fi Bluetooth ESP32-WROOM-32 поддерживает динамическую индикацию и управление ШИМ сигналом. Дополнительно ESP32 используется для получения точного времени NTP.
Протокол NTP используется для синхронизации часов компьютера или иного устройства с сервером времени по сети. Любое сетевое устройство может послать сетевой пакет определённого вида серверу времени, а тот в ответ пришлёт точное значение времени. Запросившее устройство установит это значение на своих системных часах. Таким образом осуществляется синхронизация времени.
Так как часам для подключения к NTP-серверу необходим Интернет, то плата ESP32 при авторизации в Wi-Fi сети будет работать как точка доступа, а в режиме синхронизации времени как Веб-сервер.
Для авторизации в сети Wi-Fi переводится в режим точки доступа с фиксированным IP адресом — 192.168.4.1 , для перевода ESP32 в режим точки доступа необходимо на пин D23 подать GND (нажать фиксированную кнопку AT/STA) и нажать кнопку RESET (или отключить и снова включить питание).
Далее необходимо подключится к точке доступа ESPap, ввести пароль — 12345678.
Далее в браузере в адресной строке указать адрес дочки доступа http://192.168.4.1
После как указали имя и пароль Вашей Wi-Fi сети, нажмите на кнопку «Сохранить настройки» , в энергонезависимую память будут сохранены параметры Вашей Wi-Fi сети, после чего появится сообщение:
Удалите перемычку с D23 (отжать кнопку AT/STA), отключитесь от сети ESPap, перезагрузите ESP32, подключитесь к Вашей сети Wi-Fi.
В браузере в адресной строке укажите IP адрес 192.168.1.33 (пример)
Часы содержат следующие функциональные возможности:
- Изменение временного сдвига от UTC (Москва находится в часовом поясе UTC+3)
- Установка яркости свечения индикаторов в дневное время
- Установка яркости свечения индикаторов в ночное время
- Установка интервала ночного времени работы часов
- Антиотравление катодов индикаторов тлеющего разряда (ровно в 30 минут каждого часа происходит перебор всех цифр)
Перед загрузкой скетча рекомендую ознакомится со статьей — ESP32 DevKit v1 Wi-Fi Bluetooth ESP32-WROOM-32 (Arduino IDE).
#define ID1 2 #define ID2 4 #define ID4 16 #define ID8 17 #define IN1 25 #define IN2 26 #define IN3 27 #define IN4 14 #define IN5 12 #define IN6 13 #include <WiFi.h> #include <NTPClient.h> // http://rcl-radio.ru/wp-content/uploads/2019/11/ntpclientmaster.zip #include <WiFiUdp.h> #include <WiFiClient.h> #include <WebServer.h> #include <ESPmDNS.h> #include <EEPROM.h> #include "esp_system.h" #ifndef APSSID #define APSSID "ESPap" #define APPSK "12345678" #endif const char *ssid = APSSID; const char *password = APPSK; WebServer server(80); WiFiUDP ntpUDP; ///////////////// https://www.ntppool.org/zone/@ - другие сервера ///////////////////////////// ///////////////// для России - ru.pool.ntp.org ////////////////////////////////////////////////// NTPClient timeClient(ntpUDP, "pool.ntp.org", 21600,3600123);// 21600 - временной сдвиг в секундах от UTC int ss,mm,hh,i,an,segm,brignes,brignes_low, end_night,start_night; long a[6]; unsigned long times, sum, times1; byte len_ssid, len_pass; int w,ip1,ip2,ip3,ip4,utc; hw_timer_t * timer = NULL; volatile SemaphoreHandle_t timerSemaphore; portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; void IRAM_ATTR onTimer() { portENTER_CRITICAL_ISR(&timerMux); switch(i){ case 0: an=10;anod();delayMicroseconds(2000);segm=a[0]; an=0; segment();anod();break; case 1: an=10;anod();delayMicroseconds(2000);segm=a[1]; an=1; segment();anod();break; case 2: an=10;anod();delayMicroseconds(2000);segm=a[2]; an=2; segment();anod();break; case 3: an=10;anod();delayMicroseconds(2000);segm=a[3]; an=3; segment();anod();break; case 4: an=10;anod();delayMicroseconds(2000);segm=a[4]; an=4; segment();anod();break; case 5: an=10;anod();delayMicroseconds(2000);segm=a[5]; an=5; segment();anod();break; } i++;if(i>5){i=0;} portEXIT_CRITICAL_ISR(&timerMux); xSemaphoreGiveFromISR(timerSemaphore, NULL); } void setup(){ timerSemaphore = xSemaphoreCreateBinary(); //////// GPIO /////////////// pinMode(15,OUTPUT); pinMode(23,INPUT_PULLUP);// D23 digitalWrite(15,HIGH); Serial.begin(9600); //////// AP ///////////////// if(digitalRead(23)==LOW){ WiFi.softAP(ssid, password); IPAddress myIP = WiFi.softAPIP(); Serial.print("AP IP address: "); Serial.println(myIP); server.on("/", handleRoot); server.on("/ok", handleOk); server.begin(); Serial.println("HTTP server started"); } else{ //////// STA /////////////// WiFi.mode( WIFI_STA); EEPROM.begin(512); len_ssid = EEPROM.read(96); len_pass = EEPROM.read(97); utc = EEPROM.read(201); brignes = EEPROM.read(200); brignes_low = EEPROM.read(202); start_night = EEPROM.read(203); end_night = EEPROM.read(204); if(brignes > 150){brignes=150;} if(brignes_low > 150){brignes_low=150;} if(len_pass > 64) len_pass = 0; /////// READ EEPROM unsigned char* buf_ssid = new unsigned char[32]; unsigned char* buf_pass = new unsigned char[64]; for(byte i = 0; i < len_ssid; i++) buf_ssid[i] = char(EEPROM.read(i)); buf_ssid[len_ssid] = '\x0'; const char *ssid = (const char*)buf_ssid; for(byte i = 0; i < len_pass; i++) buf_pass[i] = char(EEPROM.read(i + 32)); const char *pass = (const char*)buf_pass; buf_pass[len_pass] = '\x0'; delay(2000); Serial.print("SSID: ");Serial.print(ssid);Serial.print(" ");Serial.print("Password: ");Serial.println(pass); /////// connection WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED){delay (10);Serial.print (".");digitalWrite(15,HIGH);} Serial.println();Serial.print("Connected to ");Serial.println(ssid);Serial.print("IP address: ");Serial.println(WiFi.localIP()); ip1 = WiFi.localIP()[0]; ip2 = WiFi.localIP()[1]; ip3 = WiFi.localIP()[2]; ip4 = WiFi.localIP()[3]; server.begin(); server.on("/", web); server.on("/reset", web0); } ///////// PWM ///////////////////////// ledcSetup(2, 25000, 8); // 25000 kHz / 8 bit / cannel 1 ledcAttachPin(15, 2); // D15 / cannel 1 ledcWrite(2, 255-brignes); ///////// TIMER 3 ///////////////////// timer = timerBegin(3, 8000, true); timerAttachInterrupt(timer, &onTimer, false); timerAlarmWrite(timer, 35, true); timerAlarmEnable(timer); /////////////////////////////////////// pinMode(IN1,OUTPUT);pinMode(IN2,OUTPUT);pinMode(IN3,OUTPUT); pinMode(IN4,OUTPUT); pinMode(IN5,OUTPUT); pinMode(IN6,OUTPUT); pinMode(ID1,OUTPUT); pinMode(ID2,OUTPUT); pinMode(ID4,OUTPUT); pinMode(ID8,OUTPUT); timeClient.setTimeOffset(utc*1800); } void loop(){ delay(1000); server.handleClient(); if(digitalRead(23)==HIGH){ /////////////// times //////////////////////////// if(millis()-times>=1000){times=millis(); while(!timeClient.update()){timeClient.forceUpdate();} hh = timeClient.getHours(); mm = timeClient.getMinutes(); ss = timeClient.getSeconds(); Serial.print(hh/10);Serial.print(hh%10);Serial.print(":");Serial.print(mm/10);Serial.print(mm%10);Serial.print(":");Serial.print(ss/10);Serial.println(ss%10); ///// brightness if(hh>=0&&hh<=end_night||hh>=start_night){ledcWrite(2, 255-brignes_low); }else {ledcWrite(2, 255-brignes);} } sum = hh*10000+mm*100+ss; ///////// /////////////// if(mm==30&&ss<10){ switch(ss){ case 0: sum=000000;break; case 1: sum=111111;break; case 2: sum=222222;break; case 3: sum=333333;break; case 4: sum=444444;break; case 5: sum=555555;break; case 6: sum=666666;break; case 7: sum=777777;break; case 8: sum=888888;break; case 9: sum=999999;break;}} a[0]=sum/100000; a[1]=sum/10000%10; a[2]=sum/1000%10; a[3]=sum/100%10; a[4]=sum/10%10; a[5]=sum%10; } }// loop void segment(){ switch(segm){ case 0: digitalWrite(ID8,LOW);digitalWrite(ID4,LOW);digitalWrite(ID2,LOW);digitalWrite(ID1,LOW);break; // DEC 0 = 0b0000 case 1: digitalWrite(ID8,LOW);digitalWrite(ID4,LOW);digitalWrite(ID2,LOW);digitalWrite(ID1,HIGH);break; // DEC 1 = 0b0001 case 2: digitalWrite(ID8,LOW);digitalWrite(ID4,LOW);digitalWrite(ID2,HIGH);digitalWrite(ID1,LOW);break; // DEC 2 = 0b0010 case 3: digitalWrite(ID8,LOW);digitalWrite(ID4,LOW);digitalWrite(ID2,HIGH);digitalWrite(ID1,HIGH);break; // DEC 3 = 0b0011 case 4: digitalWrite(ID8,LOW);digitalWrite(ID4,HIGH);digitalWrite(ID2,LOW);digitalWrite(ID1,LOW);break; // DEC 4 = 0b0100 case 5: digitalWrite(ID8,LOW);digitalWrite(ID4,HIGH);digitalWrite(ID2,LOW);digitalWrite(ID1,HIGH);break; // DEC 5 = 0b0101 case 6: digitalWrite(ID8,LOW);digitalWrite(ID4,HIGH);digitalWrite(ID2,HIGH);digitalWrite(ID1,LOW);break; // DEC 6 = 0b0110 case 7: digitalWrite(ID8,LOW);digitalWrite(ID4,HIGH);digitalWrite(ID2,HIGH);digitalWrite(ID1,HIGH);break; // DEC 7 = 0b0111 case 8: digitalWrite(ID8,HIGH);digitalWrite(ID4,LOW);digitalWrite(ID2,LOW);digitalWrite(ID1,LOW);break; // DEC 8 = 0b1000 case 9: digitalWrite(ID8,HIGH);digitalWrite(ID4,LOW);digitalWrite(ID2,LOW);digitalWrite(ID1,HIGH);break; // DEC 9 = 0b1001 case 10:digitalWrite(ID8,HIGH);digitalWrite(ID4,HIGH);digitalWrite(ID2,HIGH);digitalWrite(ID1,HIGH);break; // Пусто = 0b1111 }} void anod(){ switch(an){ case 0: digitalWrite(IN1,HIGH);digitalWrite(IN2,LOW);digitalWrite(IN3,LOW);digitalWrite(IN4,LOW);digitalWrite(IN5,LOW);digitalWrite(IN6,LOW);break; case 1: digitalWrite(IN1,LOW);digitalWrite(IN2,HIGH);digitalWrite(IN3,LOW);digitalWrite(IN4,LOW);digitalWrite(IN5,LOW);digitalWrite(IN6,LOW);break; case 2: digitalWrite(IN1,LOW);digitalWrite(IN2,LOW);digitalWrite(IN3,HIGH);digitalWrite(IN4,LOW);digitalWrite(IN5,LOW);digitalWrite(IN6,LOW);break; case 3: digitalWrite(IN1,LOW);digitalWrite(IN2,LOW);digitalWrite(IN3,LOW);digitalWrite(IN4,HIGH);digitalWrite(IN5,LOW);digitalWrite(IN6,LOW);break; case 4: digitalWrite(IN1,LOW);digitalWrite(IN2,LOW);digitalWrite(IN3,LOW);digitalWrite(IN4,LOW);digitalWrite(IN5,HIGH);digitalWrite(IN6,LOW);break; case 5: digitalWrite(IN1,LOW);digitalWrite(IN2,LOW);digitalWrite(IN3,LOW);digitalWrite(IN4,LOW);digitalWrite(IN5,LOW);digitalWrite(IN6,HIGH);break; case 10:digitalWrite(IN1,LOW);digitalWrite(IN2,LOW);digitalWrite(IN3,LOW);digitalWrite(IN4,LOW);digitalWrite(IN5,LOW);digitalWrite(IN6,LOW);break; }} void web(){ timerAlarmWrite(timer, 35, false); delay(300); String webPage = "<meta charset='utf-8'><meta name='viewport' content='width=480, user-scalable=no' />"; //style webPage += "<head><style>.tab1 {background-color:#F5F5F5;border-radius: 5px;margin: auto;}"; webPage += "#menu1{list-style:none; width:100%; padding:0 0 0 0 ; margin: 0 0 0 0;background: #999;}"; webPage += "#menu1 li{float:left; font:bold 14px Arial;text-shadow: 1px 1px 0px #000;}"; webPage += "#menu1 a{color:#fff; display:block; height:36px; line-height:36px; padding:10px 10px 10px 10px; background:#4682B4; text-decoration:none;}"; webPage += "#menu1 a:hover{color:#fff; background:#555;}"; webPage += "#menu1 b{color:#fff; display:block; height:25px; line-height:36px; padding:10px 10px 10px 10px; background:#4682B4; text-decoration:none;}"; webPage += "</style></head>"; ///////////// webPage += "<TABLE class='tab1' align='center' width='470' BORDER='1' cellspacing='0' cellpadding='5'>"; webPage +="<td colspan='2'><h1><center>ESP32<br>Страница настройки Интернет часов</h1>"; webPage +="<tr><td colspan='2'><center><big>Текущее время: "; webPage += hh/10;webPage += hh%10; webPage +=":";webPage +=mm/10; webPage += mm%10; webPage +=":";webPage +=ss/10;webPage += ss%10; webPage += "</center>"; webPage += "<form method='POST' action='reset' >"; webPage += "<tr><td colspan='2'><input name='time_utc' autocomplete='off' maxlength='3' size='1'> Временной сдвиг UTC (30 мин)"; webPage += "<br><small>Установлено: ";EEPROM.begin(512);webPage += EEPROM.read(201); webPage += " (";webPage += utc*0.5;webPage += " ч.)"; webPage += "<tr><td><input name='brignes' autocomplete='off' maxlength='3' size='1'> Яркость день 0-150"; webPage += "<br><small>Установлено: ";EEPROM.begin(512);webPage += EEPROM.read(200); webPage += "<td><input name='brignes_low' autocomplete='off' maxlength='3' size='1'> Яркость ночь 0-150"; webPage += "<br><small>Установлено: ";EEPROM.begin(512);webPage += EEPROM.read(202); webPage += "<tr><td colspan='2'><center> Время работы ночного режима подсветки <tr>"; webPage += "<tr><td> Начало (18-23 ч) <input name='start_night' autocomplete='off' maxlength='3' size='1'>"; webPage += "<br><small>Установлено: ";EEPROM.begin(512);webPage += EEPROM.read(203); webPage += "<td> Конец: (1-12 ч) <input name='end_night' autocomplete='off' maxlength='3' size='1'>"; webPage += "<br><small>Установлено: ";EEPROM.begin(512);webPage += EEPROM.read(204); webPage += "<tr><td colspan='2'><center><br><input type=SUBMIT value='Сохранить настройки'>"; webPage += "</form>"; webPage += "<center><br>Заполните все ячейки формы, незаполненная ячейка формы не изменит ранее установленный параметр"; server.send(200, "text/html", webPage); timerAlarmWrite(timer, 35, true); timerAlarmEnable(timer); } void sumbit(){ if (server.args() > 0 ) { for ( uint8_t i = 0; i < server.args(); i++ ) { if (server.argName(i) == "brignes"){brignes = server.arg(i).toInt();} if (server.argName(i) == "time_utc"){utc = server.arg(i).toInt();} if (server.argName(i) == "brignes_low"){brignes_low = server.arg(i).toInt();} if (server.argName(i) == "start_night"){start_night = server.arg(i).toInt();} if (server.argName(i) == "end_night"){end_night = server.arg(i).toInt();} } Serial.println("Save"); if(brignes > 150){brignes=150;} if(brignes < 0){brignes=0;} if(brignes_low > 150){brignes_low=150;} if(brignes_low < 0){brignes_low=0;} if(brignes==NULL){}else{EEPROM.write(200,brignes);} if(utc==NULL){}else{EEPROM.write(201,utc);} if(brignes_low==NULL){}else{EEPROM.write(202,brignes_low);} if(start_night==NULL){}else{EEPROM.write(203,start_night);} if(end_night==NULL){}else{EEPROM.write(204,end_night);} EEPROM.commit(); EEPROM.end(); EEPROM.begin(512); brignes = EEPROM.read(200); utc = EEPROM.read(201); brignes_low = EEPROM.read(202); start_night = EEPROM.read(203); end_night = EEPROM.read(204); timeClient.setTimeOffset(utc*1800); if(hh>=0&&hh<=6||hh>=23){ledcWrite(2, 255-brignes_low);}else {ledcWrite(2, 255-brignes);} }} void handleRoot() { timerAlarmWrite(timer, 35, false); delay(300); String str = "<meta charset='utf-8'><meta name='viewport' content='width=480, user-scalable=no' />"; //style str += "<head><style>.tab1 {background-color:#F5F5F5;border-radius: 5px;margin: auto;}"; str += "#menu1{list-style:none; width:100%; padding:0 0 0 0 ; margin: 0 0 0 0;background: #999;}"; str += "#menu1 li{float:left; font:bold 14px Arial;text-shadow: 1px 1px 0px #000;}"; str += "#menu1 a{color:#fff; display:block; height:36px; line-height:36px; padding:10px 10px 10px 10px; background:#4682B4; text-decoration:none;}"; str += "#menu1 a:hover{color:#fff; background:#555;}"; str += "#menu1 b{color:#fff; display:block; height:25px; line-height:36px; padding:10px 10px 10px 10px; background:#4682B4; text-decoration:none;}"; str += "</style></head>"; //////////// str += "<TABLE class='tab1' align='center' width='470' BORDER='1' cellspacing='0' cellpadding='10'>"; str +="<td><h1><center>ESP32<br>Авторизация</h1>"; str += "<form method='POST' action='ok'>"; str += "<tr><td><input name='ssid' autocomplete='off'><big> Имя Wi-Fi сети"; str += "<tr><td><input name='pswd' autocomplete='off'><big> Пароль"; str += "<tr><td><center><input type=SUBMIT value='Сохранить настройки'>"; str += "</form>"; server.send ( 200, "text/html", str ); timerAlarmWrite(timer, 35, true); timerAlarmEnable(timer); } void handleOk(){ timerAlarmWrite(timer, 35, false); delay(300); String ssid_ap; String pass_ap; unsigned char* buf = new unsigned char[64]; String str = "<meta charset='utf-8'><meta name='viewport' content='width=480, user-scalable=no' />";; str += "<body>"; EEPROM.begin(512); ssid_ap = server.arg(0); pass_ap = server.arg(1); if(ssid_ap != ""){ EEPROM.write(96,ssid_ap.length()); EEPROM.write(97,pass_ap.length()); ssid_ap.getBytes(buf, ssid_ap.length() + 1); for(byte i = 0; i < ssid_ap.length(); i++) EEPROM.write(i, buf[i]); pass_ap.getBytes(buf, pass_ap.length() + 1); for(byte i = 0; i < pass_ap.length(); i++) EEPROM.write(i + 32, buf[i]); EEPROM.commit(); EEPROM.end(); const char *ast_ssid_ap = ssid_ap.c_str(); const char *ast_pass_ap = pass_ap.c_str(); ///////////////////////// WiFi.begin(ast_ssid_ap, ast_pass_ap); // Wait for connection while (WiFi.status() != WL_CONNECTED){delay (10);Serial.print (".");} /////////////////////////////////////////////////////////////////////////// str +="<big><center>Конфигурация сохранена в память<br>"; str +="<big><center>Уберите перемычку с D23 и перезагрузите ESP32</p><br>"; str +="<big><b><center>"; str += WiFi.localIP().toString(); str +="</b><hr>"; str +="<a href=\"/\">Return</a> to settings page</br>"; } else { str += "<big><center>No WIFI Net</br>\ <a href=\"/\">Return</a> to settings page</br>"; } str += "</body></html>"; server.send ( 200, "text/html", str ); timerAlarmWrite(timer, 35, true); timerAlarmEnable(timer); } void web0() { timerAlarmWrite(timer, 35, false); delay(300); String str0 = "<meta charset='utf-8'><meta name='viewport' content='width=480, user-scalable=no' />"; str0 += "<center><b><big>Настройки сохранены.<br> Для возврата на предыдущую страницу перейдите по <a href='./'>ссылке</a>"; server.send (200, "text/html", str0); sumbit(); timerAlarmWrite(timer, 35, true); timerAlarmEnable(timer); }
При прошивке ESP32 необходимо отключать питание 12 В
Форум — http://forum.rcl-radio.ru/viewtopic.php?pid=3367#p3367
Доработка № 1
Добавление в меню режима антиотравления катодов, перебор цифр 1-4 раза в час, отключение.
Скетч — http://forum.rcl-radio.ru/viewtopic.php?pid=3370#p3370
Доработка № 2
Вывод на индикаторы даты и месяца (в течении 4 секунд каждую минуту в 30 секунд), активация режима в меню.
Скетч — http://forum.rcl-radio.ru/viewtopic.php?pid=3371#p3371
Доработка № 3
Добавлено два будильника, будильник работает в нескольких режимах: по будням, по выходным, каждый день, выключено. Добавлена кнопка отключения сигнала будильника.
Скетч — http://forum.rcl-radio.ru/viewtopic.php?pid=3373#p3373