Maker.io main logo

Video Playing 2.1" Round Ornament TFT

2024-12-13 | By Adafruit Industries

License: See Original Project 3D Printing ESP32

Courtesy of Adafruit

Guide by Ruiz Brothers

 

Build an Ornament that plays Holiday video clips using an Adafruit Qualia ESP32-S3 and a 2.1" round TFT display.

Video Playing Round Ornament TFT

Load a series of your favorite video files on a micro SD card and display them on the round TFT LCD screen. Cycle through them by touching the screen.

3D print the parts to secure the display, Qualia ESP32-S2, and micro SD breakout.

The case uses ornament hooks so you can decorate your tree for the season!

Video Playing Round Ornament TFT

Video Playing Round Ornament TFT

Video Playing Round Ornament TFT

Parts

Prerequisite Guides

Take a moment to review the following guides to learn more about the products.

Video Playing Round Ornament TFT

Circuit Diagram

The diagram below provides a general visual reference for wiring of the components once you get to the Assembly page. This diagram was created using the software package Fritzing.

Adafruit Library for Fritzing

Adafruit uses the Adafruit's Fritzing parts library to create circuit diagrams for projects. You can download the library or just grab individual parts. Get the library and parts from GitHub - Adafruit Fritzing Parts.

Video Playing Round Ornament TFT

Wired Connections

  • The Qualia ESP32-S3 is powered by a 5V 1A USB power supply.

  • SCK from Qualia S3 to CLK on Micro SD

  • MISO from Qualia S3 to SO on Micro SD

  • MOSI from Qualia S3 to SI on Micro SD

  • CS from Qualia S3 to CS on Micro SD

  • A1 from Qualia S3 to DAT2 on Micro SD

  • A0 from Qualia S3 to D1 on Micro SD

  • 3.3V from Qualia S3 to 3V on Micro SD

  • GND from Qualia to GND on Micro SD

Converting Videos

Video File Preparation

You'll want to use a video file in a common format such as H.264 (.MP4 or .MOV) with a duration of anywhere from 10 seconds to 3 minutes in length. Your video clip should ideally have a 1:1 aspect ratio of 480x480.

The 2.1" display has a screen resolution of 480x480.

Online Converter

You can use the website linked below to convert your video file into MJPEG. (maximum file size is 100MB.)

Convert IO Website

Video Playing Round Ornament TFT

Click on the Choose Files icon. Navigate to your video file and click Open. Click on the dropdown icon and select MJPEG under the video section. Then, click on the gear icon to open the settings dialog.

Video Playing Round Ornament TFT

Use the following settings for best playback performance.

  • Codec: MJPEG

  • Quality: Highest

  • Resize: Custom / 480 x 480

  • Resize Method: Zoom and crop

  • Frame rate : 10 FPS

  • Rotate: Rotate by 90 degrees clockwise (this is to compensate for the screen orientation when mounted)

Click on the Convert button when finished.

Format SD

Format a micro SD card to FAT32.

Create VIDEOS folder

Make a new folder in the SD card and name it VIDEOS.

Video Playing Round Ornament TFT

Video Playing Round Ornament TFT

Conversion Completed

Click the Download button when the upload and conversion is complete.

Use the VLC media player app to playback the video for reviewing.

Rename the video file so it only contains the .MJPEG extension. Then, drag and drop it into the VIDEOS folder on the micro SD card.

Video Playing Round Ornament TFT

Mac users may need to delete .DS files inside the VIDEOS folder

3D Printing

3D Printed Parts

STL files for 3D printing are oriented to print "as-is" on FDM style machines. Parts are designed to 3D print without any support material using PLA filament. Original design source may be downloaded using the links below.

Video Playing Round Ornament TFT

Edit Design

Download STLs

2.1_Round_Ornament_STEP.zip

Slice with Settings for PLA material

The parts were sliced using CURA using the slice settings below.

  • PLA filament 200c extruder

  • 0.2 layer height

  • 10% gyroid infill

  • 60mm/s print speed

  • 60c heated bed

Video Playing Round Ornament TFT

Design Source FilesDesign Source Files

The project assembly was designed in Fusion 360. This can be downloaded in different formats like STEP, STL and more. Electronic components like Adafruit's boards, displays, connectors and more can be downloaded from the Adafruit CAD parts GitHub Repo.

Video Playing Round Ornament TFT

Software Setup and Use

Prep SD Card

Create a folder called VIDEOS on your SD card. Save your converted video files in this folder. Eject the SD card from your computer and insert it into the microSD card breakout.

Video Playing Round Ornament TFT

Upload UF2 File

The round ornament code is available as a pre-compiled .UF2 file for the 2.1" 480x480 display that you can drag and drop onto your Qualia S3 board.

Qualia S3 Ornament UF2 File

Click the link above to download the UF2 file.

Save it wherever is convenient for you.

Video Playing Round Ornament TFT

Video Playing Round Ornament TFT

Plug your board into your computer, using a known-good data-sync USB cable, directly, or via an adapter if needed.

Double-click the reset button (highlighted in red above), wait about a half a second and then tap reset again.

Video Playing Round Ornament TFT

You will see a new disk drive appear called TFT_S3BOOT.

Drag the Qualia_S3_OrnamentVideoPlayer_480x480_2.1round.UF2 file to TFT_S3BOOT.

The code will begin running by playing the first video file on the SD card. Touch the center of the screen to advance to the next video file.

Advanced Users: Source Code in Arduino IDE

Only attempt this if you are comfortable with Git and advanced Arduino use. Otherwise, use the precompiled UF2.

Qualia S3 Round Ornament Arduino Code

Copy Code
// SPDX-FileCopyrightText: 2023 Limor Fried for Adafruit Industries
//
// SPDX-License-Identifier: MIT

/*******************************************************************************
 * Motion JPEG Image Viewer
 * This is a simple Motion JPEG image viewer example

encode with
ffmpeg -i "wash.mp4" -vf "fps=10,vflip,hflip,scale=-1:480:flags=lanczos,crop=480:480" -pix_fmt yuvj420p -q:v 9 wash.mjpeg

 ******************************************************************************/
#define MJPEG_FOLDER       "/videos" // cannot be root!
#define MJPEG_OUTPUT_SIZE  (480 * 480 * 2)      // memory for a output image frame
#define MJPEG_BUFFER_SIZE (MJPEG_OUTPUT_SIZE / 5) // memory for a single JPEG frame
#define MJPEG_LOOPS        0

#include <Arduino_GFX_Library.h>
#include <Adafruit_CST8XX.h>
//#include <SD.h>      // uncomment either SD or SD_MMC
#include <SD_MMC.h>

Arduino_XCA9554SWSPI *expander = new Arduino_XCA9554SWSPI(
    PCA_TFT_RESET, PCA_TFT_CS, PCA_TFT_SCK, PCA_TFT_MOSI,
    &Wire, 0x3F);
    
Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
    TFT_DE, TFT_VSYNC, TFT_HSYNC, TFT_PCLK,
    TFT_R1, TFT_R2, TFT_R3, TFT_R4, TFT_R5,
    TFT_G0, TFT_G1, TFT_G2, TFT_G3, TFT_G4, TFT_G5,
    TFT_B1, TFT_B2, TFT_B3, TFT_B4, TFT_B5,
    1 /* hsync_polarity */, 50 /* hsync_front_porch */, 2 /* hsync_pulse_width */, 44 /* hsync_back_porch */,
    1 /* vsync_polarity */, 16 /* vsync_front_porch */, 2 /* vsync_pulse_width */, 18 /* vsync_back_porch */
    //,1, 30000000
    );

Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
// 2.1" 480x480 round display
    480 /* width */, 480 /* height */, rgbpanel, 0 /* rotation */, true /* auto_flush */,
    expander, GFX_NOT_DEFINED /* RST */, TL021WVC02_init_operations, sizeof(TL021WVC02_init_operations));

Adafruit_CST8XX ctp = Adafruit_CST8XX();  // This library also supports FT6336U!
#define I2C_TOUCH_ADDR 0x15
bool touchOK = false;

#include <SD_MMC.h>

#include "MjpegClass.h"
static MjpegClass mjpeg;
File mjpegFile, video_dir;
uint8_t *mjpeg_buf;
uint16_t *output_buf;

unsigned long total_show_video = 0;

void setup()
{
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  //while(!Serial) delay(10);
  Serial.println("MJPEG Video Playback Demo");

#ifdef GFX_EXTRA_PRE_INIT
  GFX_EXTRA_PRE_INIT();
#endif

  // Init Display
  Wire.setClock(400000); // speed up I2C 
  if (!gfx->begin()) {
    Serial.println("gfx->begin() failed!");
  }
  gfx->fillScreen(BLUE);

  expander->pinMode(PCA_TFT_BACKLIGHT, OUTPUT);
  expander->digitalWrite(PCA_TFT_BACKLIGHT, HIGH);

  //while (!SD.begin(ss, SPI, 64000000UL))
  //SD_MMC.setPins(SCK /* CLK */, MOSI /* CMD/MOSI */, MISO /* D0/MISO */);
  SD_MMC.setPins(SCK, MOSI /* CMD/MOSI */, MISO /* D0/MISO */, A0 /* D1 */, A1 /* D2 */, SS /* D3/CS */); // quad MMC!
  while (!SD_MMC.begin("/root", true))
  {
    Serial.println(F("ERROR: File System Mount Failed!"));
    gfx->println(F("ERROR: File System Mount Failed!"));
    delay(1000);
  }
  Serial.println("Found SD Card");

  //  open filesystem
  //video_dir = SD.open(MJPEG_FOLDER);
  video_dir = SD_MMC.open(MJPEG_FOLDER);
  if (!video_dir || !video_dir.isDirectory()){
     Serial.println("Failed to open " MJPEG_FOLDER " directory");
     while (1) delay(100);
  }
  Serial.println("Opened Dir");

  mjpeg_buf = (uint8_t *)malloc(MJPEG_BUFFER_SIZE);
  if (!mjpeg_buf) {
    Serial.println(F("mjpeg_buf malloc failed!"));
    while (1) delay(100);
  }
  Serial.println("Allocated decoding buffer");

  output_buf = (uint16_t *)heap_caps_aligned_alloc(16, MJPEG_OUTPUT_SIZE, MALLOC_CAP_8BIT);
  if (!output_buf) {
    Serial.println(F("output_buf malloc failed!"));
    while (1) delay(100);
  }

  expander->pinMode(PCA_BUTTON_UP, INPUT);
  expander->pinMode(PCA_BUTTON_DOWN, INPUT);

  if (!ctp.begin(&Wire, I2C_TOUCH_ADDR)) {
    Serial.println("No touchscreen found");
    touchOK = false;
  } else {
    Serial.println("Touchscreen found");
    touchOK = true;
  }
}

void loop()
{
  /* variables */
  int total_frames = 0;
  unsigned long total_read_video = 0;
  unsigned long total_decode_video = 0;
  unsigned long start_ms, curr_ms;
  uint8_t check_UI_count = 0;
  int16_t x = -1, y = -1, w = -1, h = -1;
  total_show_video = 0;

  if (mjpegFile) mjpegFile.close();
  Serial.println("looking for a file...");

  if (!video_dir || !video_dir.isDirectory()){
     Serial.println("Failed to open " MJPEG_FOLDER " directory");
     while (1) delay(100);
  }

  // look for first mjpeg file
  while ((mjpegFile = video_dir.openNextFile()) != 0) {
    if (!mjpegFile.isDirectory()) {
      Serial.print("  FILE: ");
      Serial.print(mjpegFile.name());
      Serial.print("  SIZE: ");
      Serial.println(mjpegFile.size());
      if ((strstr(mjpegFile.name(), ".mjpeg") != 0) || (strstr(mjpegFile.name(), ".MJPEG") != 0)) {
        Serial.println("   <---- found a video!");
        break;
      }
    }
    if (mjpegFile) mjpegFile.close();
  }

  if (!mjpegFile || mjpegFile.isDirectory())
  {
    Serial.println(F("ERROR: Failed to find a MJPEG file for reading, resetting..."));
    //gfx->println(F("ERROR: Failed to find a MJPEG file for reading"));

    // We kept getting hard crashes when trying to rewindDirectory or close/open dir
    // so we're just going to do a softreset
    esp_sleep_enable_timer_wakeup(1000);
    esp_deep_sleep_start(); 
  }

  bool done_looping = false;
  while (!done_looping) {
    mjpegFile.seek(0);
    total_frames = 0;
    total_read_video = 0;
    total_decode_video = 0;
    total_show_video = 0;

    Serial.println(F("MJPEG start"));
  
    start_ms = millis();
    curr_ms = millis();
    if (! mjpeg.setup(&mjpegFile, mjpeg_buf, output_buf, MJPEG_OUTPUT_SIZE, true /* useBigEndian */)) {
       Serial.println("mjpeg.setup() failed");
       while (1) delay(100);
    }
  
    while (mjpegFile.available() && mjpeg.readMjpegBuf())
    {
      // Read video
      total_read_video += millis() - curr_ms;
      curr_ms = millis();

      // Play video
      mjpeg.decodeJpg();
      total_decode_video += millis() - curr_ms;
      curr_ms = millis();

      if (x == -1) {
        w = mjpeg.getWidth();
        h = mjpeg.getHeight();
        x = (w > gfx->width()) ? 0 : ((gfx->width() - w) / 2);
        y = (h > gfx->height()) ? 0 : ((gfx->height() - h) / 2);
      }
      gfx->draw16bitBeRGBBitmap(x, y, output_buf, w, h);
      total_show_video += millis() - curr_ms;

      curr_ms = millis();
      total_frames++;
      check_UI_count++;
      if (check_UI_count >= 5) {
        check_UI_count = 0;
        Serial.print('.');
        
        if (! expander->digitalRead(PCA_BUTTON_DOWN)) {
          Serial.println("\nDown pressed");
          done_looping = true;
          while (! expander->digitalRead(PCA_BUTTON_DOWN)) delay(10);
          break;
        }
        if (! expander->digitalRead(PCA_BUTTON_UP)) {
          Serial.println("\nUp pressed");
          done_looping = true;
          while (! expander->digitalRead(PCA_BUTTON_UP)) delay(10);
          break;
        }
  
        if (touchOK && ctp.touched()) {
          CST_TS_Point p = ctp.getPoint(0);
          Serial.printf("(%d, %d)\n", p.x, p.y);
          done_looping = true;
          break;
        }
      }
    }
    int time_used = millis() - start_ms;
    Serial.println(F("MJPEG end"));
    
    float fps = 1000.0 * total_frames / time_used;
    total_decode_video -= total_show_video;
    Serial.printf("Total frames: %d\n", total_frames);
    Serial.printf("Time used: %d ms\n", time_used);
    Serial.printf("Average FPS: %0.1f\n", fps);
    Serial.printf("Read MJPEG: %lu ms (%0.1f %%)\n", total_read_video, 100.0 * total_read_video / time_used);
    Serial.printf("Decode video: %lu ms (%0.1f %%)\n", total_decode_video, 100.0 * total_decode_video / time_used);
    Serial.printf("Show video: %lu ms (%0.1f %%)\n", total_show_video, 100.0 * total_show_video / time_used);
  }
}

The source code for the round ornament is available on GitHub. It consists of an Arduino script .ino file and a header file. You will need both files to compile it in the Arduino IDE. There are a few items you'll need to manually configure in the Arduino IDE:

  • The header file requires the ESP32_JPEG library, which isn't currently available in the Arduino IDE library bundle. You'll need to install it manually from its GitHub repository.

  • Currently the Arduino GFX library is not compatible with the ESP BSP 3.0 since it uses IDF 5. You will need to use an older BSP package and manually add the Qualia S3 board to your local installation.

If you defeat these dragons though, you can update the code to run on different RGB-666 displays and customize any other parameters that you want.

Assemble

Prep SD breakout

Use an 8 pin matching cable pair to easily connect the SD breakout board to the pins on the Qualia board. Follow the wiring diagram here.

Video Playing Round Ornament TFT

Mount display

Take note of how the ribbon cable attaches to the Qualia board. Align the display to the cutout and place face down between the walls inside the case.

Slightly bend the case while gently pressing the edges of the display to fit.

Video Playing Round Ornament TFT

Mounting The Frame

Align cutout on the mounting frame to the ribbon cable.

Slide the mounting frame into the case at an angle, between the snaps on the case.

Video Playing Round Ornament TFT

Mount SD breakout

Align the SD breakout to the slot on the case.

Use M2.5x5mm screws to mount the SD breakout board to the frame.

Video Playing Round Ornament TFT

Connect SD cable

Connect the SD cables to the Qualia. Coil the cable and place the connector between the three taller standoffs.

Use M2.5x5mm screws to secure the Qualia to the frame.

Video Playing Round Ornament TFT

Video Playing Round Ornament TFT

Display Ribbon

Carefully lift the connect bracket on the Qualia.

Place the display ribbon cable into the connector on the Qualia board and gently press the bracket back down to secure the ribbon cable into place.

Video Playing Round Ornament TFT

Video Playing Round Ornament TFT

Attach Lid

Align the snaps on the lid the nubs on the inside of the case to close the enclosure.

Video Playing Round Ornament TFT

Plug in a USB battery pack or wall adapter and hang your video ornament!

Video Playing Round Ornament TFT

制造商零件编号 5792
GRAPHIC DISPLAY TFT RGB 2.1"
Adafruit Industries LLC
制造商零件编号 5800
EVAL BOARD FOR ESP32-S3
Adafruit Industries LLC
制造商零件编号 4682
MICROSD SPI/SPIO BREAKOUT BOARD
Adafruit Industries LLC
制造商零件编号 4976
1.25MM PITCH 8-PIN CABLE MATCHIN
Adafruit Industries LLC
制造商零件编号 5153
CABLE A PLUG TO C PLUG 3.28'
Adafruit Industries LLC
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum