Eadweard Muybridge’s galloping horse on Arduino 128*64 LCD, OLED and TFT displays

by Floris Wouterlood – May 23, 2020

Summary

The legacy of Eadward Muybridge (1830-1904) embraces an impressive series of photographs of animals and humans frozen in motion and time. Muybridge took this branch of photography to great height.
A landmark picture is ‘The Horse in Motion, published in 1878, the result of a session in which a horse named ‘Sallie Gardner’ galloped along a battery of 12 shutter-connected photocamera’s.

Stop-motion animation is the reverse process of freezing motion: such an animation is produced by displaying successive picture frames at high speed. Here we bring back to life Muybridges’ Sallie Gardner with the aid of a microcontroller connected to a suitable display. Several displays were tested with the ‘Sallie Gardner’ sketch running on an Arduino Uno, Nano or a Wemos D1 mini with an ESP8266 microprocessor chip.

Introduction
Arduinos are designed to microprocess: they receive data, calculate, and communicate. Output through a display is limited since Arduinos lack a graphical processor. All the hard work in a common Arduino (Uno, Nano) is performed by a single 8-bit processor (ATmega 328) that has limited memory: 1kB EEPROM, 2kB SRAM (‘dynamic memory’) and 32kB flash memory (‘program memory’).

figure 1. Reproduction of Eadweard Muybridge’s famous montage picture ‘The Horse in Motion’ proving that a galloping horse at some point has all four legs off the ground. The original picture was made in 1878. Copy provided by Library of Congress Prints and Photographs Division – Wikipedia.

Programs are loaded in program memory while variables are created and during runtime stored in dynamic memory. Users have to live with the dynamic and program memory constraints offered by a common Arduino, or they can switch to an Arduino ‘Mega’ with an ATmega2560 microprocessor (256 kB flash and 8kB SRAM). One jump higher on the ladder is the family of ESP8266 based microboards, e.g., the Wemos D1 mini: 4 MB of SRAM and 50 kB of flash memory.
The challenge in the current project was to achieve fluent stop-motion animation featuring Sallie Gardner, using a humble Arduino Nano, and to see how well the animation performs with several displays. For this purpose I had available a 128*64 pixel LCD display with ST7920 controller, a 128*64 hybrid OLED display with SSD1306 controller, a 130*130 SSD TFT screen with 1283A controller, a 240*240 pixel TFT display powered by a SSD1283A chip, and several larger Arduino compatible TFT displays driven by ILI controllers

Reduction of picture frames to monochrome 128×64 pixel bitmaps
Muybridge’s photograph ‘The Horse in Motion’, featuring Sallie Gardner is a composition of 12 frames (figure 1). Eleven of these show Sallie in various phases of movement, while the 12th frame shows Sallie and its jockey at rest. The actual galloping sequence takes eleven image frames, and this is the number of frames we want to include in our stop-motion.
Our LCD display limits images to 128×64 pixels. The original ‘The Horse in Motion’ picture is an eight-bit gray scale jpg file from which we have to cut out frames to produce the animation bitmaps. Each cut-out frame produces a file with a size of 19 kB. Eleven of these files take 198 kB which is far beyond the amount of memory available in program space of an Arduino Nano. One solution for this is to load the files one after another from sd card, but the time consumed by this process would produce a slide show rather than the illusion of motion. A better solution is to load image frames from a separate .cpp file that needs to reside in the same directory as the sketch. The third approach, used in this paper, is to include the images in the sketch. One way or another, we need to reduce the image frames for our stop-motion animation to dimensions of 128×64 pixels or less to fit the screen.
A major consideration is that LCDs and OLEDs typically work with pixels that are either ON or OFF, that is, these devices require one-bit images: black / white. One-bit images are also called ‘monochrome’.
The combination all these considerations leads to the need to cut Muybridge’s ‘The Horse in Motion’ photograph down to eleven 1-bit frames each 128 pixels wide and 64 pixels high. Smaller frames are allowed; an advantage of smaller frames is faster loading in the LCD and, for that matter, a smoother animation.

figure 2. Direct bitmap conversion from 8-bit gray level to 1-bit (monochrome) wherein the final image is a 96×64 pixel image. The conversion produces ‘dithering’ which as a ‘spray’ of black pixels mimics grey shades.

Procedure 1: Grayscale bitmap directly to monochrome bitmap
The ‘The Horse in Motion’ photograph as I acquired it from wikipedia is a grayscale, eight-bit JPG formatted file*. If we cut a frame, say Sallie Gardner nr. 11, out of the original picture with for instance Photoshop or The Gimp and reduce it to a size that fits a 128*64 screen we can subsequently export the image in monochrome bitmap format. Here, export filters dither grey tones, that is, they produces speckles sprayed in the image to sort of mimic grey shades (figure 2). The amount of dithering can be reduced by enhancing contrast and intensity of the original cut before applying the export filter. In most cases the monochrome image files need some retouch step to remove traces of dithering. The preferred format of the output image files is Microsoft bmp. Note that these monochrome images are not nicely scalable, that is, if one attempts to do that the horse becomes a stack of big black squares. Anti-aliasing can not be applied to monochrome images.

Procedure 2: Grayscale bitmap via vector to monochrome bitmap
An alternative procedure whose big advantage is that scaling remains possible is to import the original ‘The Horse in Motion’ image in a layer in a vector-based drawing program. For this purpose I use Xara ‘Photo&Graphic Designer’ running under Windows. One can use other programs that support vector images such as Adobe Illustrator, Canvas, Inkscape, and Coreldraw.

figure 3. Import of a cut-out frame (nr. 11) of the original picture in a vector program is followed by manual segmentation (upper right; red contour). Subsequent deletion of the original bitmap produces a vector contour that can be scaled indefinitely without loss of sharpness. This vector image can then be exported to 1-bit monochrome bmp image format. If your vector drawing programs doesn’t have a monochrome bitmap export filter you will need extra software to convert the export file into proper monochrome bmp.

Procedure in detail:
1. Prepare in the vector program a file with a stack of 13 layers.
2. Import the ‘The Horse in Motion’’ picture in the lowest, ‘bottom’ layer of the stack.
3. Label the layers from bottom to top: ‘bottom’ layer is ‘bitmap layer, layer two is ‘frame_00’, layer three is ‘frame_01’ and so forth.
4. Draw in the appropriate layer using a ‘pencil’ tool the contour of Sallie Gardner and its rider (by hand). Do this for each frame of the photograph and take care when a contour is finished to connect the start and end points of the contour. You will have now a closed, uninterrupted contour that can be filled in with any color of choice (figure 4). There will of course be 12 contours because the ‘Sallie Gardner’ picture is a mosaic of 12 frames. Very important for later processing, centering and calibration is that each contour is located in its own layer of the vector drawing.

figure 4. Illustration of the vector procedure. Upper part: closed contour drawings of the horse and its rider are prepared in the vector drawing program as overlay for each frame of Muybridge’s original photograph. Lower part: the underlying bitmap is deleted, a 2:1 proportion frame added and export to 1-bit bitmap files can start. Note that a big advantage of vector images (the contour drawings) is that they remain scalable without loss of quality.

5. Delete the bottom layer with the photograph. You can also hide this layer.
6. What remains is one big vector contour file that contains a stack of 12 contours, with each contour in a dedicated layer.
7. Add a layer with an outlined rectangle with 2:1 proportion, i.e., the same as that of a 128*64 pixel space.
8. Now the vector image is a stack of 13 visible layers: one layer with the frame proportions and the other layers each containing one stop-motion contour.
9. Scale the entire stack such that animation frame bitmaps, when exported, satisfy the conditions for LCD display: maximum width 128 pixels, maximum height 64 pixels. In the case of Sallie Gardner the scaling was conducted such that exported animation frame images measured 96×64 pixels, that is: height is here the limiting factor for the Sallie Gardner animation frames.
10. What remains to be done is export. Each layer that contains a contour is exported as a 96×64 pixel monochrome .bmp file.

An intermediate, monitoring step is to check with an animated GIF creating program whether all export actions have been executed correctly and indeed have produced the 1-bit monochrome bitmaps that we need for the animation. I use Fiji (ImageJ) for this purpose. Fiji is a wonderful, free, open source cross platform imaging suite built on a Java platform (https://imagej.net/Fiji). One can also check with an animated GIF how fluid the movements in the Arduino animation will be. Small corrections can at this point be made in the vector file to get the very best out of the animation.
While Arduinos can work with bitmap files loaded from sd card, the loading and processing from sd card is is not fast enough to support animation. The monochrome images that we have produced need to be embedded in the sketch in 8-bit hexadecimal code (so-called ‘c-array’), or as ‘extern’ in the sketch folder

Creating hexadecimal c-array image code
Conversion from bitmap to hexadecimal code is done with the program lcd-image-converter (freely available at https://sourceforge.net/projects/lcd-image-converter/). There exist also on line conversion tools.
Output of lcd-image-converter is a c-array file that, because it is in plain ascii text format, can be opened in any ascii editor (Notepad, Notepad++, Gedit) (figure 5).

A c-array output file produced by lcd-image-conversion.exe contains three sections: header, image and hexadecimal code.

The header section (all lines commented out) contains information about the file and how the original bitmap has been processed.
The image section contains commented-out lines with dots and colored blocks that provide a visual impression of the 1-bit image. Notice that there are 64 lines with each 96 dots or blocks, in other words a representation of what is going to be displayed on screen.
The bottom section is the most valuable part of the file: the hex array code. Pixels are coded in groups of eight; because the image will be 96 pixels wide there are 96 : 8 = 12 columns of bytes. The number of rows is 64 or equal to the height of the image.
The c-array starts with a variable: static const uint8_t, followed by the image’s file name and size (between the brackets). File size of each c-array is 768 bytes which will result in an animation that has 11×768 bytes = 8,448 bytes. With an Arduino Uno or Nano these c-arrays must be placed in program memory because the dynamic memory can hold only 2 kB of information.
The solution here is to place the c-arrays in program memory as a static const unsigned char. Each c-array then must be defined as follows:

static const unsigned char horse_02[] PROGMEM = {
… followed by the hexadecimal byte array,
and closed with };

An example is given below.

figure 5. C-array file opened with an ascii editor (gedit). Frame 0. Left column: the entire c-array file listed. C-array files are composed as follows: top section: array information (commented), middle section: visual output (commented). All you need is in fact to copy-paste the bottom section: hex code array (pink) into the Arduino sketch.
Note that the hex code consist here of 12 columns and 64 rows, corresponding with 12*8 = 96 pixels horizontally and 64 pixels vertically.

Preparation of the Arduino sketch
If an array is defined in a sketch it will by default be compiled by the Arduino IDE to run in dynamic memory. As shown above, each frame of our animation requires 768 bytes. All the c-arrays must be placed in in program memory as constant unsigned chars. To achieve this the library <avr/pgmspace.h> is required, and we must redefine each array. For frame horse_00 the c-array the redefinition is the following:

static const unsigned char horse_00[] PROGMEM = {

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

( …., …., …., etc)

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00
};

To save space only four of the in total 64 rows of twelve columns of hexadecimal coded bytes are shown here. Each byte codes for 8 pixels.
If the instruction PROGMEM is not included in the char definition the c-array will be loaded in dynamic memory. In a Wemos D1 dynamic memory space is 4 MB so in this platform there is plenty of dynamic memory available and the PROGMEM instruction and the <avr/pgmspace.h> library are not necessary.

Sketch to run the animation on a 128*64 ST7920 LCD / Arduino Nano
The stop-motion was initially developed on a 128*64 LCD, just out of curiosity to know in the first place whether it was possible to create an animation on technology so limited in graphic display. The library available to work with the 128*64LCD is <U8glib.h> by Olikraus. As can be seen in the basic sketch below, void setup(void) is empty. All action is in void draw(void).

// LCD_128x64_ST7920_nano_muybr_horse_96x64
// ST7920 controller
// uses u8glib by Olikraus
// bitmaps loaded in memory

// 10 frames 96 x 64 pixels
// as frame 01 and frame 12 of Muybridges’ The Horse in Motion
// are atypical, these are not included
// series starts with frame 02 collected from The Horse in Motion

// images drawn and hex converted from original Eadweard Muybridge print ‘Sallie Gardner’
// animation by
// Floris Wouterlood
// May 22, 2020
// public domain

// note that all graphic commands to redraw the complete screen
// must be placed in draw(void)
// no subroutines allowed in draw(void)!

#include <avr/pgmspace.h>
#include <U8glib.h>

U8GLIB_ST7920_128X64 u8g (13, 11, 12, U8G_PIN_NONE);

static const unsigned char horse_02[] PROGMEM = {

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x20, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0xf8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0xfe, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0x07, 0xff, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0x0f, 0xff, 0xc0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0x07, 0xff, 0xe0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xfc, 0x0f, 0xff, 0xf0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc, 0x1f, 0xff, 0xfc, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfe, 0x1f, 0xff, 0xff, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe7, 0xff, 0xff, 0xff, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0xff, 0xff, 0xff, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc3, 0xff, 0xf8, 0x3f, 0x80,
0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0xcf, 0xff, 0xff, 0xf0, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00,
0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00,
0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00,
0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00,
0x01, 0x87, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00,
0x07, 0xcf, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
0x07, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
0x07, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00,
0x03, 0xff, 0xa0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00,
0x01, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00,
0x00, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00,
0x00, 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xff, 0xc3, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0f, 0xff, 0xc0, 0x0f, 0xff, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1f, 0xc7, 0xc0, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1f, 0x87, 0x80, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0f, 0xc7, 0x80, 0x00, 0x7e, 0xfc, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xff, 0x80, 0x00, 0xf8, 0x7e, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, 0x00, 0xf8, 0x1f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3f, 0xf8, 0x01, 0xf0, 0x1f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0x01, 0xfc, 0xff, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0f, 0x3f, 0x83, 0xff, 0xfe, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0xc3, 0xff, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0f, 0x1f, 0xff, 0x8e, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0f, 0x3f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x06, 0x7f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

static const unsigned char horse_03 [] PROGMEM = {

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

… and so forth until the end of frame_11…
Next in the sketch follows:
uint8_t toggle = 0;

void draw(void) { // graphic commands to redraw the complete screen should be placed here

if (toggle ==0)
u8g.drawBitmapP (20, 0, 12, 64, horse_frame_01); // frame 01 of horse 96*64
else if (toggle ==1)
u8g.drawBitmapP (20, 0, 12, 64, horse_frame_02); // frame 02 of horse 96*64
else if (toggle ==2)
u8g.drawBitmapP (20, 0, 12, 64, horse_frame_03); // frame 03 of horse 96*64
else if (toggle ==3)
u8g.drawBitmapP (20, 0, 12, 64, horse_frame_04); // frame 04 of horse 96*64
else if (toggle ==4)
u8g.drawBitmapP (20, 0, 12, 64, horse_frame_05); // frame 05 of horse 96*64
else if (toggle ==5)
u8g.drawBitmapP (20, 0, 12, 64, horse_frame_06); // frame 06 of horsew 96*64
else if (toggle ==6)
u8g.drawBitmapP (20, 0, 12, 64, horse_frame_07); // frame 07 of horse 96*64
else if (toggle ==7)
u8g.drawBitmapP (20, 0, 12, 64, horse_frame_08); // frame 08 of horse 96*64
else if (toggle ==8)
u8g.drawBitmapP (20, 0, 12, 64, horse_frame_09); // frame 09 of horse 96*64
else
u8g.drawBitmapP (20, 0, 12, 64, horse_frame_10); // frame 10 of horse 96*64
}

void setup(void) {
Serial.begin (9600);
Serial.println (“Muybridge’s 1878 ‘The Horse in Motion'”);
Serial.println (“this stop-motion animation includes frames 2 – 10”);
Serial.println (“of ‘Sallie Gardner’ galloping”);
Serial.println ();
Serial.println (“frames are loaded in progmem”);
Serial.println (“May 22, 2020 – FGW”);
Serial.println ();
}

void loop(void) {
u8g.firstPage(); // picture loop
do {
draw();
} while( u8g.nextPage() );
toggle++;
if (toggle>10) toggle =0;
delay (25);
}

The complete sketch for the 128*64 ST7920 LCD wired to an Arduino Nano is supplied at the end of this document. You will find also versions of the sketch compatible with different display / microprocessor combinations. All these sketches contain the hexadecimal c-array description for each frame. I have added for each frame in a series of commented lines of empty and black characters a visual impression of how the frame looks like.

Results and discussion
From the beginning it was evident that frame nr. 11 of “The Horse in Motion” would not contribute to a fluent animation. I assumed that each of the other frames would smoothly transfer to the next and that each animation cycle of frames would smoothly overflow into the next. However, the prototype sketch running on an Arduino Nano that powers a 128*64 LCD with ST7920 controller (figure 6), hiccuped at the point where it cycles from the last frame to the first. The cause appeared to be frame nr. 0 that seems to be atypical. Maybe this frame has been included by Muybridge to produce an appealing 4×3 photomontage. Who knows. Anyway, after frame nr 0 was excluded a very smoothly running animation remained. At this point I decided to include only frames 1 through 10 in the final animation. After completion of the prototyping work the ‘production’ sketch was adapted to run on a variety of combinations of microprocessor boards and displays at hand. A selection of the tested displays is shown in figure 6 while the table below provides the essentials of the tested devices.

Running the animation on the 128*64 LCD requires reference to the <U8glib.h> library by Olikraus in the sketch to accommodate the displays’ ST7920 controller. To my surprise the animation ran so fast on the LCD that 10 ms delays had to be introduced in between frames to limit Sallie Gardner’’s gallop to a realistic, say ‘horse-friendly’ pace. On the other hand, the LCD suffers from ‘motion blur’ due to the slow response time of the individual liquid crystal pixels. This results in a sort of ‘anti-aliasing’ effect.
Also on the 128*64 OLED with the SSD1306 controller the horse gallops fast and friendly under the <u8glib.h> library. Here the response time of individual pixels is very short compared with classical LCD: there is no motion blur compared with the 128*64 LCD.
The 130*130 TFT display has a SSD1283A controller that needs the <LCDWIKI_GUI.h> and the LCDWIKI_SPI.h> libraries for compilation. On this display the animation runs smoothly and briskly when the frames are printed to TFT as fast as possible, with no delay () instruction in between successive frames. A delay up to 10 ms is acceptable.
Equestrian performance on the 130*130 TFT display connected to a Wemos D1 Mini did not produce faster animation than with a Nano as engine. In sketches written for the Wemos D1 Mini one is free to use the PROGMEM instruction. This microprocessor board has plenty SRAM available for the animation job. If PROGMEM was left out and all frame arrays were loaded as variables in dynamic memory instead of program memory, one might expect a faster animation. This however did not materialize: the speed of the animation did not change.
On the TFT display with more pixels (240*240-ST7789) the results were disappointing. Screen updates between every frame that sweep the entire screen instead of the 128×64 pixel ‘Sallie Gardner’ were markedly visible. On the 2.4, 2.8, 3.5 and 3.95 inch 240×320 and 320×480 pixel TFT displays (shields) with ILI controllers, including the 3.95 inch 320×480 ILI6814 screen, animation speed was grandiose, thanks to the support of David Prentice’s <MCUFRIEND_kbv> library of screen-in-screen display.

figure 6. Animation played on several displays in combination with either Arduino Uno or Nano, or with a Wemos D1 Mini. While all these images measure 96 pixels in width and 64 pixels in height the size of their appearance is different because of pixel dimensions in these displays. The speed of the animation differs sharply between displays depending on the ‘efficiency’ of the library included in the sketch. The head of the jockey on the OLED is yellow because this display is of the hybrid, blue-yellow type.

The 128*64 LCD is a relatively big display with in proportion big LCD pixels. The OLED, 130*130 TFT and 240*240 TFT are devices with one-inch diagonal screens where the 240*240 TFT stands out with minuscule pixels that provide extreme sharpness. Sallie Gardner will certainly win a horse race on 320*480 TFT screens with my sketches compared with the LCD, OLED and small TFTs. The secret here is that on the TFT displays running under the <MCUFRIEND_kbv> library functionality only the 96×64 pixel screen-in-screen is redrawn in between each frame. This is very economical. In these screen-in-screens pixels are directly printed to screen from the c-array, thanks to an ‘engine’ written by David Prentice. This screen-inscreen way of animation would be a big improvement if also available for the 240*240 TFT display.
Searching the internet for other Muybridge horse animations for the Arduino I encountered in the Arduino forum a sketch for the Adafruit 128*64 OLED created in 2016 by Greg Stievenart and featuring ‘Annie G’, another galloping race horse, from Muybridge’s book “Animal Locomotion” (1887), plate 626; Muybridge has published hundreds of montage pictures of all kinds of animals and humans in motion; Sallie Gardner was only the premiere of his gigantic oevre. See https://forum.arduino.cc/index.php?topic=375985.0. Greg uses the method wherein the c-arrays are separate, external files. This works fine.

Most of the work to produce an animation is invested in the preparation of the frames. The limitations as far as Arduinos concerned are display size, c-array size, available memory in the microprocessor board, number and size of the frames, display controller speed and, finally, the efficiency of the library that contains the graphical and bitmap functions that govern printing each c-array to the display. Quite a handful of constraints! This makes it the more challenging and simultaneously more fun to continue working with small microprocessor boards. An animation is a chain of events in which the slowest link determines the outcome, that is a smooth and elegant animation.

Image manipulation software used in this project

Sketches – these are ZIPped

(you must unzip them before you open them in the Arduino IDE)

LCD_128x64_ST7920_nano_muybr_horse_96x64.ino
OLED_128x64_SSD1306_nano_muybr_horse_96x64.ino
TFT_130x130_SSD1283A_nano_muybr_horse_96_64
TFT_130x130_SSD1283A_wemos_muybr_horse_96x64.ino
TFT_240x240_ST7789_240x240TFT_nano_muybr_horse_96x64.ino
TFT_240x320_ILI9341_uno_muybr_horse_96x64.ino

reference
* https://en.wikipedia.org/wiki/The_Horse_in_Motion#/mediaFile:The_Horse_in_Motion_high_res.jpg

8 thoughts on “Eadweard Muybridge’s galloping horse on Arduino 128*64 LCD, OLED and TFT displays

Add yours

  1. Pingback: thesolaruniverse
  2. it’is a magic ! make an lcd acting like a tv screen is genius ,i”ll do it soon for sur , but i ‘ve an uno not a nano is it sufficient tank u for any help

    Like

  3. oh sorry i forget to say i’ve an arduino uno + st7920 glcd and in the table there is nothing about st7920 and uno …and when i load the sketch LCD_128x64_ST7920_nano_muybr_horse_96x64.ino i”ve somthing : plenty of artifacts and the still image of what look like a horse

    Like

    1. The ST7920 uses the u8glib.h library. I was unable to tame the horse with u8g2lib.h. the correct command to draw one frame is drawBitmapP (20, 0, 12, 64, horse_nn);
      Keep runnin’ ! One strategy could be to try to get one frame correct ons creen, then add the next frame, and so forth.

      Like

Leave a comment

Blog at WordPress.com.

Up ↑