Escher — The world’s first cloud-controlled Etch-a-Sketch

Matt Welsh
11 min readDec 11, 2019

How I built a robotic Etch-a-Sketch that can be controlled from anywhere.

For the last year, I’ve been working on a side project at home which I call Escher. It’s a robotic Etch-a-Sketch that can be controlled over the Internet from anywhere in the world. It’s made possible by the Adafruit Feather HUZZAH32 development board, and Google Firebase to provide the control between the Etch-a-Sketch and a web client. Here it is in all its glory:

It also sports an easy web interface that lets you upload images to draw, or even draw with your finger directly on the virtual Etch-a-Sketch screen, which will then get drawn on the real Etch-a-Sketch.

The Escher web interface.

While there are a bunch of Arduino and Raspberry Pi-controlled Etch-a-Sketch projects out there, I wanted to build something that anyone could walk up to and use just by visiting a web page from their phone. Thus, Escher was born. This post will walk through the design and provide instructions so you can build your own.

Quick links to resources:

Design considerations

Escher needed to be easy to use. For me, this meant a web-based UI that could be accessed from any device — phone, laptop, desktop — without requiring the user to install any new software.

Escher also needs to be accessible directly via the public Internet, not requiring the user to join a special WiFi network or (shudder) pair with a Bluetooth device.

I wanted Escher to mate with a (mostly) unmodified Etch-a-Sketch. All I require in this design is that you pop off the knobs and replace them with 3D printed custom knobs with gear teeth. The Etch-a-Sketch can be easily lifted out of its base and used normally, without requiring any belts or other physical connections.

I’m a big fan of serverless architectures, so I wanted to avoid standing up a custom web server for this project. Instead, I use Google Firebase, which allows a web client to communicate indirectly with another Internet-connected device — the Etch-a-Sketch controller, in this case.

Hardware design

Let’s start with the electronics. Escher is controlled by an Adafruit HUZZAH32 development board, which features an ESP32 processor running at 240MHz, 520KB of SRAM, 4MB of flash, and integrated 802.11b/g/n and Bluetooth. This is a great little development board that is fully supported by the Arduino programming environment.

The Adafruit HUZZAH32 — Image from https://www.adafruit.com/product/3405

Escher uses two NEMA17 stepper motors to control the knobs of the Etch-a-Sketch. These are controlled using the Adafruit Motor Featherwing board, making it easy to control each stepper motor either independently or together (as we’ll see in the software section below).

For power, the HUZZAH32 needs to be powered either via a USB or a LiPo battery connection. However, the stepper motors and motor controller board require a separate 12V power supply. For this, I designed a little board that takes in 12V, passing it through to the motor controller board, and a voltage regulator to provide 5V to the HUZZAH32 over the USB port. This isn’t strictly necessary; you can get away with two power supplies (one for the motor controller and another for the HUZZAH32), but I wanted to keep things tidy.

The custom power distribution board for Escher. Totally not necessary if you want to use two power supplies instead.

3D printed base

The base is 3D printed in several parts. The two halves of the base connect using a dovetail joint behind the Etch-a-Sketch. There are also four feet that need to be glued onto the bottom. Finally, there are gears that connect both to the stepper motor shafts as well as to the knobs of the Etch-a-Sketch.

Designing the Escher base in FreeCAD.

The Etch-a-Sketch’s factory knobs can be simply popped off (with a bit of force). The replacement knobs are designed to be held in place using set screws. For this, I use M3 screws and brass heat-set inserts to hold the screws that can be embedded into the 3D printed parts using a soldering iron.

The HUZZAH32, motor controller board, and power board are designed to sit behind the Etch-a-Sketch.

Rear view, showing electronics.

The software

The hardware pieces of Escher are pretty standard stuff (if you consider CNC Etch-a-Sketches “standard stuff”). The software is where the interesting bits are. As with my previous project designing web-controlled LED displays, I decided to use Google Firebase to control Escher.

The web client and the HUZZAH32 communicate via Firebase.

The web UI is hosted on GitHub Pages, so the HTML, CSS, and JavaScript are all static content. JavaScript embedded into the web page uses the Firebase JavaScript SDK to read and write status and command messages to Firebase’s Cloud Firestore database. The HUZZAH32 controller polls the Firestore database using a simple REST interface over HTTPS.

Web client design

The user visits the web UI hosted on my GitHub Pages site. From there, they enter an access code, which is specific to the Escher device they are controlling (I have it printed on a little sticker on the Etch-a-Sketch). The idea here is to prevent someone who is not physically near the Etch-a-Sketch from knowing the access code and being able to control it remotely. (I toyed with the idea of generating one-time-use codes to access the Etch-a-Sketch UI, but decided it wasn’t worth the effort.)

Using the access code, the web app reads from the Firestore database to discover the properties of the device they are controlling. This is a “document” (in Firestore parlance) containing several fields: The device’s unique identifier, the last time it checked into Firebase, and so forth.

The web app also reads a list of image files from Firebase that can be etched onto the Etch-a-Sketch. Each image file is a Firestore document that stores its filename, upload date, and a URL for the image data itself. The image files are stored in Firebase Storage, and hence can be accessed directly from a URL generated by Firebase for that file.

G-code support

I decided to use G-code as the native “image” format for Escher. G-code is a quasi-standard format used by CNC machines, 3D printers, and laser cutters to represent the movement of a tool along a path. At its core, G-code is just a sequence of instructions to “move to position (x, y)” and this matches well with what we need for controlling an Etch-a-Sketch. G-code can also be generated by standard drawing programs, like Inkscape, so it is easy to take an existing drawing or image and generate G-code for Escher.

Escher only supports a subset of G-code commands. Specifically, it ignores anything that is not a G00/G01 (linear move) or G02/G03 (circular move) command. It also ignores the Z-axis position in each move, since, of course, an Etch-a-Sketch does not have a Z-axis. Because the Inkscape G-code plugin does not understand this, the G-code usually ends up with random lines crisscrossing the image as it repositions the tool.

Arduino firmware

The Arduino software running on the HUZZAH32 has a number of pieces. There’s a main control loop that periodically checks in with Firebase to report status and check on new commands. While etching, there’s another component that parses the G-code file and emits position information to the motor controller. The motors are controlled using the fantastic AccelStepper library for Arduino.

To set up the stepper motor controller, we first initialize the Adafruit MotorShield library and create two stepper motors as follows:

Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_StepperMotor *myStepper1 = AFMS.getStepper(200, 1);
Adafruit_StepperMotor *myStepper2 = AFMS.getStepper(200, 2);

Next, we create an AccelStepper object for each stepper motor:

void forwardstep1() {
myStepper1->onestep(BACKWARD, SINGLE);
}
void backwardstep1() {
myStepper1->onestep(FORWARD, SINGLE);
}
void forwardstep2() {
myStepper2->onestep(BACKWARD, DOUBLE);
}
void backwardstep2() {
myStepper2->onestep(FORWARD, DOUBLE);
}
AccelStepper stepper1(forwardstep1, backwardstep1);
AccelStepper stepper2(forwardstep2, backwardstep2);
stepper1.setMaxSpeed(50);
stepper2.setMaxSpeed(50);

Note that the definitions of the forwardstep and backwardstep functions are reversed: forwardstep moves backward and backwardstep moves forward! This is intentional, since we have mated the stepper motors to the Etch-a-Sketch knobs using gears, meaning the direction is reversed.

I also limit the maximum speed of the stepper motors. I found that if I run them too fast, the MultiStepper library has difficulty tracking the position of the steppers as they move together, causing one or the other to overshoot during a movement.

Finally, we create a MultiStepper object to control both AccelSteppers, and start things up.

MultiStepper mstepper;
mstepper.addStepper(stepper1);
mstepper.addStepper(stepper2);
AFMS.begin();

Backlash

During my initial testing, I quickly discovered that the Etch-a-Sketch responds in a nonlinear fashion to the movements of its knobs. In particular, if a knob has been turning in one direction, it will not immediately move when turned in the opposite direction — a form of backlash.

To correct for this, a library called EscherStepper, which sits on top of the MultiStepper component, keeps track of the last movement of the stepper motors and corrects for this. Experimentally, I found that I got fairly good results by simply adding an additional forward movement at the beginning of a move sequence when a knob is changing direction. I played around with different values; on my setup, a backlash of 10 stepper units in the x axis and 15 in the y axis worked well.

#define BACKLASH_X 10
#define BACKLASH_Y 15
// Move to the given position, specified in stepper units.
void EscherStepper::moveTo(long x, long y) {
// Figure out if we're reversing direction in x or y axes.
long dirx = x - last_x_;
if (dirx > 0) {
dirx = 1;
} else if (dirx < 0) {
dirx = -1;
}
long diry = y - last_y_;
if (diry > 0) {
diry = 1;
} else if (diry < 0) {
diry = -1;
}
if (dirx != dir_x_) {
cur_backlash_x_ += (dirx * BACKLASH_X);
}
if (diry != dir_y_) {
cur_backlash_y_ += (diry * BACKLASH_Y);
}
long target[2] = {x + cur_backlash_x_, y + cur_backlash_y_};
last_x_ = x;
last_y_ = y;
// Don't want to add backlash when transitioning from x -> 0 -> x
if (dirx != 0) {
dir_x_ = dirx;
}
if (diry != 0) {
dir_y_ = diry;
}
mstepper_.moveTo(target);
}

Cloud control

The control scheme in Escher is very similar to that used in Blinky, my cloud-controlled LED display. The HUZZAH32 periodically writes a status message to Firebase indicating its ID, last checkin time, and other values. This allows the web client to determine if the Escher device is online.

The firmware also periodically polls for a command message in Firebase. The web client writes this command message when it wants to tell Escher to start etching a G-code file. The command message is a Firebase document with several fields: the URL of the G-code file to etch, the time that the command was written to Firebase, and optional settings such as the zoom level, left and bottom offsets, etc. which can be configured in the web UI.

Upon receipt of a command message, the firmware first downloads the G-code file to the local flash storage. While the HUZZAH32 has 4 MB of flash, only about 1.3MB is available for application use. At boot time, the firmware initializes the flash partition as an FFS filesystem, using the SPIFFS library bundled with the Arduino support for ESP32 devices. We then download the G-code file directly to a file on the flash storage as follows:

// Download the given URL and save it to flash storage.
// Returns true if successful, false otherwise.
bool downloadGcode(const char* url) {
http.setTimeout(10000);
http.begin(url);
int responseCode = http.GET();
if (responseCode <= 0) {
Serial.printf("[downloadGcode] failed, error: %s\n",
http.errorToString(responseCode).c_str());
return false;
}
File outFile = SPIFFS.open("/data.gcd", FILE_WRITE);
int bytesRead = http.writeToStream(&outFile);
http.end();
outFile.close();
if (bytesRead < 0) {
return false;
} else {
return true;
}
}

Once the file has been downloaded, it is passed to the G-code parsing library (which I wrote for this project), which reads the file line-by-line, passing (x, y) movement coordinates to the EscherStepper component described above. The EscherStepper corrects for backlash and calls MultiStepper::moveTo(x, y) with the resulting stepper coordinates.

Stuff that worked, stuff that didn’t

I learned a lot doing this project. From the initial design back a year ago, Escher has evolved quite a bit, and I wanted to close off by sharing some of the lessons learned along the way.

Originally I had used belts rather than direct gearing to mate the stepper motors and the Etch-a-Sketch knobs. For this I used GT2 timing belts and pulleys, and found pulleys that would accept a shaft the size of the Etch-a-Sketch knob shafts. However, maintaining good belt tension proved to be difficult, and any time I wanted to erase the Etch-a-Sketch I had to loosen screws on the stepper motors in order to remove the belts and pick up the Etch-a-Sketch. Going with direct gears solved both problems, although it is uglier in a way.

My early design had the G-code only parsed by the web client, uploading a “command file” to the HUZZAH32 consisting of a simple series of (x, y) coordinate pairs. Doing the full rendering on the web client and avoiding any smarts on the Arduino side seemed like a win in terms of simplicity, however, it proved to be somewhat inflexible. Any time I wanted to adjust the zoom level or positioning of a G-code image on the Etch-a-Sketch screen, I had to regenerate and re-upload the command file to the Arduino device. In the end I decided to bite the bullet and reimplement my JavaScript-based G-code parser in C++ for the Arduino — this was not that difficult.

The communication between the web client and the Arduino device also went through some twists and turns. Originally, the Arduino device had its own web server, listening on a given port that it would publish to Firebase. The web client would connect directly to the Arduino’s web server to send it commands. This worked well for low-latency communication between the web client and Arduino, however, it required the web client to be on the same WiFi network as the Arduino — I suppose I could have set up port forwarding and dynamic DNS on my home network, but that seemed like overkill. Eventually I settled on having all commands round-trip through Firebase, which makes real-time interaction difficult, but the tradeoff in ease-of-use seems to be worth it.

Thanks for reading, and feel free to leave questions or comments below!

--

--

Matt Welsh

AI and Systems hacker. Formerly at Fixie.ai, OctoML, Google, Apple, Harvard CS prof. I like big models and I cannot lie.