#include #include #include #include #include #include #include #include // for ESP.restart() using std::vector; // ── PINS ───────────────────────────────────────────────────────────────────── #define TFT_CS 15 #define TFT_DC 2 #define TFT_RST -1 // not wired #define TFT_LED 21 // backlight (active-high) #define BUZZER_PIN 27 // RGB startup LEDs (all active-low) #define RGB_R_PIN 17 #define RGB_G_PIN 4 #define RGB_B_PIN 16 // therapy indicator LED = red LED on GPIO 17 (active-low) #define LED_PIN RGB_R_PIN // SD on VSPI #define SD_CS_PIN 5 #define SD_SCK_PIN 18 #define SD_MISO_PIN 19 #define SD_MOSI_PIN 23 // ── SCREEN & LAYOUT ─────────────────────────────────────────────────────────── #define XRES 320 #define YRES 240 #define HEADER_H 40 #define ITEM_H 30 #define SPACING 5 #define BUTTON_H 60 #define ITEM_Y0 (HEADER_H + SPACING) #define ITEM_Y1 (ITEM_Y0 + ITEM_H + SPACING) #define BTN_Y (ITEM_Y1 + ITEM_H + SPACING + 10) #define BTN_M 10 #define BTN_GAP 15 #define BTN_W ((XRES - BTN_M*2 - BTN_GAP*3) / 4) // reset button: half-width, half-height of normal buttons #define RESET_BTN_W (BTN_W/2) #define RESET_BTN_H (BUTTON_H/2) #define RESET_BTN_X (XRES - BTN_M - RESET_BTN_W) #define RESET_BTN_Y (SPACING) // ── TIMER-BOX (aligned with the “Freq:” line) ───────────────────────────────── #define TIMER_X 200 #define TIMER_Y 50 #define TIMER_W 60 #define TIMER_H 12 // ── THERAPY EXTRA BUTTONS (on therapy screen) ───────────────────────────────── #define SWEEP_BTN_X (BTN_M + 1*(BTN_W + BTN_GAP)) // slot #2 #define SWEEP_BTN_Y (BTN_Y) #define SWEEP_BTN_W (BTN_W) #define SWEEP_BTN_H (BUTTON_H) #define PLUS60_BTN_X (BTN_M + 2*(BTN_W + BTN_GAP)) // slot #3 #define PLUS60_BTN_Y (BTN_Y) #define PLUS60_BTN_W (BTN_W) #define PLUS60_BTN_H (BUTTON_H) // ── LIVE DUTY BUTTONS (therapy screen) ────────────────────────────────────── #define DUTY_MINUS_BTN_X (BTN_M + 3*(BTN_W + BTN_GAP)) #define DUTY_MINUS_BTN_Y (BTN_Y) #define DUTY_MINUS_BTN_W (BTN_W) #define DUTY_MINUS_BTN_H (BUTTON_H/2 - 3) #define DUTY_PLUS_BTN_X (BTN_M + 3*(BTN_W + BTN_GAP)) #define DUTY_PLUS_BTN_Y (BTN_Y + BUTTON_H/2 + 3) #define DUTY_PLUS_BTN_W (BTN_W) #define DUTY_PLUS_BTN_H (BUTTON_H/2 - 3) // ── PWM OUTPUT SETTINGS ────────────────────────────────────────────────────── #define PWM_OUT_PIN BUZZER_PIN #define PWM_CH 0 #define PWM_RES_BITS 10 #define PWM_MAX_DUTY ((1 << PWM_RES_BITS) - 1) // ── SWEEP SETTINGS ─────────────────────────────────────────────────────────── bool sweepEnabled = false; // sweep range around currentFreq (±10% by default) float sweepSpanPct = 0.10f; // speed/feel of sweep uint32_t sweepDwellMs = 10; // time between steps float sweepStepHz = 20.0f; // step per tick (auto adjusted per freq if needed) float sweepMinHz = 100.0f; float sweepMaxHz = 2000.0f; float sweepCurrentHz = 100.0f; int sweepDir = +1; uint32_t lastSweepMs = 0; // ── DYNAMIC RIFE DATA ───────────────────────────────────────────────────────── vector choroby; vector> liczby; int index_choroby = 0; // ── SPI & PERIPHERALS ───────────────────────────────────────────────────────── SPIClass sdspi(VSPI); Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST); CYD28_TouchR ts(XRES, YRES); // ── STATE ───────────────────────────────────────────────────────────────────── bool needRedraw = true; // redraw menu? bool generating = false; // are we in therapy mode? unsigned long lastTouch = 0; int P1 = 1; // 1-based selection int freqCount, currentFreq; String selectedLabel; int lastRem = -1; // last‐drawn seconds remaining int dutyPercent = 20; // live adjustable duty % for PWM output // ── PROTOTYPES ──────────────────────────────────────────────────────────────── bool readTouch(int &x,int &y); void drawMenuFull(); void drawButtons(bool mainMenu); void drawResetButton(); void handleTouch(int x,int y); void Generuj(); void drawTherapyStatic(); void updateTimerDisplay(int rem); void loadFrequencies(); void StartupScreen(); // sweep + UI helpers void drawSweepButton(); void drawPlus60Button(); void setSweepRangeForCurrentFreq(); void sweepTick(); void drawDutyButtons(); void updateDutyDisplay(); void pwmInitOutput(); void pwmSetFreqDuty(uint32_t freqHz, int dutyPct); void pwmStopOutput(); // ── SETUP ───────────────────────────────────────────────────────────────────── void setup(){ Serial.begin(115200); SPI.begin(14, 12, 13, TFT_CS); tft.begin(); tft.setRotation(1); StartupScreen(); pinMode(TFT_LED, OUTPUT); digitalWrite(TFT_LED, HIGH); ts.begin(); ts.setRotation(1); pinMode(BUZZER_PIN, OUTPUT); pwmInitOutput(); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, HIGH); sdspi.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN); if(!SD.begin(SD_CS_PIN, sdspi)){ Serial.println("ERROR: SD init failed"); } else { if(!SD.exists("/frequencies.csv")){ File f = SD.open("/frequencies.csv", FILE_WRITE); f.println("// Rife Frequencies CSV Template"); f.println("// Format: Label, f1, f2, f3, ..."); f.println("ZAPPER,30000,30000,30000,30000,30000"); f.close(); } } loadFrequencies(); drawMenuFull(); } // ── MAIN LOOP ──────────────────────────────────────────────────────────────── void loop(){ int x,y; if(!generating && readTouch(x,y) && millis() - lastTouch > 200){ lastTouch = millis(); handleTouch(x,y); if(needRedraw){ drawMenuFull(); needRedraw = false; } } } // ── STARTUP SPLASH & RGB FLASH ──────────────────────────────────────────────── void StartupScreen(){ pinMode(RGB_R_PIN, OUTPUT); pinMode(RGB_G_PIN, OUTPUT); pinMode(RGB_B_PIN, OUTPUT); tft.fillScreen(ILI9341_BLACK); tft.setTextSize(3); tft.setTextColor(ILI9341_WHITE); int16_t txtW = 11 * 18; int16_t x = (XRES - txtW) / 2; int16_t txtH = 8 * 3; int16_t y = (YRES - txtH) / 2; tft.setCursor(x, y); tft.print("KM CYD-RIFE"); unsigned long t0 = millis(); while(millis() - t0 < 3000){ int phase = (((millis() - t0) / 430) % 7); digitalWrite(RGB_R_PIN, (phase & 1) ? LOW : HIGH); digitalWrite(RGB_G_PIN, (phase & 2) ? LOW : HIGH); digitalWrite(RGB_B_PIN, (phase & 4) ? LOW : HIGH); } digitalWrite(RGB_R_PIN, HIGH); digitalWrite(RGB_G_PIN, HIGH); digitalWrite(RGB_B_PIN, HIGH); } // ── LOAD CSV DATA ───────────────────────────────────────────────────────────── void loadFrequencies(){ choroby.clear(); liczby.clear(); File f = SD.open("/frequencies.csv"); if(!f){ Serial.println("Failed to open CSV"); return; } while(f.available()){ String line = f.readStringUntil('\n'); line.trim(); if(line.startsWith("//") || line.indexOf(',')<0) continue; int c = line.indexOf(','); String lbl = line.substring(0,c); lbl.trim(); choroby.push_back(lbl); vector vf; int i = c+1; while(i < line.length()){ int j = line.indexOf(',', i); String num = j<0 ? line.substring(i) : line.substring(i, j); num.trim(); long v = num.toInt(); if(v>0) vf.push_back(v); if(j<0) break; i = j+1; } liczby.push_back(vf); } f.close(); index_choroby = choroby.size(); Serial.printf("Loaded %d entries\n", index_choroby); } // ── READ TOUCH ──────────────────────────────────────────────────────────────── bool readTouch(int &tx,int &ty){ if(!ts.touched()) return false; auto p = ts.getPointScaled(); tx = p.x; ty = p.y; return true; } // ── DRAW FULL MENU ──────────────────────────────────────────────────────────── void drawMenuFull(){ tft.fillScreen(ILI9341_BLACK); // Header tft.setTextSize(2); tft.setTextColor(ILI9341_WHITE); tft.setCursor(10,10); tft.print("Select Illness"); drawResetButton(); tft.setTextSize(2); tft.setTextColor(ILI9341_WHITE); // Two items per page int start = ((P1-1)/2)*2; for(int k=0; k<2; k++){ int idx = start + k; if(idx >= index_choroby) break; int yy = (k==0 ? ITEM_Y0 : ITEM_Y1); tft.setCursor(20, yy+6); tft.print(choroby[idx]); if(idx == P1-1){ tft.drawRect(10, yy, XRES-20, ITEM_H, ILI9341_YELLOW); } } drawButtons(true); } // ── DRAW BUTTONS ───────────────────────────────────────────────────────────── void drawButtons(bool mainMenu){ tft.setTextSize(2); tft.setTextColor(ILI9341_BLACK); if(mainMenu){ for(int b=0; b<4; b++){ int bx = BTN_M + b*(BTN_W+BTN_GAP); tft.fillRect(bx, BTN_Y, BTN_W, BUTTON_H, ILI9341_WHITE); switch(b){ case 0: tft.setCursor(bx+5, BTN_Y+20); tft.print("BACK"); break; case 1: tft.fillTriangle( bx+BTN_W/2, BTN_Y+10, bx+BTN_W/2-10, BTN_Y+BUTTON_H-10, bx+BTN_W/2+10, BTN_Y+BUTTON_H-10, ILI9341_BLACK ); break; case 2: tft.fillTriangle( bx+BTN_W/2, BTN_Y+BUTTON_H-10, bx+BTN_W/2-10, BTN_Y+10, bx+BTN_W/2+10, BTN_Y+10, ILI9341_BLACK ); break; case 3: tft.setCursor(bx+BTN_W/2-10, BTN_Y+20); tft.print("OK"); break; } } } else { // therapy/finish screen draws BACK on slot #1 tft.fillRect(BTN_M, BTN_Y, BTN_W, BUTTON_H, ILI9341_WHITE); tft.setCursor(BTN_M+5, BTN_Y+20); tft.print("BACK"); } } // ── DRAW RESET BUTTON ───────────────────────────────────────────────────────── void drawResetButton(){ tft.fillRect(RESET_BTN_X, RESET_BTN_Y, RESET_BTN_W, RESET_BTN_H, ILI9341_WHITE); tft.setTextSize(2); tft.setTextColor(ILI9341_BLACK); tft.setCursor(RESET_BTN_X+5, RESET_BTN_Y + RESET_BTN_H/2 - 6); tft.print("RST"); } // ── SWEEP UI BUTTON ─────────────────────────────────────────────────────────── void drawSweepButton(){ uint16_t fill = sweepEnabled ? ILI9341_GREEN : ILI9341_RED; tft.fillRect(SWEEP_BTN_X, SWEEP_BTN_Y, SWEEP_BTN_W, SWEEP_BTN_H, fill); tft.setTextSize(2); tft.setTextColor(ILI9341_BLACK); tft.setCursor(SWEEP_BTN_X + 10, SWEEP_BTN_Y + 20); tft.print("SWEEP"); } // ── +60s BUTTON ────────────────────────────────────────────────────────────── void drawPlus60Button(){ tft.fillRect(PLUS60_BTN_X, PLUS60_BTN_Y, PLUS60_BTN_W, PLUS60_BTN_H, ILI9341_CYAN); tft.setTextSize(2); tft.setTextColor(ILI9341_BLACK); tft.setCursor(PLUS60_BTN_X + 8, PLUS60_BTN_Y + 20); tft.print("+60s"); } // ── SET SWEEP RANGE AROUND CURRENT FREQ ─────────────────────────────────────── void setSweepRangeForCurrentFreq(){ float cf = (float)currentFreq; if (cf < 1.0f) cf = 1.0f; sweepMinHz = cf * (1.0f - sweepSpanPct); sweepMaxHz = cf * (1.0f + sweepSpanPct); if (sweepMinHz < 1.0f) sweepMinHz = 1.0f; if (sweepMaxHz < sweepMinHz + 1.0f) sweepMaxHz = sweepMinHz + 1.0f; float range = sweepMaxHz - sweepMinHz; float step = range / 250.0f; if (step < 1.0f) step = 1.0f; if (step > 200.0f) step = 200.0f; sweepStepHz = step; sweepCurrentHz = cf; if (sweepCurrentHz < sweepMinHz) sweepCurrentHz = sweepMinHz; if (sweepCurrentHz > sweepMaxHz) sweepCurrentHz = sweepMaxHz; sweepDir = +1; lastSweepMs = millis(); } // ── RUN SWEEP (called frequently during therapy) ────────────────────────────── void sweepTick(){ if(!sweepEnabled) return; uint32_t now = millis(); if(now - lastSweepMs < sweepDwellMs) return; lastSweepMs = now; sweepCurrentHz += (float)sweepDir * sweepStepHz; if(sweepCurrentHz >= sweepMaxHz){ sweepCurrentHz = sweepMaxHz; sweepDir = -1; } else if(sweepCurrentHz <= sweepMinHz){ sweepCurrentHz = sweepMinHz; sweepDir = +1; } pwmSetFreqDuty((uint32_t)sweepCurrentHz, dutyPercent); } // ── DUTY BUTTONS ───────────────────────────────────────────────────────────── void drawDutyButtons(){ tft.fillRect(DUTY_MINUS_BTN_X, DUTY_MINUS_BTN_Y, DUTY_MINUS_BTN_W, DUTY_MINUS_BTN_H, ILI9341_ORANGE); tft.setTextSize(2); tft.setTextColor(ILI9341_BLACK); tft.setCursor(DUTY_MINUS_BTN_X + 14, DUTY_MINUS_BTN_Y + 8); tft.print("D-"); tft.fillRect(DUTY_PLUS_BTN_X, DUTY_PLUS_BTN_Y, DUTY_PLUS_BTN_W, DUTY_PLUS_BTN_H, ILI9341_GREEN); tft.setCursor(DUTY_PLUS_BTN_X + 14, DUTY_PLUS_BTN_Y + 8); tft.print("D+"); } void updateDutyDisplay(){ tft.fillRect(10, 65, 90, 10, ILI9341_BLACK); tft.setTextSize(1); tft.setTextColor(ILI9341_WHITE); tft.setCursor(10, 65); tft.print("Duty: "); tft.print(dutyPercent); tft.print("%"); } void pwmInitOutput(){ #if defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 3) ledcAttach(PWM_OUT_PIN, 1000, PWM_RES_BITS); ledcWrite(PWM_OUT_PIN, 0); #else ledcSetup(PWM_CH, 1000, PWM_RES_BITS); ledcAttachPin(PWM_OUT_PIN, PWM_CH); ledcWrite(PWM_CH, 0); #endif } void pwmSetFreqDuty(uint32_t freqHz, int dutyPct){ if(freqHz < 1) freqHz = 1; if(dutyPct < 1) dutyPct = 1; if(dutyPct > 95) dutyPct = 95; uint32_t dutyValue = (uint32_t)((PWM_MAX_DUTY * (uint32_t)dutyPct) / 100U); #if defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 3) ledcDetach(PWM_OUT_PIN); ledcAttach(PWM_OUT_PIN, freqHz, PWM_RES_BITS); ledcWrite(PWM_OUT_PIN, dutyValue); #else ledcSetup(PWM_CH, freqHz, PWM_RES_BITS); ledcWrite(PWM_CH, dutyValue); #endif } void pwmStopOutput(){ #if defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 3) ledcWrite(PWM_OUT_PIN, 0); #else ledcWrite(PWM_CH, 0); #endif } // ── HANDLE TOUCH ───────────────────────────────────────────────────────────── void handleTouch(int x,int y){ // reset? if(x>=RESET_BTN_X && x=RESET_BTN_Y && yBTN_Y+BUTTON_H) return; for(int b=0; b<4; b++){ int bx = BTN_M + b*(BTN_W+BTN_GAP); if(x>=bx && x= 0 && generating){ if(rem != lastRem){ updateTimerDisplay(rem); lastRem = rem; } uint32_t tickStart = millis(); // Wait ~1 second while still processing touch & sweep while((millis() - tickStart) < 1000 && generating){ sweepTick(); int tx,ty; if(readTouch(tx,ty)){ bool backHit = (tx>=BTN_M && tx=BTN_Y && ty=SWEEP_BTN_X && tx=SWEEP_BTN_Y && ty=PLUS60_BTN_X && tx=PLUS60_BTN_Y && ty=DUTY_MINUS_BTN_X && tx=DUTY_MINUS_BTN_Y && ty=DUTY_PLUS_BTN_X && tx=DUTY_PLUS_BTN_Y && ty sweepMaxHz) sweepCurrentHz = sweepMaxHz; lastSweepMs = millis(); } drawSweepButton(); delay(250); } if(plus60Hit){ rem += 60; // add time if(rem > 3599) rem = 3599; // cap at 59:59 to avoid silly values (edit if you want) updateTimerDisplay(rem); // update immediately lastRem = rem; delay(250); } if(dutyMinusHit){ dutyPercent -= 5; if(dutyPercent < 1) dutyPercent = 1; if(sweepEnabled){ pwmSetFreqDuty((uint32_t)sweepCurrentHz, dutyPercent); } else { pwmSetFreqDuty((uint32_t)currentFreq, dutyPercent); } updateDutyDisplay(); delay(180); } if(dutyPlusHit){ dutyPercent += 5; if(dutyPercent > 95) dutyPercent = 95; if(sweepEnabled){ pwmSetFreqDuty((uint32_t)sweepCurrentHz, dutyPercent); } else { pwmSetFreqDuty((uint32_t)currentFreq, dutyPercent); } updateDutyDisplay(); delay(180); } } } // One second passed rem--; } pwmStopOutput(); digitalWrite(LED_PIN, HIGH); } if(generating){ // Full FINISH screen tft.fillScreen(ILI9341_BLACK); tft.fillRect(0,0,XRES,HEADER_H,ILI9341_BLUE); tft.setTextSize(2); tft.setTextColor(ILI9341_WHITE); tft.setCursor(10,10); tft.print("Therapy"); drawResetButton(); tft.setCursor(10,100); tft.print("FINISH"); drawButtons(false); while(generating){ int tx,ty; if(readTouch(tx,ty) && tx>=BTN_M && tx=BTN_Y && ty