diff options
| author | Christian Kolset <ckolset@colostate.edu> | 2026-08-10 10:25:59 -0600 |
|---|---|---|
| committer | Christian Kolset <ckolset@colostate.edu> | 2026-08-10 10:29:12 -0600 |
| commit | 46231c00994cd681d598961f2056dbda31c3e483 (patch) | |
| tree | 1bde191e52ad4eab89931bd1a7112be228b7282c /api_layers/firmware/esp32_stringPot/esp32_stringPot.ino | |
| parent | 03e301ac1d8d0785017b6428f4c3bed33b81efcd (diff) | |
esp32_stringPot firmware supports calibration from aruidno IDE
Diffstat (limited to 'api_layers/firmware/esp32_stringPot/esp32_stringPot.ino')
| -rw-r--r-- | api_layers/firmware/esp32_stringPot/esp32_stringPot.ino | 282 |
1 files changed, 247 insertions, 35 deletions
diff --git a/api_layers/firmware/esp32_stringPot/esp32_stringPot.ino b/api_layers/firmware/esp32_stringPot/esp32_stringPot.ino index 3fde958..63d4349 100644 --- a/api_layers/firmware/esp32_stringPot/esp32_stringPot.ino +++ b/api_layers/firmware/esp32_stringPot/esp32_stringPot.ino @@ -1,5 +1,5 @@ /* - LabUI ESP32 Firmware v1.0 + LabUI ESP32 Firmware v1.1 ───────────────────────────────────────────────────────────────────────── Author: Adam Shatila Co-Author: Christian Kolset @@ -8,36 +8,69 @@ Required library: esp32 by Espressif Systems - Calibration: - 1. Flash board with calibrationMODE = true. - 2. Set to position #1 and record actual position (measuredRange[0]) and - output position (readingRange[0]). - 3. Move to position #2 and record actual position (measuredRange[1]) - and output position (readingRange[1]). - 4. Flash board with calibrationMODE = false. + Calibration procedure: + Send calibrate and follow the prompts (recommended — pauses the data + stream and walks through both points). Or drive it manually: + 1. Move sensor to known position #1, send CAL:0:<actual_position> + Board captures its current raw reading and pairs it with that position. + 2. Move sensor to known position #2, send CAL:1:<actual_position> + Board recomputes calibration_factor / calibration_offset and saves + to EEPROM. + Send FILT:<cutoffHz> to change the Butterworth low-pass cutoff + (also saved to EEPROM). Send config at any time to print the current + calibration and filter configuration. + + Serial commands (newline-terminated): + calibrate Guided calibration wizard (recommended) + CAL:0:<pos> Capture current raw reading as calibration point 0 + CAL:1:<pos> Capture current raw reading as calibration point 1 + FILT:<hz> Set Butterworth cutoff frequency + config Print current calibration + filter configuration + help Print this command list + + Replies: + ACK:<command>\n Command executed + ERR:<message>\n Command failed / not recognized ───────────────────────────────────────────────────────────────────────── */ +#include <EEPROM.h> const int sensorPin = A0; const unsigned long sampleInterval = 2000; unsigned long lastSampleTime = 0; -//Calibration -float calibration_factor = 1.0; -float calibration_offset; -// 2 point calibration used for scaling factor -bool calibrationMODE = false; -float measuredRange[2] = {0, 150}; -float readingRange[2] = {260.26, 421.3}; +// EEPROM-persisted configuration +#define EEPROM_SIZE 64 +#define EEPROM_MAGIC 0xC1 + +struct StoredConfig { + uint8_t magic; + float calibrationPosition[2]; + float calibrationRange[2]; + float butterworth_cutoffHz; +}; +StoredConfig cfg = { + EEPROM_MAGIC, + {0, 150}, + {260.26, 421.3}, + 0.5 +}; + +// Calibration (mirrors cfg, kept as separate globals for fast access in loop()) +float calibration_factor = 1.0; +float calibration_offset = 0.0; // 2nd-order Butterworth low-pass (biquad, direct form II transposed) -float butterworth_cutoffHz = 0.5; const float butterworth_sampleHz = 1000000.0 / sampleInterval; float butter_b0, butter_b1, butter_b2, butter_a1, butter_a2; float butter_z1 = 0.0, butter_z2 = 0.0; +String serialLine; +bool streamingEnabled = true; +bool calibPointSet[2] = {false, false}; + void setup() { Serial.begin(115200); @@ -46,10 +79,11 @@ void setup() { } analogReadResolution(12); - computeButterworthCoeffs(butterworth_cutoffHz, butterworth_sampleHz); - if (calibrationMODE == false){ - computeCalibrationParameters(measuredRange, readingRange); - } + + EEPROM.begin(EEPROM_SIZE); + loadConfig(); + computeButterworthCoeffs(cfg.butterworth_cutoffHz, butterworth_sampleHz); + computeCalibrationParameters(cfg.calibrationPosition, cfg.calibrationRange); } void loop() { @@ -58,28 +92,103 @@ void loop() { if (currentTime - lastSampleTime >= sampleInterval) { lastSampleTime = currentTime; - uint32_t millivolts = analogReadMilliVolts(sensorPin); - float displacement = convert_mV_to_mm(millivolts); - float filtered_displacement = butterworthLowPass(displacement); - float volts = filtered_displacement; + float filtered_displacement = butterworthLowPass(readCalibratedPosition()); + if (streamingEnabled) { + Serial.println(filtered_displacement, 2); + } + } + + readSerialCommands(); +} + +void readSerialCommands() { + while (Serial.available()) { + char c = Serial.read(); + if (c == '\n') { + serialLine.trim(); + if (serialLine.length() > 0) { + handleCommand(serialLine); + } + serialLine = ""; + } else if (c != '\r') { + serialLine += c; + } + } +} + +void handleCommand(String line) { + if (line.startsWith("CAL:0:") || line.startsWith("CAL:1:")) { + int idx = line.charAt(4) - '0'; + float actualPos = line.substring(6).toFloat(); + float rawReading = readRawPosition(); + int otherIdx = 1 - idx; + + if (calibPointSet[otherIdx] && rawReading == cfg.calibrationRange[otherIdx]) { + Serial.println("ERR:Calibration points cannot have equal raw reading"); + return; + } + + cfg.calibrationPosition[idx] = actualPos; + cfg.calibrationRange[idx] = rawReading; + calibPointSet[idx] = true; + + if (!calibPointSet[otherIdx]) { + Serial.println("ACK:" + line + " (waiting on CAL:" + String(otherIdx) + ")"); + return; + } + + computeCalibrationParameters(cfg.calibrationPosition, cfg.calibrationRange); + saveConfig(); - // Output the voltage to the serial monitor with 3 decimal places - //Serial.print(displacement, 3); - //Serial.print(", "); - Serial.println(filtered_displacement, 2); - //Serial.println(millivolts); + Serial.println("ACK:" + line); + } else if (line.startsWith("FILT:")) { + float hz = line.substring(5).toFloat(); + if (hz <= 0) { + Serial.println("ERR:Invalid cutoff frequency"); + return; + } + cfg.butterworth_cutoffHz = hz; + computeButterworthCoeffs(cfg.butterworth_cutoffHz, butterworth_sampleHz); + saveConfig(); + + Serial.println("ACK:" + line); + } else if (line == "config") { + printConfig(); + } else if (line == "help") { + printHelp(); + } else if (line == "calibrate") { + calibrationWizard(); + } else { + Serial.println("ERR:Unknown command"); + } +} + +void loadConfig() { + EEPROM.get(0, cfg); + if (cfg.magic != EEPROM_MAGIC) { + cfg = { EEPROM_MAGIC, {0, 150}, {260.26, 421.3}, 0.5 }; + saveConfig(); } } -float convert_mV_to_mm(float mVolts){ - // Reads mV from sensor and output in mm from lower limit - float voltage = mVolts / 1000.0; +void saveConfig() { + EEPROM.put(0, cfg); + EEPROM.commit(); +} + +// Fixed sensor scaling, pre calibration_factor/offset +float readRawPosition() { + uint32_t millivolts = analogReadMilliVolts(sensorPin); + float voltage = millivolts / 1000.0; float raw_val = voltage * (635.0 / 2.863); - float volts = raw_val * (635.0 / 637.0); - return calibration_factor * volts + calibration_offset; + return raw_val * (635.0 / 637.0); } -void computeCalibrationParameters(float x[2], float y[2]){ +float readCalibratedPosition() { + return calibration_factor * readRawPosition() + calibration_offset; +} + +void computeCalibrationParameters(float x[2], float y[2]) { calibration_factor = (x[1] - x[0]) / (y[1] - y[0]); calibration_offset = x[0] - calibration_factor * y[0]; } @@ -105,3 +214,106 @@ float butterworthLowPass(float newSample) { butter_z2 = butter_b2 * newSample - butter_a2 * output; return output; } + +void printConfig() { + Serial.println("Calibration:"); + Serial.print("\tPosition:\t"); + Serial.print(cfg.calibrationPosition[0], 3); + Serial.print(",\t"); + Serial.println(cfg.calibrationPosition[1], 3); + Serial.print("\tRaw values:\t"); + Serial.print(cfg.calibrationRange[0], 3); + Serial.print(",\t"); + Serial.println(cfg.calibrationRange[1], 3); + Serial.print("\tFactor:\t"); + Serial.println(calibration_factor, 6); + Serial.print("\tOffset:\t"); + Serial.println(calibration_offset, 6); + Serial.print("Butterworth cutoff (Hz):\t"); + Serial.println(cfg.butterworth_cutoffHz, 3); +} + +void printHelp() { + Serial.println("Commands:"); + Serial.println("\tcalibrate Guided calibration wizard (recommended)"); + Serial.println("\tCAL:0:<pos> Capture current raw reading as calibration point 0"); + Serial.println("\tCAL:1:<pos> Capture current raw reading as calibration point 1"); + Serial.println("\tFILT:<hz> Set Butterworth cutoff frequency"); + Serial.println("\tconfig Print current calibration + filter configuration"); + Serial.println("\thelp Print this command list"); + interruptibleDelay(10000); +} + +// Waits up to ms, returns early (without consuming input) if a command starts arriving. +void interruptibleDelay(unsigned long ms) { + unsigned long start = millis(); + while (millis() - start < ms) { + if (Serial.available()) { + return; + } + } +} + +// Blocks until a newline-terminated line arrives; used only inside calibrationWizard(), +// where halting loop() to walk the user through steps is the point. +String blockingReadLine() { + String buf = ""; + while (true) { + if (Serial.available()) { + char c = Serial.read(); + if (c == '\n') { + buf.trim(); + return buf; + } else if (c != '\r') { + buf += c; + } + } + } +} + +void calibrationWizard() { + bool wasStreaming = streamingEnabled; + streamingEnabled = false; + + Serial.println("=== Calibration wizard (type 'cancel' anytime to abort) ==="); + + float pos[2]; + float raw[2]; + + for (int i = 0; i < 2; i++) { + Serial.println("Move sensor to known position #" + String(i + 1) + ", enter actual position, press Enter:"); + String input = blockingReadLine(); + + if (input.equalsIgnoreCase("cancel")) { + Serial.println("Calibration cancelled. Previous calibration unchanged."); + streamingEnabled = wasStreaming; + return; + } + + pos[i] = input.toFloat(); + raw[i] = readRawPosition(); + + if (i == 1 && raw[1] == raw[0]) { + Serial.println("ERR:Calibration points cannot have equal raw reading. Wizard aborted, previous calibration unchanged."); + streamingEnabled = wasStreaming; + return; + } + + Serial.println("Point " + String(i) + " captured: position=" + String(pos[i], 3) + " raw=" + String(raw[i], 3)); + } + + cfg.calibrationPosition[0] = pos[0]; + cfg.calibrationPosition[1] = pos[1]; + cfg.calibrationRange[0] = raw[0]; + cfg.calibrationRange[1] = raw[1]; + calibPointSet[0] = true; + calibPointSet[1] = true; + + computeCalibrationParameters(cfg.calibrationPosition, cfg.calibrationRange); + saveConfig(); + + Serial.println("Calibration saved."); + printConfig(); + + streamingEnabled = wasStreaming; +} |
