diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cebea1c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +firmware/logic_analyzer/root_html.h diff --git a/assets/root.html b/assets/root.html new file mode 100644 index 0000000..432de7d --- /dev/null +++ b/assets/root.html @@ -0,0 +1,40 @@ + + + + "; + + Logic Analyzer + + +
+ +
+ + + + +function connectWebSocket() { + ws = new WebSocket('ws://' + window.location.hostname + ':81'); + + ws.onopen = function() { + console.log('WebSocket connected'); + updateStatus('Подключено'); + }; + + ws.onmessage = function(event) { + const data = JSON.parse(event.data); + if (data.type === 'data') { + currentData = data.channels; + currentSamples = data.samples; + drawAllWaveforms(); + } else if (data.type === 'status') { + updateStatus(data.message); + } + }; + + ws.onclose = function() { + console.log('WebSocket disconnected'); + updateStatus('Отключено. Переподключение...'); + setTimeout(connectWebSocket, 1000); + }; + } diff --git a/firmware/logic_analyzer/logic_analyzer.ino b/firmware/logic_analyzer/logic_analyzer.ino new file mode 100644 index 0000000..471732b --- /dev/null +++ b/firmware/logic_analyzer/logic_analyzer.ino @@ -0,0 +1,170 @@ +#include +#include +#include +#include + +#include "root_html.h" + +#define CH1_PIN D0 +#define CH2_PIN D1 +#define CH3_PIN D2 +#define CH4_PIN D5 +#define CH5_PIN D6 +#define CH6_PIN D7 + +#define WIFI_AP_SSID "ESP8266_LogicAnalyzer" +#define WIFI_AP_PASS "12345678" + +#define WEBSERVER_PORT 80 +#define WEBSOCKET_PORT 81 + + +const uint8_t PINS[] = { + CH1_PIN, CH2_PIN, CH3_PIN, + CH4_PIN, CH5_PIN, CH6_PIN, +}; + +volatile bool g_sample_active = false; + +ESP8266WebServer server(WEBSERVER_PORT); +WebSocketsServer webSocket = WebSocketsServer(WEBSOCKET_PORT); + + +typedef union { + unsigned char mask; + struct { + unsigned a0:1; + unsigned a1:1; + unsigned a2:1; + unsigned a3:1; + unsigned a4:1; + unsigned a5:1; + unsigned a6:1; + unsigned a7:1; + } byte; +} Sample; + +#define SAMPLES_BUFFER_CAP 64 +Sample SAMPLES[SAMPLES_BUFFER_CAP] = {}; +volatile uint8_t g_samples_idx = 0; + + +Sample takeSample() { + Sample data; + data.mask = 0; + + int pins_len = sizeof(PINS) / sizeof(PINS[0]); + + for (uint8_t i = 0; i < pins_len; ++i) { + data.mask |= (!(!digitalRead(PINS[i])) << i); + } + + return data; +} + +void SerialSendPins(Sample ps) { + Serial.print("0b"); + Serial.println(ps.mask, BIN); +} + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) { + switch(type) { + case WStype_DISCONNECTED: + stopSampling(); + break; + + case WStype_CONNECTED: + startSampling(); + webSocket.sendTXT(num, "{\"type\":\"status\",\"message\":\"Connected\"}"); + break; + } +} + +void startSampling() { + g_sample_active = true; +} + +void stopSampling() { + g_sample_active = false; +} + +void setup() { + Serial.begin(115200); + + int pins_len = sizeof(PINS) / sizeof(PINS[0]); + + for (uint8_t i = 0; i < pins_len; ++i) { + pinMode(PINS[i], INPUT); + } + + WiFi.softAP(WIFI_AP_SSID, WIFI_AP_PASS); + Serial.print("IP: "); + Serial.println(WiFi.softAPIP()); + + setupWebServer(); + webSocket.begin(); + webSocket.onEvent(webSocketEvent); + + server.begin(); + Serial.println("Ready"); +} + +void setupWebServer() { + server.on("/", rootHandler); +} + +void rootHandler() { + server.send(200, "text/html", assets_root_html, assets_root_html_len); +} + +void appendSamples() { + if (!g_sample_active) return; + + static unsigned long lastTime = 0; + unsigned long now = micros(); + unsigned long interval_mks = 100; + + if (now - lastTime < interval_mks) return; + + lastTime = now; + + if (g_samples_idx == SAMPLES_BUFFER_CAP - 1) return; + + SAMPLES[g_samples_idx] = takeSample(); + g_samples_idx++; +} + +void websocketSendSamples() { + if (g_samples_idx != SAMPLES_BUFFER_CAP - 1) return; + + static unsigned long lastTime = 0; + unsigned long now = millis(); + + unsigned long interval_ms = 100; + + if (now - lastTime < interval_ms) return; + + lastTime = now; + + // конвертировать SAMPLES в json + String output = "{\"type\":\"data\",\"len\":" + String(SAMPLES_BUFFER_CAP) + ",\"data\":["; + for (int i = 0; i < SAMPLES_BUFFER_CAP; ++i) { + output += String(SAMPLES[i].mask); + if (i < SAMPLES_BUFFER_CAP - 1) output += ","; + } + output += "]}"; + + webSocket.broadcastTXT(output); + + g_samples_idx = 0; +} + +void loop() { + server.handleClient(); + webSocket.loop(); + + appendSamples(); + websocketSendSamples(); + + delay(1); +} diff --git a/justfile b/justfile index 1d6a95e..263cc3b 100644 --- a/justfile +++ b/justfile @@ -1,9 +1,19 @@ -alias compile := build +#!/usr/bin/env -S just --justfile +alias compile := build build: + #!/bin/sh + xxd -i assets/root.html > firmware/logic_analyzer/root_html.h + cd firmware/logic_analyzer arduino-cli compile --fqbn esp8266:esp8266:nodemcuv2 alias flash := upload + +[working-directory: 'firmware/logic_analyzer'] upload: arduino-cli upload --fqbn esp8266:esp8266:nodemcuv2 --port /dev/ttyUSB0 + + +monitor: + arduino-cli monitor --port /dev/ttyUSB0 diff --git a/logic_analyzer/logic_analyzer.ino b/logic_analyzer/logic_analyzer.ino deleted file mode 100644 index b720bba..0000000 --- a/logic_analyzer/logic_analyzer.ino +++ /dev/null @@ -1,733 +0,0 @@ -#include -#include -#include -#include -#include - -// Настройки WiFi -const char* ssid = "ESP8266_LogicAnalyzer"; -const char* password = "12345678"; - -// Настройки пинов (4 канала для ESP8266) -#define CH1_PIN 5 // D1 -#define CH2_PIN 4 // D2 -#define CH3_PIN 14 // D5 -#define CH4_PIN 12 // D6 - -ESP8266WebServer server(80); -WebSocketsServer webSocket = WebSocketsServer(81); - -// Буфер для данных - увеличен для 5 секунд при 5 кГц (25000 семплов) -#define MAX_BUFFER_SIZE 25000 -uint8_t* logicBuffer; -volatile int bufferIndex = 0; -volatile bool samplingActive = false; -volatile unsigned long sampleRate = 5000; // Максимальная частота по умолчанию -volatile unsigned long lastSampleTime = 0; - -// Флаги для отправки -volatile bool dataReady = false; -unsigned long lastSendTime = 0; - -void setup() { - Serial.begin(115200); - - // Выделяем память под буфер - logicBuffer = (uint8_t*)malloc(MAX_BUFFER_SIZE); - if (!logicBuffer) { - Serial.println("Ошибка выделения памяти!"); - return; - } - - // Настройка пинов - pinMode(CH1_PIN, INPUT_PULLUP); - pinMode(CH2_PIN, INPUT_PULLUP); - pinMode(CH3_PIN, INPUT_PULLUP); - pinMode(CH4_PIN, INPUT_PULLUP); - - // Запуск WiFi точки доступа - WiFi.softAP(ssid, password); - Serial.print("Точка доступа создана. IP: "); - Serial.println(WiFi.softAPIP()); - - // Настройка веб-сервера - setupWebServer(); - - // Запуск WebSocket - webSocket.begin(); - webSocket.onEvent(webSocketEvent); - - server.begin(); - Serial.println("Сервер запущен"); -} - -void setupWebServer() { - server.on("/", []() { - server.send(200, "text/html", getHTMLPage()); - }); -} - -String getHTMLPage() { - // HTML страница вынесена в отдельную функцию - String html = R"rawliteral( - - - - - - ESP8266 Logic Analyzer - - - -
-
-

🔍 Logic Analyzer

-
- -
-
- - -
-
- - -
-
- - -
-
- -
- -
- ⚡ Готов -
-
-
- - - - -)rawliteral"; - return html; -} - -void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) { - switch(type) { - case WStype_DISCONNECTED: - Serial.printf("[%u] Отключен\n", num); - stopSampling(); - break; - - case WStype_CONNECTED: - Serial.printf("[%u] Подключен\n", num); - webSocket.sendTXT(num, "{\"type\":\"status\",\"message\":\"Готов к работе\"}"); - break; - - case WStype_TEXT: - { - StaticJsonDocument<256> doc; - DeserializationError error = deserializeJson(doc, payload); - - if (!error) { - const char* command = doc["command"]; - - if (strcmp(command, "start") == 0) { - unsigned long newRate = doc["rate"]; - if (newRate != sampleRate) { - stopSampling(); - sampleRate = newRate; - } - startSampling(); - Serial.printf("Начат сбор данных, частота: %lu Гц\n", sampleRate); - - String statusMsg = "{\"type\":\"status\",\"message\":\"Сбор данных... " + String(sampleRate) + " Гц\"}"; - webSocket.sendTXT(num, statusMsg); - } - else if (strcmp(command, "stop") == 0) { - stopSampling(); - Serial.println("Сбор данных остановлен"); - webSocket.sendTXT(num, "{\"type\":\"status\",\"message\":\"Остановлен\"}"); - } - } - } - break; - } -} - -void startSampling() { - if (samplingActive) return; - - samplingActive = true; - bufferIndex = 0; - lastSampleTime = micros(); - dataReady = false; - Serial.println("Sampling started"); -} - -void stopSampling() { - if (!samplingActive) return; - - samplingActive = false; - if (bufferIndex > 0) { - sendData(); - bufferIndex = 0; - } - Serial.println("Sampling stopped"); -} - -void IRAM_ATTR sampleData() { - if (!samplingActive) return; - - if (bufferIndex >= MAX_BUFFER_SIZE) { - dataReady = true; - return; - } - - uint8_t sample = 0; - sample |= (!digitalRead(CH1_PIN) << 0); - sample |= (!digitalRead(CH2_PIN) << 1); - sample |= (!digitalRead(CH3_PIN) << 2); - sample |= (!digitalRead(CH4_PIN) << 3); - - logicBuffer[bufferIndex++] = sample; -} - -void sendData() { - if (bufferIndex == 0) return; - - StaticJsonDocument doc; - doc["type"] = "data"; - doc["samples"] = bufferIndex; - - JsonArray channels = doc.createNestedArray("channels"); - - for (int ch = 0; ch < 4; ch++) { - JsonArray channelData = channels.createNestedArray(); - for (int i = 0; i < bufferIndex; i++) { - channelData.add((logicBuffer[i] >> ch) & 1); - } - } - - String output; - serializeJson(doc, output); - webSocket.broadcastTXT(output); - - bufferIndex = 0; -} - -void loop() { - server.handleClient(); - webSocket.loop(); - - if (dataReady) { - dataReady = false; - sendData(); - } - - if (samplingActive) { - unsigned long now = micros(); - unsigned long interval = 1000000 / sampleRate; - - if (now - lastSampleTime >= interval) { - lastSampleTime = now; - sampleData(); - } - } - - // Небольшая задержка для стабильности - delay(1); -}