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

The Escher web interface.

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.

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
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.
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.

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.)

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.

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.

Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_StepperMotor *myStepper1 = AFMS.getStepper(200, 1);
Adafruit_StepperMotor *myStepper2 = AFMS.getStepper(200, 2);
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);
MultiStepper mstepper;
#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;
// Download the given URL and save it to flash storage.
// Returns true if successful, false otherwise.
bool downloadGcode(const char* url) {
int responseCode = http.GET();
if (responseCode <= 0) {
Serial.printf("[downloadGcode] failed, error: %s\n",
return false;
File outFile ="/data.gcd", FILE_WRITE);
int bytesRead = http.writeToStream(&outFile);
if (bytesRead < 0) {
return false;
} else {
return true;

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.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Matt Welsh

Matt Welsh

VP of Engineering at, building compilers for fast AI. Ex-Google engineering director, Ex-Apple. Systems hacker and drinker of beer.