Free shipping on all Canadian orders over $150! All prices in Canadian dollars.

Maker Festival Projects: Flying Toaster LED Panel

We had a busy time last weekend at Maker Festival, and we'd really like to thank everyone who stopped by to buy something, chat, or just marvel at our blinking lights.

One of the big hits of our stand was the Flying Toaster panel. If you're old enough to remember when people would actually buy screensavers for their computers, you'll recognise this iconic bundle of pixels from Berkeley Systems' After Dark screensaver. 

If you wish to keep the toaster flying in your home, here's what I used to build the display:

You could also use an Arduino Uno to drive the the panel, but the backpack has the advantage of power and signal connectors that plug directly into the LED matrix. After you've built the backpack (following Nootropic Design's excellent build instructions), here's the code you'll need to get the animation running:

 

/*
   Toaster32x32.ino -
   Flying Toasters in 32x32 RGB LED matrix
   scruss - 2017-07 - for elmwood.to
   freely modified from
   - Phillip Burgess's Animated Flying Toasters for Adafruit
    https://learn.adafruit.com/animated-flying-toaster-oled-jewelry

   - Adafruit RGB matrix examples by Limor Fried and Phillip Burgess
    https://learn.adafruit.com/32x16-32x32-rgb-led-matrix

   The toaster bitmaps are most likely lifted from Berkeley Systems'
   "After Dark" screen saver for the Apple Macintosh.
*/
#include <Adafruit_GFX.h>   // Core graphics library #include <RGBmatrixPanel.h> // Hardware-specific library #include "bitmaps.h"        // animation frame definitions // Colour definitions - format is RGB-565 16-bit #define BLACK    0x0000 #define BLUE     0x001F #define RED      0xF800 #define GREEN    0x07E0 #define CYAN     0x07FF #define MAGENTA  0xF81F #define YELLOW   0xFFE0 #define WHITE    0xFFFF // array of visible colours, stepped through in loop uint16_t colarray[] = { BLUE, RED, GREEN, CYAN,                        MAGENTA, YELLOW, WHITE                      }; #define COLMOD 7 // array modulus for looping int colsub = 0; // current colour array position // configuration for // 32x32 matrix with SINGLE HEADER input pinout // talking to Nootropic Matrix Backpack v2 #define CLK 8 #define OE  9 #define LAT 10 #define A   A0 #define B   A1 #define C   A2 #define D   A3 // If your matrix has the DOUBLE HEADER input, //  please use values from RGB matrix panel code examples RGBmatrixPanel matrix(A, B, C, D, CLK, LAT, OE, false); int i = 0; // animation frame counter void setup() {  matrix.begin();           // initialise LED matrix  matrix.fillScreen(BLACK); // clear the screen } void loop() {  // first draw mask in black to clean up stray pixels  // mask[i] frame is simply inverse of img[i] frame  matrix.drawBitmap(0, 0,                    (const uint8_t *)pgm_read_word(&mask[i]),                    32, 32, BLACK);  // next draw bitmap in the colour we want  matrix.drawBitmap(0, 0, (const uint8_t *)pgm_read_word(&img[i]),                    32, 32, colarray[colsub]);  i++;  if (i == 4) {    // reset frame count as there are only 4 frames    i = 0;    // change colour too at end of each animation    colsub++;    colsub %= COLMOD;  }  delay(75);  // leisurely flying toasters ensue }

 

You'll need to add a file tab to the sketch called bitmaps.h for the toaster animation frames. You can do this with the little down-arrow (⏷) tab at the end of the Arduino IDE menu bar, then select “New Tab”. Paste this code in the new tab:

 

/*
   bitmaps.h -
   Toaster32x32 bitmap definitions
   scruss 2017-07 - for elmwood.to
   generated by
     image2cpp https://javl.github.io/image2cpp/
   from images provided in Phillip Burgess's
   Animated Flying Toasters for Adafruit
   https://learn.adafruit.com/animated-flying-toaster-oled-jewelry
*/

// AVR-specific code for storing data in program flash memory
#include <avr/pgmspace.h>

static const uint8_t PROGMEM
toastermask0[] = {
  // toaster0 mask - inverse of toaster0 image
  0xff, 0xcf, 0xff, 0xff, 0xff, 0x17, 0xff, 0xff,
  0xfe, 0x27, 0xff, 0xff, 0xfc, 0x8b, 0x0f, 0xff,
  0xf8, 0x10, 0x01, 0xff, 0xf8, 0x43, 0xf8, 0x7f,
  0xf0, 0x1f, 0x80, 0x1f, 0xf0, 0x7c, 0x3f, 0x8f,
  0xe1, 0xf1, 0xf8, 0xf7, 0xe7, 0xc7, 0xc7, 0x00,
  0xcf, 0x1f, 0x08, 0x2b, 0x9e, 0x7c, 0x11, 0x00,
  0x84, 0xf0, 0x20, 0x57, 0x61, 0xe4, 0x22, 0x01,
  0x78, 0x48, 0x40, 0xaf, 0x4e, 0x10, 0x44, 0x07,
  0x53, 0x90, 0x81, 0xff, 0x5c, 0xa0, 0x90, 0x5f,
  0x7c, 0xa1, 0x07, 0x97, 0x74, 0xa1, 0x21, 0x3f,
  0x7f, 0xb5, 0x0e, 0x37, 0x77, 0xb0, 0x90, 0x7f,
  0x7f, 0xb4, 0x60, 0xf7, 0x77, 0xbb, 0x03, 0xef,
  0x7f, 0xbc, 0x0f, 0xdf, 0x77, 0xbf, 0xff, 0x3f,
  0x7f, 0xbf, 0xfc, 0xff, 0xbf, 0xbf, 0xf3, 0xff,
  0xdf, 0xbf, 0xcf, 0xff, 0xe7, 0xbe, 0x3f, 0xff,
  0xf9, 0xb1, 0xff, 0xff, 0xfe, 0x0f, 0xff, 0xff
},
toastermask1[] = {
  // toaster1 mask - inverse of toaster1 image
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0x0f, 0xff,
  0xff, 0x08, 0x01, 0xff, 0xfe, 0x43, 0xf8, 0x7f,
  0xfc, 0x1f, 0x80, 0x1f, 0xf8, 0x7c, 0x3f, 0x8f,
  0xf1, 0xf1, 0xf8, 0x07, 0xe7, 0xc7, 0xc1, 0xe7,
  0xcf, 0x1f, 0x06, 0x17, 0x9e, 0x7c, 0x00, 0x07,
  0x84, 0xf0, 0x07, 0xf7, 0x61, 0xe4, 0x18, 0x0f,
  0x78, 0x48, 0x20, 0x80, 0x4e, 0x10, 0x40, 0x2b,
  0x53, 0x90, 0x81, 0x00, 0x5c, 0xa0, 0x80, 0x59,
  0x7c, 0xa1, 0x02, 0x07, 0x74, 0xa1, 0x28, 0xbf,
  0x7f, 0xb5, 0x0f, 0xf7, 0x77, 0xb0, 0x90, 0x7f,
  0x7f, 0xb4, 0x60, 0xf7, 0x77, 0xbb, 0x03, 0xef,
  0x7f, 0xbc, 0x0f, 0xdf, 0x77, 0xbf, 0xff, 0x3f,
  0x7f, 0xbf, 0xfc, 0xff, 0xbf, 0xbf, 0xf3, 0xff,
  0xdf, 0xbf, 0xcf, 0xff, 0xe7, 0xbe, 0x3f, 0xff,
  0xf9, 0xb1, 0xff, 0xff, 0xfe, 0x0f, 0xff, 0xff
},
toastermask2[] = {
  // toaster2 mask - inverse of toaster2 image
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff,
  0xff, 0xf8, 0x01, 0xff, 0xff, 0xc3, 0xf8, 0x7f,
  0xff, 0x1f, 0x80, 0x1f, 0xfc, 0x7c, 0x3f, 0x8f,
  0xf1, 0xf1, 0xf8, 0x07, 0xe7, 0xc7, 0xc1, 0xe7,
  0xcf, 0x1f, 0x06, 0x17, 0x9e, 0x7c, 0x00, 0x07,
  0x84, 0xf0, 0x00, 0x07, 0x61, 0xe4, 0x00, 0x0f,
  0x78, 0x48, 0x00, 0x17, 0x4e, 0x10, 0x00, 0x1f,
  0x53, 0x90, 0x60, 0x77, 0x5c, 0xa0, 0x9f, 0xc9,
  0x7c, 0xa1, 0x04, 0x92, 0x74, 0xa1, 0x09, 0x24,
  0x7f, 0xb5, 0x02, 0x49, 0x77, 0xb0, 0x80, 0x11,
  0x7f, 0xb4, 0x41, 0x03, 0x77, 0xbb, 0x20, 0x43,
  0x7f, 0xbc, 0x10, 0x07, 0x77, 0xbf, 0xf8, 0x0f,
  0x7f, 0xbf, 0xfc, 0x1f, 0xbf, 0xbf, 0xf3, 0x3f,
  0xdf, 0xbf, 0xcf, 0xff, 0xe7, 0xbe, 0x3f, 0xff,
  0xf9, 0xb1, 0xff, 0xff, 0xfe, 0x0f, 0xff, 0xff
},
toaster0[] = {
  0x00, 0x30, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00,
  0x01, 0xD8, 0x00, 0x00, 0x03, 0x74, 0xF0, 0x00,
  0x07, 0xEF, 0xFE, 0x00, 0x07, 0xBC, 0x07, 0x80,
  0x0F, 0xE0, 0x7F, 0xE0, 0x0F, 0x83, 0xC0, 0x70,
  0x1E, 0x0E, 0x07, 0x08, 0x18, 0x38, 0x38, 0xFF,
  0x30, 0xE0, 0xF7, 0xD4, 0x61, 0x83, 0xEE, 0xFF,
  0x7B, 0x0F, 0xDF, 0xA8, 0x9E, 0x1B, 0xDD, 0xFE,
  0x87, 0xB7, 0xBF, 0x50, 0xB1, 0xEF, 0xBB, 0xF8,
  0xAC, 0x6F, 0x7E, 0x00, 0xA3, 0x5F, 0x6F, 0xA0,
  0x83, 0x5E, 0xF8, 0x68, 0x8B, 0x5E, 0xDE, 0xC0,
  0x80, 0x4A, 0xF1, 0xC8, 0x88, 0x4F, 0x6F, 0x80,
  0x80, 0x4B, 0x9F, 0x08, 0x88, 0x44, 0xFC, 0x10,
  0x80, 0x43, 0xF0, 0x20, 0x88, 0x40, 0x00, 0xC0,
  0x80, 0x40, 0x03, 0x00, 0x40, 0x40, 0x0C, 0x00,
  0x20, 0x40, 0x30, 0x00, 0x18, 0x41, 0xC0, 0x00,
  0x06, 0x4E, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00
},
toaster1[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xF0, 0x00,
  0x00, 0xF7, 0xFE, 0x00, 0x01, 0xBC, 0x07, 0x80,
  0x03, 0xE0, 0x7F, 0xE0, 0x07, 0x83, 0xC0, 0x70,
  0x0E, 0x0E, 0x07, 0xF8, 0x18, 0x38, 0x3E, 0x18,
  0x30, 0xE0, 0xF9, 0xE8, 0x61, 0x83, 0xFF, 0xF8,
  0x7B, 0x0F, 0xF8, 0x08, 0x9E, 0x1B, 0xE7, 0xF0,
  0x87, 0xB7, 0xDF, 0x7F, 0xB1, 0xEF, 0xBF, 0xD4,
  0xAC, 0x6F, 0x7E, 0xFF, 0xA3, 0x5F, 0x7F, 0xA6,
  0x83, 0x5E, 0xFD, 0xF8, 0x8B, 0x5E, 0xD7, 0x40,
  0x80, 0x4A, 0xF0, 0x08, 0x88, 0x4F, 0x6F, 0x80,
  0x80, 0x4B, 0x9F, 0x08, 0x88, 0x44, 0xFC, 0x10,
  0x80, 0x43, 0xF0, 0x20, 0x88, 0x40, 0x00, 0xC0,
  0x80, 0x40, 0x03, 0x00, 0x40, 0x40, 0x0C, 0x00,
  0x20, 0x40, 0x30, 0x00, 0x18, 0x41, 0xC0, 0x00,
  0x06, 0x4E, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00
},
toaster2[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00,
  0x00, 0x07, 0xFE, 0x00, 0x00, 0x3C, 0x07, 0x80,
  0x00, 0xE0, 0x7F, 0xE0, 0x03, 0x83, 0xC0, 0x70,
  0x0E, 0x0E, 0x07, 0xF8, 0x18, 0x38, 0x3E, 0x18,
  0x30, 0xE0, 0xF9, 0xE8, 0x61, 0x83, 0xFF, 0xF8,
  0x7B, 0x0F, 0xFF, 0xF8, 0x9E, 0x1B, 0xFF, 0xF0,
  0x87, 0xB7, 0xFF, 0xE8, 0xB1, 0xEF, 0xFF, 0xE0,
  0xAC, 0x6F, 0x9F, 0x88, 0xA3, 0x5F, 0x60, 0x36,
  0x83, 0x5E, 0xFB, 0x6D, 0x8B, 0x5E, 0xF6, 0xDB,
  0x80, 0x4A, 0xFD, 0xB6, 0x88, 0x4F, 0x7F, 0xEE,
  0x80, 0x4B, 0xBE, 0xFC, 0x88, 0x44, 0xDF, 0xBC,
  0x80, 0x43, 0xEF, 0xF8, 0x88, 0x40, 0x07, 0xF0,
  0x80, 0x40, 0x03, 0xE0, 0x40, 0x40, 0x0C, 0xC0,
  0x20, 0x40, 0x30, 0x00, 0x18, 0x41, 0xC0, 0x00,
  0x06, 0x4E, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00
};

// mask frame array for animation
// note that here frames 2 & 4 are the same
const uint8_t* const mask[] PROGMEM = {
  toastermask0, toastermask1, toastermask2, toastermask1
};

// image frame array for animation
// note that here frames 2 & 4 are the same
const uint8_t* const img[] PROGMEM = {
  toaster0, toaster1, toaster2, toaster1
};

 

Defining animation frames of your own is relatively simple. The Flying Toasters are just four bitmap frames, each 32×32 pixels:

 

Image:
Frame: 1 2 3 4
(same as 2)

 

In the old days, we'd have to work out bitmaps on squared paper. It was slow and annoying, but now entirely superseded by the image2cpp tool. You can drag and drop images on it, and it will create the correct data for copying into the Arduino IDE.

Unlike making an animated GIF, however, LED animations need to explicitly blank out the previous frames, or you'll quickly end up with a display that looks like this, with all the frames overlapping:

So each frame has to be written in two stages:

  1. Erase every pixel that's not in the frame by drawing in the inverse (‘mask’) image in black;
  2. Draw every pixel in the frame in the colour you want.

Since the animation frames are written to the Arduino's flash memory and can't be modified by the sketch as it runs, we have to define the masks as well as the image in the code. This is really easily done through image2cpp by selecting “Invert image colors” for each frame.

 

Image:
Mask:
Frame: 1 2 3 4
(same as 2)

 

The masks look a little confusing as the white pixels above are actually drawn in black on the RGB matrix to erase what was there before.


Stewart Russell
Stewart Russell

Author

mostly harmless