#include <QNEthernet.h>
#include <ArduinoJson.h>
#include <FreqCount.h>
#include <USBHost_t36.h>
#include "webpage.h"
using namespace qindesign::network;
// ==========================================
// USB HOST (MiniSMU)
// ==========================================
USBHost myusb;
USBHub hub1(myusb);
USBSerial userial(myusb);
// ==========================================
// NETWORK CONFIGURATION
// ==========================================
IPAddress staticIP(192, 168, 0, 105);
IPAddress subnet(255, 255, 255, 0);
IPAddress gateway(192, 168, 0, 1);
IPAddress dns(192, 168, 0, 1);
EthernetServer server(80);
// ==========================================
// SYSTEM STATE
// ==========================================
float currentEnergy = 0.0;
long currentCounts = 0;
float currentCurrent = 0.0;
float currentMirrorCurrent = 0.0;
unsigned long lastIdleUpdate = 0;
bool lastHWStatus = false;
// Connection Debounce Globals
unsigned long disconnectStartTime = 0;
bool pendingDisconnect = false;
struct Config {
float cathodeVoltage = -20.0;
float mirrorVoltage = 0.0;
} sysConfig;
// ==========================================
// FUNCTION PROTOTYPES
// ==========================================
void handleRequest(EthernetClient &client);
void sendCommonHeaders(EthernetClient &client);
void updateIdleMonitoring();
// ==========================================
// SETUP
// ==========================================
void setup() {
Serial.begin(115200);
myusb.begin();
userial.begin(115200);
Serial.println("--- IPES Measurement Engine V0.8.7 (Slave Mode) ---");
if (!Ethernet.begin(staticIP, subnet, gateway)) {
Serial.println("Failed to start Ethernet!");
}
server.begin();
}
// Helper for active waiting
void wait(unsigned long ms) {
unsigned long start = millis();
while (millis() - start < ms) {
myusb.Task();
}
}
void loop() {
myusb.Task();
bool currentHW = (bool)userial;
if (currentHW && !lastHWStatus) {
// === DEVICE CONNECTED (Stable) ===
Serial.println("SMU Connected - Initializing...");
// Robust Initialization Sequence
wait(1000);
userial.begin(115200);
// Removed *RST to prevent USB detach/reset loops
wait(200);
userial.print("*CLS\n"); // Clear Status/Error Queue
wait(50);
// Init CH1 (Sample)
Serial.println("Configuring CH1...");
userial.println("SOUR1:FUNC VOLT");
wait(50);
userial.println("SENS1:FUNC \"CURR\"");
wait(50);
userial.println("SENS1:CURR:RANG:AUTO ON");
wait(50);
userial.println("OUTP1 ON");
wait(100);
// Init CH2 (Mirror)
Serial.println("Configuring CH2...");
userial.println("SOUR2:FUNC VOLT");
wait(50);
userial.println("SENS2:FUNC \"CURR\"");
wait(50);
userial.println("SENS2:CURR:RANG:AUTO ON");
wait(50);
userial.println("OUTP2 ON");
wait(100);
Serial.println("Setting Initial Bias...");
userial.printf("SOUR1:VOLT %.2f\n", sysConfig.cathodeVoltage);
userial.printf("SOUR2:VOLT %.2f\n", sysConfig.mirrorVoltage);
Serial.println("SMU Init Complete.");
lastHWStatus = true;
pendingDisconnect = false;
}
else if (!currentHW && lastHWStatus) {
// === POTENTIAL DISCONNECT ===
if (!pendingDisconnect) {
pendingDisconnect = true;
disconnectStartTime = millis();
} else {
if (millis() - disconnectStartTime > 2000) { // 2000ms Debounce
Serial.println("SMU Disconnected (Confirmed)");
lastHWStatus = false;
pendingDisconnect = false;
}
}
}
else if (currentHW && pendingDisconnect) {
// === RECOVERED (Glitch detected) ===
pendingDisconnect = false;
}
EthernetClient client = server.available();
if (client) {
handleRequest(client);
}
updateIdleMonitoring();
}
void updateIdleMonitoring() {
if (millis() - lastIdleUpdate > 800) {
lastIdleUpdate = millis();
if (userial) {
// Read CH1 Current
while(userial.available()) userial.read();
userial.print("MEAS1:CURR?\n");
unsigned long start = millis();
String buf = "";
while (millis() - start < 100) {
myusb.Task();
if (userial.available()) {
char c = userial.read();
if ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == 'E' || c == 'e' || c == '+') buf += c;
else if (buf.length() > 0 && (c == '\n' || c == '\r' || c == ',')) {
currentCurrent = buf.toFloat() * -1000000.0;
break;
}
}
}
// Read CH2 Current
buf = "";
userial.print("MEAS2:CURR?\n");
start = millis();
while (millis() - start < 100) {
myusb.Task();
if (userial.available()) {
char c = userial.read();
if ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == 'E' || c == 'e' || c == '+') buf += c;
else if (buf.length() > 0 && (c == '\n' || c == '\r' || c == ',')) {
currentMirrorCurrent = buf.toFloat() * -1000000.0;
break;
}
}
}
}
}
}
void doMeasure(float v1, float v2, int dwell, JsonDocument &res) {
if (userial) {
userial.printf("SOUR1:VOLT %.2f\n", v1);
wait(10); // Ensure separation
userial.printf("SOUR2:VOLT %.2f\n", v2);
}
delay(50); // Settle
// Ensure the gate is set to the requested dwell in MICROSECONDS (Teensy 4.1 FreqCount uses usec)
unsigned long startGate = millis();
FreqCount.begin(dwell * 1000UL);
while (!FreqCount.available()) {
myusb.Task();
}
long counts = FreqCount.read();
FreqCount.end();
unsigned long endGate = millis();
Serial.printf("Point: %.2fV, Dwell: %dms, Actual: %lums, Counts: %ld\n", v1, dwell, endGate - startGate, counts);
float cur1 = 0.0;
float cur2 = 0.0;
if (userial) {
// Read CH1
while(userial.available()) userial.read();
userial.print("MEAS1:CURR?\n");
unsigned long t = millis();
String b = "";
while (millis() - t < 400) {
myusb.Task();
if (userial.available()) {
char ch = userial.read();
if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == 'E' || ch == 'e' || ch == '+') b += ch;
else if (b.length() > 0 && (ch == '\n' || ch == '\r' || ch == ',')) {
cur1 = b.toFloat() * -1000000.0;
break;
}
}
}
// Read CH2
b = "";
userial.print("MEAS2:CURR?\n");
t = millis();
while (millis() - t < 400) {
myusb.Task();
if (userial.available()) {
char ch = userial.read();
if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == 'E' || ch == 'e' || ch == '+') b += ch;
else if (b.length() > 0 && (ch == '\n' || ch == '\r' || ch == ',')) {
cur2 = b.toFloat() * -1000000.0;
break;
}
}
}
}
res["v"] = v1;
res["c"] = counts;
res["i"] = cur1;
res["im"] = cur2;
res["hw"] = (bool)userial;
}
void handleRequest(EthernetClient &client) {
String reqLine = client.readStringUntil('\n');
reqLine.trim();
if (reqLine.length() == 0) { client.stop(); return; }
while (client.connected() && client.available()) {
String line = client.readStringUntil('\n');
if (line == "\r" || line == "") break;
}
if (reqLine.startsWith("OPTIONS")) {
client.println("HTTP/1.1 204 No Content");
client.println("Access-Control-Allow-Origin: *");
client.println("Access-Control-Allow-Methods: POST, GET, OPTIONS");
client.println("Access-Control-Allow-Headers: Content-Type");
client.println("Connection: close\n");
client.stop();
return;
}
if (reqLine.startsWith("GET /api/status")) {
client.println("HTTP/1.1 200 OK");
sendCommonHeaders(client);
JsonDocument doc;
doc["v"] = currentEnergy;
doc["c"] = currentCounts;
doc["i"] = currentCurrent;
doc["im"] = currentMirrorCurrent;
doc["hw"] = (bool)userial;
serializeJson(doc, client);
client.println();
}
else if (reqLine.startsWith("POST /api/measure")) {
JsonDocument req;
if (!deserializeJson(req, client)) {
JsonDocument res;
doMeasure(req["v"] | 0.0, req["vm"] | sysConfig.mirrorVoltage, req["dwell"] | 500, res);
client.println("HTTP/1.1 200 OK");
sendCommonHeaders(client);
serializeJson(res, client);
client.println();
}
}
else if (reqLine.startsWith("GET /api/stop")) {
if (userial) {
userial.printf("SOUR1:VOLT %.2f\n", sysConfig.cathodeVoltage);
userial.printf("SOUR2:VOLT %.2f\n", sysConfig.mirrorVoltage);
}
client.println("HTTP/1.1 200 OK");
sendCommonHeaders(client);
client.println("{\"status\":\"stopped\"}");
}
else if (reqLine.startsWith("POST /api/config")) {
JsonDocument doc;
if (!deserializeJson(doc, client)) {
sysConfig.cathodeVoltage = doc["cathode"] | sysConfig.cathodeVoltage;
sysConfig.mirrorVoltage = doc["mirror"] | sysConfig.mirrorVoltage;
if (userial) {
userial.printf("SOUR1:VOLT %.2f\n", sysConfig.cathodeVoltage);
userial.printf("SOUR2:VOLT %.2f\n", sysConfig.mirrorVoltage);
}
client.println("HTTP/1.1 200 OK");
sendCommonHeaders(client);
client.println("{\"status\":\"updated\"}");
}
}
else {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html\nContent-Encoding: gzip");
client.print("Content-Length: "); client.println(index_html_len);
client.println("Connection: close\n");
const unsigned long CHUNK_SIZE = 1460;
unsigned long bytesSent = 0;
while (bytesSent < index_html_len) {
unsigned long remaining = index_html_len - bytesSent;
unsigned long chunk = (remaining < CHUNK_SIZE) ? remaining : CHUNK_SIZE;
size_t n = client.write(&index_html[bytesSent], chunk);
if (n == 0) { delay(1); continue; }
bytesSent += n;
}
}
client.stop();
}
void sendCommonHeaders(EthernetClient &client) {
client.println("Content-Type: application/json\nAccess-Control-Allow-Origin: *\nConnection: close\n");
}