/* NTSC, take 3.14159265
* -------------------------------------
* Created Sun Aug 26 14:59:29 EDT 2007
*
* (cleft) 2007 by Matthieu Lalonde
*
* Note: Ideal values for the resistors are 300ohm and 900ohm.
* Using this circuit: http://www.eyetap.org/ece385/vinfo_da00.png
* Bug(s):
* - Jittering of the first one of two lines (should they be skipped?)
*/

#include "WProgram.h"

/**
* Constants definition
**/
// Debug levels
#define _debugLevelDisable 0
#define _debugLevelGeneric 1
#define _debugLevelSignal 10
#define _debugLevelSimul 20
/**
* Notes about _debugLevelSignal:
*
* Using this mode you can go through each voltage levels (Sync, black, gray, white).
* This is useful for testing the circuit as you can make sure the voltages are right by using a multimeter
**/

// Select either of the above debug levels (_debugLevelDisable to disable debugging)
#define _debugMode _debugLevelGeneric

// Pins definition
#if _debugMode > _debugLevelDisable
#define _pinLED 5 // Do not use a pin from 8 to 13 or it'll be turn off automatically
#endif
#define _pinSync 8
#define _pinVideo 9

// Definition of signal levels for the AD 2-bit converter
#define _SYNC 0x00 /* 0.00v */
#define _BLACK 0x01 /* 0.33v */
#define _GRAY 0x02 /* 0.67v */
#define _WHITE 0x03 /* 1.00v */

// Defines TV Modes
#define _NTSC 1
#define _PAL 2

// Select a TV mode (PAL isn't implemented, yet)
#define _tvMode _NTSC

#if _tvMode == _NTSC
#define _tvNbrLines 262 /* Includes the last 20 lines for the vertical sync! */
#define _tvVSyncNbrLines 20 /* These 20 lines... */

#define _ntscDelayHSyncStart 4.7
#define _ntscDelayBackPorch 6 /* Normally 5.9, but this fixes a timing issue */
#define _ntscDelayFrontPorch 1.4
#define _ntscDelayPerLine 51.5
#define _ntscDelayVSync 58.8
#endif

#if _tvMode == _PAL
/* Nien */
#endif

#define _tvPixelWidth 21
#define _tvPixelHeight 16

#define _serialBauteRate 19200

/**
* Prototypes definition
**/
void generateVSync(void);
void writeBufferLine(int position);
void writeTVLines(void);
void clearFrameBuffer(void);
void fillWhiteFrameBuffer(void);
void fillGrayFrameBuffer(void);
void fillPatternFrameBuffer(void);
void loadSprite(void);

/**
* Variable definition
**/
byte frameBuffer[_tvPixelWidth][_tvPixelHeight]; // Video frame buffer
char c; // Serial buffer

// Defines a static sprite (this could come from EEPROM also)
const static byte graphic[_tvPixelWidth][_tvPixelHeight] = {
{_WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK },
{_GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY },
{_BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE },
{_WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK },
{_GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY },
{_WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK },
{_GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY },
{_BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE },
{_WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK },
{_GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY },
{_BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE },
{_WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK },
{_GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY },
{_BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE },
{_WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK },
{_GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY },
{_BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE },
{_WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK },
{_GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY },
{_BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE, _BLACK, _WHITE },
{_GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY, _GRAY }};

void setup(void) {
clearFrameBuffer();

pinMode(_pinVideo, OUTPUT);
pinMode(_pinSync, OUTPUT);

#if _debugMode > _debugLevelDisable
pinMode(_pinLED, OUTPUT);
digitalWrite(_pinLED, HIGH);

// Draws a dot in the middle
frameBuffer[(_tvPixelWidth / 2)][(_tvPixelHeight / 2)] = _WHITE;
#endif

Serial.begin(_serialBauteRate);
Serial.print("Mode: ");
}

void loop(void) {
#if _debugMode < _debugLevelSignal
// Write the tv lines
writeTVLines();
#endif

// Check for commands
if (Serial.available())
{
c = Serial.read();
Serial.println(c);

switch (c)
{
#if _debugMode < _debugLevelSignal
case 'w' :
fillWhiteFrameBuffer(); // Fill the display with white
break;
case 'g' :
fillGrayFrameBuffer(); // Fill the display with gray
break;
case 'p' :
fillPatternFrameBuffer(); // Fill the display with a calculated patter
break;
case 'c' :
clearFrameBuffer(); // Clear the display (all black)
break;
case 's' :
loadSprite(); // Load a sprite to the display
break;
#else
/**
This section is used for sinal debug mode only!
Use a multimeter to check the tv signal voltages
**/
case '1' :
PORTB = _SYNC;
break;
case '2' :
PORTB = _BLACK;
break;
case '3' :
PORTB = _GRAY;
break;
case '4' :
PORTB = _WHITE;
break;
#endif
/**
* Display usage
**/
default :
#if _debugMode < _debugLevelSignal
Serial.println("c: clear screen | p: fill with pattern 1 | s: fill with sprite | w: fill with white | g: fill with gray");
#else
Serial.println("Debug Levels >> 1: _SYNC | 2: _BLACK | 3: _GRAY | 4: _WHITE");
#endif
break;
}

Serial.print("Mode: ");
}
}

/**
* Begin the NTSC Specific block
**/
#if _tvMode == _NTSC
/**
NTSC Signal definition:
-- Horizontal sync (hsync) pulse: Start each scanline with 0.3V,
then 0V for 4.7us (microseconds), and then back to 0.3V. This tells the TV to start drawing a new scanline

-- The "Back Porch": A transition region of 0.3V for 5.9us between the
hsync pulse and the visible region, off the left edge of the TV

-- Visible scan region: This is the part you actually see.
0.3V shows up as black, 1V as white, everything in between is greyscale. The visible region lasts for 51.5us

-- The "Front Porch": A transition region of 0.3V for 1.4us before
the hsync pulse of the next line, off the right edge of the TV

-- Vertical sync (vsync) pulse: Lines 243-262 of each frame (off the bottom of the TV)
start with 0.3V for 4.7us, and the rest is 0V. This tells the TV to prepare for a new frame.
Think of it as just 0V with an inverted sync pulse

*** http://www.eyetap.org/ece385/lab5.htm ***
**/

/**
* Writes every line from what's inside the frameBuffer
*
* Warning: You cannot take the beginning and end sync out of this functions or the sync will fail
* (This has been tested on a Mega8)
**/
void writeTVLines(void)
{
unsigned int i;
for (i=0;i<(_tvNbrLines - _tvVSyncNbrLines);i++) // Correction for the 20 lines of vertical sync
{
/* Begin Line Sync */

// H Sync
PORTB = _SYNC;
delayMicroseconds(_ntscDelayHSyncStart);

// Back Porch
PORTB = _BLACK;
delayMicroseconds(_ntscDelayBackPorch);

writeBufferLine(i>>4); // Visible scan (51.5µs)

/* End Line Sync (Front Porch) */
PORTB = _BLACK;
delayMicroseconds(_ntscDelayFrontPorch);
}

// Vertical Sync follows
generateVSync(); // _tvVSyncNbrLines lines for vertical sync
}

/**
* Writes each pixel of a line
**/
void writeBufferLine(int position)
{
unsigned int ii;

for (ii = 0; ii < _tvPixelWidth; ii++)
{
PORTB = frameBuffer[ii][position];

// Waste some time to make sure the line is sent during the proper timing
delayMicroseconds((_ntscDelayPerLine / _tvPixelWidth));
}
}

/**
* Generate sync pulse for the virtual sync (lasts _tvVSyncNbrLines lines)
**/
void generateVSync(void)
{
unsigned int ii;
for (ii = 0; ii < _tvVSyncNbrLines; ii++)
{
// Begin V Sync
PORTB = _BLACK; delayMicroseconds(_ntscDelayHSyncStart);

PORTB = _SYNC; delayMicroseconds(_ntscDelayVSync);
}
}

/**
* End of the NTSC Specific block
**/
#endif

/**
* Clears the frame buffer (if mode is true it will fill it with a chess board pattern)
**/
void clearFrameBuffer(void)
{
byte index, index2;

for (index2 = 0; index2 < _tvPixelHeight; index2++)
{
for (index = 0; index < _tvPixelWidth; index++)
{
frameBuffer[index][index2] = _BLACK;
}
}
}

/**
* Fills the frame buffer with white
**/
void fillWhiteFrameBuffer(void)
{
byte index, index2;

for (index2 = 0; index2 < _tvPixelHeight; index2++)
{
for (index = 0; index < _tvPixelWidth; index++)
{
frameBuffer[index][index2] = _WHITE;
}
}
}

/**
* Fills the frame buffer with gray
**/
void fillGrayFrameBuffer(void)
{
byte index, index2;

for (index2 = 0; index2 < _tvPixelHeight; index2++)
{
for (index = 0; index < _tvPixelWidth; index++)
{
frameBuffer[index][index2] = _GRAY;
}
}
}

/**
* Fills the frame buffer with a pattern
**/
void fillPatternFrameBuffer(void)
{
byte index, index2;

for (index2 = 0; index2 < _tvPixelHeight; index2++)
{
for (index = 0; index < _tvPixelWidth; index++)
{
frameBuffer[index][index2] = (index + index2) % 3 + 1;
}
}
}

/**
* Copies a sprite to the frameBuffer
**/
void loadSprite(void)
{
unsigned int ii,jj;
for (ii=0;ii<_tvPixelWidth;ii++)
{
for(jj=0;jj<_tvPixelHeight;jj++)
{
frameBuffer[ii][jj] = graphic[ii][jj];
}
}
}