Skip to content

14. Interface and Application Programming

So for this week I focused on making of the interface for my servo. I used the pcb I designed with my ESP32-C3. Here you can read about my PCB DESIGN and here you can read about its production PCB PRODUCTION. So for the application I used html.

Group Assignment

In the group work, we looked at a way to connect embedded systems to interfaces. This helpes with understanding how to connect the interface to the hardware.

Individual assignment

So for this assignment I used html to make my interface. I thought it would be easy to make a controller with the browser so this week is kinda mixed with my Networking and Communications week where I made my board connect to the other board wirelessly through internet.

I used this code that I made with the help of chatgpt to make my already existing slider from week 11 to look better and work properly for 3 servos connected on one board. first is the Link for the week where the servo connects wirelessly with a minimal interface Then the chatgpt prompt. Link to the exchange

#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>

const char* ssid = "ESP32_Servo";
const char* password = "12345678";

WebServer server(80);

// Servos
Servo servo1;
Servo servo2;
Servo servo3;
Servo servo4;
Servo servo5;
Servo servo6;

// Servo pins
const int servoPin1 = D0;
const int servoPin2 = D1;

const int servoPin3 = D2;
const int servoPin4 = D3;

const int servoPin5 = D6;
const int servoPin6 = D7;

// Stepper pins
const int STEP_PIN = D9;
const int DIR_PIN  = D10;

// Joint positions
int joint1Pos = 90;
int joint2Pos = 90;
int joint3Pos = 90;

// Stepper control
bool stepperRunning = false;
bool stepPinState = LOW;
unsigned long lastStepMicros = 0;

const unsigned long stepIntervalMicros = 2500;  // bigger = slower
const unsigned long stepPulseWidthMicros = 5;

void setMirroredJoint(Servo &a, Servo &b, int angle) {
  angle = constrain(angle, 0, 180);
  a.write(angle);
  b.write(180 - angle);
}

void handleRoot() {
  String page = R"rawliteral(
  <!DOCTYPE html>
  <html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
      * { box-sizing: border-box; }

      body {
        margin: 0;
        min-height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        font-family: Arial, sans-serif;
        background: linear-gradient(135deg, #0f172a, #1e293b);
        color: white;
      }

      .container {
        width: 92%;
        max-width: 700px;
        background: rgba(255,255,255,0.08);
        backdrop-filter: blur(12px);
        padding: 24px;
        border-radius: 20px;
        box-shadow: 0 10px 30px rgba(0,0,0,0.35);
      }

      h1 {
        margin: 0 0 20px 0;
        font-size: 28px;
      }

      .card {
        background: rgba(255,255,255,0.05);
        border-radius: 16px;
        padding: 18px;
        margin-bottom: 16px;
      }

      .label {
        font-size: 16px;
        margin-bottom: 10px;
        color: #cbd5e1;
      }

      .value {
        margin-top: 10px;
        font-size: 28px;
        font-weight: bold;
        color: #38bdf8;
      }

      .slider {
        width: 100%;
        height: 12px;
        border-radius: 10px;
        background: #334155;
        outline: none;
        -webkit-appearance: none;
      }

      .slider::-webkit-slider-thumb {
        -webkit-appearance: none;
        appearance: none;
        width: 28px;
        height: 28px;
        border-radius: 50%;
        background: #38bdf8;
        cursor: pointer;
      }

      .slider::-moz-range-thumb {
        width: 28px;
        height: 28px;
        border-radius: 50%;
        background: #38bdf8;
        border: none;
        cursor: pointer;
      }

      .buttons {
        display: flex;
        gap: 12px;
        flex-wrap: wrap;
      }

      .btn {
        flex: 1;
        min-width: 130px;
        padding: 16px;
        border: none;
        border-radius: 14px;
        font-size: 18px;
        font-weight: bold;
        color: white;
        background: #2563eb;
        box-shadow: 0 8px 20px rgba(37,99,235,0.35);
      }

      .btn.down {
        background: #7c3aed;
        box-shadow: 0 8px 20px rgba(124,58,237,0.35);
      }

      .hint {
        margin-top: 10px;
        color: #cbd5e1;
        font-size: 14px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>ESP32 Control Panel</h1>

      <div class="card">
        <div class="label">Joint 1</div>
        <input type="range" min="0" max="180" value="90" class="slider" id="joint1" oninput="setJoint(1, this.value)">
        <div class="value"><span id="joint1Val">90</span>°</div>
      </div>

      <div class="card">
        <div class="label">Joint 2</div>
        <input type="range" min="0" max="180" value="90" class="slider" id="joint2" oninput="setJoint(2, this.value)">
        <div class="value"><span id="joint2Val">90</span>°</div>
      </div>

      <div class="card">
        <div class="label">Joint 3</div>
        <input type="range" min="0" max="180" value="90" class="slider" id="joint3" oninput="setJoint(3, this.value)">
        <div class="value"><span id="joint3Val">90</span>°</div>
      </div>

      <div class="card">
        <div class="label">Stepper</div>
        <div class="buttons">
          <button class="btn" onpointerdown="stepUp()" onpointerup="stopStepper()" onpointerleave="stopStepper()">Up</button>
          <button class="btn down" onpointerdown="stepDown()" onpointerup="stopStepper()" onpointerleave="stopStepper()">Down</button>
        </div>
        <div class="hint">Hold a button to move the stepper. Release to stop.</div>
      </div>
    </div>

    <script>
      function setJoint(num, val) {
        document.getElementById("joint" + num + "Val").textContent = val;
        fetch("/joint" + num + "?value=" + val).catch(err => console.log(err));
      }

      function stepUp() {
        fetch("/step?dir=up").catch(err => console.log(err));
      }

      function stepDown() {
        fetch("/step?dir=down").catch(err => console.log(err));
      }

      function stopStepper() {
        fetch("/step?dir=stop").catch(err => console.log(err));
      }
    </script>
  </body>
  </html>
  )rawliteral";

  server.send(200, "text/html", page);
}

void handleJoint1() {
  if (server.hasArg("value")) {
    joint1Pos = constrain(server.arg("value").toInt(), 0, 180);
    setMirroredJoint(servo1, servo2, joint1Pos);

    Serial.print("Joint 1: ");
    Serial.println(joint1Pos);
    Serial.print("Servo 2 mirrored: ");
    Serial.println(180 - joint1Pos);
  }
  server.send(200, "text/plain", "OK");
}

void handleJoint2() {
  if (server.hasArg("value")) {
    joint2Pos = constrain(server.arg("value").toInt(), 0, 180);
    setMirroredJoint(servo3, servo4, joint2Pos);

    Serial.print("Joint 2: ");
    Serial.println(joint2Pos);
    Serial.print("Servo 4 mirrored: ");
    Serial.println(180 - joint2Pos);
  }
  server.send(200, "text/plain", "OK");
}

void handleJoint3() {
  if (server.hasArg("value")) {
    joint3Pos = constrain(server.arg("value").toInt(), 0, 180);
    setMirroredJoint(servo5, servo6, joint3Pos);

    Serial.print("Joint 3: ");
    Serial.println(joint3Pos);
    Serial.print("Servo 6 mirrored: ");
    Serial.println(180 - joint3Pos);
  }
  server.send(200, "text/plain", "OK");
}

void handleStepper() {
  if (server.hasArg("dir")) {
    String dir = server.arg("dir");

    if (dir == "up") {
      stepperRunning = true;
      digitalWrite(DIR_PIN, HIGH);
      Serial.println("Stepper UP");
    } else if (dir == "down") {
      stepperRunning = true;
      digitalWrite(DIR_PIN, LOW);
      Serial.println("Stepper DOWN");
    } else if (dir == "stop") {
      stepperRunning = false;
      digitalWrite(STEP_PIN, LOW);
      stepPinState = LOW;
      Serial.println("Stepper STOP");
    }
  }

  server.send(200, "text/plain", "OK");
}

void runStepper() {
  if (!stepperRunning) return;

  unsigned long now = micros();

  if (!stepPinState) {
    if (now - lastStepMicros >= stepIntervalMicros) {
      digitalWrite(STEP_PIN, HIGH);
      stepPinState = HIGH;
      lastStepMicros = now;
    }
  } else {
    if (now - lastStepMicros >= stepPulseWidthMicros) {
      digitalWrite(STEP_PIN, LOW);
      stepPinState = LOW;
      lastStepMicros = now;
    }
  }
}

void setup() {
  Serial.begin(115200);

  ESP32PWM::allocateTimer(0);
  ESP32PWM::allocateTimer(1);
  ESP32PWM::allocateTimer(2);
  ESP32PWM::allocateTimer(3);

  servo1.setPeriodHertz(50);
  servo2.setPeriodHertz(50);
  servo3.setPeriodHertz(50);
  servo4.setPeriodHertz(50);
  servo5.setPeriodHertz(50);
  servo6.setPeriodHertz(50);

  servo1.attach(servoPin1, 1000, 2000);
  servo2.attach(servoPin2, 1000, 2000);
  servo3.attach(servoPin3, 1000, 2000);
  servo4.attach(servoPin4, 1000, 2000);
  servo5.attach(servoPin5, 1000, 2000);
  servo6.attach(servoPin6, 1000, 2000);

  setMirroredJoint(servo1, servo2, 90);
  setMirroredJoint(servo3, servo4, 90);
  setMirroredJoint(servo5, servo6, 90);

  pinMode(STEP_PIN, OUTPUT);
  pinMode(DIR_PIN, OUTPUT);
  digitalWrite(STEP_PIN, LOW);
  digitalWrite(DIR_PIN, HIGH);

  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid, password);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("Open browser at: http://");
  Serial.println(IP);

  server.on("/", handleRoot);

  server.on("/joint1", handleJoint1);
  server.on("/joint2", handleJoint2);
  server.on("/joint3", handleJoint3);

  server.on("/step", handleStepper);

  server.begin();
  Serial.println("Web server started");
}

void loop() {
  server.handleClient();
  runStepper();
}

This is the final code that i used in my final project. This is what the inteface will look like.