#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#define ADC_PIN 4 // GPIO pin for ADC
#define V_REF 3.3 // Reference voltage of ESP32-S2 ADC
// WiFi credentials (AP mode)
const char* ssid = "Indus-Oscilloscope";
const char* password = "12345678";
// Web server instance
AsyncWebServer server(80);
// Variables to store the captured waveforms
String capturedWaveforms[4];
int captureCount = 0;
// HTML + JavaScript for the oscilloscope webpage with Time and Voltage Division labels
const char* htmlPage = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IndusScope</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
margin: 0;
padding: 0;
background-color: #181818; /* Dark background */
color: #e1e1e1; /* Light text */
}
h1 {
font-size: 2em;
margin: 20px;
}
.title {
font-size: 2.5em;
font-weight: bold;
color: white; /* Bright white for title */
}
#oscilloscope {
display: block;
margin: 20px auto;
border: 2px solid #ccc;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.6);
background-color: #000000; /* Black background for canvas */
}
.controls {
margin: 20px;
}
.slider-container {
margin-bottom: 10px;
}
.info {
font-size: 1em;
margin-top: 10px;
color: #999999; /* Light text for info */
}
footer {
margin-top: 20px;
font-size: 0.9em;
color: #e1e1e1; /* Light footer text */
}
.slider-container input[type="range"] {
width: 80%;
background-color: #333333; /* Darker slider background */
}
.capture-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
margin-top: 20px;
}
.capture-item {
background-color: #222222;
padding: 10px;
border: 2px solid #ccc;
border-radius: 8px;
}
</style>
<script>
let canvas, ctx;
let timeDivision = 1; // Time scaling factor
let voltageDivision = 1; // Voltage scaling factor
let capturedWaveforms = []; // Array to store captured waveforms
const drawGrid = () => {
ctx.strokeStyle = "#FFFF00"; // Grid lines
ctx.lineWidth = 1;
const divisionsX = 10; // Vertical divisions
const divisionsY = 5; // Horizontal divisions
const timePerDiv = (timeDivision * 10).toFixed(2) + " ms/div";
const voltagePerDiv = (voltageDivision * 1).toFixed(2) + " V/div";
// Draw vertical lines and time labels
for (let x = 0; x <= canvas.width; x += canvas.width / divisionsX) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
if (x > 0) {
ctx.fillStyle = "#e1e1e1";
ctx.font = "10px Arial";
ctx.fillText(timePerDiv, x - 15, canvas.height - 5); // Time labels
}
}
// Draw horizontal lines and voltage labels
for (let y = 0; y <= canvas.height; y += canvas.height / divisionsY) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
if (y > 0) {
ctx.fillStyle = "#e1e1e1";
ctx.font = "10px Arial";
ctx.fillText(voltagePerDiv, 5, y - 5); // Voltage labels
}
}
};
const fetchData = async () => {
const response = await fetch('/data');
const json = await response.json();
return json;
};
const updateGraph = async () => {
const data = await fetchData();
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw grid
drawGrid();
// Draw waveform
ctx.strokeStyle = "#76ff03"; // Green waveform
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(0, canvas.height - data.waveform[0] * voltageDivision);
data.waveform.forEach((value, index) => {
const x = index * (canvas.width / (data.waveform.length * timeDivision));
const y = canvas.height - value * voltageDivision;
ctx.lineTo(x, y);
});
ctx.stroke();
// Display measurements
ctx.fillStyle = "#e1e1e1"; // White text for measurements
ctx.font = "14px Arial";
ctx.fillText(`Frequency: ${data.frequency.toFixed(2)} Hz`, 10, 20);
ctx.fillText(`Peak Voltage: ${data.peakVoltage.toFixed(2)} V`, 10, 40);
ctx.fillText(`Average Voltage: ${data.avgVoltage.toFixed(2)} V`, 10, 60);
requestAnimationFrame(updateGraph);
};
const updateTimeDivision = (value) => {
timeDivision = parseFloat(value);
};
const updateVoltageDivision = (value) => {
voltageDivision = parseFloat(value);
};
const captureWaveform = () => {
if (capturedWaveforms.length < 4) {
capturedWaveforms.push(canvas.toDataURL());
updateCaptureGrid();
}
};
const updateCaptureGrid = () => {
const captureGrid = document.getElementById('captureGrid');
captureGrid.innerHTML = ''; // Clear existing captures
capturedWaveforms.forEach((waveformData, index) => {
const captureItem = document.createElement('div');
captureItem.className = 'capture-item';
captureItem.innerHTML = `<img src="${waveformData}" width="100%" />`;
captureGrid.appendChild(captureItem);
});
};
window.onload = () => {
canvas = document.getElementById('oscilloscope');
ctx = canvas.getContext('2d');
updateGraph();
};
</script>
</head>
<body>
<h1 class="title">Indus Oscilloscope</h1>
<canvas id="oscilloscope" width="800" height="400"></canvas>
<div class="controls">
<div class="slider-container">
<label for="timeDivision">Time Division:</label>
<input type="range" id="timeDivision" min="0.1" max="2" step="0.1" value="1" oninput="updateTimeDivision(this.value)">
</div>
<div class="slider-container">
<label for="voltageDivision">Voltage Division:</label>
<input type="range" id="voltageDivision" min="0.5" max="2" step="0.1" value="1" oninput="updateVoltageDivision(this.value)">
</div>
<button onclick="captureWaveform()">Capture Waveform</button>
</div>
<div class="info">Adjust the sliders to change Time and Voltage Division.</div>
<div class="capture-grid" id="captureGrid">
<!-- Captured waveforms will be displayed here in a grid -->
</div>
<footer>Powered by IndusBoard Technologies</footer>
</body>
</html>
)rawliteral";
// Function to calculate signal properties
void handleDataRequest(AsyncWebServerRequest* request) {
const int samples = 100;
int rawADC[samples];
int maxADC = 0;
int minADC = 4095;
float waveform[samples];
float peakVoltage = 0.0;
float totalVoltage = 0.0;
int zeroCrossings = 0;
bool lastWasPositive = false;
for (int i = 0; i < samples; i++) {
rawADC[i] = analogRead(ADC_PIN);
if (rawADC[i] > maxADC) maxADC = rawADC[i];
if (rawADC[i] < minADC) minADC = rawADC[i];
waveform[i] = (rawADC[i] / 4095.0) * V_REF;
if (waveform[i] > peakVoltage) {
peakVoltage = waveform[i];
}
totalVoltage += waveform[i];
bool isPositive = waveform[i] > (V_REF / 2);
if (i > 0 && isPositive != lastWasPositive) {
zeroCrossings++;
}
lastWasPositive = isPositive;
delayMicroseconds(100);
}
float avgVoltage = totalVoltage / samples;
float frequency = (zeroCrossings / 2.0) / (samples * 0.0001);
String json = "{";
json += "\"waveform\":[";
for (int i = 0; i < samples; i++) {
int scaledValue = map(rawADC[i], minADC, maxADC, 10, 390);
json += String(400 - scaledValue);
if (i < samples - 1) json += ",";
}
json += "],";
json += "\"peakVoltage\":" + String(peakVoltage) + ",";
json += "\"avgVoltage\":" + String(avgVoltage) + ",";
json += "\"frequency\":" + String(frequency);
json += "}";
request->send(200, "application/json", json);
}
void setup() {
Serial.begin(115200);
pinMode(33,1);
digitalWrite(33,1);
pinMode(ADC_PIN, INPUT);
WiFi.softAP(ssid, password);
Serial.println("Access Point started. Connect to: Indus-Oscilloscope");
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html", htmlPage);
});
server.on("/data", HTTP_GET, handleDataRequest);
server.begin();
Serial.println("Server started. Go to http://192.168.4.1 in your browser.");
}
void loop() {
// Nothing needed here, handled by AsyncWebServer
}