/* * Wally — Digital Stables Digital Workhorse * Hardware Verification Sketch * * Tests every on-board peripheral: * 1. PCF8563T RTC — 1 Hz CLKOUT drives main loop via interrupt * 2. Ra-02 LoRa 433 MHz — TX test packet every 10 s, RX listen every tick * 3. DS18B20 temperature sensor (1-Wire) — read every tick * 4. TPL5010 hardware watchdog — periodic WDI pulse * * Required libraries (install via Arduino Library Manager or local path): * PCF8563TimeManager — Digital Stables (DigitalStablesEsp32Lib) * LoRa — Sandeep Mistry (search "LoRa" by Sandeep Mistry) * OneWire — Paul Stoffregen (search "OneWire") * DallasTemperature — Miles Burton (search "DallasTemperature") * * Board: ESP32 Dev Module (or FireBeetle ESP32) * Upload speed: 921600 * * Serial commands (send via Arduino Serial Monitor, line ending: None or NL): * Ping → Ok-Ping * GetTime → prints current RTC time * SetTime#date#month#year#dow#h#m#s → sets RTC time * e.g. SetTime#17#5#26#7#12#0#0 (17 May 2026, Sunday, 12:00:00) * * Pin assignments verified against Wally schematic Rev 17 / Daffodil firmware. */ #include #include #include #include #include #include // ───────────────────────────────────────────────────────────────────────────── // Pin map (verified from Daffodil.ino / Wally schematic Rev 17) // ───────────────────────────────────────────────────────────────────────────── // RTC PCF8563T — 1 Hz square-wave interrupt output #define RTC_CLK_OUT 4 // I²C (PCF8563TimeManager hard-codes SDA=21, SCL=22 internally) // #define I2C_SDA 21 // #define I2C_SCL 22 // LoRa Ra-02 — custom SPI bus (NOT the default VSPI pins on Wally) #define LORA_SCK 14 #define LORA_MISO 12 #define LORA_MOSI 13 #define LORA_SS 15 #define LORA_RST 16 #define LORA_DIO0 17 #define LORA_FREQ 433E6 // DS18B20 temperature (1-Wire) #define TEMPERATURE_PIN 27 // RTC coin-cell voltage monitor (analog) #define RTC_BATT_VOLT 36 // TPL5010 hardware watchdog #define TPL5010_DONE 25 // Pulse HIGH to reset watchdog timer #define TPL5010_WAKE 35 // Watchdog asserts HIGH here when it fires // Misc #define SLEEP_SWITCH 26 // ───────────────────────────────────────────────────────────────────────────── // Globals // ───────────────────────────────────────────────────────────────────────────── PCF8563TimeManager timeManager(Serial); volatile bool clockTicked = false; portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; RTCInfoRecord currentTimerRecord; OneWire oneWire(TEMPERATURE_PIN); DallasTemperature tempSensor(&oneWire); int loraPacketCount = 0; unsigned long lastWatchdogPet = 0; const unsigned long WD_INTERVAL_MS = 8000; // Well under TPL5010 resistor timeout int tickCount = 0; // Increments every RTC tick (1 Hz) // ───────────────────────────────────────────────────────────────────────────── // RTC 1 Hz interrupt // ───────────────────────────────────────────────────────────────────────────── void IRAM_ATTR clockTick() { portENTER_CRITICAL_ISR(&mux); clockTicked = true; portEXIT_CRITICAL_ISR(&mux); } // ───────────────────────────────────────────────────────────────────────────── // Watchdog // ───────────────────────────────────────────────────────────────────────────── void petWatchdog() { digitalWrite(TPL5010_DONE, HIGH); delayMicroseconds(200); digitalWrite(TPL5010_DONE, LOW); lastWatchdogPet = millis(); } void initWatchdog() { pinMode(TPL5010_DONE, OUTPUT); pinMode(TPL5010_WAKE, INPUT); digitalWrite(TPL5010_DONE, LOW); petWatchdog(); Serial.println("[WDG] OK — TPL5010 armed, pet interval 8 s"); } // ───────────────────────────────────────────────────────────────────────────── // LoRa — Ra-02 (SX127x) 433 MHz // ───────────────────────────────────────────────────────────────────────────── void initLoRa() { SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_SS); LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0); if (!LoRa.begin(LORA_FREQ)) { Serial.println("[LoRa] FAIL — init failed. Check wiring, RST and SS pins."); return; } LoRa.setSpreadingFactor(9); LoRa.setSignalBandwidth(125E3); LoRa.setCodingRate4(5); LoRa.setTxPower(17); Serial.println("[LoRa] OK — 433 MHz SF9 BW125 CR4/5 17 dBm"); } void loraSendPacket(const String &msg) { LoRa.beginPacket(); LoRa.print(msg); LoRa.endPacket(); Serial.printf("[LoRa] TX >> \"%s\"\n", msg.c_str()); } void loraCheckRX() { int packetSize = LoRa.parsePacket(); if (packetSize > 0) { String received = ""; while (LoRa.available()) received += (char)LoRa.read(); Serial.printf("[LoRa] RX << \"%s\" RSSI %d dBm SNR %.1f dB\n", received.c_str(), LoRa.packetRssi(), LoRa.packetSnr()); } } // ───────────────────────────────────────────────────────────────────────────── // Temperature — DS18B20 // ───────────────────────────────────────────────────────────────────────────── void initTemp() { tempSensor.begin(); int count = tempSensor.getDeviceCount(); if (count == 0) { Serial.println("[TEMP] No DS18B20 found on 1-Wire bus. Check pin " + String(TEMPERATURE_PIN)); } else { Serial.printf("[TEMP] OK — %d sensor(s) on bus\n", count); } } void printTemp() { tempSensor.requestTemperatures(); float t = tempSensor.getTempCByIndex(0); if (t == DEVICE_DISCONNECTED_C) { Serial.println("[TEMP] Sensor disconnected"); } else { Serial.printf("[TEMP] %.2f °C\n", t); } } // ───────────────────────────────────────────────────────────────────────────── // Serial command handler // ───────────────────────────────────────────────────────────────────────────── void handleSerialCommand() { if (Serial.available() == 0) return; String command = Serial.readString(); command.trim(); if (command.startsWith("Ping")) { Serial.println("Ok-Ping"); } else if (command.startsWith("GetTime")) { // Print the current RTC time currentTimerRecord = timeManager.now(); Serial.print("[RTC] "); timeManager.printTimeToSerial(currentTimerRecord); Serial.println("Ok-GetTime"); } else if (command.startsWith("SetTime")) { // Format: SetTime#date#month#year#dayOfWeek#hour#minute#second // Example: SetTime#17#5#26#7#12#0#0 (17 May 2026, Sunday, 12:00:00) timeManager.setTime(command); currentTimerRecord = timeManager.now(); Serial.print("[RTC] Time updated → "); timeManager.printTimeToSerial(currentTimerRecord); Serial.println("Ok-SetTime"); } else { Serial.print("Unknown command: "); Serial.println(command); Serial.println("Commands: Ping | GetTime | SetTime#d#m#y#dow#h#m#s"); } Serial.flush(); } // ───────────────────────────────────────────────────────────────────────────── // Setup // ───────────────────────────────────────────────────────────────────────────── void setup() { Serial.begin(115200); delay(400); Serial.println("\n================================"); Serial.println(" Wally — Hardware Verify"); Serial.println(" Digital Stables"); Serial.println("================================"); initWatchdog(); // Pet first — subsequent inits can be slow // RTC — PCF8563TimeManager uses Wire internally (SDA=21, SCL=22) pinMode(RTC_CLK_OUT, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(RTC_CLK_OUT), clockTick, RISING); timeManager.start(); timeManager.PCF8563osc1Hz(); // Enable 1 Hz square wave on CLKOUT currentTimerRecord = timeManager.now(); Serial.print("[RTC] OK — current time: "); timeManager.printTimeToSerial(currentTimerRecord); // ── To set a specific time, uncomment and edit: ── // RTCInfoRecord t; // t.year = 26; t.month = 5; t.date = 17; // t.hour = 12; t.minute = 0; t.second = 0; // timeManager.setTime(t); initLoRa(); initTemp(); Serial.println("--------------------------------"); Serial.println("Commands: Ping | GetTime | SetTime#d#m#y#dow#h#m#s"); Serial.println("Init complete. Waiting for RTC ticks.\n"); } // ───────────────────────────────────────────────────────────────────────────── // Loop — driven by 1 Hz RTC interrupt, not delay() // ───────────────────────────────────────────────────────────────────────────── void loop() { // ── Serial commands (checked every iteration) ───────────────────────────── handleSerialCommand(); // ── Wait for RTC tick ───────────────────────────────────────────────────── bool ticked = false; portENTER_CRITICAL(&mux); if (clockTicked) { clockTicked = false; ticked = true; } portEXIT_CRITICAL(&mux); if (!ticked) return; // Nothing to do until the next RTC tick tickCount++; // ── Pet watchdog ────────────────────────────────────────────────────────── if (millis() - lastWatchdogPet >= WD_INTERVAL_MS) { petWatchdog(); Serial.println("[WDG] Pet"); } // ── RTC ─────────────────────────────────────────────────────────────────── currentTimerRecord = timeManager.now(); Serial.print("[RTC] "); timeManager.printTimeToSerial(currentTimerRecord); // ── Temperature ─────────────────────────────────────────────────────────── printTemp(); // ── LoRa TX every 10 ticks (10 s) ──────────────────────────────────────── if (tickCount % 10 == 0) { String msg = "Wally #" + String(loraPacketCount++) + " " + String(currentTimerRecord.hour) + ":" + String(currentTimerRecord.minute) + ":" + String(currentTimerRecord.second); loraSendPacket(msg); } // ── LoRa RX (non-blocking, every tick) ─────────────────────────────────── loraCheckRX(); Serial.println(); }