Run ESP_UniClock on the NixieIN12v2 clock
Adds CLOCK_90 and the nixie595.ino driver: two 74HC595s on hardware SPI (GPIO13/14, latch GPIO15), multiplexed from an IRAM-only timer1 ISR. Neon dots on P1; P2 is the decimal-point line on this board. Also: - NeoPixel on any ESP8266 pin via bit-bang (GPIO12), and take the pin back after SPI.begin() claimed it as MISO - ArduinoOTA (USE_ARDUINO_OTA), with an OTA upload env in platformio.ini - DST: EU end date (last Sunday of October) instead of the first Sunday of November, and recompute the time from the rules when DST is toggled instead of shifting it by 1 h - forward declarations so PlatformIO's .ino conversion compiles - FIRMWARE_SERVER can be overridden; empty for CLOCK_90 Verified on the clock: tubes, order, dots, LEDs, LDR, NTP time with DST. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b347f1c63f
commit
0d3e878051
7 files changed
+303
-33
No files matched your search
@@ -6,8 +6,9 @@ with ArduinoOTA and Home Assistant (MQTT) support.
|
||||
|
||||
## The device
|
||||
|
||||
- On the network at **10.1.1.97** (WiFi `dlink-iot`). Stock web UI on port 80, nothing else
|
||||
open: no OTA, no API, no MQTT.
|
||||
- On the network at **10.1.1.97** (WiFi `dlink-iot`). Since 2026-10-25 it runs ESP_UniClock
|
||||
(`firmware/uniclock/`, CLOCK_90): web UI on port 80 (JSON at `/getConfiguration`,
|
||||
`/getCurrentInfos`), ArduinoOTA on UDP 8266 (hostname `nixie-in12`, no password yet).
|
||||
- Board: RetroModding (Konstantin Repnikov) "NixieIN12v2 HV2.0", stock firmware **SV1.3**
|
||||
(©2020). ESP-12 (ESP8266, MAC 30:83:98:99:BF:DE, Espressif).
|
||||
- **Powered by USB-C 5 V only** (no DC jack). Most likely the maker's "v2.0L lightweight
|
||||
@@ -34,28 +35,29 @@ with ArduinoOTA and Home Assistant (MQTT) support.
|
||||
## Pinout
|
||||
|
||||
From the 12 V v2 schematic (`docs/schematic_v2_12V.jpg`), checked against the stock firmware
|
||||
by disassembly (`docs/stock-firmware-pins.md`, tools in `tools/`). "Binary" = confirmed in
|
||||
the stock SV1.3 code; it has not yet been tested on the hardware.
|
||||
by disassembly (`docs/stock-firmware-pins.md`, tools in `tools/`). "Hardware" = also works
|
||||
on the clock with UniClock; "Binary" = only confirmed in the stock SV1.3 code.
|
||||
|
||||
| Function | GPIO | Source |
|
||||
|---|---|---|
|
||||
| 74HC595 DATA / CLOCK (OE tied to GND) | 13 / 14 = HSPI MOSI / SCK (hardware SPI, MSB first) | Binary |
|
||||
| 74HC595 LATCH | 15, plain `digitalWrite` (has 1k pull-down) | Binary |
|
||||
| WS2812 data | 12 (NeoPixel bit-bang, GRB 800 kHz; stock assumes 12 LEDs, 8 fitted) | Binary |
|
||||
| Photoresistor (LDR, 10k to GND) | ADC (A0) | Binary |
|
||||
| 74HC595 DATA / CLOCK (OE tied to GND) | 13 / 14 = HSPI MOSI / SCK (hardware SPI, MSB first) | Hardware |
|
||||
| 74HC595 LATCH | 15, plain `digitalWrite` (has 1k pull-down) | Hardware |
|
||||
| WS2812 data | 12 (NeoPixel bit-bang, GRB 800 kHz; 8 fitted, stock assumes 12). `SPI.begin()` turns GPIO12 into MISO: set it back to OUTPUT afterwards | Hardware |
|
||||
| Photoresistor (LDR, 10k to GND) | ADC (A0) | Hardware |
|
||||
| BTN1 / BTN2 (not fitted) | 0 / 2, INPUT_PULLUP. Stock: GPIO0 is a button, GPIO2 a motion/presence input that wakes the display | Binary |
|
||||
| DFPlayer Mini (stock feature) | UART0: GPIO1 TX / GPIO3 RX, 9600 Bd | Binary |
|
||||
| DS3231M RTC SDA / SCL | 4 / 5. **Stock firmware has no I2C code** (NTP only); if fitted, unknown | Schematic only |
|
||||
| DS3231M RTC SDA / SCL | 4 / 5: **no I2C device answers** (UniClock scan), so no RTC on this board; NTP only | Hardware |
|
||||
|
||||
Two chained 74HC595s make one 16-bit word, all bits active-high (confirmed from the binary;
|
||||
the high byte is sent first and goes to U2):
|
||||
- U1 QA..QH = cathodes 0..7 (bits 0..7), U2 QA = cathode 8 (bit 8), QB = cathode 9 (bit 9)
|
||||
(via 2x ULN2003, shared by all tubes)
|
||||
- U2 QC = P1 (neon dot 1, bit 10), QD..QG = anodes A1..A4 (bits 11..14, MMBTA42/92), QH = P2
|
||||
(bit 15; neon dot 2, and the decimal point if jumper JP2 is closed)
|
||||
- U2 QC = P1 (bit 10) drives **both** IN-3 neon dots. QD..QG = anodes A1..A4 (bits 11..14,
|
||||
MMBTA42/92). QH = P2 (bit 15) drives the IN-12B **decimal points** on this board (JP2 closed),
|
||||
not a neon.
|
||||
- The display is multiplexed: one anode on at a time. **A1 = leftmost tube (hour tens),
|
||||
A4 = rightmost (minute ones)**, from the stock digit order. Stock lights P1/P2 in only one
|
||||
multiplex slot.
|
||||
A4 = rightmost (minute ones)** (verified on the tubes). The dots are lit in the minute-tens
|
||||
slot only, as on stock.
|
||||
|
||||
## Programming header
|
||||
|
||||
@@ -65,7 +67,12 @@ Wiring: GND-GND, adapter TXD -> clock RXD, adapter RXD -> clock TXD, adapter 3.3
|
||||
connected (the clock is powered from its own USB-C). No auto-reset: to enter flash mode, hold
|
||||
GPIO0 to GND, tap RST to GND, then release GPIO0.
|
||||
|
||||
- esptool is installed for the user (`python -m esptool`, v5.6.0).
|
||||
- esptool is installed for the user (`python -m esptool`, v5.6.0). After `--after no-reset`
|
||||
the stub keeps running at the new baud rate and the next esptool call can't connect: do
|
||||
everything in one call, or re-enter flash mode.
|
||||
- Normal updates go over WiFi: `pio run -e nixie_in12_ota -t upload` in `firmware/uniclock/`.
|
||||
ArduinoOTA does not answer after the clock switched from AP mode to WiFi without a reboot
|
||||
(first setup); a power cycle fixes it.
|
||||
- `firmware/stock/` holds the stock flash dump. **Never overwrite or delete it**; it is the
|
||||
only way back to SV1.3 (`write-flash 0 <dump>`). Dumps are gitignored because the flash
|
||||
holds the WiFi password; never commit them.
|
||||
@@ -79,10 +86,9 @@ GPIO0 to GND, tap RST to GND, then release GPIO0.
|
||||
|
||||
## Plan
|
||||
|
||||
1. Dump and inspect the stock firmware (version strings, pins if possible).
|
||||
2. Write a test sketch that steps through the 16 shift-register bits, to verify the pinout
|
||||
and the tube order.
|
||||
3. Add a 74HC595-multiplex driver + CLOCK config to ESP_UniClock, plus ArduinoOTA and
|
||||
Home Assistant controls (display on/off, brightness, LEDs); its stock MQTT only
|
||||
publishes sensors.
|
||||
4. Work continues in VS Code (PlatformIO).
|
||||
1. Done: dump and inspect the stock firmware.
|
||||
2. Skipped (the disassembly was enough): bit-stepping test sketch.
|
||||
3. Done: 74HC595-multiplex driver (`nixie595.ino`) + CLOCK_90 in ESP_UniClock, ArduinoOTA,
|
||||
EU DST fix. Runs on the clock.
|
||||
4. Next: Home Assistant controls over MQTT (display on/off, brightness, LEDs; upstream MQTT
|
||||
only publishes sensors), OTA password via `secrets.h`.
|
||||
@@ -44,6 +44,11 @@
|
||||
#include <stddef.h>
|
||||
//#define byte uint8_t
|
||||
#include "clocks.h" //DEFINE YOUR CLOCKS SETUP IN THIS FILE!!! - clocks.h picks the single active CLOCK_NN block, which #defines the driver macro, pins and feature flags read throughout this file
|
||||
// PlatformIO turns the .ino files into one .cpp and puts prototypes for every function near the top,
|
||||
// without evaluating #ifdefs. Declare the types of optional libraries here so those prototypes compile
|
||||
// when the feature (and its #include) is switched off.
|
||||
class AsyncWiFiManager;
|
||||
struct RgbColor;
|
||||
|
||||
// Detect which generation of the ESP8266 Arduino core is in use: older cores (2.6.x/2.7.x) name the
|
||||
// RAM-resident-ISR attribute ICACHE_RAM_ATTR, while newer cores renamed it to IRAM_ATTR (shared with
|
||||
@@ -161,7 +166,9 @@ unsigned long intCounter = 0; //for testing only, interrupt counter
|
||||
byte c_MinBrightness = RGB_MIN_BRIGHTNESS; //minimum LED brightness
|
||||
byte c_MaxBrightness = RGB_MAX_BRIGHTNESS; //maximum LED brightness
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
#define FIRMWARE_SERVER "http://c.landventure.hu/store" //default OTA update server (see doFirmwareUpdate()); overridable per-clock via prm.firmwareServer / the web admin "firmware" field
|
||||
#ifndef FIRMWARE_SERVER
|
||||
#define FIRMWARE_SERVER "http://c.landventure.hu/store" //default OTA update server (see doFirmwareUpdate()); overridable per-clock via prm.firmwareServer / the web admin "firmware" field
|
||||
#endif
|
||||
String usedPinsStr; //human-readable list of registered GPIOs, built by listPins(), shown on the web admin "clock details" page
|
||||
String driverSetupStr; //driver-specific extra setup info string (set by some drivers), also shown on the web admin page
|
||||
|
||||
@@ -237,6 +244,9 @@ DNSServer dnsServer;
|
||||
#include <Wire.h>
|
||||
#include <EEPROM.h>
|
||||
#include "ArduinoJson.h"
|
||||
#ifdef USE_ARDUINO_OTA
|
||||
#include <ArduinoOTA.h>
|
||||
#endif
|
||||
|
||||
#if defined(USE_NEOPIXEL) || defined(WORDCLOCK)
|
||||
#include <NeoPixelBrightnessBus.h>
|
||||
@@ -441,8 +451,10 @@ IPAddress ip;
|
||||
// switchover DATES are not configurable from the web UI. myTZ is rebuilt with the current offsets
|
||||
// every time updateTimefromTimeserver() runs (WiFi/NTP path only - the GPS-only fallback in gps.ino
|
||||
// uses a simpler, always-on +1h DST rule, see CODE_REVIEW.md).
|
||||
// NOTE: upstream ended DST on the first Sunday of November (the US date). The EU switches at 01:00 UTC:
|
||||
// last Sunday of March 02:00 standard time, last Sunday of October 03:00 summer time.
|
||||
TimeChangeRule myDST = {"DST", Last, Sun, Mar, 2, 0};
|
||||
TimeChangeRule mySTD = {"STD", First, Sun, Nov, 2, 0};
|
||||
TimeChangeRule mySTD = {"STD", Last, Sun, Oct, 3, 0};
|
||||
Timezone myTZ(myDST, mySTD);
|
||||
time_t prevTime = 0; //last second() value that was actually rendered to the display - timeProgram() only redraws when now()!=prevTime, i.e. at most once per real second
|
||||
time_t protectTimer = 0; //timestamp of the last cathode-protect animation, used by timeProgram()'s prm.interval-minute scheduler
|
||||
@@ -589,6 +601,9 @@ void Fdelay(unsigned long d) {
|
||||
#ifdef USE_GPS
|
||||
smartDelay(1); //feed GPS
|
||||
#endif
|
||||
#ifdef USE_ARDUINO_OTA
|
||||
ArduinoOTA.handle(); //also during long animations/startup waits
|
||||
#endif
|
||||
yield();
|
||||
}
|
||||
}
|
||||
@@ -877,6 +892,39 @@ void startMDNS() {
|
||||
#endif
|
||||
}
|
||||
|
||||
// Optional ArduinoOTA (USE_ARDUINO_OTA in clocks.h): push a new firmware from PlatformIO/Arduino IDE
|
||||
// over WiFi ("pio run -t upload --upload-port <ip>"). Shows the progress in percent on the two right
|
||||
// tubes. Password from OTA_PASSWORD (secrets.h) if defined.
|
||||
void startArduinoOTA() {
|
||||
#ifdef USE_ARDUINO_OTA
|
||||
static boolean otaStarted = false;
|
||||
if (otaStarted) return;
|
||||
ArduinoOTA.setHostname(OTA_HOSTNAME);
|
||||
#ifdef OTA_PASSWORD
|
||||
ArduinoOTA.setPassword(OTA_PASSWORD);
|
||||
#endif
|
||||
ArduinoOTA.onStart([]() {
|
||||
DPRINTLN(F("ArduinoOTA: update started"));
|
||||
clearDigits();
|
||||
});
|
||||
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
|
||||
int p = (int)(progress / (total / 100 + 1));
|
||||
digit[1] = (p / 10) % 10;
|
||||
digit[0] = p % 10;
|
||||
});
|
||||
ArduinoOTA.onEnd([]() {
|
||||
DPRINTLN(F("ArduinoOTA: done, rebooting"));
|
||||
});
|
||||
ArduinoOTA.onError([](ota_error_t error) {
|
||||
DPRINTF("ArduinoOTA: error %u\n", error);
|
||||
clearDigits();
|
||||
});
|
||||
ArduinoOTA.begin();
|
||||
otaStarted = true;
|
||||
DPRINT(F("ArduinoOTA ready: ")); DPRINTLN(OTA_HOSTNAME);
|
||||
#endif
|
||||
}
|
||||
|
||||
// The Standalone(AP)-mode path (prm.wifiMode==false, or a fallback from a fully-failed STA connect):
|
||||
// the clock hosts its own WiFi hotspot at the fixed address 192.168.4.1 (prm.ApSsid/prm.ApPsw) and
|
||||
// runs a DNS server that answers every query with its own IP (dnsServer.start(53,"*",...)) so most
|
||||
@@ -1242,6 +1290,9 @@ void handleConfigChanged(AsyncWebServerRequest *request) {
|
||||
if (old_utc_offset != prm.utc_offset) { //Change time zone
|
||||
setTime(now()+(prm.utc_offset-old_utc_offset)*3600);
|
||||
updateRTC();
|
||||
mySTD.offset = prm.utc_offset * 60; //keep myTZ current for a later enableDST change
|
||||
myDST.offset = mySTD.offset + (prm.enableDST ? 60 : 0);
|
||||
myTZ = Timezone(myDST, mySTD);
|
||||
}
|
||||
}
|
||||
else if (key == "set12_24") {
|
||||
@@ -1262,13 +1313,15 @@ void handleConfigChanged(AsyncWebServerRequest *request) {
|
||||
// running clock by +-1h right away and re-syncs the RTC, instead of only taking effect on the
|
||||
// next scheduled NTP refresh (myTZ itself is rebuilt from prm.enableDST inside
|
||||
// updateTimefromTimeserver() on that next refresh).
|
||||
// FIXED: this used to shift the clock by a blind +-1h, which is wrong outside the summer period
|
||||
// (switching DST on in winter put the clock 1h ahead until the next NTP sync). Now the local
|
||||
// time is converted back to UTC with the old rules and forward with the new ones.
|
||||
prm.enableDST = (value == "true");
|
||||
if (oldDST && !prm.enableDST) { //Switching off DST
|
||||
setTime(now()-3600);
|
||||
updateRTC();
|
||||
}
|
||||
if (!oldDST && prm.enableDST) { //Switching on DST
|
||||
setTime(now()+3600);
|
||||
if (oldDST != prm.enableDST) {
|
||||
time_t utc = myTZ.toUTC(now());
|
||||
myDST.offset = mySTD.offset + (prm.enableDST ? 60 : 0);
|
||||
myTZ = Timezone(myDST, mySTD);
|
||||
setTime(myTZ.toLocal(utc));
|
||||
updateRTC();
|
||||
}
|
||||
}
|
||||
@@ -1889,6 +1942,7 @@ void setup() {
|
||||
|
||||
startMDNS();
|
||||
startServer();
|
||||
startArduinoOTA();
|
||||
#ifdef USE_MQTT
|
||||
setupMqtt();
|
||||
#endif
|
||||
@@ -3221,6 +3275,9 @@ void loop(void) {
|
||||
}
|
||||
}
|
||||
editor();
|
||||
#ifdef USE_ARDUINO_OTA
|
||||
ArduinoOTA.handle();
|
||||
#endif
|
||||
if (makeFirmwareUpdate) {
|
||||
makeFirmwareUpdate = false;
|
||||
doFirmwareUpdate();
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
ZM1500_ESP32 (CURRENTLY ACTIVE, CLOCK_72 below) -> ZM1500.ino
|
||||
WORDCLOCK -> wordclock.ino (+ font5x7.h for its scrolling/overlay font)
|
||||
DUMMY -> dummy.ino (sensor-only "clock", no physical display)
|
||||
NIXIE595_MULTIPLEX (CLOCK_90) -> nixie595.ino
|
||||
*/
|
||||
|
||||
//#define CLOCK_1 //8266, UNFI PCB clock, 4x IN-16 tubes
|
||||
@@ -110,11 +111,43 @@
|
||||
//#define CLOCK_57A //V2-WROOM ESP32, UNFI 2xHV5222 6xZ57xM clock /flat panel
|
||||
//#define CLOCK_70 //1 tube esp8266 Nixie Clock by UNFI
|
||||
//#define CLOCK_71 //2x VQC10 clock by UNFI
|
||||
#define CLOCK_72 //ESP32, UNFI board, ZM1500
|
||||
//#define CLOCK_72 //ESP32, UNFI board, ZM1500
|
||||
//#define CLOCK_80 //word clock, english version
|
||||
//#define CLOCK_81 //word clock, german version
|
||||
//#define CLOCK_82 //word clock, hungarian version
|
||||
//#define CLOCK_99 //Dummy clock, sensor box with MQTT connector
|
||||
#define CLOCK_90 //8266, RetroModding NixieIN12v2 HV2.0 (5 V): 4x IN-12, 2x IN-3, 2x 74HC595 multiplex
|
||||
|
||||
#ifdef CLOCK_90 //8266, RetroModding NixieIN12v2 HV2.0 (5 V): 4x IN-12, 2x IN-3, 2x 74HC595 multiplex
|
||||
// Pins and shift-register bit map confirmed from the stock firmware SV1.3
|
||||
// (docs/stock-firmware-pins.md in the nixie-clock repo).
|
||||
#define DEBUG
|
||||
#define FW "fw90" //firmware name
|
||||
#define MAXBRIGHTNESS 100
|
||||
#define NIXIE595_MULTIPLEX
|
||||
#define PIN_LATCH 15 //74HC595 storage clock. DATA = GPIO13 (HSPI MOSI), CLOCK = GPIO14 (HSPI SCK)
|
||||
#define SPI_FREQ 4000000 //stock uses 25 MHz; 16 bits per slot need far less
|
||||
byte anodeBits[] = {14, 13, 12, 11}; //595 word bit of each tube's anode: position 0 = rightmost (A4) ... 3 = leftmost (A1)
|
||||
#define NEON_BITS (1 << 10) //IN-3 dots on P1. P2 (bit 15) drives the IN-12B decimal points on this board (JP2 closed)
|
||||
#define NEON_POS 1 //the dots are lit in this tube's multiplex slot, as on stock
|
||||
#define USE_NEOPIXEL
|
||||
#define NEOPIXEL_PIN 12 //bit-banged, GRB 800 kHz
|
||||
byte tubePixels[] = {0, 0, 1, 1, 2, 2, 3, 3}; //8 WS2812, two per tube (strip order not verified yet)
|
||||
#define USE_RTC //DS3231 on I2C, detected at startup (address 0x68); the stock firmware never used it
|
||||
#define PIN_SDA 4
|
||||
#define PIN_SCL 5
|
||||
#define LIGHT_SENSOR_PIN A0 //LDR to Vcc, 10k to GND (REF_RESISTANCE default)
|
||||
#define MAXIMUM_LUX 100 //Lux level for maximum tube brightness
|
||||
#define AP_NAME "UNICLOCK"
|
||||
#define AP_PASSWORD ""
|
||||
#define WEBNAME "IN-12 Nixie Clock"
|
||||
#define FIRMWARE_SERVER "" //no web-triggered HTTP update; use ArduinoOTA
|
||||
#define USE_ARDUINO_OTA
|
||||
#define OTA_HOSTNAME "nixie-in12"
|
||||
#if __has_include("secrets.h")
|
||||
#include "secrets.h" //optional, gitignored: DEFAULT_SSID, DEFAULT_PSW, OTA_PASSWORD (see secrets.h.example)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//______________________ESP8266 CLOCKS by UNFI and GP ______________________________________________________
|
||||
#ifdef CLOCK_1 //8266, UNFI PCB clock, 4x IN-16 tubes
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
// ============================================================================
|
||||
// Nixie driver: two cascaded 74HC595s on hardware SPI, multiplexed anodes
|
||||
// ----------------------------------------------------------------------------
|
||||
// Hardware: RetroModding NixieIN12v2 (CLOCK_90). The two 595s hold one 16-bit
|
||||
// word that drives the whole display:
|
||||
// bits 0..9 cathodes for digits 0..9 (via 2x ULN2003, shared by all tubes)
|
||||
// bits 11..14 tube anodes (anodeBits[] in clocks.h), one tube at a time
|
||||
// bits 10, 15 neon dots P1/P2 (NEON_BITS in clocks.h)
|
||||
// The high byte is shifted out first (ends up in the second 595), MSB first.
|
||||
// DATA and CLOCK are the HSPI MOSI/SCK pins (GPIO13/GPIO14), PIN_LATCH is the
|
||||
// 595 storage clock. Pinout and bit map come from the stock firmware, see
|
||||
// docs/stock-firmware-pins.md in the nixie-clock repo.
|
||||
//
|
||||
// The multiplex ISR writes the SPI1 and GPIO registers directly instead of
|
||||
// calling the SPI library, so it runs entirely from IRAM and keeps refreshing
|
||||
// while the flash cache is off (EEPROM/SPIFFS writes, OTA).
|
||||
//
|
||||
// Platform: ESP8266 ONLY (timer1 + SPI1 registers). The ISR state machine
|
||||
// (brightness by blanking time, animMask wipe) follows multiplex74141.ino.
|
||||
// ============================================================================
|
||||
#ifdef NIXIE595_MULTIPLEX
|
||||
#include <SPI.h>
|
||||
char tubeDriver[] = "NIXIE595_MULTIPLEX";
|
||||
|
||||
#if !defined(ESP8266)
|
||||
#error "NIXIE595_MULTIPLEX is for ESP8266 only!"
|
||||
#endif
|
||||
|
||||
int maxDigits = sizeof(anodeBits); //anodeBits is byte[], so sizeof == element count
|
||||
int maxDig = maxDigits;
|
||||
|
||||
// timer1 runs at TIM_DIV16 = 5 MHz (0.2 us per tick): 11000 ticks = 2.2 ms per tube slot,
|
||||
// about 110 Hz for 4 tubes. PWM_min/PWM_max are the lit part of a slot (brightness).
|
||||
// const, so the ISR's static locals are constant-initialized (no flash-resident init guard).
|
||||
const int PWMrefresh = 11000;
|
||||
const int PWM_min = 1000;
|
||||
const int PWM_max = 10000;
|
||||
|
||||
// Shift one 16-bit word into both 595s and latch it. Also called from main code via clearTubes(),
|
||||
// so the caller must keep the ISR out (see clearTubes()).
|
||||
static inline void IRAM_ATTR write595(uint16_t w) {
|
||||
while (SPI1CMD & SPIBUSY) {}
|
||||
SPI1W0 = (w >> 8) | ((w & 0xFF) << 8); //SPI1W0 byte 0 goes out first: high byte first
|
||||
SPI1CMD |= SPIBUSY;
|
||||
while (SPI1CMD & SPIBUSY) {}
|
||||
GPOS = 1 << PIN_LATCH; //rising edge copies the shift registers to the outputs
|
||||
__asm__ __volatile__("nop; nop; nop; nop;");
|
||||
GPOC = 1 << PIN_LATCH;
|
||||
}
|
||||
|
||||
void setup_pins() {
|
||||
DPRINTLN("Nixie clock - setup pins - 74HC595 multiplex mode...");
|
||||
pinMode(PIN_LATCH, OUTPUT);
|
||||
digitalWrite(PIN_LATCH, LOW);
|
||||
regPin(PIN_LATCH, "PIN_LATCH");
|
||||
|
||||
SPI.begin(); //HSPI: GPIO14 = SCK, GPIO13 = MOSI. It also claims GPIO12 (MISO), which the NeoPixel setup takes back later.
|
||||
SPI.setFrequency(SPI_FREQ);
|
||||
SPI.setBitOrder(MSBFIRST);
|
||||
SPI.setDataMode(SPI_MODE0);
|
||||
regPin(13, "PIN_DATA (HSPI MOSI)");
|
||||
regPin(14, "PIN_CLK (HSPI SCK)");
|
||||
//16-bit transfers, so one write fills both 595s
|
||||
SPI1U1 = (SPI1U1 & ~((SPIMMOSI << SPILMOSI) | (SPIMMISO << SPILMISO)))
|
||||
| (15 << SPILMOSI) | (15 << SPILMISO);
|
||||
write595(0); //all anodes, cathodes and dots off
|
||||
|
||||
DPRINT("Tubes: "); DPRINTLN(maxDigits);
|
||||
startTimer();
|
||||
}
|
||||
|
||||
void IRAM_ATTR writeDisplay() { //timer1 ISR, single-shot: must re-arm timer1 at the end of every call
|
||||
static byte pos = 0;
|
||||
static byte state = 0;
|
||||
static int timer = PWMrefresh;
|
||||
static byte num;
|
||||
static int brightness;
|
||||
static int PWMtimeBrightness = PWM_min;
|
||||
uint16_t w;
|
||||
|
||||
if (EEPROMsaving) { //keep the tubes dark while settings are written
|
||||
write595(0);
|
||||
timer1_write(PWMrefresh);
|
||||
return;
|
||||
}
|
||||
|
||||
intCounter++;
|
||||
brightness = displayON ? prm.dayBright : prm.nightBright;
|
||||
if (brightness > MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety
|
||||
|
||||
timer = PWMrefresh;
|
||||
|
||||
// Same state machine as multiplex74141.ino:
|
||||
// state 0: next tube; show the old digit (animation) or the current digit
|
||||
// state 1: show the new digit for the rest of the lit time (only while animating)
|
||||
// state 2: blank for the rest of the slot (this sets the brightness)
|
||||
// state 3: blank output, then back to state 0
|
||||
switch (state) {
|
||||
case 0:
|
||||
pos++;
|
||||
if (pos > maxDig - 1) {
|
||||
pos = 0;
|
||||
if (autoBrightness && displayON)
|
||||
PWMtimeBrightness = max(PWM_min, PWM_max * lx / MAXIMUM_LUX);
|
||||
else
|
||||
PWMtimeBrightness = max(PWM_min, PWM_max * brightness / MAXBRIGHTNESS);
|
||||
}
|
||||
if (animMask[pos] > 0) {
|
||||
num = oldDigit[pos];
|
||||
timer = (PWMtimeBrightness * (20 - animMask[pos])) / 20;
|
||||
state = 1;
|
||||
}
|
||||
else {
|
||||
num = digit[pos];
|
||||
timer = PWMtimeBrightness;
|
||||
state = 2;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
num = newDigit[pos];
|
||||
timer = (PWMtimeBrightness * animMask[pos]) / 20;
|
||||
state = 2;
|
||||
break;
|
||||
case 2:
|
||||
timer = PWMrefresh - PWMtimeBrightness;
|
||||
state = 3;
|
||||
break;
|
||||
}
|
||||
if (timer < 500) timer = 500; //a too short timer1 reload can miss the interrupt
|
||||
|
||||
if ((brightness == 0) || (state == 3) || (!radarON)) {
|
||||
state = 0;
|
||||
w = 0;
|
||||
}
|
||||
else {
|
||||
w = 0;
|
||||
if (num <= 9) w |= 1 << num; //cathode; 10 = blank, other glyphs are not available on a Nixie
|
||||
if ((pos == NEON_POS) && colonBlinkState) w |= NEON_BITS;
|
||||
if (w) w |= 1 << anodeBits[pos];
|
||||
}
|
||||
write595(w);
|
||||
|
||||
timer1_write(timer);
|
||||
}
|
||||
|
||||
void clearTubes() {
|
||||
noInterrupts(); //the ISR also writes the 595s
|
||||
write595(0);
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// Not needed: the ISR refreshes the display continuously (same as multiplex74141.ino).
|
||||
void writeDisplaySingle() {}
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copy to secrets.h (gitignored) and fill in. All entries are optional.
|
||||
// DEFAULT_SSID/DEFAULT_PSW are only applied on a factory reset (first boot with a new EEPROM layout);
|
||||
// otherwise set the WiFi from the clock's web page.
|
||||
#define DEFAULT_SSID ""
|
||||
#define DEFAULT_PSW ""
|
||||
#define OTA_PASSWORD "" //ArduinoOTA password; remove the line for no password
|
||||
@@ -100,13 +100,17 @@ inline byte tubeForPixel(int i) {
|
||||
//than NeoEsp32Rmt7Ws2812xMethod, which can glitch the strict WS2812 bit
|
||||
//timing (~1.25us/bit) if an ISR delays the RMT hardware mid-frame.
|
||||
NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp32I2s1800KbpsMethod> strip(PixelCount+4,PixelPin); //NeoEsp32I2s1800KbpsMethod is used here because it is more reliable than NeoEsp32Rmt7Ws2812xMethod!
|
||||
#else
|
||||
#define NEOPIXEL_PIN 3
|
||||
#elif NEOPIXEL_PIN == 3 //default (see clocks.h)
|
||||
byte PixelPin = 3; // on 8266 it MUST use GPIO3 (RX pin)
|
||||
//8266 has no I2S/RMT WS2812 helper here; Neo800KbpsMethod bit-bangs the
|
||||
//protocol with cycle-counted delays, which is why GPIO3/RX is mandated -
|
||||
//it's one of the few pins whose timing isn't disturbed by the UART/boot ROM.
|
||||
NeoPixelBrightnessBus<NeoGrbFeature, Neo800KbpsMethod> strip(PixelCount+4);
|
||||
#else
|
||||
//Any other 8266 pin: cycle-counted bit-bang. Interrupts are off while Show()
|
||||
//runs (about 30 us per pixel), which briefly delays the multiplex ISR.
|
||||
byte PixelPin = NEOPIXEL_PIN;
|
||||
NeoPixelBrightnessBus<NeoGrbFeature, NeoEsp8266BitBang800KbpsMethod> strip(PixelCount+4, PixelPin);
|
||||
#endif
|
||||
|
||||
//Gamma-correction lookup table object - NOTE: currently declared but never
|
||||
@@ -121,7 +125,10 @@ void setupNeopixel() {
|
||||
DPRINT("Pixel count: "); DPRINTLN(PixelCount);
|
||||
DPRINT("Brightness:"); DPRINT(c_MinBrightness); DPRINT(" - "); DPRINTLN(c_MaxBrightness);
|
||||
neoBrightness = prm.rgbBrightness;
|
||||
strip.Begin(); //initialize the underlying I2S/RMT/bit-bang driver and GPIO
|
||||
//The bit-bang method sets the pin to OUTPUT in its constructor (before setup()), and Begin()
|
||||
//does not. A driver may have claimed the pin since (nixie595.ino: SPI.begin() makes GPIO12 MISO).
|
||||
pinMode(PixelPin, OUTPUT);
|
||||
strip.Begin(); //initialize the underlying I2S/RMT/bit-bang driver and GPIO
|
||||
strip.Show(); //push the (still all-black, default-constructed) buffer out so the strip starts dark, not showing stale/garbage pixel data
|
||||
strip.SetBrightness(neoBrightness);
|
||||
fixColor(prm.rgbFixColor); //apply the persisted fixed color immediately so the backlight is correct even before the first doAnimationMakuna() tick
|
||||
|
||||
@@ -28,3 +28,10 @@ upload_resetmethod = ck
|
||||
monitor_port = COM7
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp8266_exception_decoder
|
||||
|
||||
; Same firmware, uploaded over WiFi with ArduinoOTA (espota). Add --auth=<OTA_PASSWORD> to
|
||||
; upload_flags once secrets.h sets one.
|
||||
[env:nixie_in12_ota]
|
||||
extends = env:nixie_in12
|
||||
upload_protocol = espota
|
||||
upload_port = 10.1.1.97
|
||||
Reference in new issue
Block a user