📄 video.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0053)http://instruct1.cit.cornell.edu/courses/ee476/video/ -->
<HTML><HEAD><TITLE>Video</TITLE>
<META http-equiv=Content-Type content="text/html; charset=iso-8859-1">
<META content="MSHTML 6.00.2800.1400" name=GENERATOR></HEAD>
<BODY bgColor=#ffffff>
<P align=center><FONT size=+2>Cornell University<BR>Electrical Engineering 476
<BR>Video Generation<BR></FONT><FONT size=+2>with AVR
microcontrollers</FONT></P>
<P align=left><B>Introduction</B></P>
<P align=left>A television (TV) monitor is a good example of a device which
needs hard-realtime control. The TV is controlled by periodic synchronization
(sync) pulses. If a sync pulse is late, the TV picture quality suffers. So a
late calculation is worth nothing. The AVR mcus are fast enough to generate
black and white (but not color) sync and image content, as long as you
obsessively pay attention to the time required for every operation.</P>
<P align=left>Small TV monitors also seem to be one of the cheapest graphics
devices you can buy, include audio amplifiers and speakers, and are designed to
be almost bullet-proof. A perfect lab device. </P>
<P align=left><B>Video Generation</B></P>
<P align=left>There are several very good references for understanding how TVs
are controlled by a pulse sequence. I particularly liked the Stanford EE281 <A
href="http://www.stanford.edu/class/ee281/handouts/lab4.pdf">Handout #7
</A>entitled "TV paint". Also very useful were <A
href="http://www.rickard.gunee.com/projects/">Software generated video</A>, by
Rickard Gun閑, <A href="http://www.williamson-labs.com/480_tv.htm">Video
Tutorials</A> by Glen A. Williamson, and <A
href="http://www.geocities.com/CapeCanaveral/Launchpad/3632/dvm.htm">various
excellent projects</A> by Alberto Riccibitti.</P>
<P align=left>The goal is to generate non-interlaced, black and white video,
with enough image content to be interesting as a graphics device. The software
described here implements an NTSC-rate, non-interlaced, video signal. For ease
of teaching, I wanted as much of the code as possible to be in C, with litttle
or no assembler. The first version of the code is entirely in CodeVision C and
generates a 64 (horizontal) by 100 (vertical) bit map. The CodeVision compiler
controls (in the <CODE>Project:Configure... </CODE>menu) must be set to default
to:</P>
<UL>
<LI><CODE>signed char</CODE>
<LI>a data stack size of <=100
<LI>optimize for speed. </LI></UL>
<P align=left>The "video engine" consists of two parts:</P>
<UL>
<LI>Sync generation
<LI>Image generation </LI></UL>
<P align=left>These parts are described in the next two paragraphs.</P>
<P align=left><U>Sync Generation</U></P>
<P align=left>Since uniform pulses are required every 63.5 microseconds to cause
horizontal sync, the natural choice is to run timer 1 at full speed (8 MHz), and
interrupt on compare match after 508 timer ticks. However, if you use 63.625
microseconds (509 timer ticks), then each frame is exactly 1/60 of a second, so
building software timers is easier. If you just enter the ISR directly from
running code, the time to enter can vary by a couple of machine cycles, which is
several 100 nanoseconds. This variability causes unacceptable visual jitter on
the TV. The solution, originally used by Alberto Riccibitti is to put the mcu to
sleep just before the interrupt will occur. The version of the code shown below
puts the sleep command in a while loop which is executed once per horizontal
line for lines 1 to 230 (the image content). Between line 231 and line 262, the
main loop executes once per frame functions, such as raster updates. During this
interval, a few sleep commands are scattered through the code to keep the sync
accurate. The ISR code which generates the sync is shown below. All of the logic
for counting lines, inverting the horizontal sync to make vertical sync, and
running the i/o port are contained within the 5 microsecond pulse time. This ISR
must be entered from the mcu "sleep" state to reduce jitter.</P>
<P align=left><PRE>interrupt [TIM1_COMPA] void t1_cmpA(void)
begin
//start the Horizontal sync pulse
PORTD = syncON;
//count timer 0 at 1/usec
TCNT0=0;
//update the curent scanline number
LineCount ++ ;
//begin inverted synch after line 247 to make vertical sync
//note that at least 3 lines before this must be left at BLACK
//level for the TV to lock on properly
if (LineCount==248)
begin
syncON = 0b00100000;
syncOFF = 0;
end
//back to regular sync after line 250 to make the
//required 10 blank lines before starting a new frame
if (LineCount==251)
begin
syncON = 0;
syncOFF = 0b00100000;
end
//start new frame after line 262
if (LineCount==263)
begin
LineCount = 1;
end
//end sync pulse
PORTD = syncOFF;
end
</PRE>
<P><U>Image Generation</U></P>
<P align=left>The displayed image is stored in an 800 byte array in the RAM of
the Mega163. The image bits are packed so than 8 displayed bits are stored in
each byte. This gives a resolution of 6400 total addressable points. To speed up
dot generation (and produce a tighter raster) the eight array bytes for one line
are preloaded into registers. Each bit can then be put on the screen in 4
machine cycles. All loops must be unrolled to keep the rate constant during one
line. There are a set of variables which are declared which must be the very
first variables to be declared. The compiler puts these in registers. The code
depends on register allocation for speed. <FONT color=#ff0000>Note that
CodeVision version 1.23.7 changes register allocations.</FONT> A new version
which corrects the complier dependencies is in the the <I>Optimization and Bug
Fixes</I> section near the bottom of this page. Also near the bottom of the page
is an improved <FONT color=#ff0000>Mega32 version</FONT>.</P><PRE>char syncON, syncOFF, v1, v2, v3, v4, v5, v6, v7, v8;
int i,LineCount;
</PRE>
<P align=left>The actual image is generated by copying RAM into registers v1-v8
at the beginning of a line, then blasting them out of registers as fast as
possible to make a dense raster. The following while loop waits for each sync
pulse in sleep mode, then processes one line.</P><PRE>while(1)
begin
//precompute pixel index for next line
if (LineCount<ScreenBot && LineCount>=ScreenTop)
begin
//left-shift 3 would mean individual lines
// << 2 means line-double the pixels so that they are
//about as tall as they are wide.
//The 0xfff8 constant clips the lower 3 bits so that the
//byte address within a line can be added after the sleep.
i=(LineCount-ScreenTop)<< 2 & 0xfff8;
end
//stall here until next line starts
//sleep enable; mode=idle
//use sleep to make entry into sync ISR uniform time
#asm ("sleep");
//The code here to executes once/line.
//--Usable lines 1 to about 240, but
// the raster is written on lines 30 to 230.
// Any other processing must occur on lines 1-30 or 231-262.
if (LineCount<ScreenBot && LineCount>ScreenTop)
begin
//load the pixels for one line into registers for speed
v1 = screen[i];
v2 = screen[i+1];
v3 = screen[i+2];
v4 = screen[i+3];
v5 = screen[i+4];
v6 = screen[i+5];
v7 = screen[i+6];
v8 = screen[i+7];
//now blast the pixels out to the screen
PORTD.6=v1 & 0b10000000;
PORTD.6=v1 & 0b01000000;
PORTD.6=v1 & 0b00100000;
PORTD.6=v1 & 0b00010000;
PORTD.6=v1 & 0b00001000;
PORTD.6=v1 & 0b00000100;
PORTD.6=v1 & 0b00000010;
PORTD.6=v1 & 0b00000001;
PORTD.6=v2 & 0b10000000;
PORTD.6=v2 & 0b01000000;
PORTD.6=v2 & 0b00100000;
PORTD.6=v2 & 0b00010000;
PORTD.6=v2 & 0b00001000;
PORTD.6=v2 & 0b00000100;
PORTD.6=v2 & 0b00000010;
PORTD.6=v2 & 0b00000001;
PORTD.6=v3 & 0b10000000;
PORTD.6=v3 & 0b01000000;
PORTD.6=v3 & 0b00100000;
PORTD.6=v3 & 0b00010000;
PORTD.6=v3 & 0b00001000;
PORTD.6=v3 & 0b00000100;
PORTD.6=v3 & 0b00000010;
PORTD.6=v3 & 0b00000001;
PORTD.6=v4 & 0b10000000;
PORTD.6=v4 & 0b01000000;
PORTD.6=v4 & 0b00100000;
PORTD.6=v4 & 0b00010000;
PORTD.6=v4 & 0b00001000;
PORTD.6=v4 & 0b00000100;
PORTD.6=v4 & 0b00000010;
PORTD.6=v4 & 0b00000001;
PORTD.6=v5 & 0b10000000;
PORTD.6=v5 & 0b01000000;
PORTD.6=v5 & 0b00100000;
PORTD.6=v5 & 0b00010000;
PORTD.6=v5 & 0b00001000;
PORTD.6=v5 & 0b00000100;
PORTD.6=v5 & 0b00000010;
PORTD.6=v5 & 0b00000001;
PORTD.6=v6 & 0b10000000;
PORTD.6=v6 & 0b01000000;
PORTD.6=v6 & 0b00100000;
PORTD.6=v6 & 0b00010000;
PORTD.6=v6 & 0b00001000;
PORTD.6=v6 & 0b00000100;
PORTD.6=v6 & 0b00000010;
PORTD.6=v6 & 0b00000001;
PORTD.6=v7 & 0b10000000;
PORTD.6=v7 & 0b01000000;
PORTD.6=v7 & 0b00100000;
PORTD.6=v7 & 0b00010000;
PORTD.6=v7 & 0b00001000;
PORTD.6=v7 & 0b00000100;
PORTD.6=v7 & 0b00000010;
PORTD.6=v7 & 0b00000001;
PORTD.6=v8 & 0b10000000;
PORTD.6=v8 & 0b01000000;
PORTD.6=v8 & 0b00100000;
PORTD.6=v8 & 0b00010000;
PORTD.6=v8 & 0b00001000;
PORTD.6=v8 & 0b00000100;
PORTD.6=v8 & 0b00000010;
PORTD.6=v8 & 0b00000001;
PORTD.6=0 ;
end
</PRE>
<P align=left>The raster generation code needs to run fast to get good pixel
density. Once the TV gets to line 231, there is some extra time to do
computations, interact with users, or update state. See the code below for
examples.</P>
<P align=left><B>Video DAC</B></P>
<P align=left>Two bits of a port are used to generate three video levels:</P>
<UL>
<LI>Sync level = 0 volts
<LI>Black level = 0.3
<LI>White level = 1.0 </LI></UL>
<P>The circuit is shown below.</P>
<P><IMG height=209 src="Video.files/VideoDAC.png" width=354></P>
<P align=left><B>Graphics primitives</B></P>
<P align=left>There are a few primitive operations which are nice to have:</P>
<UL>
<LI>Draw/erase/invert a point
<LI>Detect a point intensity
<LI>Draw text
<LI>Draw lines </LI></UL>
<P>The point, line and detection graphics primitives are implemented in the <A
href="http://instruct1.cit.cornell.edu/courses/ee476/video/videoKal.c">fourth
example code</A> below. The fifth code example has improved character
generation. I implemented three character generators. The 8x8 and 3x5 pixel
character generators are very fast, but have limited placement of characters in
the x-direction. The 5x7 character generator has pixel-accurate placement, but
is much slower.</P>
<P>All drawing must be done during TV scan lines 230-260 and 1-30 to avoid
visual artifacts (flickering). Drawing is done by writing bits (pixels) into the
main <CODE>screen</CODE> array, while the array is not being displayed.</P>
<P><U>Draw a point</U></P>
<P>The point code is a function which takes three parameters: an x coordinate in
the range of 0-63, a y coordinate in the range of 0-99, and a color given as 0
means black, 1 means white, and 2 means invert the pixel. The pixels are packed
into bytes, so there is an addressing step which looks strange. There are 8
pixels/byte and 8 bytes/line. Thus, the x coordinate has to be divided by 8 to
get the byte number within a line and the y coordinate has to be multiplied by 8
to get the appropriate group of 8 bytes corresponding to a line. The
<CODE>1<<(7-(x & 0x7))</CODE> construction isolates (within the byte)
which bit must be set/cleared.</P><PRE>//plot one point
//at x,y with color 1=white 0=black 2=invert
void video_pt((char x, char y, char c)
begin
//The following odd construction
//sets/clears exactly one bit at the x,y location
i=((int)x >> 3) + ((int)y << 3) ;
if (c==1) screen[i] = screen[i] | 1<<(7-(x & 0x7));
if (c==0) screen[i] = screen[i] & ~(1<<(7-(x & 0x7)));
if (c==2) screen[i] = screen[i] ^ (1<<(7-(x & 0x7)));
end
</PRE>
<P><U>Read back a point </U>
<P>
<P align=left>The read back code is a function which takes an x coordinate in
the range of 0-63, a y coordinate in the range of 0-99, and returns a character
with value 1 if the pixel is white and 0 if it is black.</P><PRE>//return the value of one point
//at x,y with color 1=white 0=black 2=invert
char video_set((char x, char y)
begin
//The following construction
//detects exactly one bit at the x,y location
i=((int)x >> 3) + ((int)y << 3) ;
return ( screen[i] & 1<<(7-(x & 0x7)));
end
</PRE>
<P>
<P align=left><U>Draw a line</U></P>
<P align=left>The line drawing code takes two (x,y) coordinates and connects
them with a line drawn with a Breshenham algorithm. The Breshenham algorithm
avoids all multiplys and divides (except by 2), so it is fast on a small
processor. It also ensures a dense line with points in adjacent pixels. The
CodeVision compiler control panel must set to allow signed <B>char</B>s.</P><PRE>//plot a line
//at x1,y1 to x2,y2 with color 1=white 0=black 2=invert
//NOTE: this function requires signed chars
//Code is from David Rodgers,
//"Procedural Elements of Computer Graphics",1985
void video_line(char x1, char y1, char x2, char y2, char c)
begin
char x,y,dx,dy,e,j, temp;
char s1,s2, xchange;
x = x1;
y = y1;
dx = cabs(x2-x1);
dy = cabs(y2-y1);
s1 = csign(x2-x1);
s2 = csign(y2-y1);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -