RGB LED Etch-A-Sketch
A modern take on an old classic -- create colour pixel art by turning two rotary encoders to control the horizontal and vertical position of the cursor, click to change colour. All controlled by an Arduino Nano.
How it works
- Turning the left encoder moves the cursor left and right. Pressing it cycles forward through an array of eight colours
- Turning the right encoder moves the cursor up and down. Pressing it cycles backwards through the array of colours
- When you move the cursor, the selected colour remains in the previous 'pixel'
- The cursor is shown brighter than the other pixels so users can see where it is.
Supplies
Electronics
- Arduino Nano (Buy - Arduino Official Store)
- 2 x rotary encoders with push-switch (eBay UK | Amazon UK)
- 2 x white knobs for encoders (eBay UK)
- WS2812b 16 x 16 LED matrix (eBay UK | Amazon UK)
- Proto board (just a couple of small strips)
- 15 x Jumpers
- Secondary power source for WS1812b matrix (recommended, but I haven't used)
Case
- MDF / wood
- PVA glue
- Red paint
- Hot glue
Building Your Circuit
To test everything worked, I first built the circuit using a breadboard. The matrix should use an additional power source, however I did not have any problems using power directly from the Arduino -- but do this at your own risk!
Program
You will need to install the FastLED and LEDMatrix libraries.
Not all LED matrix panels are configured in the same pattern. The LEDMatrix library includes different configurations, you may need to experiment with lines 14, 15, and 16. Read the LEDMatrix library documentation for instructions.
// RGB LED Etch-A-Sketch // Author: John-Lee Langford // Website: www.mr.langford.co // Includes #include <FastLED.h> // https://github.com/FastLED/FastLED #include <LEDMatrix.h> // https://github.com/AaronLiddiment/LEDMatrix // Change the next 6 defines to match your matrix type and size #define LED_PIN 10 #define COLOR_ORDER GRB #define CHIPSET WS2812 #define MATRIX_WIDTH -16 // Set this negative if physical led 0 is opposite to where you want logical 0 #define MATRIX_HEIGHT -16 // Set this negative if physical led 0 is opposite to where you want logical 0 #define MATRIX_TYPE VERTICAL_ZIGZAG_MATRIX // See top of LEDMatrix.h for matrix wiring types cLEDMatrix<MATRIX_WIDTH, MATRIX_HEIGHT, MATRIX_TYPE> leds; // Rotary A - Horizontal movement int horizontalA = 9; // DT int horizontalB = 8; // CLK int buttonA = 7; // Button int btnA; // Button A value int horizontalALast; int horizontal, horizontalCursor = 0; unsigned char encoder_horizontalA; unsigned char encoder_horizontalB; unsigned char encoder_horizontalA_prev = 0; // Rotary B - Vertical movement int verticalA = 6; // DT int verticalB = 5; // CLK int buttonB = 4; // Button int btnB; // Button B value int verticalALast; int vertical, verticalCursor = 0; unsigned char encoder_verticalA; unsigned char encoder_verticalB; unsigned char encoder_verticalA_prev = 0; // Common unsigned long currentTime; unsigned long loopTime; bool moved = false; // Colours const int numberColours = 9; // Total number of colours available unsigned long colours[numberColours][2] = { {0xFF0000, 0x330000}, {0xFF6600, 0x331100}, {0xFFFF00, 0x333300}, {0x00FF00, 0x003300}, {0x00FFFF, 0x001111}, {0x0000FF, 0x000033}, {0xFF00FF, 0x330033}, {0xFFFFFF, 0x111111}, {0x111111, 0x000000} }; int currentColour = 0; // Set to first colour in array void setup() { FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds[0], leds.Size()); FastLED.setBrightness(255); // Clear the display in a pretty way int colourBounce = 1; for (int i = 0; i < 16; i++){ leds.DrawLine(0, i, 15, i, colours[currentColour][0]); leds.DrawLine(i, 0, i, 15, colours[currentColour][0]); FastLED.show(); delay(25); leds.DrawLine(0, i, 15, i, CRGB(0, 0, 0)); leds.DrawLine(i, 0, i, 15, CRGB(0, 0, 0)); currentColour = currentColour + colourBounce; if (i == 7) { colourBounce = -1; } } FastLED.clear(true); // Rotary A - Horizontal pinMode (horizontalA, INPUT); pinMode (horizontalB, INPUT); pinMode (buttonA, INPUT); horizontalALast = digitalRead(horizontalA); // Rotary B - Vertical pinMode (verticalA, INPUT); pinMode (verticalB, INPUT); pinMode (buttonB, INPUT); verticalALast = digitalRead(verticalA); // Common currentTime = millis(); loopTime = currentTime; } void loop() { // get the current elapsed time currentTime = millis(); if (currentTime >= (loopTime + 5)) { // Read encoder pins encoder_horizontalA = digitalRead(horizontalA); encoder_horizontalB = digitalRead(horizontalB); encoder_verticalA = digitalRead(verticalA); encoder_verticalB = digitalRead(verticalB); // Check for button presses btnA = digitalRead(buttonA); btnB = digitalRead(buttonB); if (btnA == 0) { currentColour++; if (currentColour > (numberColours - 1)) { currentColour = 0; } delay(500); } if (btnB == 0) { currentColour--; if (currentColour < 0) { currentColour = numberColours - 1; } delay(500); } // Encoder A if ((!encoder_horizontalA) && (encoder_horizontalA_prev)) { // A has gone from high to low if (encoder_horizontalB) { // B is high so counter-clockwise if (horizontalCursor > 0) { horizontalCursor --; moved = true; } } else { // B is low so clockwise if (horizontalCursor < 15) { horizontalCursor ++; moved = true; } } } encoder_horizontalA_prev = encoder_horizontalA; // Store value of A for next time // Encoder B if ((!encoder_verticalA) && (encoder_verticalA_prev)) { // C has gone from high to low if (encoder_verticalB) { // B is high so counter-clockwise if (verticalCursor < 15) { verticalCursor ++; moved = true; } } else { // D is low so clockwise if (verticalCursor > 0) { verticalCursor --; moved = true; } } } encoder_verticalA_prev = encoder_verticalA; // Store value of C for next time loopTime = currentTime; // Updates loopTime leds(horizontalCursor, verticalCursor) = colours[currentColour][0]; // Move the LED if (moved) { leds(horizontal, vertical) = colours[currentColour][1]; horizontal = horizontalCursor; vertical = verticalCursor; moved = false; } FastLED.show(); } }
Downloads
Building the Case
Once you are happy with the electronics, it's time to build the case.
I used some 6mm sheets of MDF which I had lying around. I cut four sheets into roughly A4 size then, leaving a one inch border, cut the middle out of three of them. These three sheets should, when glued together, provide enough depth to house the Arduino and other components. To achieve this, I used right-angled GPIO pins on the Arduino.
The four sheets of MDF were then stuck together using the PVA glue, using a couple of clamps to ensure a good fit with no gaps.
When the PVA had fully set, the edges were sanded-down.
Appropriately sized holes were drilled into the front of the case:
- One holes for each of the rotary encoders
- Three holes for the matrix -- one for the jumpers, one for the additional (optional) power supply, and for the unused connections to an additional panel.
You will need to measure your components and layout to determine where to put the holes.
Finally, paint it red.
Bringing It All Together
Now it's time to put the components into the case!
To keep the device as slim as possible, I used thin strips of protoboard (1 x 4) to distribute +5v and Ground and soldered connectors to them.
The rotary encoders came with nuts to screw them to the case. Everything else is held in place using hot glue.
Note: if you are building your Etch-A-Sketch using the final photo, remember that you are looking at the rotary encoders from the back. On the schematic (Step 1), the rotaries are shown facing forward.
Enjoy creating your pixel art!