Low impedance load (NTSC video) on Digital 10-11

I am building a 37x28 pixel NTSC display, modifying the code phizone posted on the Daily Duino. I started out using bits 0-3 on PORTB (Digital 8-11). Works great on Digital 8-9, doesnt work on 10-11. When I move the whole thing to PORTC (Digital 14-17) it works fine on all four lines.

I am trying to confirm whether this is due to the electrical characteristics of Digital 10-11 or if there is something that I should disable in software (related to SS/MOSI??). Pin 11 in particular is connected to the ICSP header, I dont see any active electronics on these pins; but I dont know if the header would have enough capacitance to throw off NTSC signals. I see the warnings about low impedance loads on Digital 10-13, but it looks like that is only a problem if the ICSP is in use.

For the D/A converter I am using a 1K or 330 Ohm resistor connected to each pin. There is a 100 Ohm between tip and ring on the RCA. I assume amounts to roughly 100 Ohms output impedance....

For my project I can probably move to PORTC and be done, but if it is a hardware limitation of Digital 10-13 then it would be worth documenting.

Obviously something wrong with my question.
Help me out here is this too lame or too hardcore analog?
What do I need to do to reframe my question?

I really think there is something odd about Digital 10-13 that affects small fast signals. My question is - is this register behavior or just the additional board traces and connectors.

Pin13 has the LED on it, drawing some 5mA, which should still be pretty insignificant compared to a video signal.
Pins 11 and 12 are involved in ICSP (In Circuit Serial Programming) and connect to the 6-pin ICSP connector as well as the digital output pins. But they're normally unloaded, so that really shouldn't result in any problems either.
Pin 10 isn't connected to anything but the AVR chip.

Did you remember to initialize the new pins to output in your setup() function? Pins initialized to input mode can have "weak pullups" enabled when you output a 1, which can cause them to LOOK like they're operating as output pins with very low drive...
Initializing the pins with the arduino library functions, even if you're using raw port access for the actual video, seems like a worthwhile idea; that way you shouldn't have to go searching through the atmel docs to see if there are any special functions on those pins that you need to disable by default.

Thanks much for the reply.
I am using pinMode(11, OUTPUT); to initialize. However I will double check to make sure I am not enabling the weak pullup resistors indirectly. The reason I am hammering on

The odd thing is that the same code works on Digital 8-9 (two bit mode) and on PORTC (Digital 14-17 four bit mode). The only place it fails is on Digital 10 and/or more likely just Digital 11. I realized today I could put a scope on it and see if the waveforms look odd also.

Anyone know if there is some type of termination I could put on the ICSP header to reduce capacitance/ringing/whatever??

Got it, the weak pullups were keeping the signal from dropping to 0, and I had a code error in a bitmask.

FYI, the weak pullups are enabled even with a direct port access for example "PORTC = 0xF3;" I was assuming (hoping) they were only enabled by DigitalWrite.

You can disable ALL weak pullups as follows:
MCUCR = MCUCR & 0xEF;

Probably a good idea when using a resistor ladder for D/A.

I about have my 37x28 video code finished. Does anyone know the maximum resolution that has been achieved on Arduino/NTSC? I dont find this in the playground, is this worth posting there?

I dont find this in the playground, is this worth posting there?

Yes. :slight_smile:

--Phil.

I have completed my 38x28 NTSC video code. It has at leaset 33x26 usable pixels. It also has two bit maps, one displayed and one hidden so you can render a graphic before displaying it. Finally there are functions for read/write a pixel, clear screen and swap bitmaps.

This code uses the NTSC timing from Arduino Pong (Alastair Parker) and a stripped down version by Phizone. Use of the frequencytimer2 library allows you to insert your own code to run between interrupts. The D2A converter uses the same resistor scheme as Arduino pong, but I have doubled it to use four resistors on 4 Digital out lines. There is an ascii diagram of the d2a converter in the code -- it only requires 5 resistors.

There is more detail in the code, but I basically dump the frame buffer twice, enabling two output bits at a time. The command to enable/disable output pins takes only a couple of clock cycles, so it doesnt throw off timing like bit shifting or a second for loop would. FYI, it looks like it would be possible to use the same trick to double the resolution again, but it will cost the use of the serial port pins.

I am not sure whether this is ready to go to the playground and would like to have someone else look this over. Any hints on where/how to post the code would be appreciated. I am going to re-read the playground info since the code wont fit here.

I tried using a smaller font but my code still wont fit in 9500 characters. Do I need to get someone to host this on their web site so I can link to it?

You can break it up into two posts (with obvious comments where to rejoin it hopefully) if you like.

smaller fonts :wink:

// *********** begining of NTSC video code *****************
// ******* start part 1 **********

//MediumResVideo
// Ver 1.0  1/17/2008 
//outputs 38x28 pixel NTSC video (appx 33 x 26 usable pixels)
//uses 4 pins on PORTC, should work OK on PORTB or PORTD

// PROGRAM HISTORY
//12/12/08 modified by drspectro to use FrequencyTimer2 library
//increased verrtical pixels from 14 to 28
//added support for hidden bitplane to allow two graphics to be rapidly swapped
//added readpixel function (useful for game of life)

// Based HEAVILY on the Arduino Pong code
//2008 Adapted by phizone from:
//Arduino Tv framebuffer
//2007 Alastair Parker

//SCHEMATIC

// connect the outputs of the followng resistors together then connect to RCA tip.

// Digital 17 -- 330 ohm -----|
// Digital 16 -- 1K  ---------|
// Digital 15 -- 330 ohm -----|
// Digital 14 -- 1K  ---------|
// Grouund ----- 100 ohm -----|
//           |                |
//           |                |
//        RCA RING          RCA TIP

//connect ground directly to Shield/Ring of RCA cable (not through resistor)

// Phizone and others call for 75 ohm to ground, that is probably better
// --- I just didnt have one to test with.


//THEORY OF OPERATION
//Each byte of the frameBuffer stores 4 pixels worth of data (2 bits/pixel)
//the high 4 bits are not displayed and can be used as working space while
//rendering a new bitmap, the high 4 bits can be shifted right when needed using 
//the Swap function.

//To display the visible pixels, the program steps through the fram buffer TWICE.
//The first time the DDRC command is used to disable the upper two output pins.
// DDRC = 0x0C disables digital 16 and 17 (analog 2 and 3)
// once the array has been written to video the first time, the DDRC command is
// used to disable the low two bits digital 14 and 15 and to enable 16 and 17.
// This allows us to switch between two sets of pixel data with a single instruction.

// Finally we loop through the array twice by using only three bits of the linecount variable.
// As linecount loops from 0 to 239, we look at three bits that go from 0 to 15 twice.
// An "if" statement checks to see if linecount has reached halfway down the screen and 
// changes the DDRC value at that time.

// Because the pixels are packed into each byte in a nonobvious way, the clear and set 
// routines use a simple x/y coordinate for each pixel.  Direct writes to the frame buffer
// array are more complex.  As stated elsewhere NEVER write a value of zero to the frame buffer.
// Zero is the sync signal NOT black.




#include <FrequencyTimer2.h>

// Video out voltage levels
#define  _SYNC 0x00
#define _BLACK 0x55        // 0101 0101
#define _GRAY 0xAA         // 1010 1010
#define _WHITE 0xFF        // 1111 1111

//#define _BLACK 0x05        // 0000 0101
//#define _GRAY 0x0A         // 0000 1010
//#define _WHITE 0x0F        // 0000 1111

// dimensions of screen buffer array
// the code loops through the array twice, so there are twice as many
// pixels as the height of the array.
// actual screen will be 38 pixels wide by 30 pixels high
// viewable area will probably be smaller (34 x 27 )
#define bufferWIDTH 37   // x
#define bufferHEIGHT 16  // y
#define WIDTH 37   // x  only 3-33 are really visible
#define HEIGHT 30  // y  only 2-26 are really visible


//number of lines to display
#define DISPLAY_LINES 240

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

// video pins
// changed to PORTC, should work equally well on PORTD
// just change pin numbers and PORT commands
#define DATA_PIN 14
#define SYNC_PIN 15
#define DATA_PIN2 16
#define DATA_PIN3 17

// the video frameBuffer
// two pixels of data stored the low 4 bits of each byte
// bits 0-1 store a pixel that will be drawn on the top half of the screen.
// bits 2-3 store a pixel that will be drawn on the bottom half of the screen
// bits 4-7 can be used as a place to hold the next image to display
// shift bits 4-7 right to display the new image
// codes black, grey, white and sync are the same as in Arduino Pong and phizones code
// NOTE: be careful not to write 0x00 directly to the frame array. 
// 0x00 is the SYNC signal and will foul up timing if written as a pixel
byte frameBuffer[bufferWIDTH][bufferHEIGHT];

// loop indices
byte index, index2;

// NTSC  video line loop
byte line;

// current drawing line in framebuffer
byte newLine;


// demo code in main loop
unsigned long temp;
unsigned int xtemp;
unsigned int ytemp;

// draw a pixel to the buffer
// if plane = 0, write to bitmap currently being displayed
// if plane = 1, write to high 4 bits
// if plane = anything else, write to both bitmaps
void setPixel(byte x,byte y,byte plane)
{
if (plane == 0)
{
if (y < bufferHEIGHT) 
  {
    frameBuffer[x][y]= frameBuffer[x][y] & 0xFC;   // 11111100
    frameBuffer[x][y]= frameBuffer[x][y] |(_WHITE & 0x03); 
  }
else
  { 
    frameBuffer[x][y-16]= frameBuffer[x][y-16] & 0xF3;   // 11110011
    frameBuffer[x][y-16]= frameBuffer[x][y-16] | (_WHITE & 0x0C);   // mask with 00001100
  } 
}  
else if (plane == 1)
{
if (y < bufferHEIGHT) 
  {
    frameBuffer[x][y]= frameBuffer[x][y] & 0xCF;   // 11111100
    frameBuffer[x][y]= frameBuffer[x][y] |(_WHITE & 0x30); 
  }
else
  { 
    frameBuffer[x][y-16]= frameBuffer[x][y-16] & 0x3F;   // 11110011
    frameBuffer[x][y-16]= frameBuffer[x][y-16] | (_WHITE & 0xC0);   // mask with 00001100
  } 
}  
else 
{
if (y < bufferHEIGHT) 
  {
    frameBuffer[x][y]= _WHITE;
  }
else
  { 
    frameBuffer[x][y-16]= _WHITE;   // 11110011
  } 
}
}

// ***** end part 1 ******

//***** part 2 ******

void grayPixel(byte x, byte y, byte plane)
{
if (plane == 0)
{
if (y < bufferHEIGHT) 
  {
    frameBuffer[x][y]= frameBuffer[x][y] & 0xFC;   // 11111100
    frameBuffer[x][y]= frameBuffer[x][y] |(_GRAY & 0x03); 
  }
else
  { 
    frameBuffer[x][y-16]= frameBuffer[x][y-16] & 0xF3;   // 11110011
    frameBuffer[x][y-16]= frameBuffer[x][y-16] | (_GRAY & 0x0C);   // mask with 00001100
  } 
}  
else if (plane == 1)
{
if (y < bufferHEIGHT) 
  {
    frameBuffer[x][y]= frameBuffer[x][y] & 0xCF;   
    frameBuffer[x][y]= frameBuffer[x][y] |(_GRAY & 0x30); 
  }
else
  { 
    frameBuffer[x][y-16]= frameBuffer[x][y-16] & 0x3F;   
    frameBuffer[x][y-16]= frameBuffer[x][y-16] | (_GRAY & 0xC0);   
  } 
}  
else 
{
if (y < bufferHEIGHT) 
  {
    frameBuffer[x][y]= _GRAY;
  }
else
  { 
    frameBuffer[x][y-16]= _GRAY;   // 11110011
  } 
}
}

// draw a black pixel to the buffer
void clearPixel(byte x,byte y,byte plane)
{
if (plane == 0)
{
if (y < bufferHEIGHT) 
  {
    frameBuffer[x][y]= frameBuffer[x][y] & 0xFC;   // 11111100
    frameBuffer[x][y]= frameBuffer[x][y] |(_BLACK & 0x03); 
  }
else
  { 
    frameBuffer[x][y-16]= frameBuffer[x][y-16] & 0xF3;   // 11110011
    frameBuffer[x][y-16]= frameBuffer[x][y-16] | (_BLACK & 0x0C);   // mask with 00001100
  } 
}  
else if (plane == 1)
{
if (y < bufferHEIGHT) 
  {
    frameBuffer[x][y]= frameBuffer[x][y] & 0xCF;   
    frameBuffer[x][y]= frameBuffer[x][y] |(_BLACK & 0x30); 
  }
else
  { 
    frameBuffer[x][y-16]= frameBuffer[x][y-16] & 0x3F;   
    frameBuffer[x][y-16]= frameBuffer[x][y-16] | (_BLACK & 0xC0);   // mask with 11000000
  } 
}  
else 
{
if (y < bufferHEIGHT) 
  {
    frameBuffer[x][y]= _BLACK;
  }
else
  { 
    frameBuffer[x][y-16]= _BLACK;   // 11110011
  } 
}
}

// read the value of a single pixel
// either bit plane can be read
// if a plane larger than 1 is specified, bit plane 1 is read
byte readPixel(byte x,byte y,byte plane)
{
if (plane == 0)
{
if (y < bufferHEIGHT) 
  {
    return (frameBuffer[x][y] & 0x03);
  }
else
  { 
    return ((frameBuffer[x][y-16] >>2) & 0x03);
  } 
}  
else if (plane >= 1)
{
if (y < bufferHEIGHT) 
  {
    return (frameBuffer[x][y] & 0x03);
  }
else
  { 
    return ((frameBuffer[x][y-16] >>4)& 0x03);
  } 
}  
}



// clear the screen
// clears both bit planes
void clearScreen()
{
for (index = 0; index < bufferWIDTH; index++)
for (index2=0;index2 < bufferHEIGHT;index2++)
{
 frameBuffer[index][index2] = _BLACK;
}
}

// Swaps the displayed bit plane (bit plane 0) with the hidden bit plane (bit plane 1)
// Draw new image on bit plane 1, then run swap to display
// Bit planes 0 and 1 can be cleared separately by clearScreen
void Swap()
{byte SwapTemp;
for (index = 0; index < bufferWIDTH; index++)
for (index2=0;index2 < bufferHEIGHT;index2++)
 {
 SwapTemp = frameBuffer[index][index2] << 4 ;
 frameBuffer[index][index2] = (frameBuffer[index][index2] >> 4)| SwapTemp;
 }
}

// draw video frame
extern void DrawFrame(void)
{
//moved cli out of setup - only necessary if horizontal timing gets funky
//uncomment if necessary
//cli();

for ( line =0;line< DISPLAY_LINES;++line)
{

// HSync
// front porch (1.5 us)
// writes BLACK on all 4 bits, forces to black regardless of which two bits are active
PORTC = _BLACK;
delayMicroseconds(1.5);
//sync (4.7 us)
PORTC = _SYNC;
delayMicroseconds(4.7);
// breezeway (.6us) + burst (2.5us) + colour back borch (1.6 us)
PORTC = _BLACK;
delayMicroseconds(0.6+2.5+1.6);

//calculate which line to draw to
// mask line to 4 bits, this will cause newlLine to repeat after 16 counts
// the effect is of two for loops running to a count of 16
// you cant start a second for loop in the middle of a screen paint because
// the startup delay will foul up the timing.  
newLine = (line >>3) & 0x0F;
delayMicroseconds(1);

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

// once all 16 lines in the array have been painted disable bits 0-1 on the port
// then enable bits 3-4 with DDRC command, then loop through array again.
if (line > 126)               //Note this is correct dont change to 127
 { 
   DDRC = 0x0C;
 }
else
 {
   DDRC = 0x03;
   DDRC = 0x03;
 }
 
// kludge to correct timings
// adjust as needed, can use inline NOP instructions for shorter delays
PORTC=PORTC;
PORTC=PORTC;
//PORTC=PORTC;
delayMicroseconds(2);
}
 
//vsync
//write 0 to all output bits
PORTC = _SYNC;

// wait for the remainder of the sync period
// old code used delayMicroseconds(565); 
// since this now uses a timer interrupt set by frequency2timer
// your main loop will execute for appx 565 Microseconds before the next interrupt

// sei is needed to enable interrupts
// sometimes interrupts seem to default to disabled
sei();
}

// *** end part 2 ***

//*** start part 3 ***

 // the setup routine
void setup()
{
pinMode (SYNC_PIN, OUTPUT);
pinMode (DATA_PIN, OUTPUT);
pinMode (DATA_PIN2, OUTPUT);
pinMode (DATA_PIN3, OUTPUT);
digitalWrite (SYNC_PIN, HIGH);
digitalWrite (DATA_PIN, HIGH);
digitalWrite (DATA_PIN, HIGH);
digitalWrite (DATA_PIN, HIGH);

MCUCR = MCUCR | 0x10;              // Disable weak pullup resistors

clearScreen();

// FrequencyTimer2 init
// configured to interrupt 59.94 times a second
// frequencytimer2 rounds this to a different value
// actual interrupt period printed to serial for reference
// see frequencytimer2 doc....
// period requested =    16683 micro seconds or 59.95 times a second
// period actually set = 16640 or 60.096 times a second
FrequencyTimer2::setPeriod(16683);
temp = FrequencyTimer2::getPeriod();
Serial.begin(9600); 
Serial.println("Video Test"); 
Serial.println(temp); 
FrequencyTimer2::disable();
FrequencyTimer2::setOnOverflow(DrawFrame);
FrequencyTimer2::enable();
}

void loop()
{
// your code goes here, 
// any code in loop can be replaced with your own code  

// checkerboard pattern to display bitmap "0"
for ( xtemp =3;xtemp<=33 ;++xtemp)
{
  for ( ytemp =2;ytemp<=26 ;++ytemp)
  {
   if (((xtemp + ytemp)% 2) == 1)
    {setPixel(xtemp,ytemp,0);}
    else
    {grayPixel(xtemp,ytemp,0);}
  }
}

// gray scale to hidden bitplane
for (xtemp = 3; xtemp <= 7; xtemp++)
  {
    for (ytemp = 2; ytemp <= 18; ytemp++)
      {setPixel(xtemp,ytemp,1);}
  };
for (xtemp = 8; xtemp <= 12; xtemp++)
  {
    for (ytemp = 2; ytemp <= 18; ytemp++)
      {grayPixel(xtemp,ytemp,1);}
  };
for (xtemp = 13; xtemp <= 17; xtemp++)
  {
    for (ytemp = 2; ytemp <= 18; ytemp++)
      {clearPixel(xtemp,ytemp,1);}
  };
for (xtemp = 18; xtemp <= 22; xtemp++)
  {
    for (ytemp = 2; ytemp <= 18; ytemp++)
      {setPixel(xtemp,ytemp,1);}
  };
for (xtemp = 23; xtemp <= 27; xtemp++)
  {
    for (ytemp = 2; ytemp <= 18; ytemp++)
      {grayPixel(xtemp,ytemp,1);}
  };
for (xtemp = 27; xtemp <= 33; xtemp++)
  {
    for (ytemp = 2; ytemp <= 18; ytemp++)
      {clearPixel(xtemp,ytemp,1);}
  };

for (xtemp = 3; xtemp <= 33; xtemp++)
  {
    for (ytemp = 19; ytemp <= 22; ytemp++)
      {grayPixel(xtemp,ytemp,1);}
  };

for (xtemp = 3; xtemp <= 33; xtemp++)
  {
    for (ytemp = 23 ; ytemp <= 26; ytemp++)
      {setPixel(xtemp,ytemp,1);}
  };
// swap display bitmap "0" with hidden bitmap "1"
while (1==1) 
{
Swap();
delay(1000);
};

}

//*** end part 3 ***

The comments to splice the code together didnt work like I intended. In any case the three pieces are broken up on function boundaries. Just edit them together in 1-2-3 order.

FYI, I havent had a chance to hook it up yet but was able to compile it with arduino 0011 (XP).

Thanks for taking a look.
I dont think I mentioned earlier, but it should display a gray and white checkerboard alternating with a gray bar pattern by default.

Had issues compiling using 0012 on linux, but compiled fine on 0011. Works great...

Thanks phizone I really appreciate your blog. I should have sent you an update.

Hello!
I am still investigating the issues regarding the regular Interface, see:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1188261175
And of course the primary issue is still timing.

However are you decidedly certain about the pins selected for the output lines?

Time doesn't reverse, Fords do.

/me

Certain about the data lines - see my answer in the other thread
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1188261175