summaryrefslogtreecommitdiff
path: root/api_layers/firmware
diff options
context:
space:
mode:
authorChristian Kolset <ckolset@colostate.edu>2026-08-10 10:25:59 -0600
committerChristian Kolset <ckolset@colostate.edu>2026-08-10 10:29:12 -0600
commit46231c00994cd681d598961f2056dbda31c3e483 (patch)
tree1bde191e52ad4eab89931bd1a7112be228b7282c /api_layers/firmware
parent03e301ac1d8d0785017b6428f4c3bed33b81efcd (diff)
esp32_stringPot firmware supports calibration from aruidno IDE
Diffstat (limited to 'api_layers/firmware')
-rw-r--r--api_layers/firmware/arduinoUno.ino218
-rw-r--r--api_layers/firmware/esp32_stringPot/esp32_stringPot.ino282
2 files changed, 247 insertions, 253 deletions
diff --git a/api_layers/firmware/arduinoUno.ino b/api_layers/firmware/arduinoUno.ino
deleted file mode 100644
index 8f1b252..0000000
--- a/api_layers/firmware/arduinoUno.ino
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * LabUI Arduino Firmware v1.1
- * ─────────────────────────────────────────────────────────────────────────
- * Upload this sketch to your Arduino.
- * Set baud rate to 115200 in both this sketch and LabUI.
- *
- * WHAT IT DOES
- * Continuously streams analog + digital readings to the PC.
- * Listens for commands from the PC and executes them.
- *
- * RECEIVED COMMANDS (PC → Arduino):
- * W:D13:1 digitalWrite(13, HIGH)
- * W:D6:0 digitalWrite(6, LOW)
- * P:D9:128 analogWrite(9, 128) → ~50% PWM
- * C:SETPOINT:75.0 store named parameter
- * Q:A0 reply immediately with A0 reading
- * R:ALL send full data frame immediately
- * X:STOP set all outputs LOW
- * X:RESET reset to defaults
- *
- * SENT DATA (Arduino → PC):
- * A0:3.142,A1:0.015,D2:0,D3:1\n (every SEND_INTERVAL ms)
- * ACK:W:D13:1\n (after each command)
- * ERR:Unknown command\n (on parse failure)
- * ─────────────────────────────────────────────────────────────────────────
- */
-
-// ── Configuration ─────────────────────────────────────────────────────────
-int ANALOG_PINS[6] = {A0, A1, A2, A3, A4, A5}; // actual pin numbers (reconfigured by APIN:)
-int ANALOG_IDX[6] = {0, 1, 2, 3, 4, 5}; // label indices used in output (A0, A1, …)
-int DIGITAL_IN[16] = {2, 3, 4}; // reconfigured at runtime via DPIN:IN:
-int DIGITAL_OUT[16] = {5, 6, 7, 9, 10, 11}; // reconfigured via DPIN:OUT:
-int N_ANALOG = 0; // set by APIN: command (0 = inactive until configured by PC)
-int N_DIG_IN = 0; // set by DPIN:IN: command (0 = inactive until configured)
-int N_DIG_OUT = 0; // set by DPIN:OUT: command (0 = inactive until configured)
-const int SEND_INTERVAL = 50; // ms between data frames (50 = 20 Hz)
-const long BAUD_RATE = 115200;
-
-// ── Named parameters (set via C: commands) ──────────────────────────────
-float param_setpoint = 0.0;
-float param_kp = 1.0;
-float param_ki = 0.0;
-float param_kd = 0.0;
-int param_mode = 0;
-
-unsigned long lastSend = 0;
-
-// ── Setup ─────────────────────────────────────────────────────────────────
-void setup() {
- Serial.begin(BAUD_RATE);
- for (int i = 0; i < N_DIG_IN; i++) pinMode(DIGITAL_IN[i], INPUT_PULLUP);
- for (int i = 0; i < N_DIG_OUT; i++) pinMode(DIGITAL_OUT[i], OUTPUT);
-}
-
-// ── Main loop ─────────────────────────────────────────────────────────────
-void loop() {
- handleCommands();
- sendData();
-}
-
-// ── Command handler ───────────────────────────────────────────────────────
-void handleCommands() {
- if (!Serial.available()) return;
-
- String cmd = Serial.readStringUntil('\n');
- cmd.trim();
- if (cmd.length() == 0) return;
-
- char type = cmd.charAt(0);
-
- // W:Dxx:val — digital write
- if (type == 'W') {
- int c1 = cmd.indexOf(':', 2);
- if (c1 < 0) { Serial.println("ERR:Bad W format"); return; }
- String pinStr = cmd.substring(2, c1);
- int val = cmd.substring(c1 + 1).toInt();
- int pin = pinStr.substring(1).toInt(); // strip 'D'
- digitalWrite(pin, val ? HIGH : LOW);
- Serial.print("ACK:"); Serial.println(cmd);
- }
-
- // P:Dxx:duty — PWM write (0-255)
- else if (type == 'P') {
- int c1 = cmd.indexOf(':', 2);
- if (c1 < 0) { Serial.println("ERR:Bad P format"); return; }
- int pin = cmd.substring(2, c1).substring(1).toInt();
- int duty = constrain(cmd.substring(c1 + 1).toInt(), 0, 255);
- analogWrite(pin, duty);
- Serial.print("ACK:"); Serial.println(cmd);
- }
-
- // C:NAME:value — set named parameter
- else if (type == 'C') {
- int c1 = cmd.indexOf(':', 2);
- int c2 = cmd.indexOf(':', c1 + 1);
- if (c1 < 0 || c2 < 0) { Serial.println("ERR:Bad C format"); return; }
- String name = cmd.substring(2, c1);
- float val = cmd.substring(c2 + 1).toFloat();
- if (name == "SETPOINT") param_setpoint = val;
- else if (name == "KP") param_kp = val;
- else if (name == "KI") param_ki = val;
- else if (name == "KD") param_kd = val;
- else if (name == "MODE") param_mode = (int)val;
- // Add your own parameters here:
- // else if (name == "SPEED") motor_speed = val;
- Serial.print("ACK:"); Serial.println(cmd);
- }
-
- // Q:NAME — immediate query
- else if (type == 'Q') {
- String name = cmd.substring(2);
- if (name == "SETPOINT") { Serial.print("SETPOINT:"); Serial.println(param_setpoint, 3); }
- else if (name == "KP") { Serial.print("KP:"); Serial.println(param_kp, 4); }
- else {
- // Try to read as analog pin Q:A0
- if (name.charAt(0) == 'A') {
- int pin = name.substring(1).toInt();
- float v = analogRead(pin) * (5.0 / 1023.0);
- Serial.print(name); Serial.print(":"); Serial.println(v, 3);
- }
- }
- }
-
- // R:ALL — send full frame immediately
- else if (type == 'R') {
- sendFrame();
- }
-
- // X:STOP / X:RESET — emergency stop
- else if (type == 'X') {
- String sub = cmd.substring(2);
- for (int i = 0; i < N_DIG_OUT; i++) digitalWrite(DIGITAL_OUT[i], LOW);
- Serial.print("ACK:"); Serial.println(cmd);
- }
-
- // APIN:0,1,3 — reconfigure analog inputs at runtime (indices 0–5 into A0–A5)
- else if (type == 'A') {
- if (!cmd.startsWith("APIN:")) { Serial.println("ERR:Bad A command"); return; }
- String pinList = cmd.substring(5); // "0,1,3"
- const int _APINS[] = {A0,A1,A2,A3,A4,A5};
- N_ANALOG = 0;
- int start = 0;
- for (int i = 0; i <= (int)pinList.length(); i++) {
- if (i == (int)pinList.length() || pinList[i] == ',') {
- String tok = pinList.substring(start, i); tok.trim();
- if (tok.length() > 0 && N_ANALOG < 6) {
- int idx = tok.toInt();
- if (idx >= 0 && idx < 6) {
- ANALOG_PINS[N_ANALOG] = _APINS[idx];
- ANALOG_IDX[N_ANALOG] = idx;
- N_ANALOG++;
- }
- }
- start = i + 1;
- }
- }
- Serial.print("ACK:"); Serial.println(cmd);
- }
-
- // DPIN:IN:2,3,8 or DPIN:OUT:5,6,7 — reconfigure digital I/O pins at runtime
- else if (type == 'D') {
- int c1 = cmd.indexOf(':', 2);
- int c2 = (c1 >= 0) ? cmd.indexOf(':', c1 + 1) : -1;
- if (c1 < 0 || c2 < 0) { Serial.println("ERR:Bad DPIN format"); return; }
- String dir = cmd.substring(2, c1); // "IN" or "OUT"
- String pinList = cmd.substring(c2 + 1); // "2,3,8"
- bool isIn = (dir == "IN");
- int* arr = isIn ? DIGITAL_IN : DIGITAL_OUT;
- int* cnt = isIn ? &N_DIG_IN : &N_DIG_OUT;
- int mode = isIn ? INPUT_PULLUP : OUTPUT;
- *cnt = 0;
- int start = 0;
- for (int i = 0; i <= (int)pinList.length(); i++) {
- if (i == (int)pinList.length() || pinList[i] == ',') {
- String tok = pinList.substring(start, i); tok.trim();
- if (tok.length() > 0 && *cnt < 16) {
- arr[*cnt] = tok.toInt();
- pinMode(arr[*cnt], mode);
- (*cnt)++;
- }
- start = i + 1;
- }
- }
- Serial.print("ACK:"); Serial.println(cmd);
- }
-
- else {
- Serial.print("ERR:Unknown command: "); Serial.println(cmd);
- }
-}
-
-// ── Data sender ───────────────────────────────────────────────────────────
-void sendData() {
- if (millis() - lastSend < SEND_INTERVAL) return;
- lastSend = millis();
- sendFrame();
-}
-
-void sendFrame() {
- String out = "";
- bool first = true;
- // Analog inputs — converted to 0.0–5.0 V
- for (int i = 0; i < N_ANALOG; i++) {
- float v = analogRead(ANALOG_PINS[i]) * (5.0 / 1023.0);
- if (!first) out += ",";
- out += "A" + String(ANALOG_IDX[i]) + ":" + String(v, 3);
- first = false;
- }
- // Digital inputs (INPUT_PULLUP — invert so pressed=1)
- // These share the same output line as analog — one line per frame.
- for (int i = 0; i < N_DIG_IN; i++) {
- if (!first) out += ",";
- out += "D" + String(DIGITAL_IN[i]) + ":" + String(!digitalRead(DIGITAL_IN[i]));
- first = false;
- }
- Serial.println(out);
- // Example combined output: A0:3.142,D2:0,D3:1
-}
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;
+}