Paint on the TV screen with Arduino

Hello,

I post to the french forum an experiment to interface crt monitors with Arduino.

The post is in French but you can take a look to the program if you're interested :
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1176757335

Can you briefly explain how you did it in English? I don't speak French at all!

Thanks.

-Z-

Hello Zitron,

I'm going to try it in english...

Please, take a look at the french post for pictures and code.

First go to this page to uderstand what king of signal we have to generate : http://www.retroleum.co.uk/PALTVtimingandvoltages.html. It was a little bit different in NTSC, you'll have to change some timings to according to the NTSC standard (google is your friend).

The resistors made a 2 bits DAC (Digital to Analog Converter). I have choose to connect them to the digital ouputs 8 and 9.

All the code in the loop() function genrate the composite signal according to the PAL standard. The tab memVideo[][] is used as video memory. Each byte of it represent a (big) pixel at screen ; you can video on video by putting _BLACK, _WHITE or _GRAY in a cell of these tab. it was doing by the "switch() case" wich interpret some serial input as command.

If you want to try these code, you'll have to change the content of the file "wiring.c" which is in "\arduino-0007\lib\targets\arduino". make a copy before you change it to restore it after. The changes to mades are explainded in the "Reply #4" of the french post. The changes are made to stop the millis() and delay() interupt, so can't use them in the program.

To test these experimentation, you can trace on screen bt using the keypad key 2 4 6 8 to move an invisible cursor, 5 to trace (print ?) and b g n to change color of the pen (b->White n->Black g->Gray). The first line is invisible and the pen is black at start.

Ask me if you have any question

Hi,

I'm in the UK so I've got PAL as well. I hooked up my TV tuner, and got a black screen. I thought it wasn't working, but I didn't know that's what I was supposed to get! Now it's kind of working, but I get a lot of scan line looking noise:

The actual pictures looks slightly better than the still image. I disabled and enable interrupts, doesn't seem to make any difference.

Thanks!

-Z-

really like the painting on screen but it's low res.

what do you think about this high res?

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1166667354/11#11

I figured it out, I needed to change the number of the time wasting portb = black lines to get timing just right. Would this be different on different boards? Anyway, now the image is better but still not very good:

Full Hi-res can be done, but I don't think this for loop based method is fast enough, and it doesn't scale properly. Some clever math is needed.

-Z-

Hello,

First Erik : the post http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1166667354/11#11 does not acheive a high res the picture is not an ouput of an Arduino board. I'm actually try to make an higher stable picture using an interup an then i'll try higher res.

Second Zitron : I's difficult to me to help you at distance. Yes, you can adjust timing with number of black lines but normaly you don't have to. This progam is an experimentation and it work on my old monitor, maybe timing is different on yours. But in all case you have to disable interrupts. If the timming is not good enough for your tv screen, you'll have to adjust all the delays :(. If you have an osciloscope use it, I don't have one :(. If I make interrupt program working all this pbs go out :).

Erik, Zitron,

How have you disabled interrupts ? With a cli() or by comment source code parts in wiring.c ? The cli() does not work, comment the code in wiring.c.

I've try again my code and your screen shots look's like interrupts problem.

Sorry for the mistake Erik and Zitron my last message was for Zitron. This now correct !

Zitron,

please test this code made with interrupt :

#define _SYNC 0x00
#define _BLACK  0x01
#define _GRAY  0x02
#define _WHITE  0x03

// compensation chargement us de 1 incluse
/*
#define _NORMAL_SYNC 32
#define _LONG_SYNC 240
#define _SHORT_SYNC 16
#define _LONG_SYNC_DELAI 16
#define _SHORT_SYNC_DELAI 240
*/

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

byte memVideo[_NB_PIXELS][_NB_LIGNES];
byte index, index2;
int noligne = 0;
byte diviseur = 0;

SIGNAL(SIG_OVERFLOW0)
{
  TCNT0 = 256 - 128;
  PORTB = _SYNC;
      
  if (noligne == 3 || noligne == 4)
  {
    // SYNCHRO
    while (TCNT0 < 128 + 4); // 2 uS
        
    PORTB = _BLACK;
    while (TCNT0 < 128 + 60 + 4); // 32 us 
    
    PORTB = _SYNC;
    while (TCNT0 < 128 + 60 + 4 + 4);
    
    PORTB = _BLACK;
  }
    
  // fin
  if (noligne >= 309)
  {
    // SHORT
    while (TCNT0 < 128 + 4); // 2 uS
    
    PORTB = _BLACK;
    while (TCNT0 < 128 + 60 + 4); // 32 us
    
    // SHORT
    PORTB = _SYNC;
    while (TCNT0 < 128 + 60 + 4 + 4);
    
    PORTB = _BLACK;
  }

  // image
  if (noligne >= 5 && noligne < 309)
  {
    // SYNC
    while (TCNT0 < 128 + 8);
    
    PORTB = _BLACK; // porch
    while (TCNT0 < 128 + 8 + 8);
    
    //** image ligne 52 uS
//    if (noligne >= 16 && noligne < 304)
      for (index = 0; index < _NB_PIXELS; index++)
      {
        PORTB = memVideo[index][(noligne>>4)];
        PORTB = PORTB;
        PORTB = PORTB;
      }
    
    PORTB = _BLACK;
  }
  
  if (noligne < 2)
  {
    // SYNCHRO
    while (TCNT0 < 128 + 60);
    
    PORTB = _BLACK;
    while (TCNT0 < 128 + 60 + 4);

    PORTB = _SYNC;
    while (TCNT0 < 128 + 60 + 4 + 60);

    PORTB = _BLACK;
  }
      
  // COMPTEUR LIGNE
  if (noligne == 311)
    noligne = 0;
  else
    noligne++;
}

void InitTimer()
{
  #if defined(__AVR_ATmega168__)
      sbi(TCCR0A, WGM01);
      sbi(TCCR0A, WGM00);
  #endif  
      // set timer 0 prescale factor to 8
  #if defined(__AVR_ATmega168__)
        cbi(TCCR0B, CS02);
      sbi(TCCR0B, CS01);
      cbi(TCCR0B, CS00);
  #else
        cbi(TCCR0, CS02);
      sbi(TCCR0, CS01);
      cbi(TCCR0, CS00);
  #endif

      // enable timer 0 overflow interrupt
  #if defined(__AVR_ATmega168__)
        sbi(TIMSK0, TOIE0);
  #else
        sbi(TIMSK, TOIE0);
  #endif
}

void setup()
{
  pinMode (8, OUTPUT);
  pinMode (9, OUTPUT);
  digitalWrite (8, HIGH);
  digitalWrite (9, HIGH);

  Serial.begin(19200);
  Serial.print ("GO");
  Serial.print (13, BYTE);
  
  for (index2 = 0; index2 < _NB_LIGNES; index2++)
     for (index = 0; index < _NB_PIXELS; index++)
     {
       /*memVideo[index][index2] = _BLACK;*/
       memVideo[index][index2] = (index + index2) % 3 + 1;
     }

  memVideo[2][2] = _WHITE;

  InitTimer();
}

int ligne;
char car;
int cx=0, cy=0;
byte couleur = _BLACK;

void loop()
{

}

It must be time independent.

You have to comment some code in wiring.c !
Search for "SIGNAL(SIG_OVERFLOW0)" and comment the function like this :

/*
SIGNAL(SIG_OVERFLOW0)
{
      timer0_overflow_count++;
}
*/

Tell me if it work !

Hello,

First Erik : the post http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1166667354/11#11 does not acheive a high res the picture is not an ouput of an Arduino board. I'm actually try to make an higher stable picture using an interup an then i'll try higher res.

Yes and no Benoit, i had it running on a arduino without the bootloader but i imagine it's possible to run it under the bootloader if you adapt the program and dissable all interruots....
As for high res, it's a pritty good test picture and it even has time to scroll a message, asm if faster than C and the bootloader is reeeealy slow but makes up in easy of programming.

Erik

I've read your post again, ...

Yes it run on an arduino, I've read it to quickly.

Ok for the optimizations possibles in asm, but I d'on't understand what yhe matter with bootloader and a slower program ? Could you explain us ?

I'm actually working on an (low res) code with tming "running" (I don't speak english :() with interrupt. I have sometimes "jitter" depends on code but I don't understand why ?

the code is below, have you any idea ?

#define _SYNC 0x00
#define _BLACK  0x01
#define _GRAY  0x02
#define _WHITE  0x03

// compensation chargement us de 1 incluse
/*
#define _NORMAL_SYNC 32
#define _LONG_SYNC 240
#define _SHORT_SYNC 16
#define _LONG_SYNC_DELAI 16
#define _SHORT_SYNC_DELAI 240
*/


#define _NB_PIXELS 29
#define _NB_LIGNES 19


#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

byte memVideo[_NB_PIXELS][_NB_LIGNES];
byte index, index2;
int noligne = 0;
byte diviseur = 0;

boolean bCalcul = false;

SIGNAL(SIG_OVERFLOW0)
{
  TCNT0 = 256 - 128;
  PORTB = _SYNC;
      
  if (noligne == 3 || noligne == 4)
  {
    // SYNCHRO
    while (TCNT0 < (128 + 4)); // 2 uS
        
    PORTB = _BLACK;
    while (TCNT0 < (128 + 60 + 4)); // 32 us 
    
    PORTB = _SYNC;
    while (TCNT0 < (128 + 60 + 4 + 4));
  }
    
  // fin
  if (noligne >= 309)
  {
    // SHORT
    while (TCNT0 < (128 + 4)); // 2 uS
    
    PORTB = _BLACK;
    while (TCNT0 < (128 + 60 + 4)); // 32 us
    
    // SHORT
    PORTB = _SYNC;
    while (TCNT0 < (128 + 60 + 4 + 4));
  }

  // image
  if (noligne >= 5 && noligne < 309)
  {
    // SYNC
    while (TCNT0 < (128 + 8));
    
    PORTB = _BLACK; // porch
    while (TCNT0 < (128 + 8 + 8));
    
    //** image ligne 52 uS
//##    if (noligne >= 32 && noligne < 304)
    {
      for (index = 0; index < _NB_PIXELS; index++)
      {
        PORTB = memVideo[index][(noligne>>4)];
        PORTB = PORTB;
        PORTB = PORTB;
      }
    }
  }
  
  if (noligne == 2)
  {
    // SYNCHRO
    while (TCNT0 < (128 + 60));
    
    PORTB = _BLACK;
    while (TCNT0 < (128 + 60 + 4));

    PORTB = _SYNC;
    while (TCNT0 < (128 + 60 + 4 + 4));
  }
  
  if (noligne < 2)
  {
    // SYNCHRO
    while (TCNT0 < (128 + 60));
    
    PORTB = _BLACK;
    while (TCNT0 < (128 + 60 + 4));

    PORTB = _SYNC;
    while (TCNT0 < (128 + 60 + 4 + 60));
  }
  
  PORTB = _BLACK;
      
  // COMPTEUR LIGNE
  if (noligne == 311)
    noligne = 0;
  else
    noligne++;
}

void InitTimer()
{
  #if defined(__AVR_ATmega168__)
      sbi(TCCR0A, WGM01);
      sbi(TCCR0A, WGM00);
  #endif  
      // set timer 0 prescale factor to 8
  #if defined(__AVR_ATmega168__)
        cbi(TCCR0B, CS02);
      sbi(TCCR0B, CS01);
      cbi(TCCR0B, CS00);
  #else
        cbi(TCCR0, CS02);
      sbi(TCCR0, CS01);
      cbi(TCCR0, CS00);
  #endif

      // enable timer 0 overflow interrupt
  #if defined(__AVR_ATmega168__)
        sbi(TIMSK0, TOIE0);
  #else
        sbi(TIMSK, TOIE0);
  #endif
}

void setup()
{
  pinMode (8, OUTPUT);
  pinMode (9, OUTPUT);
  digitalWrite (8, HIGH);
  digitalWrite (9, HIGH);
/*
  Serial.begin(19200);
  Serial.print ("GO");
  Serial.print (13, BYTE);
*/
  for (index2 = 0; index2 < _NB_LIGNES; index2++)
     for (index = 0; index < _NB_PIXELS; index++)
     {
       /*memVideo[index][index2] = _BLACK;*/
       memVideo[index][index2] = (index + index2) % 3 + 1;
     }

  memVideo[2][2] = _WHITE;

  InitTimer();
}

int ligne;
char car;
int cx=0, cy=0;
byte couleur = _BLACK;

void loop()
{
  /*
  if (bCalcul)
  {
    bCalcul = false;
   if (serialAvailable())
   {
     Serial.read();
   }
  }
  */
}

If I uncomment the line wich start with "//##" the screen is scrambled but stable ? ? ?

Sorry, i meant wiring instead of the bootloader, i made this mistake because i used a eprom/controler programmer to burn the hex code, so i did not use the bootloader.

//## if (noligne >= 32 && noligne < 304)
{
for (index = 0; index < _NB_PIXELS; index++)
{
PORTB = memVideo[index][(noligne>>4)];

as for your problem in above line, if i understand well you write from video memory during vertical sync and you should not.
at this point your video memory is probably empty (all 1 or all 0).
i gues i would first try to get a good sync and later worry about writing something from memory.

if (noligne == 3 || noligne == 4)
as for this kind of line, with a OR, is a cause of instability because if the noline == 3 the rest of the line is not executed.
so on all lines except line #3 you perform 2 compares and only 1 compare for line 3
try to replace it with: if (noline>2) && (noline < 5) here the compares are always executed.

sorry but i never wrote asm for controlers and dont know the AVR but i think i gues correctly what you are doing but i could make mistakes, i am only human.

i did try to write a sync generator in plain c but failed because the timing resolution is to low, in asm one can trow in one or more NOP instructions to get accurate timing.

i dont know if you realize that there is a even and a odd vertical field, they are both different in vertical sync!!! the even field starts with a full horizontal line and ends with a half hor. line, the odd field starts with a half hor. line and ends with a full hor. line.
there is also a pre vert. sync pattern ... wich i did not find in your code.

sorry again for the long post.
you can always mail me in french that i do understand but i am more fluent in english

Benoît,

Thanks for your help! I tried both commenting out wiring.c and cli(), made no difference. I tried your first code in this thread, commented out the code in wiring.c, and I get a fairly clear image but with noise bands rolling over. Maybe we have different PAL standards? I'm guessing my TV tuner was trying to automatically detect what the input format is, and the improper formating of the signal is making it choose the wrong format. Because if I try to watch TV after I'm done messing with it, I have to reset the signal to PAL-GB, otherwise the picture would be messed up. I'll see if I can test it on someone else's TV.

I'm about to give up, maybe get a dedicated chip or board for this. I love how the German guy's high res test picture says: "Hello World, I'm super good, and test image generator is easy"

-Z-

Hello,

It's "more simple" to do it in asm because you can now exactly the code that you generate and THE timing of each instruction.