End of the semester and end of my prototyping journey with Tap in the field of Calm Technology for now. After finishing a polished interface to control Tap, I created an explanatory video about the process of prototyping on Tap and the final working features, which you can watch below. Overall it was a fun experience to dive into this prototyping process and learn a thousand little things along the way, from running stepper motors to writing code for Arduino and that failure is part of the process. Now that it is all done, I would like to thank ChatGPT for helping me, a non-programmer, to code and DeepLWrite for turning my quickly scribbled blog post scripts into easily readable blog posts. Making this whole project possible.
Since I had scripted the different gestures I wanted to have for Tap in Arduino, I could now combine all the gestures into one script. I then connected the Wemos board that controls Tap to the wifi so that I could trigger each of these gestures wirelessly from my laptop via a simple interface.
Interface
To create the interface, I used Max 8 to build a simple patch to send OSC messages to my Wemos board. The patch consists of a few buttons that trigger messages with a value between 0 & 3, which are then sent as an OSC message via UDP to trigger different parts of the Arduino script.
Code
I combined all the different gesture scripts into one Arduino script that can receive OSC messages via UDP. These messages set the value for a variable which is then queried in the loop of the script to start the desired gestures. After the gestures have finished, the variable is reset to put Tap back into a suspended state.
#include <AccelStepper.h>
#include <ESP8266WiFi.h> // The Library for WIFI
#include <WiFiUdp.h> // Library for UDP
#include <OSCMessage.h> //Library for OSC
#define motorInterfaceType 1
// Define the stepper motor and the pins that is connected to // (STEP, DIR)
AccelStepper stepper1(motorInterfaceType, D5, D6);
AccelStepper stepper2(motorInterfaceType, D7, D8);
// Set the target positions for both steppers
int PositionDown = 0;
int PositionUp = 0;
// State variable to keep track of movement sequence
int state1 = 0;
int state2 = 0;
// For triggering movements
int mode = 0;
WiFiUDP Udp;
const char* ssid = "************";
const char* password = "************";
const IPAddress dstIp(192,168,1,129);
const unsigned int dstPort = 7250; // Destination OSC
const unsigned int localPort = 7300; // Reciving OSC
////////////////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
// Settings for Motor 1
stepper1.setMaxSpeed(1000);
stepper1.setAcceleration(500);
stepper1.setCurrentPosition(0);
// Settings for Motor 2
stepper2.setMaxSpeed(1000);
stepper2.setAcceleration(500);
stepper2.setCurrentPosition(0);
// Connecting to WIFI
Serial.print("Connecting WiFi ");
// Prevent need for powercyle after upload.
WiFi.disconnect();
// Use DHCP to connect and obtain IP Address.
WiFi.mode(WIFI_STA);
WiFi.begin(ssid,password);
// Wait until we have connected to the WiFi AP.
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("Done!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Udp.begin(localPort);
}
////////////////////////////////////////////////////////////////////////////////////
void loop() {
handleOSC();
if (mode == 0) {
state1 = 0;
state2 = 0;
}
if (mode == 1) {
Wave1();
Wave2();
}
if (mode == 2) {
Knock1();
}
if (mode == 3) {
Tap1();
Tap2();
}
}
////////////////////////////////////////////////////////////////////////////////////
void Wave1() {
switch (state1) {
case 0:
stepper1.setAcceleration(200);
PositionDown = 40;
stepper1.moveTo(PositionDown);
state1 = 1;
break;
case 1:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = -40;
stepper1.moveTo(PositionDown);
state1 = 2;
}
break;
case 2:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = 20;
stepper1.moveTo(PositionDown);
state1 = 3;
}
break;
case 3:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = -20;
stepper1.moveTo(PositionDown);
state1 = 4;
}
break;
case 4:
stepper1.setAcceleration(100);
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = 0;
stepper1.moveTo(PositionDown);
state1 = 5;
}
break;
case 5:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
}
break;
}
}
void Wave2() {
if (state1 >= 4){
switch (state2) {
case 0:
stepper2.setAcceleration(275);
PositionUp = 25;
stepper2.moveTo(PositionUp);
state2 = 1;
break;
case 1:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = -25;
stepper2.moveTo(PositionUp);
state2 = 2;
}
break;
case 2:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 15;
stepper2.moveTo(PositionUp);
state2 = 3;
}
break;
case 3:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = -15;
stepper2.moveTo(PositionUp);
state2 = 4;
}
break;
case 4:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 15;
stepper2.moveTo(PositionUp);
state2 = 5;
}
break;
case 5:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = -15;
stepper2.moveTo(PositionUp);
state2 = 6;
}
break;
case 6:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 0;
stepper2.moveTo(PositionUp);
state2 = 7;
}
break;
case 7:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
mode = 0;
}
break;
}
}
}
void Knock1() {
switch (state1) {
case 0:
stepper2.setAcceleration(400);
PositionUp = -20;
stepper2.moveTo(PositionUp);
state1 = 1;
break;
case 1:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 80;
stepper2.moveTo(PositionUp);
state1 = 2;
}
break;
case 2:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 60;
stepper2.moveTo(PositionUp);
state1 = 3;
}
break;
case 3:
stepper2.setAcceleration(800);
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 80;
stepper2.moveTo(PositionUp);
state1 = 4;
}
break;
case 4:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 0;
stepper2.moveTo(PositionUp);
state1 = 5;
}
break;
case 5:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
mode = 0;
}
break;
}
}
void Tap1() {
if (state2 >= 2){
switch (state1) {
case 0:
stepper1.setAcceleration(300);
PositionDown = -20;
stepper1.moveTo(PositionDown);
state1 = 1;
break;
case 1:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = 5;
stepper1.moveTo(PositionDown);
state1 = 2;
}
break;
case 2:
stepper1.setAcceleration(600);
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = -20;
stepper1.moveTo(PositionDown);
state1 = 3;
}
break;
case 3:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = 0;
stepper1.moveTo(PositionDown);
state1 = 4;
}
break;
case 4:
stepper1.setAcceleration(100);
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
}
break;
}
}
}
void Tap2() {
switch (state2) {
case 0:
stepper2.setAcceleration(400);
PositionUp = 50;
stepper2.moveTo(PositionUp);
state2 = 1;
break;
case 1:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 0;
stepper2.moveTo(PositionUp);
state2 = 2;
}
break;
case 2:
if (state1 >= 4) {
stepper2.setAcceleration(200);
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
mode = 0;
}
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////////
void handleOSC() {
OSCMessage msg("/Mode");
int size = Udp.parsePacket();
if (size > 0) {
while (size--) {
msg.fill(Udp.read());
}
if (!msg.hasError()) {
msg.dispatch("/Mode", Activating);
}
}
}
void Activating(OSCMessage &msg) {
if (msg.isInt(0)) {
int receivedMode = msg.getInt(0);
mode = receivedMode;
Serial.println("Received Mode Command");
Serial.println(mode);
}
}
outcome
After finishing the interface and the now complete Arduino script, I tested them together. With my laptop set up as a touch interface running Max 8 and the Wemos board running Tap as usual. All went quite smoothly and after some tweaking everything is now running stable. You can see the result below.
With this, my prototype for this semester’s Design & Research project is almost finished. The last step will be to make the interface a bit cleaner and more intuitive, and then I will make a video explaining my prototype Tap and how it works.
With my new prototype set up, I was ready to start scripting the different gestures for Tap. I decided to make three different gestures for now: the wave, the tap and the knock. The wave for Tap to say hello or to acknowledge a new command. The tap as a way of interacting with other objects and bringing them into the user’s focus. And finally, the knock as a more analogue way of notifying the user of, for example, a timer or a specific event.
Waving
The first gesture is the wave gesture. It is intended to let the user know that Tap is active or has accepted the new command or task. In this movement, the top rotates from left to right and the tapper waves from one side to the other a few times.
#include <AccelStepper.h>
#define motorInterfaceType 1
// Define the stepper motor and the pins that is connected to // (STEP, DIR)
AccelStepper stepper1(motorInterfaceType, D5, D6);
AccelStepper stepper2(motorInterfaceType, D7, D8);
// Set the target positions for both steppers
int PositionDown = 0;
int PositionUp = 0;
// State variable to keep track of movement sequence
int state1 = 0;
int state2 = 0;
////////////////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
// Settings for Motor 1
stepper1.setMaxSpeed(1000);
stepper1.setAcceleration(500);
stepper1.setCurrentPosition(0);
// Settings for Motor 2
stepper2.setMaxSpeed(1000);
stepper2.setAcceleration(500);
stepper2.setCurrentPosition(0);
}
////////////////////////////////////////////////////////////////////////////////////
void loop() {
Wave1();
Wave2();
}
////////////////////////////////////////////////////////////////////////////////////
void Wave1() {
switch (state1) {
case 0:
stepper1.setAcceleration(200);
PositionDown = 40;
stepper1.moveTo(PositionDown);
state1 = 1;
break;
case 1:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = -40;
stepper1.moveTo(PositionDown);
state1 = 2;
}
break;
case 2:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = 20;
stepper1.moveTo(PositionDown);
state1 = 3;
}
break;
case 3:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = -20;
stepper1.moveTo(PositionDown);
state1 = 4;
}
break;
case 4:
stepper1.setAcceleration(100);
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = 0;
stepper1.moveTo(PositionDown);
state1 = 5;
}
break;
case 5:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
}
break;
}
}
void Wave2() {
if (state1 >= 4){
switch (state2) {
case 0:
stepper2.setAcceleration(275);
PositionUp = 25;
stepper2.moveTo(PositionUp);
state2 = 1;
break;
case 1:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = -25;
stepper2.moveTo(PositionUp);
state2 = 2;
}
break;
case 2:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 15;
stepper2.moveTo(PositionUp);
state2 = 3;
}
break;
case 3:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = -15;
stepper2.moveTo(PositionUp);
state2 = 4;
}
break;
case 4:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 15;
stepper2.moveTo(PositionUp);
state2 = 5;
}
break;
case 5:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = -15;
stepper2.moveTo(PositionUp);
state2 = 6;
}
break;
case 6:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 0;
stepper2.moveTo(PositionUp);
state2 = 7;
}
break;
case 7:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
}
break;
}
}
}
Tapping
The second gesture is the tap gesture. It is intended as an object-based reminder for things like drinking water or airing out your room. It can also be used in combination with objects to create a different sound for timers and reminders than knocking on the ground. In this gesture, the tapper moves down a quarter turn and the entire base then rotates the tapper twice against the object before returning to the default position.
#include <AccelStepper.h>
#define motorInterfaceType 1
// Define the stepper motor and the pins that is connected to // (STEP, DIR)
AccelStepper stepper1(motorInterfaceType, D5, D6);
AccelStepper stepper2(motorInterfaceType, D7, D8);
// Set the target positions for both steppers
int PositionDown = 0;
int PositionUp = 0;
// State variable to keep track of movement sequence
int state1 = 0;
int state2 = 0;
////////////////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
// Settings for Motor 1
stepper1.setMaxSpeed(1000);
stepper1.setAcceleration(500);
stepper1.setCurrentPosition(0);
// Settings for Motor 2
stepper2.setMaxSpeed(1000);
stepper2.setAcceleration(500);
stepper2.setCurrentPosition(0);
}
////////////////////////////////////////////////////////////////////////////////////
void loop() {
Tap1();
Tap2();
}
////////////////////////////////////////////////////////////////////////////////////
void Tap1() {
if (state2 >= 2){
switch (state1) {
case 0:
stepper1.setAcceleration(300);
PositionDown = -20;
stepper1.moveTo(PositionDown);
state1 = 1;
break;
case 1:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = 5;
stepper1.moveTo(PositionDown);
state1 = 2;
}
break;
case 2:
stepper1.setAcceleration(600);
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = -20;
stepper1.moveTo(PositionDown);
state1 = 3;
}
break;
case 3:
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
PositionDown = 0;
stepper1.moveTo(PositionDown);
state1 = 4;
}
break;
case 4:
stepper1.setAcceleration(100);
if (stepper1.distanceToGo() != 0) {
stepper1.run();
} else {
stepper1.stop();
}
break;
}
}
}
void Tap2() {
switch (state2) {
case 0:
stepper2.setAcceleration(400);
PositionUp = 50;
stepper2.moveTo(PositionUp);
state2 = 1;
break;
case 1:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 0;
stepper2.moveTo(PositionUp);
state2 = 2;
}
break;
case 2:
if (state1 >= 4) {
stepper2.setAcceleration(200);
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
}
}
break;
}
}
Knocking
The third gesture is the knock gesture. It is intended as an analogue notification for timers, reminders or other events. In this gesture, only the tapper itself moves. It moves slightly backwards and then knocks twice on the ground before returning to its normal position.
#include <AccelStepper.h>
#define motorInterfaceType 1
// Define the stepper motor and the pins that is connected to // (STEP, DIR)
AccelStepper stepper1(motorInterfaceType, D5, D6);
AccelStepper stepper2(motorInterfaceType, D7, D8);
// Set the target positions for both steppers
int PositionDown = 0;
int PositionUp = 0;
// State variable to keep track of movement sequence
int state1 = 0;
int state2 = 0;
////////////////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
// Settings for Motor 1
stepper1.setMaxSpeed(1000);
stepper1.setAcceleration(500);
stepper1.setCurrentPosition(0);
// Settings for Motor 2
stepper2.setMaxSpeed(1000);
stepper2.setAcceleration(500);
stepper2.setCurrentPosition(0);
}
////////////////////////////////////////////////////////////////////////////////////
void loop() {
Knock1();
}
////////////////////////////////////////////////////////////////////////////////////
void Knock1() {
switch (state1) {
case 0:
stepper2.setAcceleration(400);
PositionUp = -20;
stepper2.moveTo(PositionUp);
state1 = 1;
break;
case 1:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 80;
stepper2.moveTo(PositionUp);
state1 = 2;
}
break;
case 2:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 60;
stepper2.moveTo(PositionUp);
state1 = 3;
}
break;
case 3:
stepper2.setAcceleration(800);
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 80;
stepper2.moveTo(PositionUp);
state1 = 4;
}
break;
case 4:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
PositionUp = 0;
stepper2.moveTo(PositionUp);
state1 = 5;
}
break;
case 5:
if (stepper2.distanceToGo() != 0) {
stepper2.run();
} else {
stepper2.stop();
}
break;
}
}
The scripting and testing of the gestures went almost smoothly with the help of ChatGPT for the coding part. Except for one problem where the lower motor started to overheat and slowly melt my 3D print. Luckily I caught it before it did any irreversible damage. The problem ended up being that I set the current flow in the motor controller for the lower motor too high, which was quickly fixed and everything went smoothly again. From here I will now start to implement all the different gestures into one script and add the functionality of triggering the different gestures via OSC messages so that I can then create a dedicated interface to control Tap.
After I started programming specific gestures for Tap, the cardboard and hot glue construction began to fail. This meant that the top motor was slowly falling backwards and I couldn’t operate the prototype in this state. So I decided to put the gesture work on hold until the next step and build a more robust prototype for it.
CAD
I started by measuring the two motors and getting the dimensions of my paper prototype. I then scribbled a few quick and dirty drawings of some of the possible solutions. When I was happy with my solution on paper, how the construction should look and that the motors, cables and everything had its place, I moved on to 3D modelling. I started by modelling the two motors to have a reference point and then went on to model the base to hold the top motor and a tapper that would fit perfectly on the motor’s axis. For the time being, I decided to build only the upper half of the tap, as that was all that was needed for now.
3D Print
I then loaded my finished 3D model into my FDM printer and printed the base for the upper motor in neutral white and the tapper in dark blue. Both colours stayed in the scheme of my already built power supply to create a certain consistency. The whole printing process took about 5 1/2 hours and then the parts were ready for assembly.
Assembly
Once I had disassembled the cardboard and the hot glue build and had freed up my two motors, I checked to see if the two new parts would fit on their axles. Both fit perfectly and the assembly was quick and easy. Which shows that measuring twice as I did this time has its advantages, unlike when I build the case for my power supply. To finish, I glued the lid of the base and the upper motor inside the base with Pattafix, which is a strong enough adhesive for now and makes dismantling or replacing parts easier.
Testing
With the newly completed version of the prototype, I then did some live testing to see if it would hold up in motion. For the test, I scripted a short moving gesture for Tap, based on the script from the last blog post. As you can see in the video below, everything stayed in place and the setup worked even stabler and smoother than the cardboard version.
With Tap set up like this, I can now move on to the next steps, as mentioned in my last blog post, to start scripting different gestures and behaviours for Tap. Giving him the ability to communicate by moving his tapper and his body.
As mentioned in my last blog post, this time I decided to build a rough working prototype consisting of the two motors and some cardboard. To test if I could get the basic movements I wanted out of it.
Assembly
The first step was to build a cardboard base to act as a platform for the second motor. This base was then hot glued to the axle of the first or bottom motor. On top of the base, the second or top motor was attached to the base with its axle in a horizontal position rather than vertical. This arrangement allows the bottom motor to rotate the top motor around its own axis and the top motor to rotate the tapper.
The second step was to attach a temporary arrow-shaped tap to the upper motor shaft with some hot glue. The arrow shape was chosen so that it would be easier to see if it was turning in the right direction and the right amount. With this setup in place, I could now start writing Arduino code to control the two motors at the same time and give them a chosen motion.
Code
After trying out the sample scripts for rotating both motors from the earlier steps of my project. I started to create a basic script that would be my starting point for getting all the movements I wanted out of Tap. The idea was to make both motors rotate to a certain point and back individually at the same time, with as much control over speed and acceleration as possible. This would then be my starting point for scripting more advanced movements and gestures later in the project. After a lot of mistrails where my board kept crashing or the motors would only rotate one after the other and not at the same time, and some discussion with ChatGPT and the help of tutorials, I ended up with the script below which achieves exactly what I wanted and needed.
#include <AccelStepper.h>
#define motorInterfaceType 1
// Define the stepper motor and the pins that is connected to // (STEP, DIR)
AccelStepper stepper1(motorInterfaceType, D5, D6);
AccelStepper stepper2(motorInterfaceType, D7, D8);
// Set the target positions for both steppers
int targetPosition1 = 200;
int targetPosition2 = 25;
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
// Settings for Motor 1
stepper1.setMaxSpeed(250);
stepper1.setAcceleration(150);
stepper1.setCurrentPosition(0);
// Settings for Motor 2
stepper2.setMaxSpeed(1000);
stepper2.setAcceleration(500);
stepper2.setCurrentPosition(0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void loop() {
// Move stepper1 towards their target positions
stepper1.moveTo(targetPosition1);
stepper1.run();
// Move stepper2 towards their target positions
stepper2.moveTo(targetPosition2);
stepper2.run();
// Check if stepper1 have reached their target positions
if (stepper1.distanceToGo() == 0) {
// Update target positions for next movement
targetPosition1 = (targetPosition1 == 0) ? 200 : 0;
}
// Check if stepper2 have reached their target positions
if (stepper2.distanceToGo() == 0) {
// Update target positions for next movement
targetPosition2 = (targetPosition2 == -25) ? 25 : -25;
}
}
Outcome
Now that the rotation and base setup is working, the next step will be to script a more defined movement or gesture for the motors to perform, and then trigger it with an OSC message from an interface on my laptop.
This week I tackled the problem of the power supply and decided to make a more polished prototype for it, as I do not expect any changes to this part of the project for the time being. This means that I want to enclose all the functional parts in housings and make it look like a normal power supply as used in household or consumer electronics.
Set-Up
To start with, I ordered a 240V to 12V transformer with a 2.5A output, which will give me a bit of headroom and also power the Wemos board. The rest of the set up consists of a textile power cord, some Arduinio wires, a plug, some shrink sleeves and my custom printed case for the transformer. As you can see in the picture below, there are two versions of my custom printed case. I would like to say it is an iteration, but it is because of a measurement error on my part and the first version is not usable. Which just goes to show that the old carpenter’s adage „measure twice, cut once“ also applies to 3D printing in rapid prototyping.
Assembly
I started by connecting the textile power cord to my custom case, and while the glue was drying on that end, I attached the plug to one side and the arduino wires for use in the breadboard to the other. I started by attaching the textile power cord to my custom housing, and while the glue was drying on that end, I attached the plug to one side and the arduino wires for use in the breadboard to the other. When that was done, I connected both ends of the cable to the transformer and fixed the transformer in its custom housing. The final result with the build together housing is shown in the picture below.
Now that the power supply is finished, the setup for controlling both motors at full power is complete. The next step will be to build a rough functional prototype capable of generating the movements needed for Tap.
As a next step, I have ordered a second stepper motor for my more advanced prototype. This time, because I wanted to keep the size of the tap small, I ordered a Nema 17 pancake stepper motor. The difference is the height of the motor block, which is much thinner in this motor. After receiving the motor, I set up the second motor in the same way as the first and connected the new motor, a second motor controller and my Wemos board all together on the breadboard.
Two motor Mock-Up
Once everything was set up, I adapted the code running on my Wemos to control two motors instead of one, and tested two motors running in the same way as before the single motor. First in direction and interval and then in direction, acceleration and interval.
Results
After some problems with changing the code to allow for both motors to be driven at the same time, the set up was running quite smoothly. The only problem I ran into was that each of the motors required 1A at full power and my current temporary power source (a simple house-hold 12V plug-in transformer) only supplied 1.5A. This meant that as soon as I started playing around with faster speeds or resistance, one of the motors would miss steps, stop or just fail for a moment. So before I can really start building the first working prototype, I need to fix this problem. So my next step will be to build a custom power source that will provide enough power to run both motors at full speed.
For my first technical mock-up I needed a controller, a motor diver and a motor itself. For the controller I am using an Arduino based board and the motor driver will depend on the motor I am using. The motor needs to be able to rotate in both directions, only move a certain distance and hold a certain load when stationary. As I’ve never worked with electric motors before, I did some quick research to see which type of motor would best suit my needs. I came up with two options: a servo motor or a stepper motor. I chose the stepper motor for my project because it offers greater accuracy in movement, full torque at standstill and is more reliable with a longer life.1
Set-Up
For the first setup I chose a Wemos D1 mini board, which I have used in various projects, an A4988 driver module for the stepper motor, a MINEBA stepper motor with 200 steps and a step angle of 1.8°, a Breadboard and a 12v plug-in power supply. The setup is influenced by the tutorial I found online on how to control stepper motors with an Arduino based board:
This is also the tutorial I followed for my first steps.
Technical Mock-Up
Once everything was set up, I was able to control the stepper motor with the sample codes given in the tutorial in terms of interval, amount and direction of rotation, and additionally the acceleration of the movement, as shown in the videos below. This will be the basis for my upcoming first prototype of tap. The prototype will consist of two stepper motors mounted on top of each other and will already mimic the full functionality of tap.
About the : „Brushing Interface- DIY multi-touch interface for expressive gestural performance“ from Jaehoon Choi
In this excursion into sound design, I’ll be exploring a paper from the International Conference on New Interfaces for Musical Expression (NIME23). The paper is about a new type of interface that transforms brush movements into electronic sounds, creating a method for creating electronic music in a natural and expressive way.
Summary
The paper presents the Brushing Interface, a DIY multi-touch interface designed to translate brushing gestures into expressive musical performances. It consists of 216 handmade force-sensitive resistive sensors and 8 piezo microphones for precise gesture tracking and sound production. The interface combines a unique gesture mapping strategy with continuous gesture tracking, enabling flexible and expressive performances. The hardware system, including the sensors, was built inexpensively and the software was developed using Max7 for real-time sound processing and gesture mapping. The interface offers four performative approaches: using the standard brush sound, applying audio effects, real-time audio synthesis and changing presets. A composition called „Drifting“ demonstrates the interface’s capabilities. Overall, the Brushing Interface expands the possibilities of gestural expression in musical performance, offering richness and versatility.1
Commentary
As an interaction design student, I find Jaehoon Choi’s work on the brushing interface fascinating. The concept of transforming brushing gestures into a true musical/sonic performance opens up new avenues for exploring embodied interaction and expressive communication through technology. The DIY approach to building the hardware system is in line with the interaction design idea of iterating and testing with self-created prototypes before scaling up to finished, industrialised products. It also emphasises hands-on experimentation and customisation, which can empower designers and users alike to create personalised and meaningful experiences.
One aspect of the paper that stands out is the integration of multi-dimensional parameter mapping and continuous gesture tracking, enabling an expressive performance that can be configured in a variety of ways. This emphasis on flexibility and adaptability is very much in line with the principles of interaction design, which prioritise designing for different user needs and contexts. The Brushing interface is an example of how technology can be designed to support nuanced and intuitive forms of interaction, encouraging deeper engagement and creative expression.
However, while the paper provides a comprehensive overview of the design and implementation of the brush interface, there are some areas that could be further elaborated or addressed. For example, while the DIY approach is commendable for its affordability and accessibility, there may be limitations in terms of scalability, reliability and reproducibility, especially when considering larger scale applications or commercialisation. In addition, while the paper touches on the potential for improvisational performance, further research is needed into how the interface can support more planned & structured inputs or outputs, and how easy it is to learn and repeat to produce the same output again.
In terms of relevance to Calm Technologies, the Brushing Interface offers an interesting perspective on how technology can be seamlessly integrated into our daily lives in a subtle and non-intrusive way. By utilising the tactile and familiar action of brushing, the interface invites users to engage in a calming and almost natural interaction.
In conclusion, the Brushing interface represents an innovative fusion of art, design and technology, with implications for both musical performance and interaction design. While there are areas for further refinement and exploration, the work serves as a valuable contribution to the field, inspiring future research and creative endeavours in the realm of expressive gestural interfaces for musical performance, as well as Calm interfaces for our everyday interactions with the digital ecosphere.
To get started with prototyping my own personal Tap and exploring what it could actually be used for, I went to the Google Projects description website and looked at the data they have published to share with everyone. There is a short video and a description of the intended use and functions of Tap. These are tapping, waving, pointing and rotating. These functions are then used to visualise notifications, with the addition of more intense movement for more important notifications. They also provided a downloadable folder with the CAD data of the outer shell of each of the objects and a short guide on how to make a quick prototype of the air object. So I decided to analyse what would be needed for the functions of tap from the videos and the structure and size from the CAD data provided.1
Functionality
The core function of Tap is tapping. The secondary function is to rotate itself to create more advanced tapping and other movements. For tapping itself, there is the small stick at the front of the upper cylinder of the object, which is able to rotate around an axis to hit the surface the object is standing on on both sides of the rotations. For the rotations, the object is divided into two equal cylinders. The lower one acts as a foot or stand and the upper one rotates around the foot and holds the stick. In order to achieve this functionality for my prototype, apart from the housing itself, which is mostly given. I will need two sperate motors, a microcontroller and a power source of some kind, either a battery or a traditional cable, to replicate these functions.
Structure
The structure of the Tap is quite simple, consisting mainly of two different parts. The stick for tapping itself on the front of it and a main body consisting of two equally sized cylinders on top of each other. All in all, in terms of size and shape, it can be compared to a soda can for the main body, with a pen-sized stick on the front to create the tapping sound.
Paper Mock-Up
Looking at a 3D model on a 2D screen and reading the dimensions of the model doesn’t give you a sense of the object itself or its size. So I decided to make a paper mock-up of the dimensions provided by Google to experience them first hand.
After finishing and inspecting the paper mock-up, I was surprised by how small and delicate it felt, in contrast to the image I had in my head from looking at the visualisations of the project on the website. As Calm Technology tries to blend into the background and only draw attention to itself when necessary, this seemed quite fitting. This means that I will try to make my version of Tap no bigger than the original, if possible from a functional and aesthetic point of view. Maybe even smaller or slimmer, if that is within the parameters of the object I just mentioned.
Next Step
My next step will be to create a mock-up for the functions of Tap. This means researching motors, how to control them with my skills and which motors to use to achieve the desired function, and then actually trying to make them work in a small technical mock-up, consisting of just one motor for now.