/
spaceswimmer
/
perf-source
Обзор
Документация
Войти
/
spaceswimmer
/
perf-source
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
materials/perf2.cpp
866 строк
32 KB
spaceswimmer
master version 2 updated, added linting
03 мар 2026, 13:13
03 мар 2026, 13:13
f52c519
Код
Авторство
О чём код?
#include <WiFi.h> #include <ESPAsyncWebServer.h> #include <DNSServer.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <math.h> #include <SPI.h> // === Пины управления === #define CONTROL_PIN_1 5 // Реле (актив LOW: при запуске = LOW, ожидание = HIGH) #define CONTROL_PIN_2 8 // Доп. выход (актив HIGH: при запуске = HIGH, ожидание = LOW) #define BUTTON_PIN 0 // Кнопка (GND при нажатии, INPUT_PULLUP) #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); const char* ssid = "Sweep"; const char* password = "SonikaSweep"; AsyncWebServer server(80); DNSServer dnsServer; float fMin_exact = 24.66, fMax_exact = 48.8; int fMin_display = 25, fMax_display = 49; float tMax = 10.0; String law = "linear"; bool isRunning = false; unsigned long startTime = 0; float currentProgress = 0.0; uint8_t currentPotValue = 0; // === Настройки SPI для потенциометра TPL0501 === const int POT_CS = 2; // Chip Select для TPL0501 const int POT_SCK = 6; // Serial Clock const int POT_SDI = 7; // Serial Data Input const int POT_SDO = -1; // Serial Data Output (не используется) uint8_t lastWrittenPotValue = 0; uint8_t startPotValue = 0; // Начальное значение при запуске развертки // === Для кнопки === bool lastButtonState = HIGH; unsigned long lastDebounceTime = 0; const unsigned long debounceDelay = 50; bool buttonWasPressed = false; // Флаг: было ли стабильное нажатие const unsigned char bitmap_128x64[] PROGMEM = { // 'IMG-20240328-WA0004', 128x64px 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0x07,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0x00,0x7f,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x80,0x00,0x9f,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0x00,0x00,0x4f,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0x00,0x00,0x47,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf8,0x00,0x1c,0x33,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x01,0xfe,0x69,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0x0f,0xfe,0xf9,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xc0,0x1f,0xff,0x01,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x80,0x7f,0xff,0x20,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x80,0xff,0xff,0x22,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0x0f,0xff,0x00,0xff,0xff,0x22,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0x03,0xff,0x70,0x01,0xc0,0x23,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfe,0x19,0xff,0x03,0xfc,0x00,0x01,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfe,0x1c,0xff,0x1f,0xfe,0x3e,0x01,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0x1e,0xfe,0x7f,0xfc,0x62,0x15,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0x8e,0x7c,0xff,0xf8,0x9c,0x14,0x7f,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xe7,0x7b,0xff,0xf9,0x70,0x05,0xbf,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xe7,0x73,0xff,0xf3,0x60,0x05,0xbf,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xe7,0x64,0x7f,0xf2,0xc0,0xe3,0xdf,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xc6,0x4f,0xff,0xe6,0x85,0x01,0xdf,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0x9e,0xcf,0xf8,0xe7,0x80,0x01,0xef,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0x78,0x80,0xb8,0x65,0x04,0x00,0xaf,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfe,0x11,0x1f,0xff,0xe4,0x00,0x00,0xaf,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfe,0x33,0x03,0xff,0xe4,0x40,0x00,0x4f,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfd,0xe3,0x3c,0xbf,0xe4,0x00,0x1c,0x07,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfd,0x92,0x3f,0x7f,0xe4,0x08,0x00,0x83,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfd,0xc2,0x35,0xff,0xe4,0x0c,0x02,0x3b,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfd,0xf0,0xda,0xff,0xf6,0x14,0x92,0x7d,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfd,0x79,0xff,0xff,0xf2,0x16,0x04,0xfd,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfd,0xde,0xff,0xf7,0xf2,0x1b,0x88,0xec,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfc,0x37,0xd0,0xef,0xf9,0x08,0xe1,0xee,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfe,0xef,0xf1,0xcf,0xf0,0x80,0x03,0xee,0x7e,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfe,0x19,0x85,0x1f,0xfa,0xc0,0x07,0xdf,0x7d,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0x30,0x1c,0x3f,0xf9,0x20,0x0f,0xdf,0x78,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfe,0x09,0xf8,0x7f,0xf8,0xce,0x1f,0xbe,0x78,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfe,0x00,0x00,0x7f,0xf0,0x78,0x3f,0x7e,0x3c,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0x80,0x18,0xf0,0x00,0x00,0x7c,0xfe,0x1c,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xfa,0xc0,0x00,0xc1,0xe1,0xff,0x00,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xf8,0xc0,0x04,0x7f,0x0f,0xff,0x01,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xfd,0xdb,0xff,0x00,0xff,0xff,0x27,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xf1,0xb9,0xff,0xef,0xff,0xff,0x3f,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xc4,0x7d,0xff,0xff,0xef,0xff,0x3f,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0x9f,0xfc,0xcf,0xff,0xdf,0xff,0x3f,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0x0f,0xbe,0x8f,0xfe,0xdf,0xfe,0x7f,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xf8,0x0e,0x5f,0x1f,0xfe,0xbf,0xfe,0x7f,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xe3,0x1f,0x9f,0x1f,0xfc,0x7f,0xbf,0x3f,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xe3,0x7f,0x00,0xbf,0xfc,0xff,0x7f,0x0f,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xe0,0x1c,0x3f,0xbf,0xf9,0xfc,0x7f,0xf3,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xe0,0x5c,0xff,0xa7,0xf3,0xf0,0xff,0xfb,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xe0,0x79,0xff,0xbf,0xf0,0x0c,0xff,0xf1,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xe0,0x19,0xff,0xbf,0xf1,0xfe,0x7f,0xfb,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfc,0xf0,0x1b,0xff,0xbf,0xed,0xfe,0x1e,0x73,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfa,0x78,0x03,0xff,0xa3,0xef,0xfe,0xc0,0x03,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xfa,0x9c,0x07,0xff,0x8f,0xde,0xfe,0xf8,0x67,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xf0,0xce,0x0f,0xff,0x80,0x5e,0x7e,0xfc,0x0f,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xf0,0x47,0xff,0xff,0x8f,0x1f,0x7e,0xfc,0x9f,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xe0,0x03,0xff,0xff,0x90,0x1f,0x06,0x7e,0x3f,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xe2,0x71,0xff,0xff,0x3f,0x9f,0x02,0x7f,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xe0,0xdc,0xbf,0xff,0x7f,0xcf,0x07,0x7f,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xc1,0x80,0x1f,0xfe,0x2c,0x0e,0x0c,0x7f,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xb5,0x80,0xc7,0xfe,0x5e,0xce,0x92,0x7f,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0x00,0x00,0x03,0xfe,0x00,0x0e,0x00,0x7f,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, }; struct SchedulePoint { unsigned long timeMs; uint8_t potValue; }; SchedulePoint schedule[600]; int scheduleSize = 0; // === Логика управления пинами (индивидуальная) === void setControlPins(bool active) { // active = true -> развёртка запущена // active = false -> развёртка остановлена // Пин 5: актив LOW digitalWrite(CONTROL_PIN_1, active ? LOW : HIGH); // Пин 8: актив HIGH digitalWrite(CONTROL_PIN_2, active ? HIGH : LOW); Serial.printf("Control: active=%d -> pin5=%d, pin8=%d\n", active, digitalRead(CONTROL_PIN_1), digitalRead(CONTROL_PIN_2)); } // === Функция для инициализации потенциометра === void setupDigitalPot() { pinMode(POT_CS, OUTPUT); digitalWrite(POT_CS, HIGH); SPI.begin(POT_SCK, POT_SDO, POT_SDI, POT_CS); SPI.setFrequency(1000000); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); writePotentiometer(1); lastWrittenPotValue = 1; Serial.println("Digital potentiometer initialized"); } // === Функция записи значения в потенциометр === void writePotentiometer(uint8_t value) { digitalWrite(POT_CS, LOW); SPI.transfer(0x00); SPI.transfer(value); digitalWrite(POT_CS, HIGH); Serial.print("Potentiometer set to: "); Serial.println(value); } void setup() { Serial.begin(115200); Serial.println("\n=== Frequency Sweep Controller ==="); // === Инициализация пинов === pinMode(CONTROL_PIN_1, OUTPUT); pinMode(CONTROL_PIN_2, OUTPUT); pinMode(BUTTON_PIN, INPUT_PULLUP); // Устанавливаем начальное состояние: развёртка не активна setControlPins(false); // === 1. Инициализация OLED === Serial.println("Initializing OLED..."); Wire.begin(8, 9); Wire.setClock(400000); bool oledOK = false; if(display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println("OLED: OK at 0x3C"); oledOK = true; } else if(display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) { Serial.println("OLED: OK at 0x3D"); oledOK = true; } else { Serial.println("OLED: FAILED"); } if(oledOK) { display.clearDisplay(); display.drawBitmap(0, 0, bitmap_128x64, 128, 64, SSD1306_WHITE); display.display(); delay(2000); } if(oledOK) { display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1306_WHITE); display.setCursor(0,45); display.print("by Sonica"); display.display(); delay(1500); } // === 2. Инициализация цифрового потенциометра === Serial.println("Initializing digital potentiometer..."); setupDigitalPot(); // === 3. Настройка Wi-Fi === Serial.println("Initializing Wi-Fi AP..."); WiFi.persistent(false); WiFi.mode(WIFI_AP); bool apStarted = WiFi.softAP(ssid, password, 10, 0, 4); if(apStarted) { Serial.println(" ✅ Access point started!"); Serial.print(" SSID: "); Serial.println(ssid); Serial.print(" IP: "); Serial.println(WiFi.softAPIP()); WiFi.setTxPower(WIFI_POWER_8_5dBm); delay(500); } else { Serial.println(" ❌ ERROR: Failed to start AP!"); } // === 4. Настройка DNS === dnsServer.start(53, "*", WiFi.softAPIP()); // === 5. Показываем системный экран === if(oledOK) { displayParametersScreen(); } // === 6. Настройка веб-сервера === setupWebServer(); server.begin(); Serial.println("HTTP server started on port 80"); generateSchedule(); } void loop() { dnsServer.processNextRequest(); // === Обработка кнопки (срабатывание при отпускании) === bool reading = digitalRead(BUTTON_PIN); if (reading != lastButtonState) { lastDebounceTime = millis(); } if ((millis() - lastDebounceTime) > debounceDelay) { // Стабильное состояние if (reading == LOW) { // Кнопка нажата стабильно buttonWasPressed = true; } else if (reading == HIGH && buttonWasPressed) { // Кнопка была нажата, а теперь отпущена → переключаем if (isRunning) { stopSweep(); } else { startSweep(); } buttonWasPressed = false; } } lastButtonState = reading; // === Логика развёртки === if(isRunning && scheduleSize > 0) { unsigned long elapsed = millis() - startTime; unsigned long total = tMax * 1000; if(elapsed >= total) { // Развертка завершена isRunning = false; currentProgress = 100.0; setControlPins(false); // выключаем пины writePotentiometer(startPotValue); currentPotValue = startPotValue; lastWrittenPotValue = startPotValue; displayParametersScreen(); Serial.print("Sweep completed! Potentiometer reset to start value: "); Serial.println(startPotValue); } else { currentProgress = (float)elapsed / (float)total * 100.0; int index = findCurrentPoint(elapsed); if(index >= 0 && index < scheduleSize) { uint8_t newPotValue = schedule[index].potValue; if(newPotValue != currentPotValue) { currentPotValue = newPotValue; writePotentiometer(currentPotValue); lastWrittenPotValue = currentPotValue; Serial.print("Time: "); Serial.print(elapsed/1000.0, 1); Serial.print("s, Pot: "); Serial.print(currentPotValue); Serial.print(", Freq: "); Serial.print(potToFrequency(currentPotValue), 1); Serial.println(" Hz"); } } static unsigned long lastDisplayUpdate = 0; if(millis() - lastDisplayUpdate > 100) { lastDisplayUpdate = millis(); displayProgressScreen(); } } } delay(50); } // === Функции запуска/остановки (вызываются из веб-сервера и кнопки) === void startSweep() { if (scheduleSize == 0) return; if (isRunning) return; isRunning = true; startTime = millis(); currentProgress = 0.0; currentPotValue = schedule[0].potValue; startPotValue = schedule[0].potValue; writePotentiometer(currentPotValue); lastWrittenPotValue = currentPotValue; setControlPins(true); // активируем пины Serial.print("Sweep started! Initial pot value: "); Serial.println(startPotValue); } void stopSweep() { if (!isRunning) return; isRunning = false; currentProgress = 0.0; setControlPins(false); // деактивируем пины if (startPotValue > 0) { writePotentiometer(startPotValue); currentPotValue = startPotValue; lastWrittenPotValue = startPotValue; Serial.print("Sweep stopped. Potentiometer reset to start value: "); Serial.println(startPotValue); } displayParametersScreen(); Serial.println("Sweep stopped by user"); } // === Остальные функции БЕЗ ИЗМЕНЕНИЙ === int findCurrentPoint(unsigned long elapsed) { for(int i = 0; i < scheduleSize; i++) { if(schedule[i].timeMs >= elapsed) { return i > 0 ? i-1 : 0; } } return scheduleSize - 1; } float potToFrequency(uint8_t potValue) { return fMin_exact + (float)(potValue - 1) * (fMax_exact - fMin_exact) / 234.0; } uint8_t frequencyToPot(float frequency) { float value = 1 + (frequency - fMin_exact) * 234.0 / (fMax_exact - fMin_exact); if(value < 1) value = 1; if(value > 235) value = 235; return (uint8_t)value; } void displayParametersScreen() { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0,0); display.println("Sweep Controller"); display.drawLine(0, 9, 127, 9, SSD1306_WHITE); display.setCursor(0,12); display.print("F: "); display.print(fMin_display); display.print("-"); display.print(fMax_display); display.println(" Hz"); display.print("T: "); display.print(tMax, 1); display.println(" s"); display.print("Law: "); display.println(law); display.setCursor(0,45); display.print("Wi-Fi: "); display.print(ssid); display.setCursor(0,55); display.print("GPIO: "); display.print(digitalRead(CONTROL_PIN_1)); display.print("/"); display.print(digitalRead(CONTROL_PIN_2)); display.display(); } void displayProgressScreen() { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0,0); display.println("Sweep Progress:"); display.drawLine(0, 9, 127, 9, SSD1306_WHITE); int barWidth = map((int)currentProgress, 0, 100, 0, 120); display.drawRect(0, 12, 120, 10, SSD1306_WHITE); display.fillRect(2, 14, barWidth, 6, SSD1306_WHITE); display.setCursor(0, 25); display.print("Progress: "); display.print(currentProgress, 1); display.println("%"); display.print("Time: "); display.print((millis() - startTime) / 1000.0, 1); display.print(" / "); display.print(tMax, 1); display.println(" s"); float currentFreq = potToFrequency(currentPotValue); display.setCursor(0, 45); display.print("Pot: "); display.print(currentPotValue); display.print(" Freq: "); display.print(currentFreq, 1); display.println(" Hz"); display.setCursor(0, 55); display.print("GPIO: "); display.print(digitalRead(CONTROL_PIN_1)); display.print("/"); display.print(digitalRead(CONTROL_PIN_2)); display.display(); } void generateSchedule() { scheduleSize = 0; unsigned long totalTimeMs = (unsigned long)(tMax * 1000); unsigned long stepTimeMs = 100; for(unsigned long t = 0; t <= totalTimeMs && scheduleSize < 600; t += stepTimeMs) { uint8_t potValue; if(law == "linear") { float normalizedTime = (float)t / (float)totalTimeMs; potValue = 1 + (uint8_t)(normalizedTime * 234); } else { float normalizedTime = (float)t / (float)totalTimeMs; float I = exp(normalizedTime * log(236.0)) - 1.0; if(I > 235.0) I = 235.0; if(I < 1.0) I = 1.0; potValue = (uint8_t)I; } schedule[scheduleSize].timeMs = t; schedule[scheduleSize].potValue = potValue; scheduleSize++; } Serial.print("Generated "); Serial.print(scheduleSize); Serial.println(" points"); } void setupWebServer() { server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ String html = R"rawliteral( <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Sweep Controller</title> <style> body { font-family: Arial; padding: 20px; max-width: 800px; margin: auto; } .container { background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } h1 { color: #2c3e50; text-align: center; } .info-box { background: #e8f4fc; padding: 15px; border-radius: 8px; margin: 15px 0; border-left: 4px solid #3498db; } .control-group { margin: 20px 0; } label { display: block; margin: 10px 0 5px; font-weight: 600; } input, select { padding: 10px; margin: 5px 0 15px; width: 100%; max-width: 300px; } .button-group { display: flex; flex-wrap: wrap; gap: 10px; margin: 20px 0; } button { padding: 10px 20px; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; flex: 1; min-width: 150px; } #startBtn { background: #2ecc71; color: white; } #stopBtn { background: #e74c3c; color: white; } #stopBtn:disabled { background: #95a5a6; } button:not(#startBtn):not(#stopBtn) { background: #3498db; color: white; } .status { margin: 20px 0; padding: 15px; background: #f8f9fa; border-radius: 8px; font-family: monospace; } #chartContainer { margin: 20px 0; padding: 15px; border: 2px solid #ddd; border-radius: 8px; background: #fafafa; } canvas { width: 100%; height: 300px; background: white; } </style> </head> <body> <div class="container"> <h1>🎛️ Sweep Controller</h1> <div class="info-box"> <strong>Физическое соответствие фиксировано:</strong><br> Потенциометр 1-235 ↔ Частота 24.66-48.8 Гц<br> Вводимые Fmin и Fmax определяют только диапазон отображения </div> <div class="control-group"> <label>Display Fmin (25-49 Hz):</label> <input type="number" id="fmin" value="25" min="25" max="49"> <label>Display Fmax (25-49 Hz):</label> <input type="number" id="fmax" value="49" min="25" max="49"> <label>Time (5-60 seconds):</label> <input type="number" id="tmax" value="10" min="5" max="60"> <label>Sweep Law:</label> <select id="law"> <option value="linear">Linear</option> <option value="exponential">Exponential</option> </select> </div> <div class="button-group"> <button onclick="updateChart()">📈 Update Graph</button> <button onclick="sendToESP()">📤 Send to ESP32</button> <button onclick="startSweep()" id="startBtn">▶️ Start Sweep</button> <button onclick="stopSweep()" id="stopBtn" disabled>⏹️ Stop Sweep</button> </div> <div class="status" id="status"> Status: Ready </div> <div id="chartContainer"> <canvas id="chart"></canvas> </div> <div style="margin-top: 20px; font-size: 12px; color: #666;"> <strong>Physical correspondence (fixed):</strong><br> Potentiometer: 1-235 ↔ Frequency: 24.66-48.8 Hz<br> Display values only affect visualization range </div> </div> <script> function drawSimpleChart(labels, data, minY, maxY) { const canvas = document.getElementById('chart'); const ctx = canvas.getContext('2d'); const width = canvas.width; const height = canvas.height; const padding = 40; ctx.clearRect(0, 0, width, height); const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; ctx.strokeStyle = '#eee'; ctx.lineWidth = 1; for(let i = 0; i <= 5; i++) { const y = padding + (i * chartHeight / 5); ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(width - padding, y); ctx.stroke(); ctx.fillStyle = '#666'; ctx.font = '12px Arial'; ctx.textAlign = 'right'; const value = maxY - (maxY - minY) * i / 5; ctx.fillText(value.toFixed(0), padding - 10, y + 4); } for(let i = 0; i <= 5; i++) { const x = padding + (i * chartWidth / 5); ctx.beginPath(); ctx.moveTo(x, padding); ctx.lineTo(x, height - padding); ctx.stroke(); ctx.fillStyle = '#666'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; const timeValue = (labels[labels.length-1] * i / 5).toFixed(1); ctx.fillText(timeValue, x, height - padding + 20); } ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(padding, padding); ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); ctx.fillStyle = '#000'; ctx.font = '14px Arial'; ctx.textAlign = 'center'; ctx.fillText('Time (seconds)', width / 2, height - 5); ctx.save(); ctx.translate(15, height / 2); ctx.rotate(-Math.PI / 2); ctx.fillText('Frequency (Hz)', 0, 0); ctx.restore(); if(data.length < 2) return; ctx.strokeStyle = '#3498db'; ctx.lineWidth = 3; ctx.beginPath(); const xScale = chartWidth / (labels.length - 1); const yScale = chartHeight / (maxY - minY); for(let i = 0; i < data.length; i++) { const x = padding + i * xScale; const y = height - padding - (data[i] - minY) * yScale; if(i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } } ctx.stroke(); ctx.fillStyle = 'rgba(52, 152, 219, 0.1)'; ctx.lineTo(padding + (data.length - 1) * xScale, height - padding); ctx.lineTo(padding, height - padding); ctx.closePath(); ctx.fill(); } const fMin_exact_fixed = 24.66; const fMax_exact_fixed = 48.8; function frequencyToPot(frequency) { let value = 1 + (frequency - fMin_exact_fixed) * 234 / (fMax_exact_fixed - fMin_exact_fixed); return Math.max(1, Math.min(235, Math.round(value))); } function potToFrequency(potValue) { return fMin_exact_fixed + (potValue - 1) * (fMax_exact_fixed - fMin_exact_fixed) / 234; } function updateChart() { const fmin_display = parseInt(document.getElementById('fmin').value) || 25; const fmax_display = parseInt(document.getElementById('fmax').value) || 49; const tmax = parseFloat(document.getElementById('tmax').value) || 10; const law = document.getElementById('law').value; const pot_min = frequencyToPot(fmin_display); const pot_max = frequencyToPot(fmax_display); const steps = 50; const labels = []; const data = []; for(let i = 0; i <= steps; i++) { const t = (i / steps) * tmax; labels.push(t); let potValue; if(law === 'linear') { const normalizedTime = i / steps; potValue = pot_min + normalizedTime * (pot_max - pot_min); } else { const normalizedTime = i / steps; const I = pot_min + (pot_max - pot_min) * (Math.exp(normalizedTime * Math.log(236)) - 1) / 235; potValue = Math.max(pot_min, Math.min(pot_max, I)); } const freq = potToFrequency(potValue); data.push(freq); } drawSimpleChart(labels, data, 25, 49); document.getElementById('status').innerHTML = 'Graph updated | Display: ' + fmin_display + '-' + fmax_display + ' Hz | ' + 'Potentiometer: ' + pot_min + '-' + pot_max + ' | ' + 'Actual frequency: ' + potToFrequency(pot_min).toFixed(1) + '-' + potToFrequency(pot_max).toFixed(1) + ' Hz'; } async function sendToESP() { const params = { fmin: document.getElementById('fmin').value, fmax: document.getElementById('fmax').value, tmax: document.getElementById('tmax').value, law: document.getElementById('law').value }; try { const form = new FormData(); form.append('fmin', params.fmin); form.append('fmax', params.fmax); form.append('tmax', params.tmax); form.append('law', params.law); const response = await fetch('/set_params', { method: 'POST', body: form }); const text = await response.text(); document.getElementById('status').innerHTML = '✅ ' + text; } catch(e) { document.getElementById('status').innerHTML = '❌ Error: ' + e; } } async function startSweep() { const response = await fetch('/start', {method: 'POST'}); if(response.ok) { document.getElementById('startBtn').disabled = true; document.getElementById('stopBtn').disabled = false; document.getElementById('status').innerHTML = '▶️ Sweep started!'; checkStatus(); } } async function stopSweep() { const response = await fetch('/stop', {method: 'POST'}); if(response.ok) { document.getElementById('startBtn').disabled = false; document.getElementById('stopBtn').disabled = true; document.getElementById('status').innerHTML = '⏹️ Sweep stopped!'; } } async function checkStatus() { try { const response = await fetch('/status'); const text = await response.text(); if(text.includes("Running")) { setTimeout(checkStatus, 1000); } else { document.getElementById('startBtn').disabled = false; document.getElementById('stopBtn').disabled = true; document.getElementById('status').innerHTML = '✅ Sweep completed!'; } } catch(e) { setTimeout(checkStatus, 2000); } } window.onload = function() { updateChart(); ['fmin', 'fmax', 'tmax', 'law'].forEach(id => { document.getElementById(id).addEventListener('change', updateChart); }); const canvas = document.getElementById('chart'); canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; updateChart(); }; window.addEventListener('resize', function() { const canvas = document.getElementById('chart'); canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; updateChart(); }); </script> </body> </html> )rawliteral"; request->send(200, "text/html", html); }); server.on("/set_params", HTTP_POST, [](AsyncWebServerRequest *request){ if(request->hasParam("fmin", true)) { fMin_display = request->getParam("fmin", true)->value().toInt(); fMax_display = request->getParam("fmax", true)->value().toInt(); tMax = request->getParam("tmax", true)->value().toFloat(); law = request->getParam("law", true)->value(); fMin_exact = (float)fMin_display; fMax_exact = (float)fMax_display; if(fMin_display < 25) fMin_display = 25; if(fMax_display > 49) fMax_display = 49; if(tMax < 5) tMax = 5; if(tMax > 60) tMax = 60; generateSchedule(); displayParametersScreen(); uint8_t pot_min = frequencyToPot(fMin_exact); uint8_t pot_max = frequencyToPot(fMax_exact); Serial.print("Display F="); Serial.print(fMin_display); Serial.print("-"); Serial.print(fMax_display); Serial.print(" Hz -> Pot="); Serial.print(pot_min); Serial.print("-"); Serial.print(pot_max); Serial.print(" (Actual F="); Serial.print(potToFrequency(pot_min), 1); Serial.print("-"); Serial.print(potToFrequency(pot_max), 1); Serial.print(" Hz), T="); Serial.print(tMax); Serial.print(" s, Law="); Serial.println(law); request->send(200, "text/plain", "OK. Pot range: " + String(pot_min) + "-" + String(pot_max)); } else { request->send(400, "text/plain", "Missing params"); } }); server.on("/start", HTTP_POST, [](AsyncWebServerRequest *request){ startSweep(); request->send(200, "text/plain", "Sweep started"); }); server.on("/stop", HTTP_POST, [](AsyncWebServerRequest *request){ stopSweep(); request->send(200, "text/plain", "Sweep stopped"); }); server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request){ String status = isRunning ? "Running: " + String(currentProgress, 1) + "%" : "Ready. Points: " + String(scheduleSize); request->send(200, "text/plain", status); }); server.begin(); }