/* * Spirit Box with Touchscreen * OpenParanormal Project * * FM radio scanner with touchscreen controls * Rapidly scans through FM frequencies * * Hardware: * - ESP32 DevKit * - 2.8" or 3.5" ILI9341 TFT LCD Touchscreen * - TEA5767 FM Radio Module * - PAM8403 Amplifier (optional, for louder audio) * - Speaker (8Ω 0.5W) * * Wiring: * TEA5767: * VCC → 3.3V * GND → GND * SDA → GPIO 21 * SCL → GPIO 22 * * TFT Display (example pins, adjust in User_Setup.h): * VCC → 3.3V * GND → GND * CS → GPIO 15 * RST → GPIO 4 * DC → GPIO 2 * MOSI → GPIO 23 * SCK → GPIO 18 * MISO → GPIO 19 * T_CS → GPIO 21 (touchscreen) * * Libraries needed: * - TFT_eSPI * - Radio by Matthias Hertel */ #include #include #include #include TFT_eSPI tft = TFT_eSPI(); TEA5767 radio; // Spirit Box settings bool scanning = false; float frequency = 87.5; // Start frequency (MHz) int sweepSpeed = 50; // Milliseconds per step bool reverseMode = false; int volume = 5; // 0-15 // Button coordinates struct Button { int x, y, w, h; String label; uint16_t color; }; Button btnScan = {10, 60, 140, 50, "START SCAN", TFT_GREEN}; Button btnReverse = {160, 60, 140, 50, "REVERSE", TFT_BLUE}; Button btnSpeedUp = {10, 120, 90, 40, "FASTER", TFT_ORANGE}; Button btnSpeedDown = {110, 120, 90, 40, "SLOWER", TFT_ORANGE}; Button btnVolUp = {210, 120, 90, 40, "VOL+", TFT_PURPLE}; Button btnVolDown = {210, 170, 90, 40, "VOL-", TFT_PURPLE}; void setup() { Serial.begin(115200); // Initialize TFT tft.init(); tft.setRotation(1); // Landscape tft.fillScreen(TFT_BLACK); // Initialize I2C Wire.begin(); // Initialize radio radio.init(); radio.setFrequency(frequency); radio.setVolume(volume); // Draw UI drawUI(); } void loop() { // Check for touch uint16_t touchX, touchY; bool pressed = tft.getTouch(&touchX, &touchY); if (pressed) { handleTouch(touchX, touchY); delay(200); // Debounce } // Perform scanning if active if (scanning) { scan(); } } void drawUI() { tft.fillScreen(TFT_BLACK); // Title tft.setTextColor(TFT_CYAN); tft.setTextSize(2); tft.setCursor(60, 10); tft.println("SPIRIT BOX"); // Status bar tft.setTextSize(1); tft.setCursor(10, 35); tft.print("Freq: "); tft.print(frequency, 1); tft.print(" MHz | Speed: "); tft.print(sweepSpeed); tft.print("ms | Vol: "); tft.println(volume); // Draw buttons drawButton(btnScan); drawButton(btnReverse); drawButton(btnSpeedUp); drawButton(btnSpeedDown); drawButton(btnVolUp); drawButton(btnVolDown); } void drawButton(Button &btn) { tft.fillRoundRect(btn.x, btn.y, btn.w, btn.h, 8, btn.color); tft.drawRoundRect(btn.x, btn.y, btn.w, btn.h, 8, TFT_WHITE); tft.setTextColor(TFT_WHITE); tft.setTextSize(2); // Center text int textW = btn.label.length() * 12; int textX = btn.x + (btn.w - textW) / 2; int textY = btn.y + (btn.h / 2) - 8; tft.setCursor(textX, textY); tft.println(btn.label); } void handleTouch(uint16_t x, uint16_t y) { // Check which button was pressed if (isTouched(btnScan, x, y)) { scanning = !scanning; btnScan.label = scanning ? "STOP SCAN" : "START SCAN"; btnScan.color = scanning ? TFT_RED : TFT_GREEN; drawButton(btnScan); Serial.println(scanning ? "Scanning started" : "Scanning stopped"); } else if (isTouched(btnReverse, x, y)) { reverseMode = !reverseMode; btnReverse.color = reverseMode ? TFT_YELLOW : TFT_BLUE; drawButton(btnReverse); Serial.println(reverseMode ? "Reverse mode" : "Forward mode"); } else if (isTouched(btnSpeedUp, x, y)) { sweepSpeed = max(10, sweepSpeed - 10); updateStatus(); Serial.print("Speed: "); Serial.println(sweepSpeed); } else if (isTouched(btnSpeedDown, x, y)) { sweepSpeed = min(500, sweepSpeed + 10); updateStatus(); Serial.print("Speed: "); Serial.println(sweepSpeed); } else if (isTouched(btnVolUp, x, y)) { volume = min(15, volume + 1); radio.setVolume(volume); updateStatus(); Serial.print("Volume: "); Serial.println(volume); } else if (isTouched(btnVolDown, x, y)) { volume = max(0, volume - 1); radio.setVolume(volume); updateStatus(); Serial.print("Volume: "); Serial.println(volume); } } bool isTouched(Button &btn, uint16_t x, uint16_t y) { return (x >= btn.x && x <= btn.x + btn.w && y >= btn.y && y <= btn.y + btn.h); } void scan() { // Increment or decrement frequency if (reverseMode) { frequency -= 0.1; if (frequency < 87.5) frequency = 108.0; } else { frequency += 0.1; if (frequency > 108.0) frequency = 87.5; } // Set radio frequency radio.setFrequency(frequency); // Update display updateStatus(); // Wait based on sweep speed delay(sweepSpeed); } void updateStatus() { // Clear status area tft.fillRect(0, 30, 320, 20, TFT_BLACK); // Redraw status tft.setTextColor(TFT_CYAN); tft.setTextSize(1); tft.setCursor(10, 35); tft.print("Freq: "); tft.print(frequency, 1); tft.print(" MHz | Speed: "); tft.print(sweepSpeed); tft.print("ms | Vol: "); tft.println(volume); }