commit 228bfd031ec63d9ff142acb43f9a31f50d78849b Author: willem Date: Tue May 3 07:21:56 2022 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c21683f --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +.DS_Store +.pio +.vscode + +# kicad Temporary files +*.000 +*.bak +*.bck +*.kicad_pcb-bak +*.kicad_sch-bak +*.kicad_prl +*.sch-bak +*~ +_autosave-* +*.tmp +*-save.pro +*-save.kicad_pcb +fp-info-cache + +# Netlist files (exported from Eeschema) +*.net + +# Autorouter files (exported from Pcbnew) +*.dsn +*.ses + +# Exported BOM files +*.xml +*.csv + +# other files +CAD/Leo_muziekdoos_ESP32/~$ESP32 Pins.xlsx diff --git a/esp32nixie/.gitignore b/esp32nixie/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/esp32nixie/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/esp32nixie/doc/Scheme_Nixie_Clock_MCU_NCM109_v1.1-min-1.jpg b/esp32nixie/doc/Scheme_Nixie_Clock_MCU_NCM109_v1.1-min-1.jpg new file mode 100644 index 0000000..13eb08b Binary files /dev/null and b/esp32nixie/doc/Scheme_Nixie_Clock_MCU_NCM109_v1.1-min-1.jpg differ diff --git a/esp32nixie/doc/hv5122.pdf b/esp32nixie/doc/hv5122.pdf new file mode 100644 index 0000000..bd9ed06 Binary files /dev/null and b/esp32nixie/doc/hv5122.pdf differ diff --git a/esp32nixie/include/README b/esp32nixie/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/esp32nixie/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/esp32nixie/lib/README b/esp32nixie/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/esp32nixie/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/esp32nixie/platformio.ini b/esp32nixie/platformio.ini new file mode 100644 index 0000000..0454d9a --- /dev/null +++ b/esp32nixie/platformio.ini @@ -0,0 +1,21 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp-wrover-kit] +platform = espressif32 +board = esp-wrover-kit +framework = arduino +lib_deps = + paulstoffregen/Time@^1.6.1 + jchristensen/Timezone@^1.2.4 + khoih-prog/ESP_WifiManager@^1.10.2 + raronoff/clickButton +lib_ldf_mode = deep+ +monitor_speed = 115200 diff --git a/esp32nixie/src/LEDControl.h b/esp32nixie/src/LEDControl.h new file mode 100644 index 0000000..2a9023e --- /dev/null +++ b/esp32nixie/src/LEDControl.h @@ -0,0 +1,137 @@ +/* + ESP32 NTP Nixie Tube Clock Program + + LEDControl.h - LED Control Code +*/ + +#ifndef LED_CONTROL_H +#define LED_CONTROL_H + +// A 24 bit color type +typedef struct { + byte red; + byte green; + byte blue; +} +RGB24; + +// Misc color definitions +RGB24 black = { 0, 0, 0}; +RGB24 blue = { 0, 0, 127}; +RGB24 green = { 0, 127, 0}; +RGB24 cyan = { 0, 127, 127}; +RGB24 red = {127, 0, 0}; +RGB24 magenta = {127, 0, 127}; +RGB24 yellow = {127, 127, 0}; +RGB24 white = {127, 127, 127}; + +#define LED_RED_CHANNEL 1 +#define LED_GREEN_CHANNEL 2 +#define LED_BLUE_CHANNEL 3 + +// LEDControl Class Definition +class LEDControl { + public: + // Class constructor + LEDControl(int _redPin, int _greenPin, int _bluePin) { + + // Save incoming pin assignments + redPin = _redPin; + greenPin = _greenPin; + bluePin = _bluePin; + + // Set up the output pins for the RGB LED + pinMode(redPin, OUTPUT); + pinMode(greenPin, OUTPUT); + pinMode(bluePin, OUTPUT); + + // Each channel is set up for 12kHz and 10-bit resolution + ledcSetup(LED_RED_CHANNEL, 12000, 10); + ledcSetup(LED_GREEN_CHANNEL, 12000, 10); + ledcSetup(LED_BLUE_CHANNEL, 12000, 10); + + ledcAttachPin(redPin, LED_RED_CHANNEL); + ledcAttachPin(greenPin, LED_GREEN_CHANNEL); + ledcAttachPin(bluePin, LED_BLUE_CHANNEL); + + // Turn off RGB LEDs + ledcWrite(LED_RED_CHANNEL, 0); + ledcWrite(LED_GREEN_CHANNEL, 0); + ledcWrite(LED_BLUE_CHANNEL, 0); + } + + // Input a value 0 to 255 to get a color value. + // The colors are a transition r - g - b - back to r. + RGB24 colorWheel(int wheelPos) { + RGB24 color; + + wheelPos %= 256; + + if (wheelPos < 85) { + color.red = 255 - wheelPos * 3; + color.green = wheelPos * 3; + color.blue = 0; + } + else if (wheelPos < 170) { + wheelPos -= 85; + color.red = 0; + color.green = 255 - wheelPos * 3; + color.blue = wheelPos * 3; + } + else { + wheelPos -= 170; + color.red = wheelPos * 3; + color.green = 0; + color.blue = 255 - wheelPos * 3; + } + return color; + } + + // Set the RGB LEDs color + void setLEDColor(byte red, byte green, byte blue) { + red = gammaArray[red]; + green = gammaArray[green]; + blue = gammaArray[blue]; + + // Change 8 bits to 10 bits + int rred = map(red, 0, 255, 0, 1023); + int rgrn = map(green, 0, 255, 0, 1023); + int rblu = map(blue, 0, 255, 0, 1023); + + // Use PWM to control LED brightness + ledcWrite(LED_RED_CHANNEL, rred); + ledcWrite(LED_GREEN_CHANNEL, rgrn); + ledcWrite(LED_BLUE_CHANNEL, rblu); + } + + // Set the RGB LEDs color + void setLEDColor(RGB24 color) { + setLEDColor(color.red, color.green, color.blue); + } + + private: + // Private data + int redPin, greenPin, bluePin; + + // Gamma correction array + const byte gammaArray [256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, + 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, + 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, + 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, + 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, + 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, + 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, + 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110, 112, 114, + 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137, 138, 140, 142, + 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175, + 177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, + 215, 218, 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255 + }; +}; + +#endif diff --git a/esp32nixie/src/NTP.h b/esp32nixie/src/NTP.h new file mode 100644 index 0000000..deebc88 --- /dev/null +++ b/esp32nixie/src/NTP.h @@ -0,0 +1,168 @@ +/* + ESP32 NTP Nixie Tube Clock Program + + NTP.h - Network Time Protocol Functions +*/ + +#ifndef NTP_H +#define NTP_H + +#include +#include + +// Define the time between sync events +#define SYNC_INTERVAL_HOURS 1 +#define SYNC_INTERVAL_MINUTES (SYNC_INTERVAL_HOURS * 60L) +#define SYNC_INTERVAL_SECONDS (SYNC_INTERVAL_MINUTES * 60L) + +#define NTP_SERVER_NAME "time.nist.gov" // NTP request server +#define NTP_SERVER_PORT 123 // NTP requests are to port 123 +#define LOCALPORT 2390 // Local port to listen for UDP packets +#define NTP_PACKET_SIZE 48 // NTP time stamp is in the first 48 bytes of the message +#define RETRIES 20 // Times to try getting NTP time before failing + +class NTP { + public: + NTP(NixieTubeShield& shield) : _shield(shield) { + // Login succeeded so set UDP local port + udp.begin(LOCALPORT); + }; + + static NTP& getInstance() { + return *_instance; + } + + static void createSingleton(NixieTubeShield& shield) { + NTP* ntp = new NTP(shield); + _instance = ntp; + } + + // Get NTP time with retries on access failure + time_t getTime() { + unsigned long result; + + for (int i = 0; i < RETRIES; i++) { + result = _getTime(); + if (result != 0) { + // Update RTC + tmElements_t tm; + breakTime(result, tm); + _shield.setRTCDateTime(tm); + return result; + } + Serial.println("Problem getting NTP time. Retrying..."); + delay(300); + } + Serial.println("NTP Problem - Could not obtain time. Falling back to RTC"); + + return _getRTCTime(); + } + + private: + // Static NTP instance + static NTP* _instance; + + // NTP Time Provider Code + time_t _getTime() { + // Set all bytes in the buffer to 0 + memset(packetBuffer, 0, NTP_PACKET_SIZE); + + // Initialize values needed to form NTP request + packetBuffer[0] = 0xE3; // LI, Version, Mode + packetBuffer[2] = 0x06; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + packetBuffer[12] = 0x31; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 0x31; + packetBuffer[15] = 0x34; + + // All NTP fields initialized, now send a packet requesting a timestamp + udp.beginPacket(NTP_SERVER_NAME, NTP_SERVER_PORT); + udp.write(packetBuffer, NTP_PACKET_SIZE); + udp.endPacket(); + + // Wait a second for the response + delay(1000); + + // Listen for the response + if (udp.parsePacket() == 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]; + + Serial.println("Got NTP time"); + + return secsSince1900 - 2208988800UL; + } else { + return 0; + } + } + + // Get system time from real-time clock + time_t _getRTCTime() { + bool isRTCAvailable = true; + tmElements_t m; + _shield.getRTCTime(m); + + byte prevSeconds = m.Second; + unsigned long RTC_ReadingStartTime = millis(); + + Serial.print("Real-time clock: "); + Serial.print(m.Hour); + Serial.print(":"); + Serial.println(m.Minute); + Serial.print(":"); + Serial.println(m.Second); + + while (prevSeconds == m.Second) { + _shield.getRTCTime(m); + if ((millis() - RTC_ReadingStartTime) > 3000) { + Serial.println("Warning! RTC did not respond!"); + isRTCAvailable = false; + break; + } + } + + // Set system time if RTC is available + if (isRTCAvailable) { + Serial.println("Got time from RTC"); + return makeTime(m); + } else { + return 0; + } + } + + // Instance of shield + NixieTubeShield& _shield; + + // A UDP instance to let us send and receive packets over UDP + WiFiUDP udp; + + // Buffer to hold outgoing and incoming packets + byte packetBuffer[NTP_PACKET_SIZE]; +}; + +NTP* NTP::_instance = 0; + +time_t getNTPTime() { + return NTP::getInstance().getTime(); +} + +// Initialize the NTP code +void initNTP(NixieTubeShield& shield) { + // Create instance of NTP class + NTP::createSingleton(shield); + + // Set the time provider to NTP + setSyncProvider(getNTPTime); + + // Set the interval between NTP calls + setSyncInterval(SYNC_INTERVAL_SECONDS); +} + +#endif diff --git a/esp32nixie/src/NixieTubeShield.h b/esp32nixie/src/NixieTubeShield.h new file mode 100644 index 0000000..c4bebe4 --- /dev/null +++ b/esp32nixie/src/NixieTubeShield.h @@ -0,0 +1,304 @@ +/* + ESP32 NTP Nixie Tube Clock Program + + NixieTubeShield.h - Interface to Nixie Tube Shield +*/ + +#ifndef NIXIE_TUBE_SHIELD_H +#define NIXIE_TUBE_SHIELD_H + +#include "ClickButton.h" +#include +#include "LEDControl.h" +#include "Tone.h" + +// *************************************************************** +// ESP32 Pin Configuration +// *************************************************************** + +#define LED_GREEN 14 +#define LED_RED 13 +#define LED_BLUE 15 +#define HV_ENABLE 17 //TODO +#define LATCH_ENABLE 5 +#define NEON_DOTS 25 +#define MODE_BUTTON 12 +#define UP_BUTTON 16 +#define DOWN_BUTTON 4 +#define BUZZER_PIN 32 + +#define DS1307_ADDRESS 0x68 + +// *************************************************************** +// Digit Data Definitions +// Each digit, except blank, has a single active low bit in a +// field of 10 bits +// *************************************************************** +#define DIGIT_0 0 +#define DIGIT_1 1 +#define DIGIT_2 2 +#define DIGIT_3 3 +#define DIGIT_4 4 +#define DIGIT_5 5 +#define DIGIT_6 6 +#define DIGIT_7 7 +#define DIGIT_8 8 +#define DIGIT_9 9 +#define DIGIT_BLANK 0xFF + +#define BLANK_DIGIT 10 + +#define UpperDotsMask 0x80000000 +#define LowerDotsMask 0x40000000 + +ClickButton setButton(MODE_BUTTON, LOW, CLICKBTN_PULLUP); +ClickButton upButton(UP_BUTTON, LOW, CLICKBTN_PULLUP); +ClickButton downButton(DOWN_BUTTON, LOW, CLICKBTN_PULLUP); + +// Class Definition +class NixieTubeShield : public LEDControl { + unsigned int SymbolArray[10]={1, 2, 4, 8, 16, 32, 64, 128, 256, 512}; + + public: + + // Class Constructor + NixieTubeShield() : LEDControl(LED_RED, LED_GREEN, LED_BLUE) { + + // Setup output pins + pinMode(HV_ENABLE, OUTPUT); + pinMode(LATCH_ENABLE, OUTPUT); + pinMode(NEON_DOTS, OUTPUT); + pinMode(MODE_BUTTON, INPUT_PULLUP); + pinMode(UP_BUTTON, INPUT_PULLUP); + pinMode(DOWN_BUTTON, INPUT_PULLUP); + //pinMode(NEON_DOTS_LOWER, OUTPUT); + + // Set default outputs + digitalWrite(HV_ENABLE, LOW); + digitalWrite(LATCH_ENABLE, LOW); + digitalWrite(NEON_DOTS, LOW); + + setButton.debounceTime = 20; // Debounce timer in ms + setButton.multiclickTime = 30; // Time limit for multi clicks + setButton.longClickTime = 2000; // time until "held-down clicks" register + + upButton.debounceTime = 20; // Debounce timer in ms + upButton.multiclickTime = 30; // Time limit for multi clicks + upButton.longClickTime = 2000; // time until "held-down clicks" register + + downButton.debounceTime = 20; // Debounce timer in ms + downButton.multiclickTime = 30; // Time limit for multi clicks + downButton.longClickTime = 2000; // time until "held-down clicks" register + + // Initialize digit storage to all ones so all digits are off + for (int i = 0; i < 6; i++) { + digits[i] = DIGIT_BLANK; + } + } + + // High voltage control + void hvEnable(boolean state) { + digitalWrite(HV_ENABLE, state ? HIGH : LOW); + } + + // Neon lamp control + void dotsEnable(boolean state) { + digitalWrite(NEON_DOTS, state ? HIGH : LOW); + dotsEnabled = state; + } + + // Set the NX1 (most significant) digit + void setNX1Digit(int d) { + digits[5] = NUMERIC_DIGITS[d]; + } + + // Set the NX2 digit + void setNX2Digit(int d) { + digits[4] = NUMERIC_DIGITS[d]; + } + + // Set the NX3 digit + void setNX3Digit(int d) { + digits[3] = NUMERIC_DIGITS[d]; + } + + // Set the NX4 digit + void setNX4Digit(int d) { + digits[2] = NUMERIC_DIGITS[d]; + } + + // Set the NX5 digit + void setNX5Digit(int d) { + digits[1] = NUMERIC_DIGITS[d]; + } + + // Set the NX6 (least significant) digit + void setNX6Digit(int d) { + digits[0] = NUMERIC_DIGITS[d]; + } + + void show() { + digitalWrite(LATCH_ENABLE, LOW); // allow data input (Transparent mode) + unsigned long Var32=0; + + //-------- REG 1 ----------------------------------------------- + Var32=0; + + Var32|=(unsigned long)(SymbolArray[digits[0]])<<20; // s2 + Var32|=(unsigned long)(SymbolArray[digits[1]])<<10; //s1 + Var32|=(unsigned long) (SymbolArray[digits[2]]); //m2 + + if (dotsEnabled) Var32|=LowerDotsMask; + else Var32&=~LowerDotsMask; + + if (dotsEnabled) Var32|=UpperDotsMask; + else Var32&=~UpperDotsMask; + + SPI.transfer(Var32>>24); + SPI.transfer(Var32>>16); + SPI.transfer(Var32>>8); + SPI.transfer(Var32); + + //------------------------------------------------------------------------- + + //-------- REG 0 ----------------------------------------------- + Var32=0; + + Var32|=(unsigned long)(SymbolArray[digits[3]])<<20; // m1 + Var32|=(unsigned long)(SymbolArray[digits[4]])<<10; //h2 + Var32|=(unsigned long)SymbolArray[digits[5]]; //h1 + + if (dotsEnabled) Var32|=LowerDotsMask; + else Var32&=~LowerDotsMask; + + if (dotsEnabled) Var32|=UpperDotsMask; + else Var32&=~UpperDotsMask; + + SPI.transfer(Var32>>24); + SPI.transfer(Var32>>16); + SPI.transfer(Var32>>8); + SPI.transfer(Var32); + + digitalWrite(LATCH_ENABLE, HIGH); // latching data + } + + // Do anti-poisoning routine and then turn off all tubes + void doAntiPoisoning(void) { + bool dots = false; + dotsEnable(false); + + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 11; i++) { + setNX1Digit(i); + setNX2Digit(i); + setNX3Digit(i); + setNX4Digit(i); + setNX5Digit(i); + setNX6Digit(i); + + // Make the changes visible + show(); + + // Small delay + delay(500); + + setLEDColor(i%3==0?255:0, i%3==1?255:0, i%3==2?255:0); + dots = !dots; + dotsEnable(dots); + } + } + } + + void processButtons() { + setButton.Update(); + upButton.Update(); + downButton.Update(); + } + + bool isSetButtonClicked() { + return (setButton.clicks > 0); + } + + bool isSetButtonLongClicked() { + return (setButton.clicks < 0); + } + + bool isUpButtonClicked() { + return (upButton.clicks > 0); + } + + bool isUpButtonLongClicked() { + return (upButton.clicks < 0); + } + + bool isDownButtonClicked() { + return (downButton.clicks > 0); + } + + bool isDownButtonLongClicked() { + return (downButton.clicks < 0); + } + + void getRTCTime(tmElements_t &m) { + Wire.beginTransmission(DS1307_ADDRESS); + Wire.write(zero); + Wire.endTransmission(); + + Wire.requestFrom(DS1307_ADDRESS, 7); + + m.Second = bcdToDec(Wire.read()); + m.Minute = bcdToDec(Wire.read()); + m.Hour = bcdToDec(Wire.read() & 0b111111); //24 hour time + m.Wday = bcdToDec(Wire.read()); //0-6 -> sunday - Saturday + m.Day = bcdToDec(Wire.read()); + m.Month = bcdToDec(Wire.read()); + m.Year = bcdToDec(Wire.read()); + } + + void setRTCDateTime(const tmElements_t &m) { + Wire.beginTransmission(DS1307_ADDRESS); + Wire.write(zero); //stop Oscillator + + Wire.write(decToBcd(m.Second)); + Wire.write(decToBcd(m.Minute)); + Wire.write(decToBcd(m.Hour)); + Wire.write(decToBcd(m.Wday)); + Wire.write(decToBcd(m.Day)); + Wire.write(decToBcd(m.Month)); + Wire.write(decToBcd(m.Year)); + + Wire.write(zero); //start + + Wire.endTransmission(); + } + + private: + byte decToBcd(byte val) { + // Convert normal decimal numbers to binary coded decimal + return ( (val / 10 * 16) + (val % 10) ); + } + + byte bcdToDec(byte val) { + // Convert binary coded decimal to normal decimal numbers + return ( (val / 16 * 10) + (val % 16) ); + } + + // Private data + // Array of codes for each nixie digit + int NUMERIC_DIGITS[11] = { + DIGIT_0, DIGIT_1, DIGIT_2, DIGIT_3, DIGIT_4, + DIGIT_5, DIGIT_6, DIGIT_7, DIGIT_8, DIGIT_9, + DIGIT_BLANK + }; + + byte zero = 0x00; //workaround for issue #527 + int RTC_hours, RTC_minutes, RTC_seconds, RTC_day, RTC_month, RTC_year, RTC_day_of_week; + + // Storage for codes for each nixie digit + // Digit order is: NX6, NX5, NX4, NX3, NX2, NX1 + uint16_t digits[6]; + + bool dotsEnabled = false; +}; + +#endif diff --git a/esp32nixie/src/Tone.h b/esp32nixie/src/Tone.h new file mode 100644 index 0000000..f7ffa50 --- /dev/null +++ b/esp32nixie/src/Tone.h @@ -0,0 +1,124 @@ +#ifndef TONE_H +#define TONE_H + +/************************************************* + * Public Constants + *************************************************/ + +#define NOTE_B0 31 +#define NOTE_C1 33 +#define NOTE_CS1 35 +#define NOTE_D1 37 +#define NOTE_DS1 39 +#define NOTE_E1 41 +#define NOTE_F1 44 +#define NOTE_FS1 46 +#define NOTE_G1 49 +#define NOTE_GS1 52 +#define NOTE_A1 55 +#define NOTE_AS1 58 +#define NOTE_B1 62 +#define NOTE_C2 65 +#define NOTE_CS2 69 +#define NOTE_D2 73 +#define NOTE_DS2 78 +#define NOTE_E2 82 +#define NOTE_F2 87 +#define NOTE_FS2 93 +#define NOTE_G2 98 +#define NOTE_GS2 104 +#define NOTE_A2 110 +#define NOTE_AS2 117 +#define NOTE_B2 123 +#define NOTE_C3 131 +#define NOTE_CS3 139 +#define NOTE_D3 147 +#define NOTE_DS3 156 +#define NOTE_E3 165 +#define NOTE_F3 175 +#define NOTE_FS3 185 +#define NOTE_G3 196 +#define NOTE_GS3 208 +#define NOTE_A3 220 +#define NOTE_AS3 233 +#define NOTE_B3 247 +#define NOTE_C4 262 +#define NOTE_CS4 277 +#define NOTE_D4 294 +#define NOTE_DS4 311 +#define NOTE_E4 330 +#define NOTE_F4 349 +#define NOTE_FS4 370 +#define NOTE_G4 392 +#define NOTE_GS4 415 +#define NOTE_A4 440 +#define NOTE_AS4 466 +#define NOTE_B4 494 +#define NOTE_C5 523 +#define NOTE_CS5 554 +#define NOTE_D5 587 +#define NOTE_DS5 622 +#define NOTE_E5 659 +#define NOTE_F5 698 +#define NOTE_FS5 740 +#define NOTE_G5 784 +#define NOTE_GS5 831 +#define NOTE_A5 880 +#define NOTE_AS5 932 +#define NOTE_B5 988 +#define NOTE_C6 1047 +#define NOTE_CS6 1109 +#define NOTE_D6 1175 +#define NOTE_DS6 1245 +#define NOTE_E6 1319 +#define NOTE_F6 1397 +#define NOTE_FS6 1480 +#define NOTE_G6 1568 +#define NOTE_GS6 1661 +#define NOTE_A6 1760 +#define NOTE_AS6 1865 +#define NOTE_B6 1976 +#define NOTE_C7 2093 +#define NOTE_CS7 2217 +#define NOTE_D7 2349 +#define NOTE_DS7 2489 +#define NOTE_E7 2637 +#define NOTE_F7 2794 +#define NOTE_FS7 2960 +#define NOTE_G7 3136 +#define NOTE_GS7 3322 +#define NOTE_A7 3520 +#define NOTE_AS7 3729 +#define NOTE_B7 3951 +#define NOTE_C8 4186 +#define NOTE_CS8 4435 +#define NOTE_D8 4699 +#define NOTE_DS8 4978 + +#define TONE_CHANNEL 5 + +class Tone { +public: + Tone() { + } + + void begin(uint8_t pin) { + ledcSetup(TONE_CHANNEL, 1E5, 12); + ledcAttachPin(pin,TONE_CHANNEL); + } + + void play(unsigned int freq, unsigned long duration) { + ledcWriteTone(TONE_CHANNEL, freq); + // TODO + //tone(_pin, freq, duration); + } + + void stop() { + ledcWrite(TONE_CHANNEL,0); + } + +private: + uint8_t _pin; +}; + +#endif diff --git a/esp32nixie/src/main.cpp b/esp32nixie/src/main.cpp new file mode 100644 index 0000000..e5d082c --- /dev/null +++ b/esp32nixie/src/main.cpp @@ -0,0 +1,521 @@ +/* + ESP32 NTP Nixie Tube Clock Program + + This hardware/software combination implements a Nixie tube digital clock that + never needs setting as it gets the current time and date by polling + Network Time Protocol (NTP) servers on the Internet. The clock's time + is synchronized to NTP time periodically. Use of the TimeZone + library means that Daylight Savings Time (DST) is automatically + taken into consideration so no time change buttons are necessary. + Clock can run in 24 hour or 12 hour mode. + + This program uses the WiFiManager library to allow WiFi credentials to be set + via a web interface. + + How it works: + + When the program is first started it creates a wireless access point called NixieClock + that the user needs to connect to and then if the user points his/her browser to + 192.168.4.1, a page is presented that allows the credentials for the actual WiFi network + to be entered. This only needs to be done once since the credentials will be stored in + EEPROM and will be used from that point forward. + + Once WiFi connection has been established, it uses NTP to initialize the real-time clock (RTC). + If ESP32 cannot connect to the WiFi network using the stored crecentials, it will fall back + to the real-time clock (RTC). + + Press the mode button to enter WiFi AP configuration mode. + Long-press the mode button to reset the ESP32. + + The hardware consists of the following parts: + ESP32 + Nixie Tubes Clock Arduino Shield NCS314 for xUSSR IN-14 Nixie Tubes from eBay + + The connections between the ESP32 board and the Nixie Clock Shield are as follows: + + ESP32 Pin Shield Pin Function + -------------------------------------------------------------------- + GIOP18 SCK / avr19 SPI Clock + GIOP23 MOSI / avr17 Master Out Slave In (data from ESP32->clock) + 5 (SS) LE / avr16 Latch Enable + 25 DOT1 -- Neon dot 1 + 25 DOT2 -- Neon dot 2 + 14 PWM1 / avr12 Green LED drive + 13 PWM2 / avr15 Red LED drive + 15 PWM3 / avr5 Blue LED drive + 17 SHTDN High voltage enable + 12 SET / avr23 Set (mode) button + 16 UP / avr24 Up button + 4 DOWN / avr25 Down button + 32 BUZZER /avr4 Buzzer pin + + The ESP32 is powered (via Vin) with 5VDC from a 5 volt regulator driven from + the 12 VDC supply. The shield is powered directly from the 12 VDC supply. + + Based on ESP8266_NTPNixieClock by Craig A. Lindley and NixeTubesShieldNCS314 by GRA & AFCH. + + Copyright (c) 2019 Tomer Verona + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +//#include + +#include "LEDControl.h" +#include "NixieTubeShield.h" +#include "NTP.h" + +// *************************************************************** +// Start of user configuration items +// *************************************************************** + +// Name of AP when configuring WiFi credentials +#define AP_NAME "NixieClock" + +// Checks WiFi connection. Reset after this time, if WiFi is not connected +#define WIFI_CHK_TIME_SEC 3600 +#define WIFI_CHK_TIME_MS (WIFI_CHK_TIME_SEC * 1000) + +// Set to false for 24 hour time mode +#define HOUR_FORMAT_12 true + +// Nixie tubes are turned off at night to increase their lifetime +// Clock off and on times are in 24 hour format +#define CLOCK_OFF_HOUR 23 +#define CLOCK_ON_HOUR 07 + +// Suppress leading zeros +// Set to false to having leading zeros displayed +#define SUPPRESS_LEADING_ZEROS true + +// Define the timezone in which the clock will operate +// See the Timezone library for details +// US Pacific Time Zone (Las Vegas, Los Angeles) +TimeChangeRule usPDT = {"PDT", Second, Sun, Mar, 2, -420}; +TimeChangeRule usPST = {"PST", First, Sun, Nov, 2, -480}; +Timezone TZ(usPDT, usPST); + +// *************************************************************** +// End of user configuration items +// *************************************************************** + +// Instantiate the Nixie Tube Shield object +NixieTubeShield SHIELD; + +// Instantiate the WifiManager object +//WiFiManager wifiManager; + +// *************************************************************** +// Utility Functions +// *************************************************************** + +// Misc variables +int previousMinute = 0; +int minutes = 0; +boolean dotToggle = false; +boolean clockOn = true; + +// This function is called once a second +void updateDisplay(void) { + + // Get the current time and date + // Get the time for specified timezone + time_t localTime = TZ.toLocal(now()); + + // Determine if clock should be on or off + int hr = hour(localTime); + + // Clock is on between these hours + if ((hr >= CLOCK_ON_HOUR) && (hr < CLOCK_OFF_HOUR)) { + if (!clockOn) { + clockOn = true; + Serial.println("Clock is On"); + + // Clock should be on so turn high voltage on + SHIELD.hvEnable(true); + } + } else { + if (clockOn) { + clockOn = false; + + Serial.println("Clock is Off"); + + // Clock is going off so shut things down in an orderly fashion + // First turn off the high voltage so the nixie's go off + SHIELD.hvEnable(false); + + // Next turn off the dots + SHIELD.dotsEnable(false); + SHIELD.setNX1Digit(BLANK_DIGIT); + SHIELD.setNX2Digit(BLANK_DIGIT); + SHIELD.setNX3Digit(BLANK_DIGIT); + SHIELD.setNX4Digit(BLANK_DIGIT); + SHIELD.setNX5Digit(BLANK_DIGIT); + SHIELD.setNX6Digit(BLANK_DIGIT); + SHIELD.show(); + + // Finally turn the LEDs off as well + SHIELD.setLEDColor(black); + } + + // No need to continue as the clock is effectively off + return; + } + + // Get the current minute + minutes = minute(localTime); + + // Dispatch events in priority order + + // 60 minute event is anti-poisoning routine + if ((minutes != previousMinute) && (minutes == 0)) { + previousMinute = minutes; + + // Set all LEDs to red to indicate anti-poisoning + SHIELD.setLEDColor(red); + + // Do anti-poisoning function + SHIELD.doAntiPoisoning(); + } + + // 15 minute event is rainbow display + else if ((minutes != previousMinute) && (minutes != 0) && ((minutes % 15) == 0)) { + previousMinute = minutes; + + // Turn off dots + SHIELD.dotsEnable(false); + + // Blank all nixie tube digits + SHIELD.setNX1Digit(BLANK_DIGIT); + SHIELD.setNX2Digit(BLANK_DIGIT); + SHIELD.setNX3Digit(BLANK_DIGIT); + SHIELD.setNX4Digit(BLANK_DIGIT); + SHIELD.setNX5Digit(BLANK_DIGIT); + SHIELD.setNX6Digit(BLANK_DIGIT); + + // Blank the nixie tubes + SHIELD.show(); + + // Cycle the LEDs through rainbows of color + for (int cycles = 0; cycles < 4; cycles++) { + for (int cycle = 0; cycle < 256; cycle += 16) { + // Set the LED's color + SHIELD.setLEDColor(SHIELD.colorWheel(cycle)); + delay(400); + } + } + } + + // 10 minute event is the date display + else if ((minutes != previousMinute) && (minutes != 0) && ((minutes % 10) == 0)) { + previousMinute = minutes; + + // Turn off dots + SHIELD.dotsEnable(false); + + // Set all LEDs to blue to indicate date display + SHIELD.setLEDColor(blue); + + SHIELD.dotsEnable(true); + + // Get the current month 1..12 + int now_mon = month(localTime); + + // Display the NX1 digit + if (now_mon >= 10) { + SHIELD.setNX1Digit(now_mon / 10); + } else { + if (SUPPRESS_LEADING_ZEROS) { + SHIELD.setNX1Digit(BLANK_DIGIT); + } else { + SHIELD.setNX1Digit(0); + } + } + // Display the NX2 digit + SHIELD.setNX2Digit(now_mon % 10); + + // Get the current day 1..31 + int now_day = day(localTime); + + // Display the NX3 digit + if (now_day >= 10) { + SHIELD.setNX3Digit(now_day / 10); + } else { + if (SUPPRESS_LEADING_ZEROS) { + SHIELD.setNX3Digit(BLANK_DIGIT); + } else { + SHIELD.setNX3Digit(0); + } + } + // Display the NX4 digit + SHIELD.setNX4Digit(now_day % 10); + + // Get the current year + int now_year = year(localTime) - 2000; + + // Display the NX5 digit + if (now_year >= 10) { + SHIELD.setNX5Digit(now_year / 10); + } else { + if (SUPPRESS_LEADING_ZEROS) { + SHIELD.setNX5Digit(BLANK_DIGIT); + } else { + SHIELD.setNX5Digit(0); + } + } + // Display the NX6 digit + SHIELD.setNX6Digit(now_year % 10); + + // Display date on clock + SHIELD.show(); + + // Delay for date display + delay(10000); + + } else { + // Display the time + + int now_hour; + float colorInc; + + // Make the dots blink off and on + if (dotToggle) { + dotToggle = false; + SHIELD.dotsEnable(true); + + } else { + dotToggle = true; + SHIELD.dotsEnable(false); + } + + // Set the LED's color depending upon the hour + // Get the current hour + if (HOUR_FORMAT_12) { + // Using 12 hour format + now_hour = hourFormat12(localTime); + // Calculate the color of the LEDs based on the hour in 12 hour format + colorInc = 256 / 12.0; + } else { + // Using 24 hour format + now_hour = hour(localTime); + // Calculate the color of the LEDs based on the hour in 24 hour format + colorInc = 256 / 24.0; + } + // Set the shields's LED color + SHIELD.setLEDColor(SHIELD.colorWheel(colorInc * now_hour)); + + // Display the NX1 digit + if (now_hour >= 10) { + SHIELD.setNX1Digit(now_hour / 10); + } else { + if (SUPPRESS_LEADING_ZEROS) { + SHIELD.setNX1Digit(BLANK_DIGIT); + } else { + SHIELD.setNX1Digit(0); + } + } + // Display the NX2 digit + SHIELD.setNX2Digit(now_hour % 10); + + // Get the current minute + int now_min = minute(localTime); + + // Display the NX3 digit + SHIELD.setNX3Digit(now_min / 10); + + // Display the NX4 digit + SHIELD.setNX4Digit(now_min % 10); + + // Get the current second + int now_sec = second(localTime); + + // Display the NX5 digit + SHIELD.setNX5Digit(now_sec / 10); + + // Display the NX6 digit + SHIELD.setNX6Digit(now_sec % 10); + + // Display time on clock + SHIELD.show(); + } +} + +// Get WiFi SSID +String WIFI_GetSSID() { + // wifi_config_t conf; + // esp_wifi_get_config(WIFI_IF_STA, &conf); + // return String(reinterpret_cast(conf.sta.ssid)); + return String("iot"); +} + +// Get WiFi Password +String WIFI_GetPassword() { + // wifi_config_t conf; + // esp_wifi_get_config(WIFI_IF_STA, &conf); + // return String(reinterpret_cast(conf.sta.password)); + return String("Rijnstraat214"); +} + +// Attempt to connect to WiFi +void WIFI_Connect() { + WiFi.disconnect(); + Serial.println("Connecting to WiFi..."); + WiFi.begin(WIFI_GetSSID().c_str(), WIFI_GetPassword().c_str()); + + for (int i = 0; i < 40; i++) { + if (WiFi.status() != WL_CONNECTED) { + delay (250); + SHIELD.setLEDColor(black); + Serial.print("."); + delay (250); + SHIELD.setLEDColor(green); + } else { + break; + } + } + + if (WiFi.status() == WL_CONNECTED) { + Serial.println(""); + Serial.println("WiFi Connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + } else { + Serial.println("Wifi NOT connected"); + } +} + +void WIFI_StartAccessPoint() { + // Starts an access point with the specified name + // and goes into a blocking loop awaiting configuration + Serial.println("Starting Wifi AP"); + //WiFiManager wifiManager; + //wifiManager.startConfigPortal(AP_NAME); +} + +// *************************************************************** +// Program Setup +// *************************************************************** + +void setup() { + // Turn off the high voltage for the clock + SHIELD.hvEnable(false); + + // Turn off DAC channels + dac_output_disable(DAC_CHANNEL_1); + dac_output_disable(DAC_CHANNEL_2); + dac_i2s_disable(); + + Wire.begin(); + + // Configure serial interface + Serial.begin(115200); + delay(1000); + Serial.println(); + + pinMode(LATCH_ENABLE, OUTPUT); + + // Setup SPI interface to defaults for shield + SPI.begin(); + SPI.setDataMode (SPI_MODE2); // Mode 2 SPI + SPI.setClockDivider(2000000); // SCK = 2MHz + + // Reset saved settings for testing purposes + // Should be commented out for normal operation + // wifiManager.resetSettings(); + + WiFi.mode(WIFI_AP_STA); + Serial.println(WIFI_GetSSID()); + Serial.println(WIFI_GetPassword()); + + // If WiFi setup is not configured, start access point + // Otherwise, connect to WiFi + if (0 == WIFI_GetSSID().length()) { + WIFI_StartAccessPoint(); + } else { + WIFI_Connect(); + } + delay(100); + + initNTP(SHIELD); + + // Set all LEDs to black or off + SHIELD.setLEDColor(black); + + // Turn off the dots + SHIELD.dotsEnable(false); + + // Turn on the high voltage for the clock + SHIELD.hvEnable(true); + + // Do the anti poisoning routine + SHIELD.doAntiPoisoning(); + + Serial.println("\nReady!\n"); +} + +// *************************************************************** +// Program Loop +// *************************************************************** + +unsigned long nextConnectionCheckTime = 0; +int previousSecond = 0; + +void loop() { + // Check WiFi connectivity + if (millis() > nextConnectionCheckTime) { + Serial.print("\n\nChecking WiFi... "); + if (WiFi.status() != WL_CONNECTED) { + Serial.println("WiFi connection lost. Reconnecting..."); + WIFI_Connect(); + } else { + Serial.println("Wifi connected"); + } + nextConnectionCheckTime = millis() + WIFI_CHK_TIME_MS; + } + + // Process button status + SHIELD.processButtons(); + + // If set button is pressed, enter Wifi AP mode + if (SHIELD.isSetButtonClicked()) { + WIFI_StartAccessPoint(); + } + + // If set button is long-pressed, restart ESP + if (SHIELD.isSetButtonLongClicked()) { + esp_restart(); + } + + // Update the display only if time has changed + if (timeStatus() != timeNotSet) { + if (second() != previousSecond) { + previousSecond = second(); + + // Display updated once a second + updateDisplay(); + } + } + delay(10); +} \ No newline at end of file diff --git a/esp32nixie/test/README b/esp32nixie/test/README new file mode 100644 index 0000000..b94d089 --- /dev/null +++ b/esp32nixie/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html