_____       _             __                                               _ _           _   _             
 |_   _|     | |           / _|                 ___        /\               | (_)         | | (_)            
   | |  _ __ | |_ ___ _ __| |_ __ _  ___ ___   ( _ )      /  \   _ __  _ __ | |_  ___ __ _| |_ _  ___  _ __  
   | | | '_ \| __/ _ \ '__|  _/ _` |/ __/ _ \  / _ \/\   / /\ \ | '_ \| '_ \| | |/ __/ _` | __| |/ _ \| '_ \ 
  _| |_| | | | ||  __/ |  | || (_| | (_|  __/ | (_>  <  / ____ \| |_) | |_) | | | (_| (_| | |_| | (_) | | | |
 |_____|_| |_|\__\___|_|  |_| \__,_|\___\___|  \___/\/ /_/    \_\ .__/| .__/|_|_|\___\__,_|\__|_|\___/|_| |_|
                                                                | |   | |                                    
                                                                |_|   |_|                                    

  

Week 13 — Interface & Application Programming

Building a Web-Based Control Panel for a Kinetic Object

This week I focused on interface and application programming, using my final project, Glowmorph, as both the motivation and the testbed. My goal was to create a lightweight, web-based control panel that could communicate directly with Glowmorph over USB serial.

Rather than relying on prebuilt interfaces or opaque software, I wanted to understand what it means to build a tiny application from scratch—one that translates user input into physical behavior in real time.

The interface needed to:

Because my final project uses a Pico W, I initially imagined controlling the system over Wi-Fi. However, after early instability and power issues, I decided to stay focused on Web Serial and HTML sliders first. Removing unnecessary complexity made it easier to isolate what was actually going wrong.

Getting Started (and Admitting My Limits)

My programming experience is limited, and time was even more constrained. Glowmorph is mechanically complex, and I didn’t want to spend my final weeks debugging avoidable software mistakes. I treated this assignment as a proof of concept: could I reliably command a kinetic system from a browser without crashes, brownouts, or unsafe motion?

To accelerate the process, I consulted ChatGPT extensively to help scaffold both the HTML interface and the Arduino logic. Rather than blindly copying code, I treated the interaction as a collaborative debugging exercise—testing, breaking, observing physical behavior, and iterating.

The full conversation is documented here: ChatGPT session link

HTML Side: A Minimal Control Panel

I started with a very simple HTML page that runs locally in Chrome and uses the Web Serial API. The interface is intentionally bare: a connect button, a few mode buttons, and two sliders.

All commands are sent as plain-text strings (for example, CONT, FWD, or SPEED 1.5). Keeping the protocol readable turned out to be crucial for debugging once things began behaving unpredictably.

Glowmorph Control Panel

HTML showing basic motor control: Speed, amplitude, and some controls over motor behavior.

Serial port connection implemented through HTML

HTML (Web Serial Interface)



Speed (deg/tick)
Amplitude (max angle)

Arduino Side: Code Meets Physics

Yikes! That's a lot of motors!

My initial attempt tried to drive all 11 motors in Glowmorph simultaneously. Although the code compiled, the physical system repeatedly browned out, seized, or entered unstable oscillations.

Rather than treating this as a purely software problem, I scaled the system back to one motor, and later to three motors. This revealed an important distinction between instant position jumps (which caused stalling) and smoothly slewed motion (which felt calm and controlled).

The key fix was introducing a target angle and interpolating toward it at a controlled rate. Button presses became gentle nudges rather than hard commands.

Arduino (Safe, Slewed Motion Control)

#include #include #include Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40, Wire1); #define SERVO_MIN 150 #define SERVO_MAX 600 #define ANGLE_MIN 25 #define ANGLE_MAX 220 #define SERVO_CH 0 float amplitude = ANGLE_MAX; float speedDegPerTick = 1.0; float jogStep = 2.0; bool running = false; bool dirForward = true; float angle = ANGLE_MIN; float target = ANGLE_MIN; int angleToPulse(float angleDeg) { angleDeg = constrain(angleDeg, 0, 270); return SERVO_MIN + (angleDeg / 270.0f) * (SERVO_MAX - SERVO_MIN); } void applyAngle(float a) { a = constrain(a, ANGLE_MIN, ANGLE_MAX); pwm.setPWM(SERVO_CH, 0, angleToPulse(a)); } void handleSerialCommand(String cmd) { cmd.trim(); if (cmd == "CONT") running = true; else if (cmd == "PAUSE") running = false; else if (cmd == "FWD") target = min(target + jogStep, amplitude); else if (cmd == "BWD") target = max(target - jogStep, (float)ANGLE_MIN); else if (cmd.startsWith("AMP")) amplitude = constrain(cmd.substring(4).toFloat(), ANGLE_MIN, ANGLE_MAX); else if (cmd.startsWith("SPEED")) speedDegPerTick = constrain(cmd.substring(6).toFloat(), 0.1f, 10.0f); } void setup() { Serial.begin(115200); Wire1.setSDA(26); Wire1.setSCL(27); Wire1.begin(); pwm.begin(); pwm.setPWMFreq(50); applyAngle(angle); } void loop() { if (Serial.available()) handleSerialCommand(Serial.readStringUntil('\n')); if (running) { target = dirForward ? amplitude : ANGLE_MIN; if (fabs(angle - target) < 0.01f) dirForward = !dirForward; } float delta = target - angle; if (fabs(delta) > 0.001f) { angle += constrain(delta, -speedDegPerTick, speedDegPerTick); applyAngle(angle); } delay(20); }

Takeaways

This week gave me confidence that Glowmorph’s behavior can be shaped intentionally. The control panel is no longer just a test tool, bt rather it’s becoming part of the project’s identity.

Back to Home