SSD1306 I2C OLED - Muybridge Animation

I found this Muybridge image of a horse running:

I cut up all of the frames using MSPaint. I then opened each frame with Image2GLCD and saved each frame. I then, entered each frame into the code attached below. I would have posted the code to this window... there is a maximum of 9000 letters. You'll see in the video I used an Arduino UNO SMD and a SSD1306 OLED. Have fun!

I posted a video of the OLED in action: Arduino UNO I2C SSD1306 Muybridge Animation - YouTube

OLEDHorse.ino (94.2 KB)

This looked like fun. So I tried it for myself. However I simplified it somewhat by putting the tables in a separate tab:
horses.cpp:

#include <Arduino.h>

static const unsigned char PROGMEM horse00[] = {
...
 
};

const uint8_t *horses[] = {     //global array of pointers to the private data 
   horse00, horse01, horse02, horse03, horse04, horse05, horse06, horse07,
   horse08, horse09, horse10, horse11, horse12, horse13, horse14
};

OLED_Horse.ino:

/*
 *  Image of Horse Gallping originally photograph by Eadweard Muybridge. For more information
 *  about Eadweard Muybridge go to: https://en.wikipedia.org/wiki/Eadweard_Muybridge
 *  Image: http://animationresources.org/wp-content/uploads/2015/05/Muybridge_race_horse_gallop.jpg
 *  Software to convert images to 128x64 at: http://www.ablab.in/image2glcd-software/
 *
 *  Minimal sketch to run animation with 128x64 I2C SSD1306. Written by Greg Stievenart with no
 *  claim to or any information provided in this code.
 */

#include <Wire.h>                     // requried to run I2C SSD106
#include <SPI.h>                      // requried to run I2C SSD106
#include <Adafruit_GFX.h>             // https://github.com/adafruit/Adafruit-GFX-Library
#include <Adafruit_SSD1306.h>         // https://github.com/adafruit/Adafruit_SSD1306

#define OLED_CLK 13
#define OLED_MOSI 11
#define OLED_RESET 8
#define OLED_DC 9
#define OLED_CS 10

Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS); // I use SPI
//Adafruit_SSD1306 display(OLED_RESET); // reset required for SSD1306

extern const uint8_t *horses[];       // private tables and public pointer array in separate file

int framecount = 0;

void setup() {

    display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // required to run SSD1306
    display.clearDisplay();
}

void loop() 
{
    display.fillRect(0, 0, 128, 64, WHITE);
    if (++framecount > 14) framecount = 0;
    display.drawBitmap(0, 0, horses[framecount], 128, 64, BLACK);
    display.display();
}

Then I wondered what it would look like on a TFT. This just required a helper function to draw a Mono image:

TFT_horse.ino

/*
 *  Image of Horse Gallping originally photograph by Eadweard Muybridge. For more information
 *  about Eadweard Muybridge go to: https://en.wikipedia.org/wiki/Eadweard_Muybridge
 *  Image: http://animationresources.org/wp-content/uploads/2015/05/Muybridge_race_horse_gallop.jpg
 *  Software to convert images to 128x64 at: http://www.ablab.in/image2glcd-software/
 *
 *  Minimal sketch to run animation with 128x64 I2C SSD1306. Written by Greg Stievenart with no
 *  claim to or any information provided in this code.
 */

#include <Adafruit_GFX.h>             // https://github.com/adafruit/Adafruit-GFX-Library
#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;

extern const uint8_t *horses[];
#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF


int framecount = 0;

void draw_mono(int x, int y, const uint8_t *buf, int w, int h, uint16_t fg, uint16_t bg)
{
    uint8_t row, col, c, first = 1, idx, mask;
    uint8_t *temp = (uint8_t*)buf;
    uint16_t color;
    tft.setAddrWindow(x, y, x + w - 1, y + h - 1); //we draw left-right, then down
    for (row = 0; row < h; row++) {
        mask = 0;
        for (col = 0; col < w; col++) {
            //            if (mask == 0) mask = 0x80, c = pgm_read_byte((uint8_t*)buf + col/8 + (row * (w/8)));
            if (mask == 0) mask = 0x80, c = pgm_read_byte(temp++);
            color = (c & mask) ? fg : bg;
            tft.pushColors(&color, 1, first);
            first = 0;
            mask >>= 1;
        }
    }
}

void setup(void)
{
    uint16_t ID = tft.readID();
    if (ID == 0xD3D3) ID = 0x9481; // write-only shield
    tft.begin(ID);
    tft.fillScreen(BLUE);
}

void loop()
{
    if (++framecount > 14) framecount = 0;
    draw_mono(0, 0, horses[framecount], 128, 64, BLACK, CYAN);
}

If your TFT library has a Mono Bitmap method, you don't even need a helper function.

Both the OLED and the TFT animate at about the same speed on a Uno.
I have not altered stievenart's image data. The Monochrome image might look better with "less" contrast.

David.

Very impressive. I thought this post would take a different path with people cutting up other images. It would make more sense to make a library file and a sketch player.

I have attached an image of a Star Wars AT-AT that someone might animate:

I think that people may well add other animations. Silhouettes or line drawings work well with Monochrome displays.

You can fit 16 Frames into Flash memory and still run it on a Uno.
You can adjust the number or order of frames very easily.

You can store 16-colour, 256-colour or even 65k colour animations on a Flash chip or SD card.
Of course there is a trade-off between size and frame rate.

An animated colour GIF or multiple JPEGs might fit in a Mega but the processing might limit the frame rate.
A Uno will not have enough SRAM.

The subject looks fun.

David.

I have an SPI version of the display connected to a NodeMCU (EPS8266) (which can be programmed via the Arduin0 IDE), I just changed the animation sketch start to:

#define OLED_SDA D7 //MOSI
#define OLED_SCL D5 //CLK
#define OLED_DC D4 //DC
#define OLED_CS 12 // no need of connecting anything to this pin, just use some pin number
#define OLED_RESET D3 //RES

// this is the constructor to call the OLED display where we specify exact pin numbers.
Adafruit_SSD1306 display(OLED_SDA, OLED_SCL, OLED_DC, OLED_RESET, OLED_CS);

It ran at 60fps (16ms per frame) which is a bit too fast, a 60ms delay in the loop was needed to run at UNO equivalent speed.

david_prentice:
This looked like fun. So I tried it for myself. However I simplified it somewhat by putting the tables in a separate tab:
horses.cpp:

david_prentice,

Can you post the image file as an attachment to this thread? I would like to try it out.

~stievenart

I have zipped up the OLED_horse sketch and the TFT_horse sketch and attached them.

The "data" is exactly the same i.e. the horses.cpp file.

It should be pretty straightforward to create other animations but requires some familiarity with Paint or other Imaging programs. Most importantly, rendering frames that look attractive in Monochrome.

I can copy your multi-pane image and turn it into a Bitmap with Paint.
I can probably write a PC program to cut it up into 16 frames. I can't see how to do that in Paint.
And then I need to experiment with different Grayscales and scaling to 128x64 Mono.

If I can work out how to do that, I could turn the frames into JPEGs. I can probably fit 16 low-res JPEGs into Flash memory but there is no way to display the JPEG with a Uno.

You just can't fit colour Bitmaps into Flash memory. A 32kB Uno is too small. The OLED animation is well cool on a Uno. It looks a little small on a TFT.

I would need to learn about animated GIFs. Perhaps I can display them on a Uno. Do you have any experience?

David.

OLED_Horse.zip (7.63 KB)

TFT_Horse.zip (7.83 KB)

Why do you use software SPI on the hardware SPi pins?

Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS); // I use SPI
//Adafruit_SSD1306 display(OLED_DC, OLED_RESET, OLED_CS); // hardware SPI

Adafruit_SSD1306::Adafruit_SSD1306(int8_t SID, int8_t SCLK, int8_t DC, int8_t RST, int8_t CS) : Adafruit_GFX(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT) {
  cs = CS;
  rst = RST;
  dc = DC;
  sclk = SCLK;
  sid = SID;
  hwSPI = false;
}

Absolutely no reason at all. I use the hardware pins so I might as well use the hardware constructor(). Others might use GPIO pins.

As it happens, the bit bashed software SPI works at a reasonable animation speed on a Uno.
Bodmer inserted a delay for the Due.

The animation is feasible on a Monochrome 128x64 or even a 128x64 window on a TFT. The data for 16 frames can fit in a Uno. Colour data for 16 frames would need compression to fit into Flash.

I have not tried it yet. JPEG or animated GIF would need the SRAM and processing power of a Due or Zero.
Even then, you may not get a realistic animation frame rate.

It would be nice if "Paintshop experts" produced some other animated frames.

David.

david_prentice:
I can probably fit 16 low-res JPEGs into Flash memory but there is no way to display the JPEG with a Uno.

A MEGA can do that
http://forum.arduino.cc/index.php?topic=376768.msg2597824#msg2597824
I have done it with an STM32 using the same library.

Just changed the setup to use HW SPI on the NodeMCU and there is some speed improvement to ~100frames per second without a delay() in the loop.

Start of sketch was changed to:

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Using hardware SPI pins:
#define OLED_SDA D7 //MOSI
#define OLED_SCL D5 //CLK
#define OLED_DC D4  //DC
#define OLED_CS 12  // no need of connecting anything to this pin, just use some pin number
#define OLED_RESET D3 //RES

// Don't specify MOSI and SCLK so hardware SPI used
Adafruit_SSD1306 display(OLED_DC, OLED_RESET, OLED_CS);

Hi

Beautiful work

I Need your help, please:

I got a project that needs a simple animation, but the animation must have an option to rotate around the center of the screen due to a precise angle command

I'm sure there is a simple solution for it (animation and rotation) but I do not know where to look

Thanks a lot

Tal

TalBY:
Hi
I Need your help, please:

I got a project that needs a simple animation, but the animation must have an option to rotate around the center of the screen due to a precise angle command

I'm sure there is a simple solution for it (animation and rotation) but I do not know where to look

Tal

I am going to attempt to answer this with an animation that works with a loop and sin and cosine. I made another quick video of the outcome at: http://www.youtube.com/watch?v=AOkIm04BdFk

This animation is not done the with individual frames shown previously, but drawing lines and circles using the Adafruit drawing library. See attached code:

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4

Adafruit_SSD1306 display(OLED_RESET);

int hSpin = 30;
int vSpin = 32;
int SpinRadius = 60;

void setup(){
  
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
}

void loop(){

  for(int i=60; i>0; i--){                       //Change loop to "for(int i=0; i<60; i++){" to spin clockwise
    display.fillRect(0, 0, 128, 64, BLACK);      //Blanks out last frame to black
    display.setTextColor(WHITE);
    display.setTextSize(3);
    display.setCursor(74,25);                                       // Position of text
    display.print(i);                                                
    display.fillCircle(hSpin, vSpin, SpinRadius / 2, WHITE);        // Draws a white filled circle
    display.fillCircle(hSpin, vSpin, (SpinRadius / 2) - 1 , BLACK); // draws a black circle to refresh line movement
 
    float SpinAngle = i * 6; 
    SpinAngle = SpinAngle / 57.296;                                 // converts degrees to radians
    int a = (hSpin + (sin(SpinAngle) * (SpinRadius / 2)));          //coordinate for end of line position horizontally
    int b = (vSpin - (cos(SpinAngle) * (SpinRadius / 2)));          //coordinate for end of line position vertically

    display.drawLine(hSpin,vSpin,a,b,WHITE); // draws rotating line from center out to horizontal and vertical position.
    display.display();                       // refresh screen
    delay(10);                               //Set delay to 1000 and mimick second hand movement
  }
}

I took that last example to a new personal height... I made the circular path and divided in half. Effectively making an elliptical path. I put a second elliptical path just below and made four equal distance points on each ellipse forming a square on each ellipse. I then connected the top and bottom squares forming a cube. So, now it's a rotating cube.

See cube rotating at: OLEDCube - SSD1306 I2C OLED Animation - YouTube

Extra code to be added to last example:

   float SpinAngle = i * 6; 
    SpinAngle = SpinAngle / 57.296;
    float SpinAngle2 = SpinAngle + 90 / 57.296;
    
    int a = (hSpin + (sin(SpinAngle) * (SpinRadius / 2)));
    int b = (vSpin - (cos(SpinAngle) * (SpinRadius / 2)))/2;
    int c = (hSpin - (sin(SpinAngle) * (SpinRadius / 2)));
    int d = (vSpin + (cos(SpinAngle) * (SpinRadius / 2)))/2;

    int a1 = (hSpin + (sin(SpinAngle2) * (SpinRadius / 2)));
    int b1 = (vSpin - (cos(SpinAngle2) * (SpinRadius / 2)))/2;
    int c1 = (hSpin - (sin(SpinAngle2) * (SpinRadius / 2)));
    int d1 = (vSpin + (cos(SpinAngle2) * (SpinRadius / 2)))/2;

    int e = (hSpin + (sin(SpinAngle) * (SpinRadius / 2)));
    int f = (vSpin2 - (cos(SpinAngle) * (SpinRadius / 2)))/2;
    int g = (hSpin - (sin(SpinAngle) * (SpinRadius / 2)));
    int h = (vSpin2 + (cos(SpinAngle) * (SpinRadius / 2)))/2;

    int e1 = (hSpin + (sin(SpinAngle2) * (SpinRadius / 2)));
    int f1 = (vSpin2 - (cos(SpinAngle2) * (SpinRadius / 2)))/2;
    int g1 = (hSpin - (sin(SpinAngle2) * (SpinRadius / 2)));
    int h1 = (vSpin2 + (cos(SpinAngle2) * (SpinRadius / 2)))/2;
    
    display.drawLine(a,b,a1,b1,WHITE);
    display.drawLine(a1,b1,c,d,WHITE);
    display.drawLine(c,d,c1,d1,WHITE);
    display.drawLine(c1,d1,a,b,WHITE);

    display.drawLine(e,f,e1,f1,WHITE);
    display.drawLine(e1,f1,g,h,WHITE);
    display.drawLine(g,h,g1,h1,WHITE);
    display.drawLine(g1,h1,e,f,WHITE);
    
    display.drawLine(a,b,e,f,WHITE);
    display.drawLine(g,h,c,d,WHITE);
    display.drawLine(a1,b1,e1,f1,WHITE);
    display.drawLine(g1,h1,c1,d1,WHITE);

A second version with the ellipses turn opposite to each other causing the cube animation to have depth or perspective.

See cube with perspective rotating at: OLEDDepth - SSD1306 I2C OLED Animation - YouTube

Revised addition code:

   SpinAngle = SpinAngle / 57.296;
    float SpinAngle2 = SpinAngle + 90 / 57.296;
    
    int a = (hSpin - (sin(SpinAngle) * (SpinRadius / 2)));
    int b = (vSpin - (cos(SpinAngle) * (SpinRadius / 2)))/2;
    int c = (hSpin + (sin(SpinAngle) * (SpinRadius / 2)));
    int d = (vSpin + (cos(SpinAngle) * (SpinRadius / 2)))/2;

    int a1 = (hSpin + (sin(SpinAngle2) * (SpinRadius / 2)));
    int b1 = (vSpin + (cos(SpinAngle2) * (SpinRadius / 2)))/2; 
    int c1 = (hSpin - (sin(SpinAngle2) * (SpinRadius / 2)));  
    int d1 = (vSpin - (cos(SpinAngle2) * (SpinRadius / 2)))/2;

    int e = (hSpin + (sin(SpinAngle) * (SpinRadius / 2)));
    int f = (vSpin2 - (cos(SpinAngle) * (SpinRadius / 2)))/2;
    int g = (hSpin - (sin(SpinAngle) * (SpinRadius / 2)));
    int h = (vSpin2 + (cos(SpinAngle) * (SpinRadius / 2)))/2;

    int e1 = (hSpin - (sin(SpinAngle2) * (SpinRadius / 2)));
    int f1 = (vSpin2 + (cos(SpinAngle2) * (SpinRadius / 2)))/2;
    int g1 = (hSpin + (sin(SpinAngle2) * (SpinRadius / 2)));
    int h1 = (vSpin2 - (cos(SpinAngle2) * (SpinRadius / 2)))/2;
    
    display.drawLine(a,b,a1,b1,WHITE);
    display.drawLine(a1,b1,c,d,WHITE);
    display.drawLine(c,d,c1,d1,WHITE);
    display.drawLine(c1,d1,a,b,WHITE);

    display.drawLine(e,f,e1,f1,WHITE);
    display.drawLine(e1,f1,g,h,WHITE);
    display.drawLine(g,h,g1,h1,WHITE);
    display.drawLine(g1,h1,e,f,WHITE);
    
    display.drawLine(a,b,g,h,WHITE);
    display.drawLine(a1,b1,g1,h1,WHITE);
    display.drawLine(c,d,e,f,WHITE);
    display.drawLine(c1,d1,e1,f1,WHITE);

The old TVout library has a demo of a spinning 3D wireframe cube

zoomx:
The old TVout library has a demo of a spinning 3D wireframe cube
arduino-tvout/examples/DemoPAL/DemoPAL.pde at master · cbmeeks/arduino-tvout · GitHub

I'll bet your library doesn't have a wireframe of a spinning dodecahedron...

See spinning dodecahedron animation at: http://youtu.be/y-KIPsp4hps

I made a second dodecahedron animation with perspective by rotating the bottom elliptical path opposite to the top three paths.

Dodecahedron animation with perspective at: https://youtu.be/VPEzzP9_Wyc

Well done!

It's not my library. I have a copy of it in github (but the link is not my copy) because the original repository is on Google Code that has closed.

Anyway Claus Ilginnis made a better work fixing more bugs here

But there is not any spinning dodecahedron!
See the spinning cube here

from 0:55. The rotation is not limited on one axis.

I just started exploring this subject and decided to go in a different direction. I created a Mac/Linux program to take animated GIFs and turn them into a compact form that's easy to play back (my own byte oriented compression). The player code doesn't need any RAM and runs well on an ATtiny85. I'm currently documenting everything and will release it as open source soon. Anyone interested can contact me with suggestions/requests before I publish it.

Here's a video of it running a short animation on an ATtiny85:

Youtube Video

I just added a blog post about it:

Practical animation on I2C SSD1306 displays

Interesting!

Maybe you can consider cheap MCU like the STM32F103 (bluepill but STM32F4 series also!) or the ESP8266 for bigger animations!

I am very impressed with decoding a GIF with a tiny85.

We look forward to your posts.

David.