DVD screensaver slab

The bouncing DVD logo, on your desk. A tiny screen in a 3D-printed stand plays three pixel-art loops: the DVD bounce, a running dino and a space invader. Print three parts, click in an ESP32-C6 screen, and learn how bouncing, pixel art and smooth animation work.

DifficultyBeginner
Build time45 min
Est. cost$20
Parts5
Files8
Steps13

Things you'll need

QtyPartType
1
Waveshare ESP32-C6-LCD-1.47 board
Waveshare ESP32-C6-LCD-1.47 board The brain and a 1.47-inch screen in one board, with tiny BOOT and RESET buttons. No touch, no soldering.
electronics
3
Printed parts
Printed parts Body with stand, front frame and leg. All three are in the 3MF below on one plate, no supports.
printed
1
USB-C data cable Powers the board and loads the program. It must be a data cable, not a charge-only one.
electronics
16 g
PLA filament Black makes the colours glow, like an old TV. Any PLA works.
consumable
1
microSD card (optional) Only for the 'play your own GIFs' challenge in Level up. Not needed for the built-in loops.
electronics

Tools & software

1
Chrome or Edge browser Loads the program onto the board straight from this page. Nothing to install.
1
VS Code + PlatformIO (optional) Free. Only needed if you want to change the code later. See 'Level up' at the end.

Files & downloads

Download all

Flash writes a precompiled image over USB-C in Chrome or Edge. The .ino below is the source — you do not need Arduino IDE unless you want to change the sketch.

What you're building

Remember the DVD logo that bounced around old TV screens, while everyone waited for it to hit the corner? This is that, on your desk. A tiny screen leans back in a printed stand. The logo bounces off the edges and changes colour every time it hits one. Press a button and it switches to a running dino or a bouncing space invader.

No soldering. No wires. You print three parts, drop in one board, press it into the stand, plug in one cable, and click one button on this page to load the program.

Note

Will it ever hit the corner? Yes! The logo's position is just two numbers, and every frame it adds its speed to them. Whether it lands exactly in a corner depends only on the screen size, the logo size and the speed, so you can work it out. Lesson 1 tells you when to watch.

What you'll learn

  • How speed is just a number added every frame, and a bounce is flipping its sign
  • How one number can pick from a table of colours
  • How pixel art is a grid of numbers
  • Why redrawing only what moved makes animation smooth
  • How one button does two jobs: a short press and a long press

Meet the parts

The black printed body with stand, front frame and leg
Three printed parts: the body joined to its stand plate (top), the front frame (bottom left) and the leg (bottom right).
  1. Body with stand: a box with a pocket for the screen, joined to a flat plate that becomes the stand. One end of the box has an opening for the USB-C plug, and both long edges have a little scoop so you can reach the board's buttons.
  2. Front frame: the rectangle the screen looks out of. The board sits inside it.
  3. Leg: a flat piece with a small tab. It plugs into the notch under the stand and props it up from behind.
  4. The board: a Waveshare ESP32-C6 with a 1.47-inch screen. Brain and display in one.
Note

Before you start. The printer works on its own for about 35 minutes. You do about 5 minutes of clicking parts together and 2 minutes of loading the program. No tools needed.


Part 1: Print

One plate, three parts, one colour. Start the printer, then read Part 2 while it works.

1

Download the 3MF

Grab Print-ready 3MF from Files & downloads above and open it in Bambu Studio. Using another slicer? Open the STL instead; all three parts are in it, already lying flat.

Download the 3MF
2

Pick a colour and print

Settings are already in the file: 0.2 mm layers, 2 walls, 15% infill, no supports. About 35 minutes on a Bambu P1S.

Pick a colour and print
3

Check the parts

Pick off any strings or blobs, especially inside the frame, around the USB-C opening in the body, and on the leg's tab. The board has to drop into the frame without catching.


Part 2: Put it together

No glue, no screws. Three moves: board into the frame, frame into the body, leg under the stand. If a part feels tight, wiggle it; never force it.

1

Drop the board into the frame

Lay the front frame face-down. Put the board in screen-down, so you see the chips, with the USB-C port at the open end of the frame. The two tiny buttons on either side of the port line up with gaps in the frame wall. Got a microSD card for the Level-up challenge? Slide it into the slot next to the USB-C port now.

Drop the board into the frame
2

Press the frame into the body

Turn the frame over so the screen faces you. Press it into the pocket on the front of the body, with the USB-C end at the end that has the opening for the cable. Press until it sits flat.

3

Slide on the leg

Push the leg's tab into the notch under the stand until it stops. Now the screen leans back and stands on its own, with the cable coming out of the right-hand side.

Slide on the leg

Part 3: Load the program

New boards come with a factory demo. You are going to replace it with the DVD program, straight from this web page. That takes about a minute.

Note

Word to know: flash. Flashing means copying a program onto a chip. The chip keeps it even when unplugged, like saving a game.

1

Plug in

Connect the USB-C cable from the board to a computer (Windows, Mac, Chromebook or Linux). It must be a data cable, not a charge-only one.

2

Click Flash to board

Scroll up to Files & downloads and click Flash to board. Use Chrome or Edge. Pick the port that appears, click Install, and wait for the green tick.

3

Watch it wake up

A DVD logo appears and starts bouncing. On a brand-new board the picture starts tall, so in the stand it looks sideways. That's normal; the next step fixes it.

4

Turn the picture

Find BOOT: the tiny button in the scoop on the bottom edge, right next to the cable. Hold it down with a fingernail for about one second, until the picture flips, then let go. Now the logo bounces across the wide screen. It remembers this, even after you unplug it. (Nothing turned and the logo just started over? You pressed RESET. Try the button in the other scoop.)

Turn the picture
Tip

Which button is which? BOOT is in the bottom scoop, RESET in the top scoop. Not sure? Give one a quick press. If the next loop starts, that was BOOT. If the screen goes black and the DVD logo starts over, that was RESET, which does no harm.

Tip

No port in the list? Hold BOOT, tap RESET, let go of BOOT, then click Flash again. On a Mac the port is usually called usbmodem. Phones and Safari can't flash; use a computer.


Play it

Three things to try.

1

Watch it bounce

The logo glides across the screen. Every time it touches an edge it bounces off and changes colour: eight colours, then it starts again.

Watch it bounce
2

Quick press on BOOT = next loop

Press once for the dino run: a little dino hops over cacti. Again for the space invader: a pixel alien bouncing through the stars. Once more and you're back to DVD.

3

Hold BOOT = turn the picture

Hold for about a second to switch between tall and wide. Use wide in the stand. Tall is handy if you take the screen out and stand it up on its end.

The finished stand on a desk with a pink DVD logo on the screen
Pink is the second of the eight colours. Count the bounces and guess the next one.

How it works: five tiny lessons

You don't need to read the whole program (it's in Files & downloads if you're curious). Each lesson below shows the one small piece of code that does the job, and gives you something to try.

Lesson 1: Bouncing is flipping a sign

The logo's position is two numbers: g_sx across and g_sy down. Its velocity (speed plus direction) is two more: it moves 2.2 pixels across and 1.6 pixels down every frame, roughly 25 to 30 frames a second.

When it reaches an edge, the program flips the sign of that velocity: +2.2 becomes −2.2, so it heads back the other way. That is the whole bounce.

gif-player.ino
g_sx += g_svx;                                     // move across by 2.2 (or back, if it's negative)
g_sy += g_svy;                                     // move down by 1.6 (or up)
if (g_sx < 2 || g_sx > maxX) { g_svx = -g_svx; hit = true; }  // hit a side wall: flip across
if (g_sy < 2 || g_sy > maxY) { g_svy = -g_svy; hit = true; }  // hit top or bottom: flip up/down
if (hit) g_hue += 32;                              // a new colour on every bounce

Simplified from drawDvdFrame().

Tip

Try it: the corner hunt. We ran the program's exact maths on a computer. On the wide screen, the first perfect corner is bounce number 121, about two and a half to three minutes after the DVD loop starts. Press BOOT three times to restart the DVD loop, start a stopwatch, and watch the corners!

Lesson 2: One number picks a colour

The colour comes from a table of eight colours. The program keeps one number, g_hue, from 0 to 255, and adds 32 on every bounce. Dividing by 32 turns it into a slot from 0 to 7. After 8 bounces it has added 256, which rolls a byte back over to 0, and the colours start again.

Each colour is {red, green, blue}, each from 0 to 255. The screen squeezes that into 16 bits: 5 for red, 6 for green, 5 for blue.

gif-player.ino
static const uint8_t kHues[][3] = {
  {61, 255, 184}, {255, 79, 216}, {122, 162, 255}, {255, 196, 64},  // mint, pink, blue, gold
  {255, 88, 88},  {72, 255, 128}, {255, 160, 40},  {180, 120, 255}, // red, green, orange, purple
};
const uint8_t *p = kHues[(h / 32) & 7];   // 0-255 → slot 0 to 7

Simplified from hsv565().

Tip

Try it: watch for the colours in order: mint, pink, blue, gold, red, green, orange, purple, then mint again. If the logo is gold now, what colour will it be after three more bounces? (Orange.)

Lesson 3: Pixel art is a grid of numbers

The space invader is drawn from an 8 × 8 grid. Each number is a colour slot: 0 is the background, 1 mint, 2 pink and 4 white. Every square is blown up to 7 × 7 real pixels, so the alien is 56 × 56 on screen. The letters D, V and D are tiny grids too, 5 squares wide and 7 tall.

gif-player.ino
static const uint8_t SPRITE[8][8] = {   // 0 = background, 1 = mint, 2 = pink, 4 = white
  {0, 0, 1, 1, 1, 1, 0, 0},
  {0, 1, 1, 2, 2, 1, 1, 0},
  {1, 1, 4, 1, 1, 4, 1, 1},             // the two 4s are its eyes
  {1, 1, 1, 1, 1, 1, 1, 1},
  {1, 2, 1, 1, 1, 1, 2, 1},
  {0, 1, 2, 2, 2, 2, 1, 0},
  {0, 0, 1, 1, 1, 1, 0, 0},
  {0, 0, 0, 1, 1, 0, 0, 0},
};
static const int SPR_SCALE = 7;         // each square becomes 7 × 7 pixels

The invader, exactly as it is in the program.

Tip

Try it on paper: copy the grid onto squared paper and colour it in: 1 = green, 2 = pink, 4 = white, 0 = leave blank. That's the invader! Then design your own 8 × 8 creature for Level up.

Lesson 4: Only redraw what moved

Sending the whole wide screen to the display means 320 × 172 pixels × 2 bytes: about 110 KB every frame. That's slow, and you'd see it flicker.

So the program draws just the logo into a small picture in memory (149 × 59 pixels, about 17 KB), copies that to the screen in one go, then paints background over only the thin strip the logo just left behind. Less to send means faster and smoother.

gif-player.ino
fbFill(bw, bw, bh, COL_BG);                        // 1. clear a small picture in memory
fbCapsule(bw, 0, 0, bw, bh, c);                    // 2. draw the coloured oval...
fbCapsule(bw, 7, 8, bw - 14, bh - 16, COL_BG);     //    ...and cut out its middle
fbDvdWord(bw, bw, bh, ts, c);                      // 3. stamp the letters D V D
blitFb(x, y, bw, bh, bw);                          // 4. copy it to the screen in one go
eraseOldNotNew(g_prevX, g_prevY, x, y, bw, bh, COL_BG);  // 5. wipe only the strip it left behind

From drawDvdFrame(). bw and bh are the logo's width and height.

Tip

Try it: the logo moves at most 2.2 pixels a frame, so the strip it leaves behind is tiny. Work out how many pixels the program sends per frame (149 × 59) compared with the whole screen (320 × 172). About how many times less is that? (About 6 times.)

Lesson 5: One button, two jobs

BOOT does two things, so the program has to measure how long you hold it. The moment you press, it starts a stopwatch. If you're still holding at 0.7 seconds, it's a long press: turn the picture. If you let go before that, it's a short press: next loop.

The wide-or-tall choice is saved in the chip's flash memory with Preferences, which is why it remembers after you unplug.

gif-player.ino
if (down && !g_bootDown) { g_bootDown = true; g_bootDownAt = now; }   // just pressed: start the stopwatch
if (down && !g_bootLong && now - g_bootDownAt >= 700) {               // still down after 0.7 s?
  g_bootLong = true;
  toggleOrient();                                                     // long press: turn the picture
}
if (!down && g_bootDown) {                                            // just let go
  if (!g_bootLong && now - g_bootDownAt > 40) nextClip();             // short press: next loop
  g_bootDown = false;
}

Simplified from pollBoot(). The 40 ms check ignores the tiny flickers a button makes as it closes.

Tip

Try it: press BOOT and keep holding. The picture turns before you let go. Now do a quick press: nothing happens until you release. Why can't a short press act straight away? (Because until you let go, it might still turn into a long one.)


Secret commands

Your computer can chat with the board through the same USB-C cable. That chat is called serial. Open the Serial Monitor in VS Code (the plug icon in the PlatformIO bar) or any serial app at 115200, type a word, press Enter.

Type this What happens
help lists the commands
status which loop is playing, how many frames it has drawn, and the screen size
next same as a quick press on BOOT
rotate same as holding BOOT: tall or wide
list lists the loops (and any GIFs it has found on a card)
sd looks for a microSD card and lists the GIFs in its gifs folder
Tip

Frame counter: type status twice, ten seconds apart, and subtract the two frame= numbers. Divide by 10 and you have the real frames per second.


Level up: change the code

Ready to make it yours? The easiest way to change the code is PlatformIO, a free add-on for VS Code. It downloads the right libraries and settings for this board by itself.

  1. Install VS Code, then open Extensions and install PlatformIO IDE.
  2. Download platformio.zip from Files & downloads, unzip it, and open the gif-player-platformio folder in VS Code (File → Open Folder).
  3. Open gif-player/gif-player.ino. Find the line you want to change, then save.
  4. Plug in the board and click the → (Upload) arrow in the blue bar at the bottom of VS Code. The first build takes a few minutes while it downloads everything; after that it takes seconds.

Ideas, easiest first:

  • Faster logo: in resetBuiltin(), change g_svx = 2.2f and g_svy = 1.6f to 3.5f and 2.5f. (The corner maths changes too!)
  • Your own colours: change the numbers in kHues.
  • Your own invader: redraw the SPRITE grid with your creature from Lesson 3. Slot 3 is blue if you want a fourth colour.
  • Challenge: play your own GIFs. The program can already read GIFs from a microSD card (type sd to see them), but the BOOT button only steps through the three built-in loops. Change nextClip() like this so it counts your GIFs too:
gif-player.ino
static void nextClip() {
  ensureSd();                                // look for the card (only once)
  g_index = (g_index + 1) % playlistLen();   // 3 built-in loops + your GIFs
  startIndex();
}

The challenge edit. It builds, but treat it as an experiment.

Warning

This one is a real challenge. Use a FAT32 microSD card with a folder called gifs, and small GIFs (320 × 172 pixels or smaller). Big or unusual GIFs may play slowly or not at all. If the screen freezes, press RESET, try a smaller GIF, or put nextClip() back the way it was.


If something goes wrong

  • The picture is sideways. Hold BOOT for about a second until it turns.
  • Screen stays black after flashing. Press RESET once. Still black? Try a different USB-C cable; many are charge-only.
  • No port shows up. Hold BOOT, tap RESET, let go of BOOT, click Flash again.
  • The frame won't go into the body. Turn it round: the USB-C end must be at the end with the cable opening.
  • The board won't sit flat in the frame. Look for a blob of plastic inside the frame and trim it.
  • The stand wobbles. Push the leg's tab all the way into its notch.
  • Screen looks a bit dim. On purpose: the program runs the backlight at half power.

Build photos

Licensed under CC BY-SA 4.0. Check with the author before commercial use.

Community makes

Built this? Share a photo of your print so others can see how it turned out.

No makes yet. Be the first.