Skip to content

Week 15, Interface Programming

Assignment

Group assignment

  • Compare as many tool options as possible.
  • Document your work on the group work page and reflect on your individual page what you learned.

See group page.

Individual assignment

  • Write an application for the embedded board that you made that interfaces a user with an input and/or output device(s)

Individual Work

Control Board

My final work project will rely upon a stepper motor to control card distribution. I have fabricated boards which control an Nema 17 stepper motor driven by an ESP32 microprocessor. For Interface Programming, I will set up a Wifi interface to control the stepper motor.

The Nema 17 motor needs higher voltage than can be provided from the microprocessor, so I have made a breakout board which outputs 12V. This is based on the QC Hack Power Board. I have my own re-design for this. Notably, I added pin headers so I could place that in a socket.

Schematic and PCB for the QC power hack board

The KiCad files for this QC hack board.

To include in other boards, I made a footprint in KiCad for placement of a socket.

I also made a board which has a socket for a Xiao microprocessor, a socket for the QC hack, and a socket for an A4988 motor controller. The A4988 is used to control the motor. The board also has a 4 pin connector for a Nema 17 step motor. In addition, the board has some additional features for the final project, but are not used for this step now.

My motor control board is documented in the final project pages.

PCB for the motor control

The KiCad files for the control board.

These were fabricated on the Roland MX-20 mill, and the components were soldered on.

Control board with A4988 and power board connected to stepper motor

Programming

Since I wanted to control via WiFi, I followed an online tutorial at Last Minute Engineers. This has some basic code which I modified to fix my pin numbers for the LED.

I compiled this with the Arduino IDE and uploaded it to the ESP. I then connnected to the ESP32 network, and browsed to 192.168.1.1. This gave a web page where I could turn the LED off and on.

The other key step is controlling the motor. The following code turns the motor repeatedly backwards and forwards one cycle.

// Define pin connections & motor's steps per revolution

// Pins for original board
// Dir: D8 (GPIO7), Step: D7 (GPIO44)
// Steps per revolution: 3200 (due to microstepping)
const int dirPin = 7;
const int stepPin = 44;
const int stepsPerRevolution = 3200;

void setup()
{
    // Declare pins as Outputs
    pinMode(stepPin, OUTPUT);
    pinMode(dirPin, OUTPUT);
}
void loop()
{
    // Set motor direction clockwise
    digitalWrite(dirPin, HIGH);

    // Spin motor slowly
    for(int x = 0; x < stepsPerRevolution; x++)
    {
        digitalWrite(stepPin, HIGH);
        delayMicroseconds(200);
        digitalWrite(stepPin, LOW);
        delayMicroseconds(200);
    }
    delay(1000); // Wait a second

    // Set motor direction counterclockwise
    digitalWrite(dirPin, LOW);

    // Spin motor quickly
    for(int x = 0; x < stepsPerRevolution; x++)
    {
        digitalWrite(stepPin, HIGH);
        delayMicroseconds(100);
        digitalWrite(stepPin, LOW);
        delayMicroseconds(100);
    }
    delay(1000); // Wait a second
}

These are the two key functions I want to control. Basically, I will take the logic to control the motors (without repeating cycles), and use that in the logic for the web server control.

All together

My real world example is a meeting monitor. I work from home, and my daughter often bursts into my office when I am in a meeting. I’d like to control the motor which holds a sign indicating whether I am in a meeting or free. For this logic, I will turn the motor one-half turn, and the motor has a two-sided sign which shows the current status. The following code creates an interface to change the meeting status.

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

/* Put your SSID & Password */
const char* ssid = "ESP32";  // Enter SSID here
const char* password = "12345678";    //Enter Password here

// Pins for motorcontrol
const int dirPin = 7;
const int stepPin = 44;
const int halfTurn = 1600;

/* Put IP Address details */
IPAddress local_ip(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

WebServer server(80);

bool meetingSTATUS = LOW;

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

  // Declare motor pins as Outputs
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);

  WiFi.softAP(ssid, password);
  WiFi.softAPConfig(local_ip, gateway, subnet);
  delay(100);

  server.on("/", handle_OnConnect);


  server.on("/meeting",handle_meeting);
  server.on("/free", handle_free);

  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");
  Serial.println(WiFi.localIP());
}
void loop() {
  server.handleClient();
}

void handle_OnConnect() {
  Serial.println("Connecting");
  server.send(200, "text/html", SendHTML(meetingSTATUS));
}

void handle_meeting() {
  meetingSTATUS = HIGH;
  Serial.println("Meeting: ON");
  digitalWrite(dirPin, LOW);


    for(int x = 0; x < halfTurn; x++)
    {
        digitalWrite(stepPin, HIGH);
        delayMicroseconds(100);
        digitalWrite(stepPin, LOW);
        delayMicroseconds(100);
    }

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

void handle_free() {
  meetingSTATUS = LOW;
  Serial.println("Meeting: OFF");
    digitalWrite(dirPin, HIGH);


    for(int x = 0; x < halfTurn; x++)
    {
        digitalWrite(stepPin, HIGH);
        delayMicroseconds(100);
        digitalWrite(stepPin, LOW);
        delayMicroseconds(100);
    }
  server.send(200, "text/html", SendHTML(false));
}


void handle_NotFound() {
  server.send(404, "text/plain", "Not found");
}

String SendHTML( uint8_t meeting) {
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>Meeting Monitor</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr += ".button {display: block;width: 100px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr += ".button-on {background-color: #3498db;}\n";
  ptr += ".button-on:active {background-color: #2980b9;}\n";
  ptr += ".button-off {background-color: #34495e;}\n";
  ptr += ".button-off:active {background-color: #2c3e50;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>Meeting Monitor</h1>\n";
  ptr += "<h3>Change the meeting sign</h3>\n";

  if (meeting) {
    ptr += "<h1 style=\"color:red;\">CURRENTLY IN MEETING</h1><a class=\"button button-off\" href=\"/free\">SET TO FREE</a>\n";
  } else {
    ptr += "<h1 style=\"color:green;\">CURRENTLY FREE</h1><a class=\"button button-on\" href=\"/meeting\">SET TO MEETING</a>\n";
  }

  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}

In creating this code, I made the HTML use color and larger text than the example.

Here is a video of the set up in action. I push the button when a meeting starts, and then afterwards can mark myself as free.

Hero video

With this tool, my daughter might not burst into my office when I am in a meeting.

Application on different program

While the example above created an interface via an interface programmed into the microprocessor, stronger possibilities arise with the application program running on a separate computer or system.

To demonstrate possiblities there, I returned to the board design for the weeks covering Electronics Design and Electronics Production. The board in that work is a very simple board which has a button and and LED. The board was designed for Electronics Design week, and fabricated and tested in Electronics Production week.

While using the same track layout, a new version of the board was fabricated. There are some minor variations in how the board is being used for this work. The board also will be programmed in C++ and it will use a ESP32-S3 microprocessor. Previous work used micropython and an RP2040. Given the Xiao socket, there is easy flexibility to put a different microprocessor, and the microprocessors can be programmed in multiple ways.

Programming

To create an application on a separate computer, I relied upon the Web Serial API. This allows ability for computers to communicate to peripherals via a serial connection.

This requires a program on the ESP32, and a separate interface on the controlling computer (using HTML in this case).

For the ESP32 code, the following program in C++ was compiled and uploaded with the Arduino IDE.

uint8_t LEDpin = D0;
bool LEDstatus = LOW;

// Set up the serial connection
void setup() {
  Serial.begin(115200);
  pinMode(LEDpin, OUTPUT);
  digitalWrite(LEDpin, LOW);
  Serial.println("Ready. Type 'on' or 'off'.");
}

// Loop to monitor serial connection
// When receiving an appropriate 'on' or 'off'
// it will change the state of the LED
void loop() {
  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');
    command.trim();
    if (command == "on") {
      LEDstatus = HIGH;
      digitalWrite(LEDpin, HIGH);
      Serial.println("LED is ON");
    } else if (command == "off") {
      LEDstatus = LOW;
      digitalWrite(LEDpin, LOW);
      Serial.println("LED is OFF");
    } else {
      Serial.println("Unknown command. Use 'on' or 'off'.");
    }
  }
}

The code is available here.

For the HTML interface, the following code was used in the web browser.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Web Serial LED Control</title>
</head>
<body>
  <h1>Web Serial LED Control</h1>
  <button id="connect">Connect to ESP32</button>
  <button id="ledOn" disabled>Turn ON</button>
  <button id="ledOff" disabled>Turn OFF</button>
  <pre id="log"></pre>

  <script>
    let port, writer, reader;
    const log = document.getElementById("log");

    document.getElementById("connect").addEventListener("click", async () => {
      try {
        port = await navigator.serial.requestPort();
        await port.open({ baudRate: 115200 });

        const textDecoder = new TextDecoderStream();
        const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
        reader = textDecoder.readable.getReader();

        const textEncoder = new TextEncoderStream();
        const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
        writer = textEncoder.writable.getWriter();

        readSerialOutput();

        document.getElementById("ledOn").disabled = false;
        document.getElementById("ledOff").disabled = false;
        log.textContent += "Connected\n";
      } catch (err) {
        log.textContent += "Connection failed: " + err + "\n";
      }
    });

    document.getElementById("ledOn").addEventListener("click", () => {
      writer.write("on\n");
    });

    document.getElementById("ledOff").addEventListener("click", () => {
      writer.write("off\n");
    });

    async function readSerialOutput() {
      while (true) {
        const { value, done } = await reader.read();
        if (done) break;
        log.textContent += value;
        log.scrollTop = log.scrollHeight;
      }
    }
  </script>
</body>
</html>

This code is available here.

Running

Then the ESP32 is connected the computer via USB and the HTML loaded into the web brower. Presented with the buttons, the “connect” button initiates the serial connection. Then, the on and off button control the LED.

Hero shot: Webserial application controlling an onboard LED

Overview

While I successfully created two very simple interfaces, this only scratches the surface of what is possible. There certainly are many more sophisticated and polished interfaces possible. The group page discusses some additional possibilities.