Another nifty use for the Video Nub Shank is to create a test pattern generator for a composite TV or display.
Ladyada tests out little displays we're building, and wanted a way to easily check the geometry, rotation, orientation, and colors.
I created a SPMTE NTSC color bar test pattern in code using the Adafruit GFX library running the ESP_8_BIT_composite library by Roger Cheng. While the colors are not totally accurate to SMPTE standards due to the RGB332 bit depth used, it's close enough to do the job.
Also, it looks really rad on an old CRT.
Install Library and Code
To run the Test Pattern Generator, you'll need to download the ESP_8_BIT_composite library, uncompress the .zip, and then move the folder to your Arduino libraries directory.
Rename the folder from ESP_8_BIT_composite-master to ESP_8_BIT_composite.
In the Arduino IDE, open the File > Examples > ESP_8_BIT Color Composite Video Library > SMPTE_NTSC_Color_Bars.ino file.
Settings, Compile, Upload
In order to compile and upload to the board, first select Tools > Board > ESP32 Arduino > Adafruit QT Py ESP32.
Then, match the other settings shown here. Very important: Click Tools > PSRAM > Disabled. If you don't disable the PSRAM the video will suffer vertical sync issues and jiggle up and down!
When you're ready, click Sketch > Upload (or use the upload button) to compile and upload the code to the board.
/* Adafruit NTSC SMPTE color bars copyright: 2022 John Park for Adafruit Industries License: MIT resolution is 256x224, colors are approximate at best uses composite video generator library on ESP32 by Roger Cheng Connect GPIO25 (A0 on QT Py ESP32 Pico) to signal line, usually the center of composite video plug. Please disable PSRAM option before uploading to the board, otherwise there may be vertical jitter. */ #include <ESP_8_BIT_GFX.h> // Create an instance of the graphics library ESP_8_BIT_GFX videoOut(true /* = NTSC */, 8 /* = RGB332 color */); uint8_t WHITE = 0xFF ; uint8_t DIM_WHITE = 0xB6 ; uint8_t YELLOW = 0xF4 ; uint8_t TEAL = 0x1C ; uint8_t GREEN = 0x70 ; uint8_t MAGENTA = 0x83 ; uint8_t RED = 0x82 ; uint8_t BLUE = 0x0B ; uint8_t DARK_BLUE = 0x06 ; uint8_t PURPLE = 0x23 ; uint8_t BLACK = 0x00 ; uint8_t GRAY = 0x24 ; uint8_t LIGHT_GRAY = 0x29 ; uint8_t height_tall = 149 ; uint8_t height_squat = 19 ; uint8_t height_med = 56 ; uint8_t width_med = 36 ; uint8_t width_large = 46; uint8_t width_skinny = 12 ; uint8_t row2_y = height_tall ; uint8_t row3_y = height_tall + height_squat ; void setup() { // Initial setup of graphics library videoOut.begin(); } void loop() { // Wait for the next frame to minimize chance of visible tearing videoOut.waitForFrame(); // Clear screen videoOut.fillScreen(0); // Draw rectangles //row 1 videoOut.fillRect(0, 0, width_med, height_tall, DIM_WHITE); videoOut.fillRect(width_med, 0, width_med, height_tall, YELLOW); videoOut.fillRect(width_med*2, 0, width_med, height_tall, TEAL); videoOut.fillRect(width_med*3, 0, width_med, height_tall, GREEN); videoOut.fillRect(width_med*4, 0, width_med, height_tall, MAGENTA); videoOut.fillRect(width_med*5, 0, width_med, height_tall, RED); videoOut.fillRect(width_med*6, 0, width_med, height_tall, BLUE); //row 2 videoOut.fillRect(0, row2_y, width_med, height_squat, BLUE); videoOut.fillRect(width_med, row2_y, width_med, height_squat, GRAY); videoOut.fillRect(width_med*2, row2_y, width_med, height_squat, MAGENTA); videoOut.fillRect(width_med*3, row2_y, width_med, height_squat, GRAY); videoOut.fillRect(width_med*4, row2_y, width_med, height_squat, TEAL); videoOut.fillRect(width_med*5, row2_y, width_med, height_squat, GRAY); videoOut.fillRect(width_med*6, row2_y , width_med, height_squat, DIM_WHITE); //row 3 videoOut.fillRect(0, row3_y, width_large, height_med, DARK_BLUE); videoOut.fillRect(width_large, row3_y, width_large, height_med, WHITE); videoOut.fillRect(width_large*2, row3_y, width_large, height_med, PURPLE); videoOut.fillRect(width_large*3, row3_y, width_large, height_med, GRAY); videoOut.fillRect(width_large*4, row3_y, width_skinny, height_med, BLACK); videoOut.fillRect(width_large*4+width_skinny, row3_y, width_skinny, height_med, GRAY); videoOut.fillRect(((width_large*4)+(width_skinny*2)), row3_y , width_skinny, height_med, LIGHT_GRAY); videoOut.fillRect(width_med*6, row3_y , width_med, height_med, GRAY); // Draw text videoOut.setCursor(144, 180); videoOut.setTextColor(0xFF); videoOut.print("Adafruit NTSC"); videoOut.setCursor(144, 190); videoOut.setTextColor(0xFF); videoOut.print("composite video"); }
How it Works
The ESP_8_BIT_composite library makes it easy to use the Adafruit GFX library commands to display images on screen.
videoOut Creation
First, you'll import the library and create and instance of ESP_8_BIT_GFX called videoOut. The true
argument chooses NTSC (vs. PAL), and the 8
chooses 8-bit RGB332 color.
#include <ESP_8_BIT_GFX.h> // Create an instance of the graphics library ESP_8_BIT_GFX videoOut(true /* = NTSC */, 8 /* = RGB332 color */);
Colors and Sizes
Next, a number of colors are defined to approximate the NTSC color bars. This was accomplished with the incredibly cool 3D color picker Roger Cheng created here, which outputs hex values for RGB332 color space.
Colors and Sizes
Next, a number of colors are defined to approximate the NTSC color bars. This was accomplished with the incredibly cool 3D color picker Roger Cheng created here, which outputs hex values for RGB332 color space.
The pixel dimensions are defined for the different sizes of bars.
uint8_t WHITE = 0xFF ; uint8_t DIM_WHITE = 0xB6 ; uint8_t YELLOW = 0xF4 ; uint8_t TEAL = 0x1C ; uint8_t GREEN = 0x70 ; uint8_t MAGENTA = 0x83 ; uint8_t RED = 0x82 ; uint8_t BLUE = 0x0B ; uint8_t DARK_BLUE = 0x06 ; uint8_t PURPLE = 0x23 ; uint8_t BLACK = 0x00 ; uint8_t GRAY = 0x24 ; uint8_t LIGHT_GRAY = 0x29 ; uint8_t height_tall = 149 ; uint8_t height_squat = 19 ; uint8_t height_med = 56 ; uint8_t width_med = 36 ; uint8_t width_large = 46; uint8_t width_skinny = 12 ; uint8_t row2_y = height_tall ; uint8_t row3_y = height_tall + height_squat ;
void setup() { // Initial setup of graphics library videoOut.begin(); }
Main Loop
In the main loop, the library waits for a frame, then clears the screen, and draws the bars, based on some procedural rules.
// Wait for the next frame to minimize chance of visible tearing videoOut.waitForFrame(); // Clear screen videoOut.fillScreen(0); // Draw rectangles //row 1 videoOut.fillRect(0, 0, width_med, height_tall, DIM_WHITE); videoOut.fillRect(width_med, 0, width_med, height_tall, YELLOW); videoOut.fillRect(width_med*2, 0, width_med, height_tall, TEAL); videoOut.fillRect(width_med*3, 0, width_med, height_tall, GREEN); videoOut.fillRect(width_med*4, 0, width_med, height_tall, MAGENTA); videoOut.fillRect(width_med*5, 0, width_med, height_tall, RED); videoOut.fillRect(width_med*6, 0, width_med, height_tall, BLUE); //row 2 videoOut.fillRect(0, row2_y, width_med, height_squat, BLUE); videoOut.fillRect(width_med, row2_y, width_med, height_squat, GRAY); videoOut.fillRect(width_med*2, row2_y, width_med, height_squat, MAGENTA); videoOut.fillRect(width_med*3, row2_y, width_med, height_squat, GRAY); videoOut.fillRect(width_med*4, row2_y, width_med, height_squat, TEAL); videoOut.fillRect(width_med*5, row2_y, width_med, height_squat, GRAY); videoOut.fillRect(width_med*6, row2_y , width_med, height_squat, DIM_WHITE); //row 3 videoOut.fillRect(0, row3_y, width_large, height_med, DARK_BLUE); videoOut.fillRect(width_large, row3_y, width_large, height_med, WHITE); videoOut.fillRect(width_large*2, row3_y, width_large, height_med, PURPLE); videoOut.fillRect(width_large*3, row3_y, width_large, height_med, GRAY); videoOut.fillRect(width_large*4, row3_y, width_skinny, height_med, BLACK); videoOut.fillRect(width_large*4+width_skinny, row3_y, width_skinny, height_med, GRAY); videoOut.fillRect(((width_large*4)+(width_skinny*2)), row3_y , width_skinny, height_med, LIGHT_GRAY); videoOut.fillRect(width_med*6, row3_y , width_med, height_med, GRAY);
Text
In order to make it simple to verify that a display is showing the video in the proper orientation/rotation a small bit of text is printed to the screen. You can adjust this text to suit your needs.
// Draw text videoOut.setCursor(144, 180); videoOut.setTextColor(0xFF); videoOut.print("Adafruit NTSC"); videoOut.setCursor(144, 190); videoOut.setTextColor(0xFF); videoOut.print("composite video");
Text editor powered by tinymce.