Project

Shake It Off! A Digital Drawing Pad Inspired by the Iconic Etch A Sketch

March 27, 2024 by Kristijan Nelkovski

In this project, which uses the Arduino IDE, we create and demonstrate a digital drawing pad using an ESP32-based e-paper display dev board, rotary encoders for drawing control, and an accelerometer for shake detection to initiate an erase.

Designed by French electrician Andre Cassagnes, the Etch A Sketch is a mechanical drawing toy first introduced back in the 1950s (Figure 1). This device allows for a continuous line to be scraped into an aluminum dust-coated pane of glass using a pulley system-mounted stylus underneath it that’s controlled by two knobs, one for vertical and one for horizontal motion.

 

The Etch A Sketch drawing toy.

Figure 1. The Etch A Sketch drawing toy. Image used courtesy of Spin Master

 

By shaking this device, the aluminum dust that has fallen down to the bottom is able to recoat the surface of the glass. This erases the drawing “etched” into the display.

 

Project Overview

In this project, my aim was to create a dynamic and interactive drawing pad, as shown in Figure 2, that mimics the functions of an Etch A Sketch while also retaining some of the toy’s aesthetics and familiar tactile feel.

 

The All About Circuits Digital Drawing Display in operation

Figure 2. The All About Circuits Digital Drawing Display in operation.

 

For the display, I used the Inkplate 6 from Soldered Electronics. This is a 6-inch e-paper development board powered by an embedded ESP32 microcontroller. I also used two KY-040 rotary encoder modules, an MPU6050 accelerometer, and an optional LiPo battery (the Inkplate has dedicated charging circuitry and a JST input).

 

Disclosure: Soldered Electronics provided the author with a couple of its development boards for evaluation and potential use in projects like this one.

 

For this project, you need to be familiar with the Arduino IDE and have a basic comprehension of sensors and microcontrollers. If you don’t, taking a look at the All About Circuits’ Beginners Guide to the Arduino could be helpful. Because these modules can be connected using standard jumper wires (no need for a breadboard or custom PCB), I’ve designed an optional 3D printable case that helps hold everything together and improves mobility.

 

Electronic Paper

Electronic paper (e-paper) is a technology developed to mimic the appearance of ink on paper. It reflects ambient light off of an electronically controllable pigmented surface. This is different from conventional display technologies such as LCD and LED which project images through direct light emissions. Electrophoretic displays are a type of e-paper that uses miniature capsules filled with charged particles of different colors capable of switching positions whenever an electric charge is applied.

E-paper is visible under direct sunlight, uses less power, and provides a wider viewing angle than LED and LCD displays. Its low refresh rate and monochrome output are perfect for ebook readers, digital price tags, and this Digital Drawing Pad project.

 

Rotary Encoder

A rotary encoder is a mechanical position sensor that converts rotational motion into an electrical signal using two disks, one stationary and one connected to a rotatable shaft, with partially overlapping conductive terminals that produce data lines that are 90 degrees out of phase with each other.

 

Accelerometer

Additionally, an accelerometer is a microelectromechanical sensor that measures the rate of change in velocity from motion using a small mass connected to a fixed frame through a set of flexible springs integrated into a semiconductor chip.

 

Bill of Materials (BOM)

My bill of materials is provided in Table 1. You could use different components for the rotary encoder and accelerometer.

 

Table 1. Digital Drawing Display bill of materials.
Part Quantity Notes
Inkplate 6 1  
Rotary encoder 2 KY-040 module.
Accelerometer 1 GY-521 - an MPU6050-based accelerometer module.
Jumper wires 12 (min) Male to male.
90-degree pin headers   Soldered to all output rows of the board so that the jumper wires will connect parallel to the Inkplate. The results in a thinner box.
Enclosure 1 Included stl files for a 3D printable enclosure. (optional)
Battery 1 The Inkplate 6 has integrated charging capabilities for Li-ion batteries. (optional)
Barrel jack (or USB breakout) 1 For charging (and reprogramming) the device while it's in the custom enclosure. (optional)

 

Hardware Connections

The connections between the Inkplate 6 and the rest of the components are shown in the following table.

 

Table 2. Wiring description.
Inkplate 6 Pins Module Pins
Pin 39 CLK (horizontal encoder)
Pin 96 DT (horizontal encoder)
3V3 + (horizontal encoder)
GND GND (horizontal encoder)
Pin 13 CLK (vertical encoder)
Pin 14 DT (vertical encoder)
3V3 + (vertical encoder)
GND GND (vertical encoder)
SDA SDA (mpu6050)
SCL SCL (mpu6050)
3V3 VCC (mpu6050)
GND GND (mpu6050)

 

Note that the Inkplate 6 has multiple ground and 3.3 V pins to connect jumper wires to. Figure 3 is an image of the Digital Drawing Pad without the case to highlight the connections to the rotary encoders and accelerometer module.

 

The Digital Drawing Pad without the case showing connects to rotary encoders and accelerometer.

Figure 3. The Digital Drawing Pad without the case.

 

Setting up the Arduino IDE

To get started, we first need to set up the Arduino IDE with Inkplate’s board definitions. To do this, in the “Preferences” menu found under “File -> Preferences” copy and paste the following link as a new line into the “Additional Boards Manager URLs” field:

https://raw.githubusercontent.com/SolderedElectronics/Dasduino-Board-Definitions-for-Arduino-IDE/master/package_Dasduino_Boards_index.json

Figure 4 shows my “Additional Boards Manager URLs” window. You only need to add the last link for the Inkplate boards; the other links are unrelated to this project.

 

The Arduino Additional Boards Manager showing the new project URL added at the bottom.

Figure 4. The Arduino Additional Boards Manager with the new project URL added at the bottom. (click to enlarge)

 

Next, look up “inkplate” in the “Boards Manager” window found under “Tools -> Board -> Boards Manager” and install the relevant package (Figure 5).

 

The Arduino Boards Manager window after finding and installing the Inkplate Boards package.

Figure 5. The Arduino Boards Manager window after finding and installing the Inkplate Boards package.

 

We have to install Soldered’s “Inkplate.h” library and Adafruit’s “Adafruit_MPU6050.h” library. This can be done by searching for these two libraries in the Arduino library manager found under “Tools -> Manage Libraries” and installing them, as demonstrated in Figures 6 and 7.

 

The Arduino Library Manager window after finding and installing the Inkplate.h library.

Figure 6. The Arduino Library Manager window after finding and installing the Inkplate.h library.

 

The Arduino Library Manager window after finding and installing Adafruit’s MPU6050 library.

Figure 7. The Arduino Library Manager window after finding and installing Adafruit’s MPU6050 library.

 

The process of installing the board definitions and libraries is similar between different versions of the Arduino IDE. After all of this is done, we can create a new sketch and start working on the code for our drawing pad.

 

Creating the Arduino Sketch

At the top of our sketch, the first thing that we have to do is write an “#if” directive that checks whether or not we’ve selected the correct board when we try to upload our code to the Inkplate.

#if !defined(ARDUINO_ESP32_DEV) && !defined(ARDUINO_INKPLATE6V2)
#error "Wrong board."
#endif

 

Next, as with all programming languages, we need to declare the libraries that will be used in this project.

#include <Inkplate.h>#include <Adafruit_MPU6050.h> #include <Adafruit_Sensor.h> 

 

Define Objects, Pins, and Global Variables

After writing out the libraries, we have to create two objects, one for the display and one for the sensor.

Inkplate display(INKPLATE_1BIT);
Adafruit_MPU6050 mpu;

 

Next we can define the signal pins for our rotary encoders. We’re only going to use the KY-040 module's CLK and DT signal pins as we don’t need the integrated momentary switch function (the SW pin). For these two devices I've chosen pins 39, 36, 14, and 13 of the ESP32 (Inkplate 6). Note that I will use the suffixes “_H” for the horizontal encoder and “_V” for the vertical one throughout the code.

#define CLK_H 39
#define DT_H 36
#define CLK_V 13
#define DT_V 14

 

Now, we need to create three variables for each encoder, “currentStateCLK” for its current state, “lastStateCLK” for saving a previous state, and a counter called “counter,” which will be initially set to zero.

int counter_H = 0;
int currentStateCLK_H;
int lastStateCLK_H;
int counter_V = 0;
int currentStateCLK_V;
int lastStateCLK_V;

 

Establish the Image Starting Point

Lastly, we have to define somewhere for our drawing pad to start drawing. I've chosen the middle of the display at a horizontal position of 400 and a vertical position of 300. This is because the Inkplate’s dimensions are 800 by 600 pixels.

int position_H = 400;
int position_V = 300;

 

Implementing the Drawing Function

For drawing our continuous line we first need to define its basic building blocks. While a single pixel on the large Inkplate display would be way too small, filling up a small circle works pretty well.

I created a dedicated drawing function called “newDot()” that takes no arguments (void type) and will be called in the main loop of the sketch whenever a rotary encoder change is detected and a continuation i.e. a new dot in the line is required. It calls two functions from the Inkplate library (derived from Adafruit’s GFX library), one for actually drawing the dot (circle), and another for facilitating a partial refresh of the display.

void newDot() {
  display.fillCircle(position_H + counter_H * 3, position_V + counter_V * 3, 2, BLACK);
  display.partialUpdate();
}

 

A partial refresh on this display takes only 264 ms, while a total one takes a whole second more. Partial refreshes on e-paper create image artifacts when pixels that were black in a previous frame have to be white in the next one. Because we’re drawing a continuous line, this never occurs, but we do need total refreshes to clear the entire display.

The function that draws the dot itself is called “fillCircle” and takes four arguments: the horizontal and vertical starting coordinates for the circle, its radius, and a color for its interior.

Our program calculates the coordinates for new circles by taking the starting horizontal and vertical position values and adding a corresponding counter variable offset by a constant relative to the size of the circle. This ensures that when the encoders rotate and increment the value of their respective counter variables, an almost seamless line with a rounded edge forms on the display.

For example, in my code, the circle radius is two pixels, while the offset radius is three pixels. Additionally, because the Inkplate 6 is only black and white, the interior color is set to black.

Of course, you can always play around with these parameters and test different shapes, sizes, positions, and offsets depending on how and what you want to draw using this project.

 

Setting up the Arduino Serial Monitor for Debug

Next, in the setup function, we need to start the Arduino serial monitor for debugging purposes and initialize the display and sensor objects. If these initializations fail, the serial monitor will show an appropriate error message and go into an infinite delay loop (if you’re not debugging, you could potentially skip these functionalities).

Serial.begin(9600);
if (!display.begin()) {
  Serial.println("Display error.");
  while (1) {
    delay(10);
  }
}
if (!mpu.begin()) {
  Serial.println("Accelerometer error.");
  while (1) {
    delay(10);
  }
}

 

Configuring and Reading the Rotary Encoders

We also have to configure the four rotary encoder pins as inputs.

pinMode(CLK_H, INPUT);
pinMode(DT_H, INPUT);
pinMode(CLK_V, INPUT);
pinMode(DT_V, INPUT);

 

We also need to read the CLK states of the encoders so that there’s a starting point to compare future current states in the main loop. These values will be saved in the “lastStateCLK” variables.

lastStateCLK_H = digitalRead(CLK_H);
lastStateCLK_V = digitalRead(CLK_V);

 

Reading the Encoders in the Loop Function

Once our code’s loop function begins, the current states of the CLK lines of both encoders have to be read and saved into the “currentStateCLK” variables.

currentStateCLK_H = digitalRead(CLK_H);
currentStateCLK_V = digitalRead(CLK_V);

 

Next, we have to check for changes in the states of our encoders. This is done using two nested “if” statements for each encoder—one for rotation and one for direction.

if (currentStateCLK_H != lastStateCLK_H && currentStateCLK_H == 1) {
  if (digitalRead(DT_H) != currentStateCLK_H) {
    counter_H++;
    newDot();
  } else {
    counter_H--;
    newDot();
  }
}
if (currentStateCLK_V != lastStateCLK_V && currentStateCLK_V == 1) {
  if (digitalRead(DT_V) != currentStateCLK_V) {
    counter_V++;
    newDot();
  } else {
    counter_V--;
    newDot();
  }
}

 

The first “if” statement tells us when the encoder has been turned by comparing the current and previous states of the clock line. If they aren’t equal, it means that the encoder was rotated. Within the same “if” statement, we cover double turning by making sure that the current state of the encoder is equivalent to one.

 

Deciphering the Direction of Rotation

The second “if” statement reads out the state of the data line of the encoder and compares it to its current clock state. If these two values are not equal, it means that the encoder has been rotated counterclockwise. Otherwise, it means that the encoder has been rotated clockwise. This is because as mentioned previously, depending on the direction of rotation, one of these signals is always lagging behind the other by 90 degrees.

After this check, the current states of both clock signals are saved in the “lastStateCLK” global variables so that they can be compared with the next states in one of the later iterations of the loop function.

lastStateCLK_H = currentStateCLK_H;
lastStateCLK_V = currentStateCLK_V;

 

Shake Detection in the Loop Function

Finally, we need a way to reset our canvas or start drawing from a location other than the middle of the display. In order to achieve this, we first have to declare three sensor event objects for our MPU6050 module. Note that besides being an accelerometer, this chip is also a gyroscope and a temperature sensor.

I’m going to use the “getEvent” function from Adafruit’s MPU6050 library, which requires that we read out information for all three of these functions. This library offers multiple ways to read sensor information, and there are ways of reading acceleration data only.

sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);

 

Although we’ve read and saved the acceleration, gyro, and temperature data, we will only use the accelerometer object values (the x, y, and z axes acceleration vectors).

We can determine the magnitude of acceleration by calculating the root of the sum of the squares of all three of these vectors. We have to subtract gravity, and then square the resulting value so that we get a range of large positive numbers that are going to represent the shaking of our drawing pad.

float shake = sqrt(sq(a.acceleration.x) + sq(a.acceleration.y) + sq(a.acceleration.z));
shake -= 9.81;
shake = sq(shake); 

 

With a little bit of trial and error, we can determine what shake values constitute an adequate action for erasing the display. The following if statement is the shake sensitivity range that I’ve left my sensor on. You can change these values depending on your accelerometer and drawing needs. You don’t want your drawings to be erased at the slightest bump, but you also don’t want to have to vigorously shake your drawing pad all the time when you want a blank canvas.

 if (shake > 400 && shake < 450) {
  Serial.println(shake);
  display.clearDisplay();
  display.display();
  delay(1260);
 }

 

This if statement does a total display refresh and waits for 1.26 seconds (the Inkplate’s refresh time) before letting you start drawing again. This is the end of the loop function and the end of the code for our project. Figure 8 at the end of this article has the full code.

 

Uploading the Arduino Code to the Display

The Arduino IDE uploading process works the same as with any other development board. However, you must pick Inkplate 6 in the “Tools -> Board -> Inkplate Boards“ menu!

 

The Digital Drawing Pad in Action

Here is a video demonstrating the operation of the Digital Drawing Pad. I have accelerated the playback on this video because the partial refresh rate of the display is about 4 frames per second, so drawing on it is pretty slow.

 

Your Turn to Make It Even Better

In this project, we developed a rudimentary digital recreation of the old Etch A Sketch, but our code can always be expanded with more functions. For example, you could create a menu and employ the integrated momentary switches from the rotary encoder modules as selection buttons. Additionally, because the Inkplate 6 has an SD card slot, you could save your drawings and view them again later.

If you get the color version of this development board, you could potentially draw using multiple colors and implement a third encoder (or another type of switch) as a color or brush selector.

How would you expand on this concept? What would you add to this project? Share your thoughts and ideas in the comments below!

Finally, as promised above, here in Figure 8 is the full code to help you get started in replicating or improving upon this project.

 

Figure 8. Arduino code for the Digital Drawing Display project