Composite video from Arduino

I figure the one thing that the arduino is missing is a display. I'm short on cash so buying a display was out of the question. But i do have a PSone with a 5" LCD. I did a little search online and found a previous attempt at composite video on the arduino in the forums.
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1166667354/0
But it seam there where still problems with it. I then found this site.
http://dailyduino.com/archives/368
So i figured i'd give it a try and see how it went.
I can't say i understand even half of the code but it does work... to an extent. Problem i'm having, and the problem presented in the thread, is getting the V sync worked out. It displays what its designed to say on the screen but its rolling from bottom to top.
Maybe someone wiser then I can figure out what could be changed to get this to sync up.

//Adapted by phizone from:
//
//Arduino Tv framebuffer
//Alastair Parker
//2007

// Video out voltage levels
#define _SYNC 0x00
#define _BLACK 0x01
#define _GRAY 0x02
#define _WHITE 0x03

// dimensions of the screen
#define WIDTH 38
#define HEIGHT 14

//number of lines to display
#define DISPLAY_LINES 240

// update speed for the main loop of the game
#define UPDATE_INTERVAL 1

//video pins
#define DATA_PIN 8
#define SYNC_PIN 9

// the video frameBuffer
byte frameBuffer[WIDTH][HEIGHT];

// loop indices
byte index, index2;

// pal video line loop
byte line;
// current drawing line in framebuffer
byte newLine;

// if displaying the title
boolean showingTitle = true;

// draw a pixel to the buffer
void setPixel(byte x,byte y)
{
frameBuffer[x][y]= _WHITE;
}

void grayPixel(byte x, byte y)
{
frameBuffer[x][y]= _GRAY;
}

// draw a black pixel to the buffer
void clearPixel(byte x,byte y)
{
frameBuffer[x][y]= _BLACK;
}

//draw the title message
void drawArduinoPong()
{

//DAILY
setPixel(6,3);
setPixel(7,3);
setPixel(11,3);
setPixel(12,3);
setPixel(14,3);
setPixel(15,3);
setPixel(16,3);
setPixel(18,3);
setPixel(21,3);
setPixel(23,3);
setPixel(6,4);
setPixel(8,4);
setPixel(10,4);
setPixel(12,4);
setPixel(15,4);
setPixel(18,4);
setPixel(21,4);
setPixel(22,4);
setPixel(23,4);
setPixel(6,5);
setPixel(8,5);
setPixel(10,5);
setPixel(11,5);
setPixel(12,5);
setPixel(15,5);
setPixel(18,5);
setPixel(22,5);
setPixel(6,6);
setPixel(7,6);
setPixel(10,6);
setPixel(12,6);
setPixel(14,6);
setPixel(15,6);
setPixel(16,6);
setPixel(18,6);
setPixel(19,6);
setPixel(20,6);
setPixel(22,6);

//DUINO
setPixel(9,8);
setPixel(10,8);
setPixel(13,8);
setPixel(15,8);
setPixel(17,8);
setPixel(18,8);
setPixel(19,8);
setPixel(21,8);
setPixel(24,8);
setPixel(27,8);
setPixel(28,8);
setPixel(9,9);
setPixel(11,9);
setPixel(13,9);
setPixel(15,9);
setPixel(18,9);
setPixel(21,9);
setPixel(22,9);
setPixel(24,9);
setPixel(26,9);
setPixel(29,9);
setPixel(9,10);
setPixel(11,10);
setPixel(13,10);
setPixel(15,10);
setPixel(18,10);
setPixel(21,10);
setPixel(23,10);
setPixel(24,10);
setPixel(26,10);
setPixel(29,10);
setPixel(9,11);
setPixel(10,11);
setPixel(13,11);
setPixel(14,11);
setPixel(15,11);
setPixel(17,11);
setPixel(18,11);
setPixel(19,11);
setPixel(21,11);
setPixel(24,11);
setPixel(27,11);
setPixel(28,11);
}
// clear the screen
void clearScreen()
{
for (index = 0; index < WIDTH; index++)
for (index2=0;index2<=HEIGHT;++index2)
{
frameBuffer[index][index2] = _BLACK;
}
}

// the setup routine
void setup()
{
cli();
pinMode (SYNC_PIN, OUTPUT);
pinMode (DATA_PIN, OUTPUT);
digitalWrite (SYNC_PIN, HIGH);
digitalWrite (DATA_PIN, HIGH);
clearScreen();
drawArduinoPong();
}

void loop()
{
// iterate over the lines on the tv
for ( line =0;line< DISPLAY_LINES;++line)
{

// HSync
// front porch (1.5 us)
PORTB = _BLACK;
delayMicroseconds(1.5);
//sync (4.7 us)
PORTB = _SYNC;
delayMicroseconds(4.7);
// breezeway (.6us) + burst (2.5us) + colour back borch (1.6 us)
PORTB = _BLACK;
delayMicroseconds(0.6+2.5+1.6);


//calculate which line to draw to
newLine = line >>4;
delayMicroseconds(1);

//display the array for this line
// a loop would have been smaller, but it messes the timing up
PORTB = frameBuffer[0][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[1][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[2][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[3][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[4][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[5][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[6][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[7][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[8][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[9][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[10][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[11][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[12][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[13][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[14][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[15][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[16][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[17][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[18][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[19][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[20][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[21][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[22][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[23][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[24][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[25][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[26][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[27][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[28][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[29][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[30][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[31][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[32][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[33][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[34][newLine];
delayMicroseconds(1);
PORTB = frameBuffer[35][newLine];
delayMicroseconds(1);

// klugdge to correct timings
PORTB = frameBuffer[36][newLine];
PORTB=PORTB;
PORTB=PORTB;
PORTB=PORTB;
delayMicroseconds(2);
}

//vsync
PORTB = _SYNC;

// wait for the remainder of the sync period

delayMicroseconds(565);

}

I've tried to change the #define _SYNC 0x00 value but it doesn't seam to change anything. I'm pretty sure this is an issue with the hardware that we may not be able to overcome. But i sure do want to give it a try. This is a simple setup and could have allot of uses.

I think i'll start looking into the pin out of the playstation AV connection and see if i can go strait digital to the screen.

You may be having problems that this display is only 240 lines high.

US standard images are 262.5 lines per field (525 lines total), and whilst older analogue monitors will be OK with short fields, more modern digital sets may not be so forgiving.

(BTW, it isn't really composite, which combines luminance and chrominance into a single signal, it's just luminance)

Yes there are 525 scan lines on a TV of which only 486 are visible. So i have changed the number of scan lines to 243. I then went to the bottom of the code and played around with the ms delay in the last line and got a stable image with it set at 50 on this LCD. Though you can still see the scan line running threw the image. So there is still a bit of tweaking to be done. On a standard def CRT TV the image is still screwy. But i think once i get everything dialed in perfectly on the LCD that will be fixed as well.

I won't re post the code since the changes are simple. I may bring the scan lines up to 262 and see how it goes. The code errors out if i try and do a decimal place. But when i set the lines that high the image is blank.

EDIT: The best image i've been able to get so far on my LCD is using 100 in the last line of the code. But the timing is still off to propperly support a stable image on the CRT. Guess its time to move the arduino to the laptop so i can test it while on the TV.

BTW, if i get this all worked out, how hard will it be to have it coverted into a library?

library are not that hard, its the first thing I tried when I got an arduino

Right now i'm at work so i'm unable to play with the settings right now. I wish i could get the IDE running on the PCs here. Then i'd bring my arduino with me and work on it more.

Though i have been reviewing the code and trying to get my head around all these timings. I have found a couple of lines in the code that are references to the original game this code was adapted from so i'll be removing those. Correct me if i'm wrong. As far as i can tell defining gray isn't needed at all. Either the areas specified will be black or white. Trying to do gray scale here would be too much for the arduino to handle.

I am considering redefining the size of the pixels. Since i'll have to redefine the number of scan lines to fit an NTSC display. Been trying to figure out how the width and height of the pixels first came to be. I figure with a NTSC display being 720x480 the width would need to be 40 and the height would have to be 15. Thats the only way to break it down into large pixels evenly. So each pixel the arduino will display will take up 48x32 block of pixels. The Display lines will remain at 240.

Current untested sketch. These changes have likely effected the timing.

//Adapted by phizone from:
//
//Arduino Tv framebuffer
//Alastair Parker
//2007

// Video out voltage levels
#define _SYNC 0x00
#define _BLACK 0x01

#define _WHITE 0x03

// dimensions of the screen
#define WIDTH 40
#define HEIGHT 15

//number of lines to display
#define DISPLAY_LINES 240

//video pins
#define DATA_PIN 8
#define SYNC_PIN 9

// the video frameBuffer
byte frameBuffer[WIDTH][HEIGHT];

// loop indices
byte index, index2;

// video line loop
byte line;
// current drawing line in framebuffer
byte newLine;

// draw a pixel to the buffer
void setPixel(byte x,byte y)
{
  frameBuffer[x][y]= _WHITE;
}



// draw a black pixel to the buffer
void clearPixel(byte x,byte y)
{
  frameBuffer[x][y]= _BLACK;
}

//draw the title message
void drawscreen()
{

  //DAILY
  setPixel(6,3);
  setPixel(7,3);
  setPixel(11,3);
  setPixel(12,3);
  setPixel(14,3);
  setPixel(15,3);
  setPixel(16,3);
  setPixel(18,3);
  setPixel(21,3);
  setPixel(23,3);
  setPixel(6,4);
  setPixel(8,4);
  setPixel(10,4);
  setPixel(12,4);
  setPixel(15,4);
  setPixel(18,4);
  setPixel(21,4);
  setPixel(22,4);
  setPixel(23,4);
  setPixel(6,5);
  setPixel(8,5);
  setPixel(10,5);
  setPixel(11,5);
  setPixel(12,5);
  setPixel(15,5);
  setPixel(18,5);
  setPixel(22,5);
  setPixel(6,6);
  setPixel(7,6);
  setPixel(10,6);
  setPixel(12,6);
  setPixel(14,6);
  setPixel(15,6);
  setPixel(16,6);
  setPixel(18,6);
  setPixel(19,6);
  setPixel(20,6);
  setPixel(22,6);

  //DUINO
  setPixel(9,8);
  setPixel(10,8);
  setPixel(13,8);
  setPixel(15,8);
  setPixel(17,8);
  setPixel(18,8);
  setPixel(19,8);
  setPixel(21,8);
  setPixel(24,8);
  setPixel(27,8);
  setPixel(28,8);
  setPixel(9,9);
  setPixel(11,9);
  setPixel(13,9);
  setPixel(15,9);
  setPixel(18,9);
  setPixel(21,9);
  setPixel(22,9);
  setPixel(24,9);
  setPixel(26,9);
  setPixel(29,9);
  setPixel(9,10);
  setPixel(11,10);
  setPixel(13,10);
  setPixel(15,10);
  setPixel(18,10);
  setPixel(21,10);
  setPixel(23,10);
  setPixel(24,10);
  setPixel(26,10);
  setPixel(29,10);
  setPixel(9,11);
  setPixel(10,11);
  setPixel(13,11);
  setPixel(14,11);
  setPixel(15,11);
  setPixel(17,11);
  setPixel(18,11);
  setPixel(19,11);
  setPixel(21,11);
  setPixel(24,11);
  setPixel(27,11);
  setPixel(28,11);
}
// clear the screen
void clearScreen()
{
  for (index = 0; index < WIDTH; index++)
    for (index2=0;index2<HEIGHT;++index2)
  {
    frameBuffer[index][index2] = _BLACK;
  }
  }

// the setup routine
void setup()
{
  cli();
  pinMode (SYNC_PIN, OUTPUT);
  pinMode (DATA_PIN, OUTPUT);
  digitalWrite (SYNC_PIN, HIGH);
  digitalWrite (DATA_PIN, HIGH);
  clearScreen();
  drawscreen();
}

void loop()
{
  // iterate over the lines on the tv
  for ( line =0;line< DISPLAY_LINES;++line)
  {

    // HSync
    // front porch (1.5 us)
    PORTB = _BLACK;
    delayMicroseconds(1.5);
    //sync (4.7 us)
    PORTB = _SYNC;
    delayMicroseconds(4.7);
    // breezeway (.6us) + burst (2.5us) + colour back borch (1.6 us)
    PORTB = _BLACK;
    delayMicroseconds(0.6+2.5+1.6);


    //calculate which line to draw to
    newLine = line >>4;
    delayMicroseconds(1);

    //display the array for this line
    // a loop would have been smaller, but it messes the timing up
    PORTB = frameBuffer[0][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[1][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[2][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[3][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[4][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[5][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[6][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[7][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[8][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[9][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[10][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[11][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[12][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[13][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[14][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[15][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[16][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[17][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[18][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[19][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[20][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[21][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[22][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[23][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[24][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[25][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[26][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[27][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[28][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[29][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[30][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[31][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[32][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[33][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[34][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[35][newLine];
    delayMicroseconds(1);

    // klugdge to correct timings
    PORTB = frameBuffer[36][newLine];
    PORTB=PORTB;
    PORTB=PORTB;
    PORTB=PORTB;
    delayMicroseconds(2);
  }

  //vsync
  PORTB = _SYNC;

  // wait for the remainder of the sync period

  delayMicroseconds(100);

}

can't help but wonder if this would be better suited on the analog pins.

can't help but wonder if this would be better suited on the analog pins.

The analogue pins are inputs - why do you think that?

I figure it would be better with an analogue output or at least take advantage of a PWM since this is an analog signal we are trying to create for an analog TV. Even though all i'm after is for a good looking display on that PS1 LCD. But i figure if i'm going to get this working i better make it so it will work on any display that uses composite input.

An RS-170 (NTSC minus the colour carrier) video signal is an analogue signal with a very specific waveform and a bandwidth of around 4 - 5MHz.
Your PWM signals ("analogue outputs") from the Arduino run at around 4-500Hz, and are digital signals.

I've come across some interesting findings trying to break down this code. I've got everything set to give a nice solid display on my LCD. Problem is the very top row, row 0 is displayed at the very bottom of the screen. Column 34 is off a bit in its width on the LCD but gets cut off at just the right size on the TV. Also on the TV we loose row 1. it gets cut off.

This project definitely needs a solid power source to the arduino. When using a battery pack of 4 AAA batteries that are a bit weak, the image isn't very solid on the LCD. The TV however is more forgiving here.

So as a reminder this is how you hook this up.

Code will be broken into 2 sections to fit in the limited post space.

/* 
Adapted by phizone from:

Arduino Tv framebuffer
Alastair Parker
2007

Modified for use on NTSC monitors and displays
2009
Michael Pilcher

*/

// Video out voltage levels
#define _SYNC 0x00
#define _BLACK 0x01
#define _WHITE 0x03

// dimensions of the screen
#define WIDTH 35  // sets # of columns 0-34. Column 34 is oversized on some displays
#define HEIGHT 15  // sets # of rows 0-14. row 0 wrapped around to the bottom

//number of lines to display
#define DISPLAY_LINES 255

//video pins
#define DATA_PIN 9
#define SYNC_PIN 8

// the video frameBuffer
byte frameBuffer[WIDTH][HEIGHT];

// loop indices
byte index, index2;

// video line loop
byte line;
// current drawing line in framebuffer
byte newLine;

// draw a pixel to the buffer
void setPixel(byte x,byte y)
{
  frameBuffer[x][y]= _WHITE;
}

// draw a black pixel to the buffer
void clearPixel(byte x,byte y)
{
  frameBuffer[x][y]= _BLACK;
}

//draw the title message
void drawScreen()
{
  // possible unusable column. may appear too wide on some displays
  setPixel(34,0);
  setPixel(34,1);
  setPixel(34,2);
  setPixel(34,3);
  setPixel(34,4);
  setPixel(34,5);
  setPixel(34,6);  
  setPixel(34,7);
  setPixel(34,8);
  setPixel(34,9);
  setPixel(34,10);
  setPixel(34,11);
  setPixel(34,12);
  setPixel(34,13);
  setPixel(34,14);
  
  // row 0, this row displays at the bottom of the screen not the top
  setPixel(1,0);
  setPixel(2,0);
  setPixel(3,0);
  setPixel(4,0);
  setPixel(5,0);
  setPixel(6,0);
  setPixel(7,0);
  setPixel(8,0);
  setPixel(9,0);
  setPixel(10,0);
  setPixel(11,0);
  setPixel(12,0);
  setPixel(13,0);
  setPixel(14,0);
  setPixel(15,0);
  setPixel(16,0);
  setPixel(17,0);
  setPixel(18,0);
  setPixel(19,0);
  setPixel(20,0);
  setPixel(21,0);
  setPixel(22,0);
  setPixel(23,0);
  setPixel(24,0);
  setPixel(25,0);
  setPixel(26,0);
  setPixel(27,0);
  setPixel(28,0);
  setPixel(29,0);
  setPixel(30,0);
  setPixel(31,0);
  setPixel(32,0);
  setPixel(33,0);
   
  // checked test pattern, CHANGE THESE TO BUILD YOUR IMAGE
  setPixel(0,2);
  setPixel(0,4);
  setPixel(0,6);  
  setPixel(0,8);
  setPixel(0,10);
  setPixel(0,12);
  setPixel(0,14);
  setPixel(1,1);
  setPixel(1,3);
  setPixel(1,5);
  setPixel(1,7);
  setPixel(1,9);
  setPixel(1,11);
  setPixel(1,13);
  setPixel(2,2);
  setPixel(2,4);
  setPixel(2,6);
  setPixel(2,8);
  setPixel(2,10);
  setPixel(2,12);
  setPixel(2,14);
  setPixel(3,1);
  setPixel(3,3);
  setPixel(3,5);
  setPixel(3,7);
  setPixel(3,9);
  setPixel(3,11);
  setPixel(3,13);
  setPixel(4,2);
  setPixel(4,4);
  setPixel(4,6);
  setPixel(4,8);
  setPixel(4,10);
  setPixel(4,12);
  setPixel(4,14);
  setPixel(5,1);
  setPixel(5,3);
  setPixel(5,5);
  setPixel(5,7);
  setPixel(5,9);
  setPixel(5,11);
  setPixel(5,13);
  setPixel(6,2);
  setPixel(6,4);
  setPixel(6,6);
  setPixel(6,8);
  setPixel(6,10);
  setPixel(6,12);
  setPixel(6,14);
  setPixel(7,1);
  setPixel(7,3);
  setPixel(7,5);
  setPixel(7,7);
  setPixel(7,9);
  setPixel(7,11);
  setPixel(7,13);
  setPixel(8,2);
  setPixel(8,4);
  setPixel(8,6);
  setPixel(8,8);
  setPixel(8,10);
  setPixel(8,12);
  setPixel(8,14);
  setPixel(9,1);
  setPixel(9,3);
  setPixel(9,5);
  setPixel(9,7);
  setPixel(9,9);
  setPixel(9,11);
  setPixel(9,13);
  setPixel(10,2);
  setPixel(10,4);
  setPixel(10,6);
  setPixel(10,8);
  setPixel(10,10);
  setPixel(10,12);
  setPixel(10,14);
  setPixel(11,1);
  setPixel(11,3);
  setPixel(11,5);
  setPixel(11,7);
  setPixel(11,9);
  setPixel(11,11);
  setPixel(11,13);
  setPixel(12,2);
  setPixel(12,4);
  setPixel(12,6);
  setPixel(12,8);
  setPixel(12,10);
  setPixel(12,12);
  setPixel(12,14);
  setPixel(13,1);
  setPixel(13,3);
  setPixel(13,5);
  setPixel(13,7);
  setPixel(13,9);
  setPixel(13,11);
  setPixel(13,13);
  setPixel(14,2);
  setPixel(14,4);
  setPixel(14,6);
  setPixel(14,8);
  setPixel(14,10);
  setPixel(14,12);
  setPixel(14,14);
  setPixel(15,1);
  setPixel(15,3);
  setPixel(15,7);
  setPixel(15,5);
  setPixel(15,9);
  setPixel(15,11);
  setPixel(15,13);
  setPixel(16,2);
  setPixel(16,4);
  setPixel(16,6);
  setPixel(16,8);
  setPixel(16,10);
  setPixel(16,12);
  setPixel(16,14);
  setPixel(17,1);
  setPixel(17,3);
  setPixel(17,5);
  setPixel(17,7);
  setPixel(17,9);
  setPixel(17,11);
  setPixel(17,13);
  setPixel(18,2);
  setPixel(18,4);
  setPixel(18,6);
  setPixel(18,8);
  setPixel(18,10);
  setPixel(18,12);
  setPixel(18,14);
  setPixel(19,1);
  setPixel(19,3);
  setPixel(19,5);
  setPixel(19,7);
  setPixel(19,9);
  setPixel(19,11);
  setPixel(19,13);
  setPixel(20,2);
  setPixel(20,4);
  setPixel(20,6);
  setPixel(20,8);
  setPixel(20,10);
  setPixel(20,12);
  setPixel(20,14);
  setPixel(21,1);
  setPixel(21,3);
  setPixel(21,5);
  setPixel(21,7);
  setPixel(21,9);
  setPixel(21,11);
  setPixel(21,13);
  setPixel(22,2);
  setPixel(22,4);
  setPixel(22,6);
  setPixel(22,8);
  setPixel(22,10);
  setPixel(22,12);
  setPixel(22,14);
  setPixel(23,1);
  setPixel(23,3);
  setPixel(23,5);
  setPixel(23,7);
  setPixel(23,9);
  setPixel(23,11);
  setPixel(23,13);
  setPixel(24,2);
  setPixel(24,4);
  setPixel(24,6);
  setPixel(24,8);
  setPixel(24,10);
  setPixel(24,12);
  setPixel(24,14);
  setPixel(25,1);
  setPixel(25,3);
  setPixel(25,5);
  setPixel(25,7);
  setPixel(25,9);
  setPixel(25,11);
  setPixel(25,13);
  setPixel(26,2);
  setPixel(26,4);
  setPixel(26,6);
  setPixel(26,8);
  setPixel(26,10);
  setPixel(26,12);
  setPixel(26,14);
  setPixel(27,1);
  setPixel(27,3);
  setPixel(27,5);
  setPixel(27,7);
  setPixel(27,9);
  setPixel(27,11);
  setPixel(27,13);
  setPixel(28,2);
  setPixel(28,4);
  setPixel(28,6);
  setPixel(28,8);
  setPixel(28,10);
  setPixel(28,12);
  setPixel(28,14);
  setPixel(29,1);
  setPixel(29,3);
  setPixel(29,5);
  setPixel(29,7);
  setPixel(29,9);
  setPixel(29,11);
  setPixel(29,13);
  setPixel(30,2);
  setPixel(30,4);
  setPixel(30,6);
  setPixel(30,8);
  setPixel(30,10);
  setPixel(30,12);
  setPixel(30,14);
  setPixel(31,1);
  setPixel(31,3);
  setPixel(31,5);
  setPixel(31,7);
  setPixel(31,9);
  setPixel(31,11);
  setPixel(31,13);
  setPixel(32,2);
  setPixel(32,4);
  setPixel(32,6);
  setPixel(32,8);
  setPixel(32,10);
  setPixel(32,12);
  setPixel(32,14);
  setPixel(33,1);
  setPixel(33,3);
  setPixel(33,5);
  setPixel(33,7);
  setPixel(33,9);
  setPixel(33,11);
  setPixel(33,13);
  
   
}

Code continued:

// clear the screen
void clearScreen()
{
  for (index = 0; index < WIDTH; index++)
    for (index2 = 0; index2< HEIGHT; index2++)
  {
      frameBuffer[index][index2] = _BLACK;
  }
/*clears stray pixel at (34,0) that doesn't get cleared
    by the above for() loops. If the loop that reads
    for (index = 0; index < WIDTH; index++)
    reads
    for (index = 0; index <= WIDTH; index++)
    the timing is thrown off and the image gets scrambled
  */
  frameBuffer[35][0] = _BLACK;
}

// the setup routine
void setup()
{
cli();
pinMode (SYNC_PIN, OUTPUT);
pinMode (DATA_PIN, OUTPUT);
digitalWrite (SYNC_PIN, HIGH);
digitalWrite (DATA_PIN, HIGH);
clearScreen();
drawScreen();
}

void loop()
{
  // iterate over the lines on the tv
  for ( line =0;line< DISPLAY_LINES;++line)
  {

    // HSync
    // front porch (1.5 us)
    PORTB = _BLACK;
    delayMicroseconds(1.5);
    //sync (4.7 us)
    PORTB = _SYNC;
    delayMicroseconds(4.7);
    // breezeway (.6us) + burst (2.5us) + colour back borch (1.6 us)
    PORTB = _BLACK;
    delayMicroseconds(4.7);

    //calculate which line to draw to
    newLine = line >>4;
    delayMicroseconds(1);

    //display the array for this line
    // a loop would have been smaller, but it messes the timing up
    PORTB = frameBuffer[0][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[1][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[2][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[3][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[4][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[5][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[6][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[7][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[8][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[9][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[10][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[11][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[12][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[13][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[14][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[15][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[16][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[17][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[18][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[19][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[20][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[21][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[22][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[23][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[24][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[25][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[26][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[27][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[28][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[29][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[30][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[31][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[32][newLine];
    delayMicroseconds(1);
    PORTB = frameBuffer[33][newLine];
     delayMicroseconds(1);
    PORTB = frameBuffer[34][newLine];
    delayMicroseconds(1);
    // klugdge to correct timings
    PORTB = PORTB;
    delayMicroseconds(1);
    PORTB = PORTB;
    delayMicroseconds(1);
    PORTB = PORTB;
    PORTB = PORTB;
    PORTB = PORTB;
    delayMicroseconds(1);
  }

//vsync
PORTB = _SYNC;

// wait for the remainder of the sync period

delayMicroseconds(125);

}

Here are the coordinates for the classic "HELLO WORLD"

// hello world
  // H
  setPixel(2,2);
  setPixel(2,3);
  setPixel(2,4);
  setPixel(2,5);
  setPixel(2,6);
  setPixel(3,4);
  setPixel(4,4);
  setPixel(5,2);
  setPixel(5,3);
  setPixel(5,4);
  setPixel(5,5);
  setPixel(5,6);
  
  // E
  setPixel(7,2);
  setPixel(7,3);
  setPixel(7,4);
  setPixel(7,5);
  setPixel(7,6);
  setPixel(8,2);
  setPixel(8,4);
  setPixel(8,6);
  setPixel(9,2);
  setPixel(9,6);

  // L
  setPixel(11,2);
  setPixel(11,3);
  setPixel(11,4);
  setPixel(11,5);
  setPixel(11,6);
  setPixel(12,6);
  setPixel(13,6);
    
  // L
  setPixel(15,2);
  setPixel(15,3);
  setPixel(15,4);
  setPixel(15,5);
  setPixel(15,6);
  setPixel(16,6);
  setPixel(17,6);
  
  // O
  setPixel(19,3);
  setPixel(19,4);
  setPixel(19,5);
  setPixel(20,2);
  setPixel(20,6);
  setPixel(21,2);
  setPixel(21,6);
  setPixel(22,3);
  setPixel(22,4);
  setPixel(22,5);
  
  // W
  setPixel(2,10);
  setPixel(2,11);
  setPixel(2,12);
  setPixel(3,13);
  setPixel(3,14);
  setPixel(4,12);
  setPixel(5,13);
  setPixel(5,14);
  setPixel(6,10);
  setPixel(6,11);
  setPixel(6,12);
  
  // 0
  setPixel(8,11);
  setPixel(8,12);
  setPixel(8,13);
  setPixel(9,10);
  setPixel(9,14);
  setPixel(10,10);
  setPixel(10,14);
  setPixel(11,11);
  setPixel(11,12);
  setPixel(11,13);
  
  // R
  setPixel(13,10);
  setPixel(13,11);
  setPixel(13,12);
  setPixel(13,13);
  setPixel(13,14);
  setPixel(14,10);
  setPixel(14,12);
  setPixel(15,10);
  setPixel(15,12);
  setPixel(15,13);
  setPixel(16,11);
  setPixel(16,14);
  
  // L
  setPixel(18,10);
  setPixel(18,11);
  setPixel(18,12);
  setPixel(18,13);
  setPixel(18,14);
  setPixel(19,14);
  setPixel(20,14);
  
  // D
  setPixel(22,10);
  setPixel(22,11);
  setPixel(22,12);
  setPixel(22,13);
  setPixel(22,14);
  setPixel(23,10);
  setPixel(23,14);
  setPixel(24,10);
  setPixel(24,14);
  setPixel(25,11);
  setPixel(25,12);
  setPixel(25,13);

I also updated the code in the above post. After creating Hello world i found that the pixel at (34,0) was not being cleared. I tried to fix the for() statement in the clearScreen section to wipe that pixel but it caused timing issues. So i added a line to clear that single area without effecting timings. For now that pixel isn't usable. Since its in the bottom right corner i'm not even going to bother trying to fix it. It can just stay off.

OK this has hit a dead end for me. Its just too much to try and get this to do anything. if you try to make it do more then show a single preset image your fighting timings. I've tried to take the original pong code, that this came from, and get it working on an NTSC display but timings are a nightmare. If you make a couple of small changes to the code you end up throwing everything off.

There is a lot of information avalible at the below link on composite video from a AVR chip. Their TellyMate video shield is a great little product and the avalible source code can show you how they implemented the timing needed. It is a major effort to get the timing just right, so a shield based product makes a lot of sense rather then trying to implement it inside an Arduino along with other application code.

Lefty

Great link. Thanks.

Guess i need to add it to my wish list. or build it myself.

I've been trying to do composite video myself, by using PWM to generate the sync signal. I've tried this on an LCD and CRT and it syncs ok for me (though it was better on the old CRT than the LCD.)

The sync signal is now on pin 9 (as its a PWM pin) and the picture is on pin 8 so if you use the same circuit as before the wires need to be swapped over.

It's still very hard to do anything more than display a static picture without losing the timing, but I've got a few ideas there...

byte fb[56][12];

static inline void fbput(char x,char y,char a) 
{
  if(x<0 || x>95 || y<0 || y>55)return;
  a ? fb[y][x>>3] |= 1<<(x&7) : fb[y][x>>3] &= ~(1<<(x&7)) ;
}

#define OPWM  0b11000010
#define OPORT 0b00000010
#define NOP __asm __volatile ("nop")

void setup()
{
    cli();
    DDRB |= 0b00000011;
  
    TCCR1A=OPWM;
    TCCR1B=0b00011010;   //1 count = 0.5uS    
    ICR1H=0;
    ICR1L=126;    //period 63.5uS (126/2+1) (?) 127 for pal
    OCR1AH=0;
    OCR1AL=9;   //4.5uS sync pulse (?)
    
    int i,j;
    for(i=0;i<12;i++)for(j=0;j<56;j++)fb[j][i]=0;
      
//    for(i=0;i<96;i++)for(j=0;j<56;j++)fbput(i,j,(i^j)&1);
    for(i=0;i<96;i++){fbput(i,0,1);fbput(i,55,1);}
    for(j=0;j<56;j++){fbput(0,j,1);fbput(95,j,1);}
    
    float a;
    for(a=0;a<M_PI*2;a+=M_PI/128)
    {
       fbput(48.5+27*cos(a),28.5+26*sin(a),1); 
       fbput(48.5+3*cos(a),29.5+6*sin(a),1); 
       fbput(34.5+6*cos(a),23.5+6*sin(a),1); 
       fbput(62.5+6*cos(a),23.5+6*sin(a),1); 
       if(a<=M_PI)fbput(48.5+16*cos(a),37.5+6*sin(a),1);      
    }         
}

void loop()
{
  char i,j,k;
  register char t;
  int line=0;
  for(;;)
  {
    while(TCNT1L != 0); //wait for start of line
    line++;
    
    if(line>=4 && line <= 6) 
    {
      //vsync pulses
      PORTB=0; 
      TCCR1A=OPORT; while(TCNT1L < 59); 
      PORTB=2; while(TCNT1L < 63);   
      PORTB=0; while(TCNT1L < 123);   
      PORTB=2; while(TCNT1L < 126); 
      TCCR1A=OPWM;
      continue;
    }
    if(line<=9) // lines 1-3 and 7-9
    {
      //pre-equalising / post equalising pulses
      PORTB=0;
      TCCR1A=OPORT; while(TCNT1L < 4);
      PORTB=2; while(TCNT1L < 64);
      PORTB=0; while(TCNT1L < 68); 
      PORTB=2; while(TCNT1L < 126);
      TCCR1A=OPWM;
      continue;
    }
    if(line>=30 && line<254)
    {
      j=20;
      while(TCNT1L < j);
      NOP;NOP; // helps stop jittering
      for(i=0;i<12;i++)
      {
        t=fb[(line-30)>>2][i];   
          PORTB = t;t>>=1;NOP;    
          PORTB = t;t>>=1;NOP;    
          PORTB = t;t>>=1;NOP;    
          PORTB = t;t>>=1;NOP;    
          PORTB = t;t>>=1;NOP;    
          PORTB = t;t>>=1;NOP;    
          PORTB = t;t>>=1;NOP;    
          PORTB = t;
      }
      PORTB=0;
      continue;
    }           
    if(line==262)     line=0;   // 312 for pal
  }
}

If it's any help, the following diagram is taken from some notes on Monochrome Composite video signals that I wrote whilst developing the TellyMate. It shows the PAL signal, but the NTSC signal is similar. Note that this isn't a proper interlaced signal - it's a fake-progressive signal - most TVs understand it though.

No matter what you do, you're always going to be up against timing issues when displaying TV signals - there's just not a lot of free time. There's a lot of time-sensitive code in the TellyMate to enable it to read and process serial data without corrupting the display at any time. For more details, see the article about the design of the TellyMate.

If you've got a 'spare' Arduino, here's a thread that details how you can turn an Arduino into a TellyMate and here's a diagram on flickr.

@digimike: you might find that the 75R resistor is not required - most TVs should already have this. Including the 75R on 'your side' effectively halves the voltages being output. See this AVRFreaks thread.

I hope this helps!

Thanks for the great info there. I hadn't scrolled to the bottom of the page till today. Didn't know all the code and stuff was made available.