summaryrefslogtreecommitdiff
path: root/api_layers/firmware
diff options
context:
space:
mode:
authorChristian Kolset <ckolset@colostate.edu>2026-08-07 16:08:48 -0600
committerChristian Kolset <ckolset@colostate.edu>2026-08-07 16:08:48 -0600
commitf0e070f8bdf42ab2b60d318bbd7f65afbfabaab2 (patch)
tree1d62090e848f211495aa2b3154d260deba8049a1 /api_layers/firmware
parent66d69b65379826f9282515d4a227178c4d7e0597 (diff)
Device Firmware updated.
- Moved arduino file to sub-directory. - Updated esp32_stringPot low-pass filter from EMA to butterwoth.
Diffstat (limited to 'api_layers/firmware')
-rw-r--r--api_layers/firmware/arduinoUno/arduinoUno.ino218
-rw-r--r--api_layers/firmware/esp32_stringPot/esp32_stringPot.ino78
2 files changed, 284 insertions, 12 deletions
diff --git a/api_layers/firmware/arduinoUno/arduinoUno.ino b/api_layers/firmware/arduinoUno/arduinoUno.ino
new file mode 100644
index 0000000..8f1b252
--- /dev/null
+++ b/api_layers/firmware/arduinoUno/arduinoUno.ino
@@ -0,0 +1,218 @@
+/*
+ * 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 a2efc64..9edda7b 100644
--- a/api_layers/firmware/esp32_stringPot/esp32_stringPot.ino
+++ b/api_layers/firmware/esp32_stringPot/esp32_stringPot.ino
@@ -14,10 +14,21 @@
const int sensorPin = A0;
const unsigned long sampleInterval = 2000;
unsigned long lastSampleTime = 0;
-const double lowPassFilter_alpha = 0.00675;
-const unsigned long calibration_factor = 1.07517;
-const long calibration_offset = -267.25;
-float filtered_displacement = 0.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};
+
+
+// 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;
void setup() {
@@ -28,9 +39,15 @@ void setup() {
}
analogReadResolution(12);
+ if (calibrationMODE == false){
+ computeCalibrationParameters(measuredRange, readingRange);
+ }
+ computeButterworthCoeffs(butterworth_cutoffHz, butterworth_sampleHz);
}
void loop() {
+ handleSerialCommands();
+
unsigned long currentTime = micros();
if (currentTime - lastSampleTime >= sampleInterval) {
@@ -38,13 +55,13 @@ void loop() {
uint32_t millivolts = analogReadMilliVolts(sensorPin);
float displacement = convert_mV_to_mm(millivolts);
- filtered_displacement = lowPassFilter(displacement, filtered_displacement, lowPassFilter_alpha);
+ float filtered_displacement = butterworthLowPass(displacement);
float volts = filtered_displacement;
// Output the voltage to the serial monitor with 3 decimal places
//Serial.print(displacement, 3);
//Serial.print(", ");
- Serial.println(filtered_displacement, 3);
+ Serial.println(filtered_displacement, 2);
//Serial.println(millivolts);
}
}
@@ -53,14 +70,51 @@ float convert_mV_to_mm(float mVolts){
// Reads mV from sensor and output in mm from lower limit
float voltage = mVolts / 1000.0;
float raw_val = voltage * (635.0 / 2.863);
- //float inverted_val = 650.0 - raw_val;
- //float volts = inverted_val * (635.0 / 637.0);
float volts = raw_val * (635.0 / 637.0);
-
return calibration_factor * volts + calibration_offset;
}
-float lowPassFilter(float newSample, float prevOutput, float alpha) {
- // Simple exponential low-pass filter (EMA) formula
- return alpha * newSample + (1.0 - alpha) * prevOutput;
+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];
+}
+
+void computeButterworthCoeffs(float cutoffHz, float sampleHz) {
+ // 2nd-order Butterworth LPF via bilinear transform
+ float omega = tan(PI * cutoffHz / sampleHz);
+ float omega2 = omega * omega;
+ float sqrt2 = 1.41421356f;
+ float a0 = omega2 + sqrt2 * omega + 1.0f;
+
+ butter_b0 = omega2 / a0;
+ butter_b1 = 2.0f * butter_b0;
+ butter_b2 = butter_b0;
+ butter_a1 = 2.0f * (omega2 - 1.0f) / a0;
+ butter_a2 = (omega2 - sqrt2 * omega + 1.0f) / a0;
+}
+
+float butterworthLowPass(float newSample) {
+ // Direct form II transposed biquad
+ float output = butter_b0 * newSample + butter_z1;
+ butter_z1 = butter_b1 * newSample - butter_a1 * output + butter_z2;
+ butter_z2 = butter_b2 * newSample - butter_a2 * output;
+ return output;
+}
+
+void handleSerialCommands() {
+ // "C:<cutoffHz>\n" sets Butterworth cutoff frequency, e.g. "C:15.0\n"
+ if (Serial.available() > 0) {
+ String line = Serial.readStringUntil('\n');
+ line.trim();
+
+ if (line.startsWith("C:")) {
+ float newCutoff = line.substring(2).toFloat();
+ if (newCutoff > 0.0 && newCutoff < butterworth_sampleHz / 2.0) {
+ butterworth_cutoffHz = newCutoff;
+ computeButterworthCoeffs(butterworth_cutoffHz, butterworth_sampleHz);
+ butter_z1 = 0.0;
+ butter_z2 = 0.0;
+ }
+ }
+ }
}