Introduction: ESP32 Small TV

About: hardware and software, Hardware like Arduino board, Phase locked loop circuit, and C++ programming language, C# programming language

I got this idea, since SHARP LQ5AW136 TFT-LCD Modele, Features Dual mode type [NTSE(M)and PAL(B.G) standards. Which is 273 scanning lines. It consists of 74,880 pixels (RGB strip configuration and full color) 5" diagonal size.

The built-in video interface circuit and control circuit are responsive to two sets of standard (R-G-B) analog video signals. and VSY, HSY signals. and the clock runs @ 7.4 MHZ.

I cut out the board from an old CRT TV, this board consists of a TV tuner and I2C BUS CONTROL NTSC 1CHIP COLOR TV IC. and most importantly the IX3253CE IC has R-G-B and VSY HSY signal output signal at 5V. good then.

So I started building everything. it took a lot of work to make it happen.



Supplies

  1. SHARP LQ5AW136 TFT-LCD
  2. The TV board that I cut from an old CRT TV
  3. Adafruit-si5351 clock generator board
  4. 7 LM2596 DC-DC Buck converter
  5. I2C multiplexer board
  6. DC dule real buck converter board
  7. ESP32 and breakout board
  8. 3 potentiometer
  9. Rotary encoder
  10. Remote control
  11. IR receiver
  12. HP printer power supply outputs 32v
  13. HDMI to composite convert [from Android box to the TV]
  14. Composite to HDMI convert [from digital TV box to VGA monter] HDMI to VGA cable

Step 1: SHARP LQ5AW136 TFT-LCD Display

INTRODUCTION

SHARP AVTFT displays are generally meant to be

driven with either an NTSC Composite Video or with discrete

RGB Video signals. SHARP’s AVTFTs have video

compatible inputs that enable easy interfacing with a camcorder,

a VCR, or the video output from a CCD camera.

Most AVTFTs have two video input channels. One channel

may be an NTSC Composite input and the other will

be a discrete RGB Video input. An example of this video

input configuration is the LQ056A3CH01.

The LQ5AW116 is a dual video input channel

AVTFT with discrete RGB inputs. It has a composite

video input, but the display electronics merely use this

input to strip off the sync information, disregarding the

video content of the composite signal.

The LQ5AW116 was designed for discrete RGB

video sources.

LQ5AW116/136 OPERATION

The LQ5AW116 is an AVTFT (Audio Visual Thin

Film Transistor) LCD. It can display one of two video

input channels by making pin 7, VSW = HIGH

(channel 1) or VSW = LOW (channel 2). The display

can generate an LCD pixel clock internally or it can use

an external clock depending on the setting of CLCK

input. If the video signal input is NTSC decoded RGB,

then CLCK = HIGH, and an internal PLL will generate a

pixel clock that is phase-locked to the sync input. By

doing this, pin 22 (CLK), pin 1 (HSYNC) and pin 2

(VSYNC) become outputs. If an RGB video signal is

available and the user would like to control the LCD

timing, set CLCK = LOW, making CLK, HSYNC, and

VSYNC inputs.

Pin 8 (SAM) determines the external clock sampling

mode. In Independent Data Sampling mode

(pin 8 = HIGH), the external clock (18.2 MHz to 19.6 MHz)

is three times faster than in Simultaneous mode

(pin 8 = LOW) and video data is sampled sequentially

(first red, then green, and then blue). In Simultaneous

mode, all three video inputs are sampled at the same

point in time with a slower clock (6.8 MHz to 7.6 MHz).


Step 2: CRT TV BOARD

And for the CRT TV, the main IC IX3253CE.

IX3253 provides PIF, SIF, VIDEO, CHROMA, and deflection circuits for NTSC color TV.

This IC has an RGB primary color output circuit and VSY and HSY output signals. and audio output.

this IC needs 9V to operate.

We can control this IC through I2C, for color and other settings.

This board has a TV tuner, which consists of a PLL IC SN761627, which it controls through I2C.

this TV tuner needs 5V and 30V

I do use POT to control the Vertical position.

I did use ESP32 to control both the ix3253ce IC and the PLL IC by I2C.

I did cut this board from another TV IT has BA7657F ultra wideband video signal switchers, I use it between the sharp display and the CRT TV board, so there will be no flicker on the screen.

I made some connections in the back of the CRT TV board for VSY and HSY control and I2C connection and voltage input.

I did use the rotary encoder that came with the display from the car.

Step 3: Esp32 Composite Video OUTPUT

I did use ESP_8_BIT Color Composite Video Out Library, to draw the NTSC 8 color bars to Calibrate the color and other siting by using I2C to control the IX3253CE IC, and for VSY and HSY signal control.

and I used the same library to display FM radio stations, I used the TV tuner for FM radio.

I can switch between internal audio and external audio input, the same for video, through I2C for IX3253CE IC.

this IC takes composite video signal input [ether from the TV tuner on board which is RF signal or external video input] and it outputs R-G-B VSY HSY, which I used to drive the SHARP TFT display.

So ESP32 generates composite video, two different frames, one for color and the other settings for IX3253CE IC, for FM radio stations, and for AGC control.

IX3253 is analog so I connected it to a digital TV box, I set the TV tuner on channel 3, by programming the PLL IC inside the TV tuner by using I2C.

for external video input, I used a manual switch to switch between ESP32 and other composite video input, in my case I got an Android smart box and a SONY play station, so I used another switch to switch between the smart box and the play station. So I did expand the video input.

I can play games while listening to the FM radio.

I did program everything using Visual Studio Code IDE and platform IO.


*******************************************************************************

#include <Arduino.h>

#include<Wire.h>

const byte I2C_multiplexerAddressS = 0x70;

const byte SNTV_TURER_ADDRESS = 0x60;



byte osc =0x01;

int CBFraquency = 4500; // CB radio start fraquncy scan

int FM915Frequency = 17370; // 915 MHZ start fraquncy scan  17370

int TVFrequency = 2030; // tv start fraquncy scan

int FMFrequency = 2708; // fm radio start fraquncy scan

int AirPortFrequency =3385;

int frSelect =0;

int fraquncy[5] = {TVFrequency,FMFrequency,AirPortFrequency,FM915Frequency,CBFraquency,};

void SNTVTuner(int data ,byte osci){

  Wire.beginTransmission(I2C_multiplexerAddressS);

  Wire.write(1 << 0);

  Wire.endTransmission();


  int fpd =  data + 107;

  Wire.beginTransmission(SNTV_TURER_ADDRESS);

  Wire.write(fpd >> 8);

  Wire.write(fpd & 0xFF);

  Wire.write(0xC0);

  Wire.write(osci == 1 ? 0x01 : osci == 2 ? 0x02 : osci == 3 ? 0x04 : 0x01);

  Wire.endTransmission();

}


*********************************************************************

#include <Arduino.h>

#include <IRremote.h>

// SONY REMOTE CONTROL

// Define constants for IR remote control codes

const unsigned int IR_UP = 752;

const unsigned int IR_DOWN = 2800;

const unsigned int IR_RIGHT = 3280;

const unsigned int IR_LEFT = 720;

const unsigned int IR_CHANNEL_UP = 144;

const unsigned int IR_CHANNEL_DOWN = 2192;

const unsigned int IR_INPUT = 2640;

const unsigned int IR_DISPLAY = 1488;

const unsigned int IR_VIDEO_1 = 48;

const unsigned int IR_VIDEO_2 = 2096;

const unsigned int IR_VIDEO_3 = 1072;

const unsigned int IR_MENU = 112;

const unsigned int IR_OK = 6272;

const unsigned int IR_RECALL = 3536;

const unsigned int IR_VOLUME_UP = 1168;

const unsigned int IR_VOLUME_DOWN = 3216;

const unsigned int IR_0 = 2320;

const unsigned int IR_1 = 16;

const unsigned int IR_2 = 2064;

const unsigned int IR_3 = 1040;

const unsigned int IR_4 = 3088;

const unsigned int IR_5 = 528;

const unsigned int IR_6 = 2576;

const unsigned int IR_7 = 1552;

const unsigned int IR_8 = 3600;

const unsigned int IR_9 = 272;


const int MENU_OPTION_1 = 1;

const int MENU_OPTION_2 = 2;

const int MENU_OPTION_3 = 3;


int currentMenuOption = MENU_OPTION_1;


const int RECV_PIN = 15;


IRrecv irrecv(RECV_PIN);

decode_results results;


void handleIRInput(unsigned int irValue) {

  // Handle IR input and update the menu option accordingly

  switch (irValue) {

    case IR_MENU:

      // Go to the main menu

      currentMenuOption = MENU_OPTION_1;

      break;

    case IR_RIGHT:

      // Move to the next menu option

      currentMenuOption++;

      if (currentMenuOption > MENU_OPTION_3) {

        currentMenuOption = MENU_OPTION_1; // Wrap around to the first option

      }

      break;

    case IR_LEFT:

      // Move to the previous menu option

      currentMenuOption--;

      if (currentMenuOption < MENU_OPTION_1) {

        currentMenuOption = MENU_OPTION_3; // Wrap around to the last option

      }

      break;

    // Add more cases for other remote control buttons as needed

  }

}

void IR_SetUP(){

   irrecv.enableIRIn();  

}



void RemoteControlloop() {

  if (irrecv.decode(&results)) {

    handleIRInput(results.value);

    irrecv.resume(); // Receive the next value

  }


  // Your main loop logic goes here

  // Check the current menu option and perform corresponding actions

  switch (currentMenuOption) {

    case MENU_OPTION_1:

      // Handle actions for menu option 1

      break;

    case MENU_OPTION_2:

      // Handle actions for menu option 2

      break;

    case MENU_OPTION_3:

      // Handle actions for menu option 3

      break;

    // Add more cases for additional menu options as needed

  }

}


************************************************************************************

#include <Arduino.h>

#include <TA1268NTSCcolorTVIC.h>

const int sw = 4;

const int clk = 17;

const int dta = 16;

const int flagButton = 18;

const int numTowButton =19;

const int pointingRightButton = 23;


int currentClock =0;

int previceClock =0;

int f=1;

void RotaryEncoderSetUP(){

 pinMode(sw,INPUT);

 pinMode(clk,INPUT);

 pinMode(dta,INPUT);

 pinMode(flagButton,INPUT);

 pinMode(numTowButton,INPUT);

 pinMode(pointingRightButton,INPUT);

}

void RotaryEncoder(){

   currentClock = digitalRead(clk);

if(currentClock != previceClock){

  if(currentClock != digitalRead(dta)){

   fraquncy[frSelect]--;

   SNTVTuner(fraquncy[frSelect],osc);

   //Serial.print("Fraquncy: ");

   //Serial.println(fraquncy[frSelect]);

  }

  else{

   fraquncy[frSelect]++;

   SNTVTuner(fraquncy[frSelect],osc);

  // Serial.print("Fraquncy: ");

   //Serial.println(fraquncy[frSelect]);

  }

}

previceClock = currentClock;


}



void RotaryEncoderSwitch(){

if(digitalRead(flagButton) == LOW){

f++;

if(f>2)f=1;

switch (f)

{

case 1:

//video input 1

osc = 0x01;

fraquncy[0] = 2030;

 valu[8]=0x3f;

 SNTVTuner(fraquncy[0],osc);

 setDeviceValue(slaveAddress,subAddres[8],valu[8]);

 //Serial.print(functionName[8] );

 //Serial.println(valu[8],HEX);

break;

case 2:

// video input 2

setDeviceValue(slaveAddress,subAddres[8],valu[8]+0x40);

 //Serial.print(functionName[8] );

 //Serial.println(valu[8],HEX);

break;


}

}

}


********************************************************************************

#include <Arduino.h>

#include <ESP_8_BIT_GFX.h>

#include <RotaryEncoder.h>

#include <RadioTwoerIcon.h>



// Create an instance of the graphics library

ESP_8_BIT_GFX videoOut(true /* = NTSC */, 8 /* = RGB332 color */);



// Vertical margin to compensate for aspect ratio

const int margin = 29;

void DisplaySetUP(){

     videoOut.begin();

     videoOut.copyAfterSwap = true; // Depends on data from the previous buffer

     videoOut.fillScreen(0);

     videoOut.waitForFrame();

}



int highlightedLine = 0;

uint16_t rgbColor[8] = {0xFF,0xFC,0x1F,0x1C,0xE3,0xE0,0x03,0x00,};

int cursor[16]={70,80,90,100,110,120,130,140,150,160,170,180,190,200,210,220,};


void DrawText() {

  videoOut.setTextSize(1);


  const int startCursorX = 50;

  const int lineSpacing = 10;


  for (int x = 0; x < 16; x++) {

    videoOut.setCursor(120, 70);

    videoOut.print("Clock speed: ");

    videoOut.println(clockSpeed);

    videoOut.setCursor(startCursorX, cursor[x]);


    if (x == highlightedLine) {

      videoOut.setTextColor(0xFF); // Highlighted text color

      videoOut.fillRect(startCursorX, cursor[x] - 2, videoOut.width(), 10, 0x00);

    } else {

      videoOut.setTextColor(0x07E0); // Default text color

    }


    videoOut.print(functionName[x] + ": ");

    videoOut.println(valu[x], HEX);

  }

}


void drawColorBars() {

  videoOut.fillScreen(0);

  uint8_t barWidth = videoOut.width() / 8;

  int barHeight = videoOut.height() / 4;


  for (int i = 0; i < 8; ++i) {

    uint16_t color = rgbColor[i];


    for (int x = 0; x < barWidth; ++x) {

      for (int y = 0; y < barHeight; ++y) {

        videoOut.drawPixel(i * barWidth + x, y, color);

      }

    }

  }


  DrawText();

  videoOut.waitForFrame();

}




void drawRadioIcon(int16_t x, int16_t y, uint16_t color) {

  // Draw radio icon at the specified position (x, y)

  // For simplicity, a basic radio icon with a circle and an antenna is drawn.


  // Draw the circle

  for (int i = 0; i < 360; ++i) {

    float radian = radians(i);

    int16_t circleX = static_cast<int16_t>(x + 8 * cos(radian)); // Adjust the radius as needed

    int16_t circleY = static_cast<int16_t>(y + 8 * sin(radian)); // Adjust the radius as needed

   videoOut.drawPixel(circleX, circleY, color);

  }


  // Draw the antenna

 videoOut.drawLine(x, y - 8, x, y - 16, color); // Adjust the length as needed

}

int labelIndex=0;

const int lableNumber[2] = {0x08,0x0A};

const int lableCursor[2] ={120,160};


void drawRadioHighlightedText() {

  videoOut.fillScreen(0);


  const int startCursorX = 50;

  const int lineSpacing = 10;


  for (int x = 0; x < 2; x++) {

    videoOut.setTextSize(2);

  videoOut.setCursor(50, 40);

  videoOut.print("FM: ");

  videoOut.println(fraquncy[frSelect]);

  videoOut.setCursor(50, 80);

  videoOut.print("OSC: ");

  videoOut.println(osc);

    videoOut.setCursor(startCursorX, lableCursor[x]);


    if (x == labelIndex) {

      videoOut.setTextColor(0xFF); // Highlighted text color

      videoOut.fillRect(startCursorX, lableCursor[x] - 2, videoOut.width(), 10, 0x00);

    } else {

      videoOut.setTextColor(0x07E0); // Default text color

    }


    videoOut.print(functionName[lableNumber[x]] + ": ");

    videoOut.println(valu[lableNumber[x]], HEX);

  }

   videoOut.waitForFrame();

}



void DisplayRadio(){

  videoOut.fillScreen(0);


  // Display the radio icon

  //drawRadioIcon(150, 40, 0xF800);


  // Display information

  videoOut.setTextSize(2);

  videoOut.setTextColor(0x07E0);

  videoOut.setCursor(50, 40);

  videoOut.print("FM: ");

  videoOut.println(fraquncy[frSelect]);

  videoOut.setCursor(50, 80);

  videoOut.print("OSC: ");

  videoOut.println(osc);

  videoOut.setCursor(50, 120);

  videoOut.print("RF AGC: ");

  videoOut.println(valu[8], HEX);

  videoOut.setCursor(50, 160);

  videoOut.print("PIF VCO: ");

  videoOut.println(valu[10], HEX);


  // Display the frame

  videoOut.waitForFrame();

}





int displaySelect =1;

void DisplayEsp32(int disp){

 switch (disp)

 {

 case 1:

  drawColorBars();

  break;

 case 2:

 DisplayRadio();

 //drawRadioHighlightedText();

 break;

 }


}

***********************************************************************************************************************

#include <Arduino.h>

#include <Adafruit_SI5351.h>

Adafruit_SI5351 clockgen = Adafruit_SI5351();

int clockSpeed=53;

const byte I2C_multiplexerAddresS = 0x70;

void ClockSetUP(){

       Wire.beginTransmission(I2C_multiplexerAddresS);  // TCA9548A address

    Wire.write(1 << 2);          // send byte to select bus

    Wire.endTransmission();

    if (clockgen.begin() != ERROR_NONE)

  {

    /* There was a problem detecting the IC ... check your connections */

   Serial.print("Ooops, no Si5351 detected ... Check your wiring or I2C ADDR!");

    while(1);

  }

  //Serial.println("OK!");

  clockgen.setupPLL(SI5351_PLL_B, 15, 2, 3);

  clockgen.setupMultisynth(1, SI5351_PLL_B, clockSpeed, 1, 2);

   clockgen.enableOutputs(true);


}


void PLL(uint8_t p){

  Wire.beginTransmission(I2C_multiplexerAddresS);  // TCA9548A address

    Wire.write(1 << 2);          // send byte to select bus

    Wire.endTransmission();

    clockgen.setupPLL(SI5351_PLL_B, 15, 2, 3);

  //Serial.println(p);

  clockgen.setupMultisynth(1, SI5351_PLL_B, p, 1, 2);

 clockgen.enableOutputs(true);

}

Step 4: The Final Product

Make Some Noise Contest

Runner Up in the
Make Some Noise Contest