WHAT WE DONE DID...
When the project came to a close we had nearly obtained to our original goal of outputting a simple game to the TV. We origninally hoped to play something along the lines of an electronic air hockey but the issues with generating the required signals for TV output proved to be no trivial task. The Tic-Tac-Toe game gave us the opprotunityto make use of our character library and also gives an example of how the user could output to the screen in real time (the button presses for the game).
Tic Tac Toe: Code Here
We used a standard keypad where the numbers 1-9 mapped to the positions of the game grid, a task running
off the Timer0 interupt was used to detect a button press.
After debouncing the button press, a function called 'taken' was called. This function
checked to see if it was a valid button press 1-9 and that no other player currently
occupied that square. Button A was used to reset after a draw or to start the next
round after a win. If it was a valid move for one of the players, that players' "square occupied" array was
updated and the function checked to see if that made them a winner. If it did, the score was updated,
a flashing line was drawn through the
winning combo and a short time later the score screen popped up. After pressing A to continue the loser
then started the next round.
The Telly:
The generation of the TV signals is what this project was really all about. This required very precise us timing
intervals, which was accomplished by using Timer1 clear on CompA match interrupt set to go off every 64 us
(which was the time it took to draw each line to the TV). Within this interrupt we toggled PORTC to encode the
image information and PORTD for the syncing information. See the HW and Schematics page to see how this is all
set up to generate the required output voltages.
Upon entering the interrupt we first set PORTC.0 = 0V (since there should be no image data while syncing)
and PORTD.5 was also set to 0V. We then checked what line we were on to see if it was a normal image line
that starts off with an hsync or if we were at the bottom of the screen and had to output the vertical sync pulses.
If it was an hsync we would wait an additional 4us (from the time that PORTD.5 = 0V) and then set PORTD.5 = 5v
to accomplish the precisely timed hsync. The code would then jump to the display code.
If, upon entering the interupt, it was decided that a vsync was necessary we jumped to the vsync loop. There was
a counter in the loop which made it execute 5 times (we know the documentation says 3 vsyncs but what can i tell
you...). After spitting out the vsyncs the line counter was now reset to 255 and the next time the interrupt was entered
it started with the hsync(ideally to do the post-equalization pulses) and in our case image data.
Because we used a char to keep track of the line number, a need for a vsync occured every 255 lines, after ouputting
the 5 vsync pulses we seem to have chopped off the bottom 2 lines of our TV but that didn't seem to hurt the final
results.
Image Display:
Along with normal data display, there were 2 special cases
at this point. Since each vertical sync is supposed to be preceeded by 3 pre-equalization pulses and followed
by 13 post-equalization pulses, you need to check for when you are on these lines and simply output no data. We
implemented the pre-equalization pulses which was enough to generate a steady TV output, if we had more time with
the project the effects of the post-equalization pulses would be investigated. When you are not towards the very
end the screen (pre-equalization pulses) or at the very top (post-equalizatoin pulses) you need to use the time
between the hsync and the image data to load the registers with the data for that line of the screen. You have
roughly 8us to do these computations before shifting your image rightward, in older TV's this 8us time was used to
bring the beam back to the next line.
Realistic resolution:
There are approximately 52 us of display time per scan line with 8 us to
setup display information. The
scheme we used was to have a screen buffer in SRAM that held the state of each
of our “pixels.” Then in the 8
us we buffered a line from memory into registers.
Then the registers were shifted left one bit at a time and the carry-bit
in SREG was outputted to PORTC.0 as the picture information of the signal.
We chose to buffer 8 bytes of memory which gave a horizontal resolution
of 64. The number of lines we could
easily store and manipulate were 32. So
the total resolution was 64x32 (256 bytes). Our actual screen array was 512 bytes. This was because there was some strange bug in displaying our output.
The display actually draws the first 256 bytes, draws black lines the rest of the screen and then vsyncs and
draws the next 256 bytes. This added a nice feature that if the byte 0 was all black and byte 256 all white,
it would flash in the same position on the screen. It wasn't what we were really going for but we ran with it.
The Mega163 only has 1024 bytes of SRAM.
The limiting factor to how much horizontal resolution is a function of
how many registers you can safely load in the 8 us setup time.
The limit to vertical resolution is 262 (NTSC standard), but in our case
was also limited by the amount of SRAM. The
screen array could not be much more than size 300 before we ran out of memory.
With 64 pixels horizontally, that gave about
0.8125 us per pixel to output. We
could have outputted pixels at a rate of 1 per 3 cycles (lsl, in, out) or every
0.375us, but then only half of the screen was used.
So we just added nop’s to extend the width of each pixel to fill the
screen. To enlarge the picture
vertically, we just redrew a line 4 times which lengthened the pixels.
Screen
Buffer
The screen buffer was 512 bytes in size. Each bit (or pair of bits as
explained above) represents a pixel. A logic 1 outputs 5V to PORTC.0 and a
logic 0 outputs 0V to PORTC.0. So 0 is for black and 1 is for white.
The screen buffer is only edited in the slow C code. The fast assembly
code is used to buffer the output in registers before displaying. Character
Library
A character library was generated, using a 8 pixel x 8 pixel area for each
character. Each character is actually 6x6 pixels with a 1 pixel black
buffer all around. Each capital letter A-Z and number 0-9 can be drawn by
call the appropriate function. The parameter passed to the function tells
the function the array offset of screen[ ] from which to start drawing.
With this method, 4 lines of 8 characters each can be displayed. And
also any character can occupy any of 192 positions on the screen. Display
Anomalies
Yes, everything is not perfect in TV Land. Many many hours were
contributed to trying to clear up the screen anomalies. These came in the
form of a flashing set of pixels on the last line, stray pixels that flash, and
screens that won't properly erase even thought the screen buffer is set to 0x00
(all black). More than likely these were not due to synchronization
problems but rather to how memory was accessed. Programming in assembly
provides almost no memory protection. Even in C the programmer has little
protection for harming vital memory. It is our belief that this lack of
protection and lack of experience using assembly to access memory is what has
led to these problems with the display. Almost everyone who sees our
screenshots asks, "Why are there those white dots?" A few stray
pixels, albeit annoying, do not really take away from the project.