TVout: NTSC and PAL Composite Video Output.

I had a thread in the old forums and as this section is for video output I though I would start a new thread here.

Anyway, TVout is an interrupt driven composite video output Library using only 2 resistors to generate a black and white signal at configurable resolutions (defaults to 128x96). It can output text with several included fonts, draw simple shapes, bitmaps, generate 1bit audio, and allows direct access to the frame buffer for custom display routines.

This Library does restrict the use of interrupts (like the ones used by the Serial library) to combat this it includes 2 hooks for calling functions at fixed intervals. One occurs every scan-line and is suitable for very fast methods such as polling the serial connection; this is demon-straighted in the add-on library pollserial. The second occurs once per frame for longer methods like polling a game-pad or plying music in the background.

Full documentation on this library can be found here: Google Code Archive - Long-term storage for Google Code Project Hosting.

Output Schematic:

Specific Connections per device can be found on the Google code page.

Example Video and Source Code (excluding image data):

#include <TVout.h>
#include <fontALL.h>
#include "schematic.h"
#include "TVOlogo.h"

TVout TV;

int zOff = 150;
int xOff = 0;
int yOff = 0;
int cSize = 50;
int view_plane = 64;
float angle = PI/60;

float cube3d[8][3] = {
  {xOff - cSize,yOff + cSize,zOff - cSize},
  {xOff + cSize,yOff + cSize,zOff - cSize},
  {xOff - cSize,yOff - cSize,zOff - cSize},
  {xOff + cSize,yOff - cSize,zOff - cSize},
  {xOff - cSize,yOff + cSize,zOff + cSize},
  {xOff + cSize,yOff + cSize,zOff + cSize},
  {xOff - cSize,yOff - cSize,zOff + cSize},
  {xOff + cSize,yOff - cSize,zOff + cSize}
};
unsigned char cube2d[8][2];


void setup() {
  TV.begin(NTSC,120,96);
  TV.select_font(font6x8);
  intro();
  TV.println("I am the TVout\nlibrary running on a freeduino\n");
  TV.delay(2500);
  TV.println("I generate a PAL\nor NTSC composite  video using\ninterrupts\n");
  TV.delay(2500);
  TV.println("My schematic:");
  TV.delay(1500);
  TV.bitmap(0,0,schematic);
  TV.delay(10000);
  TV.clear_screen();
  TV.println("Lets see what\nwhat I can do");
  TV.delay(2000);
  
  //fonts
  TV.clear_screen();
  TV.println(0,0,"Multiple fonts:");
  TV.select_font(font4x6);
  TV.println("4x6 font FONT");
  TV.select_font(font6x8);
  TV.println("6x8 font FONT");
  TV.select_font(font8x8);
  TV.println("8x8 font FONT");
  TV.select_font(font6x8);
  TV.delay(2000);
  
  TV.clear_screen();
  TV.print(9,44,"Draw Basic Shapes");
  TV.delay(2000);
  
  //circles
  TV.clear_screen();
  TV.draw_circle(TV.hres()/2,TV.vres()/2,TV.vres()/3,WHITE);
  TV.delay(500);
  TV.draw_circle(TV.hres()/2,TV.vres()/2,TV.vres()/2,WHITE,INVERT);
  TV.delay(2000);
  
  //rectangles and lines
  TV.clear_screen();
  TV.draw_rect(20,20,80,56,WHITE);
  TV.delay(500);
  TV.draw_rect(10,10,100,76,WHITE,INVERT);
  TV.delay(500);
  TV.draw_line(60,20,60,76,INVERT);
  TV.draw_line(20,48,100,48,INVERT);
  TV.delay(500);
  TV.draw_line(10,10,110,86,INVERT);
  TV.draw_line(10,86,110,10,INVERT);
  TV.delay(2000);
  
  //random cube forever.
  TV.clear_screen();
  TV.print(16,40,"Random Cube");
  TV.print(28,48,"Rotation");
  TV.delay(2000);
  
  randomSeed(analogRead(0));
}

void loop() {
  int rsteps = random(10,60);
  switch(random(6)) {
    case 0:
      for (int i = 0; i < rsteps; i++) {
        zrotate(angle);
        printcube();
      }
      break;
    case 1:
      for (int i = 0; i < rsteps; i++) {
        zrotate(2*PI - angle);
        printcube();
      }
      break;
    case 2:
      for (int i = 0; i < rsteps; i++) {
        xrotate(angle);
        printcube();
      }
      break;
    case 3:
      for (int i = 0; i < rsteps; i++) {
        xrotate(2*PI - angle);
        printcube();
      }
      break;
    case 4:
      for (int i = 0; i < rsteps; i++) {
        yrotate(angle);
        printcube();
      }
      break;
    case 5:
      for (int i = 0; i < rsteps; i++) {
        yrotate(2*PI - angle);
        printcube();
      }
      break;
  }
}

void intro() {
unsigned char w,l,wb;
  int index;
  w = pgm_read_byte(TVOlogo);
  l = pgm_read_byte(TVOlogo+1);
  if (w&7)
    wb = w/8 + 1;
  else
    wb = w/8;
  index = wb*(l-1) + 2;
  for ( unsigned char i = 1; i < l; i++ ) {
    TV.bitmap((TV.hres() - w)/2,0,TVOlogo,index,w,i);
    index-= wb;
    TV.delay(50);
  }
  for (unsigned char i = 0; i < (TV.vres() - l)/2; i++) {
    TV.bitmap((TV.hres() - w)/2,i,TVOlogo);
    TV.delay(50);
  }
  TV.delay(3000);
  TV.clear_screen();
}

void printcube() {
  //calculate 2d points
  for(byte i = 0; i < 8; i++) {
    cube2d[i][0] = (unsigned char)((cube3d[i][0] * view_plane / cube3d[i][2]) + (TV.hres()/2));
    cube2d[i][1] = (unsigned char)((cube3d[i][1] * view_plane / cube3d[i][2]) + (TV.vres()/2));
  }
  TV.delay_frame(1);
  TV.clear_screen();
  draw_cube();
}

void zrotate(float q) {
  float tx,ty,temp;
  for(byte i = 0; i < 8; i++) {
    tx = cube3d[i][0] - xOff;
    ty = cube3d[i][1] - yOff;
    temp = tx * cos(q) - ty * sin(q);
    ty = tx * sin(q) + ty * cos(q);
    tx = temp;
    cube3d[i][0] = tx + xOff;
    cube3d[i][1] = ty + yOff;
  }
}

void yrotate(float q) {
  float tx,tz,temp;
  for(byte i = 0; i < 8; i++) {
    tx = cube3d[i][0] - xOff;
    tz = cube3d[i][2] - zOff;
    temp = tz * cos(q) - tx * sin(q);
    tx = tz * sin(q) + tx * cos(q);
    tz = temp;
    cube3d[i][0] = tx + xOff;
    cube3d[i][2] = tz + zOff;
  }
}

void xrotate(float q) {
  float ty,tz,temp;
  for(byte i = 0; i < 8; i++) {
    ty = cube3d[i][1] - yOff;
    tz = cube3d[i][2] - zOff;
    temp = ty * cos(q) - tz * sin(q);
    tz = ty * sin(q) + tz * cos(q);
    ty = temp;
    cube3d[i][1] = ty + yOff;
    cube3d[i][2] = tz + zOff;
  }
}

void draw_cube() {
  TV.draw_line(cube2d[0][0],cube2d[0][1],cube2d[1][0],cube2d[1][1],WHITE);
  TV.draw_line(cube2d[0][0],cube2d[0][1],cube2d[2][0],cube2d[2][1],WHITE);
  TV.draw_line(cube2d[0][0],cube2d[0][1],cube2d[4][0],cube2d[4][1],WHITE);
  TV.draw_line(cube2d[1][0],cube2d[1][1],cube2d[5][0],cube2d[5][1],WHITE);
  TV.draw_line(cube2d[1][0],cube2d[1][1],cube2d[3][0],cube2d[3][1],WHITE);
  TV.draw_line(cube2d[2][0],cube2d[2][1],cube2d[6][0],cube2d[6][1],WHITE);
  TV.draw_line(cube2d[2][0],cube2d[2][1],cube2d[3][0],cube2d[3][1],WHITE);
  TV.draw_line(cube2d[4][0],cube2d[4][1],cube2d[6][0],cube2d[6][1],WHITE);
  TV.draw_line(cube2d[4][0],cube2d[4][1],cube2d[5][0],cube2d[5][1],WHITE);
  TV.draw_line(cube2d[7][0],cube2d[7][1],cube2d[6][0],cube2d[6][1],WHITE);
  TV.draw_line(cube2d[7][0],cube2d[7][1],cube2d[3][0],cube2d[3][1],WHITE);
  TV.draw_line(cube2d[7][0],cube2d[7][1],cube2d[5][0],cube2d[5][1],WHITE);
}

Tetris Using either a Hackvision or a Video Game Shield with a wii classic controller(can wire it up without the shield too) for input:

i tried compiling the code got errors... help?

Sure post the errors and Ill see what I can do.

DemoNTSC.cpp:1:19: error: TVout.h: No such file or directory
DemoNTSC.cpp:2:21: error: fontALL.h: No such file or directory
DemoNTSC:5: error: 'TVout' does not name a type
DemoNTSC.cpp: In function 'void setup()':
DemoNTSC:28: error: 'TV' was not declared in this scope
DemoNTSC:28: error: 'NTSC' was not declared in this scope
DemoNTSC:29: error: 'font6x8' was not declared in this scope
DemoNTSC:46: error: 'font4x6' was not declared in this scope
DemoNTSC:50: error: 'font8x8' was not declared in this scope
DemoNTSC:61: error: 'WHITE' was not declared in this scope
DemoNTSC:63: error: 'INVERT' was not declared in this scope
DemoNTSC.cpp: In function 'void intro()':
DemoNTSC:141: error: 'TV' was not declared in this scope
DemoNTSC:145: error: 'TV' was not declared in this scope
DemoNTSC:149: error: 'TV' was not declared in this scope
DemoNTSC.cpp: In function 'void printcube()':
DemoNTSC:156: error: 'TV' was not declared in this scope
DemoNTSC:159: error: 'TV' was not declared in this scope
DemoNTSC.cpp: In function 'void draw_cube()':
DemoNTSC:204: error: 'TV' was not declared in this scope
DemoNTSC:204: error: 'WHITE' was not declared in this scope

You need to install the library, go to the google code page linked to in the first post. Simply download TVoutBeta1 and unzip it to your arduino/libraries folder. The posted sketch is an example sketch that can be found in File-Examples-TVout once the library has been installed.

i did as u said and put it in "C:\Users_________\Documents\arduino-0022[1]\arduino-0022\libraries, but when i tried to open arduino i got a message saying "The library cannot be used. library names must contain only basic letters & numbers. (ASCII only and no spaces, and cannot start with a number)"....

what now?

Place the three folders in the zip directly into the Documents\Arduino\libraries folder so it would contain:
pollserial
TVout
TVoutfonts

i'm having trouble, it would apear to be a problem w/ some sort of rate because i'm getting the signal to go through but it is very choppy. ocasionaly somthing will line up but not very often and not the whole pic

Link to old thread (mdmetzle, you should put this in your original post at the top of this thread, so that others know exactly where it is):

http://arduino.cc/forum/index.php/topic,8814.0.html

:slight_smile:

Awesome! I love the retro-looking arduino TV icon! Will try this sometime. I now have a few RBBB boards and some ATMEGAs. Just don't have time. =(

Thank you for sharing your great TV Out library with the rest of us.

I have it running on an Arduino Mega 1280 with standard resolution and on NTSC format.

  1. Compatibility problem

I'm having some problems. It works great when connected to a WinTV USB dongle, I get the picture crystal clear. I am running 1.0beta1 and have no other code to interfer, this is a simple test setup.

But when I connect it to a small 3.5" TFT monitor I got off Ebay, it seems to find the signal and show about one frame of it, and then looses the signal again, then syncs and shows one frame and looses sync againg.

This means it switches from the blue "no input" screen to the wanted input and back, at a rate of about 4 switches per second.

Are there any timing issues or other things that could be improved to get this monitor working? I have tried the 75ohm to VID trick, but it makes no difference at all.

  1. Servo control with TVout

My second question is, how can I drive a single servo with 1000-2000 microsecond pulses while using TVout? It doesn't seem to work with the Servo library, as you're probably using the interrupts. Any pointers?

Thanks in advance!

@TECH GEEK
If you are still having troubles getting it to work properly I would try a different tv if possible and if the problem persists double check all the connections. The last thing to try is putting a 75ohm resistor across video connection and GND.

@laarka
Yes there are some timing issues. The problem is in the vertical sync the approach I took is no where near what the signal is supposed to be. This signal will work just fine on regular tube tvs but on newer tvs can have issue with it. If your small lcd will accept a PAL signal try that as my capture card works far better with PAL than NTSC and maybe your little TV will too.

Yes the interrupts will screw with any existing time sensitive code. To combat this I wrote a replacement for millis and delay that will remain accurate (just call TV.delay() and TV.millis()).
There are some restrictions to the accuracy with these replacement functions as they work by counting scan-lines. Their are also function pointers now that will for automatic management of devices in the background. To see how they work take a look at the included serial terminal example and the pollserial library which is just a modified version of hardware serial.

@crosh
yeah that would be a good link to add drrr.

mdmetzle:
Yes there are some timing issues. The problem is in the vertical sync the approach I took is no where near what the signal is supposed to be.

Is this signal or timing related? I mean if it's timing related, it must be fixable in the software. If it's signal related, a hardware fix must be done, or what? I saw a mention under issues on your googlecode page that you would look at this after beta1 ... is that now? :stuck_out_tongue:

I have already tried the PAL experiment before writing you :wink: No difference at all - same blinkety-blink no matter if it's NTSC or PAL. I have verified the monitor works by connecting a NTSC camera.

If you need a guinea pig, just let me know - it's a total showstopper for my project not being able to use this small monitor.

Yes the interrupts will screw with any existing time sensitive code. To combat this I wrote a replacement for millis and delay that will remain accurate (just call TV.delay() and TV.millis()).

Driving servos is quite timing sensitive too - we're talking pulses between 1000 and 2000 microseconds. Do you think it's doable to just drive one or two reliably? I have zero experience with coding this stuff myself, but I guess anything is possible.

I have no need for delay() in my code, but I do need millis() a lot for the main routine :slight_smile:

Well the issue that has stopped me from changing the vsync is the fact that I still don't have an oscilloscope or a logic analyzer to actually confirm the signal. In addition to that I started working in my field and don't have as much time right now to work on this.

If you are just trying to display text then maybe the tellymate firmware or tvtext would do what is needed right away.

mdmetzle:
Well the issue that has stopped me from changing the vsync is the fact that I still don't have an oscilloscope or a logic analyzer to actually confirm the signal.

Okay, fighting in the dark can be quite frustrating.

mdmetzle:
If you are just trying to display text then maybe the tellymate firmware or tvtext would do what is needed right away.

Thank you for the pointer. As far as I can see, the schematic is very similar, except for a different resistor value for pixel data.

So what you're saying is the firmware for the tellymate is very compatible with different monitors, and the problems your library are facing are just plain timing issues?

In that case I'll stick with your library and pester you until we can work out what is wrong - I'll gladly help if I can.

How fast a scope is needed to solve this - I guess the cheapest route is the new DSO Quad from SeedStudio?

Alright I've broken down and ordered a TellyMate as per your recommendation. It's only £12,50 so at least I can get on with the project.

But my offer to help still stands if theres anything I can do please let me know.

i DON'T THINK THE TELLY(sorry) will help. it sounds like it has to do with the tv having an auto-on type option. my tellymate will work on on small tv and not the other. I am getting ready to try the tv out curciut. Prolly flash the tellymate to bootloader?

As a side note the tv-out design works with the tv the telemate did not work with, but will not work with unit the telymate will work on. I am not using the 75ohm-ground resistor. I also have the nano dso toy o-scope if you want to see the signals.

Hi all.

For the beginning I want to thank you to mdmetzle for this great work and of course to all contributors to improvement of this library. And just after that I want to switch to my problem.. maybe someone can help.

I also started to use this library and of course coming from R/C world, I started to develop my own OSD as many others. Why I did start? Not because I don't like others on the market or because they are not good, but as always because of low cost.
I already have an Arduino Duemilanove Board - and succeeded with drawing of the OSD on the screen(like HUD). Nothing complicated because of is TVout library.

As second step, i did wrote the part of code where I am reading the data from GPS NMEA sentences. But of course, because this library is interrupt driven the serial doesn't work even if using pollserial(). This 2 parts works well apart from each other but not together. Together I have problems .. screen flickering and/or missing GPS info (incomplete NMEA sentence).

Also, thinking that the purpose of OSD is to overlay the text over image some modification need to be done to accomplish this, and maybe using sync generator will also help improving the timing?

So, the big question here: Will anyone help me interfacing my arduino board with LM1881?! Maybe someone already did it and can share useful info about that.
As I know, LM1881 should be connected to INT0 and INT1 of ATMEGA.. but the rest.. I don't know :frowning:

so.. here I will stop "crying" .. and will return later with any other info that I will learn.