Text on TV

Basic Operation

A composite video signal is split into 'scanlines', each of which is 64us long.

[an NTSC scanline is 63.55us]

Every scanline is generated in the same way.

Two global function pointers are used. One holds the current 'sync' routine to call, the other holds the current 'render' routine to call. These pointers are updated as different sections of the field are reached.

Each scanline is started by an interrupt. This is generated by an appropriately configured timer (in CTC mode). This interrupt must be regular and jitter free. A jittery scanline start will result in a display reminiscent of a poor video recording from the 80s.

Normally, AVRs have a slightly jittery interrupt latency. Before the processor can leap off to service an interrupt, the currently-executing-instruction must be completed. Because different instructions take different numbers of cycles to execute (typically one, two or three), that means jitter. It's usually only a few cycles, but that's too much for this application.

Video Output with jitter

output with 1 clock-cycle jitter

To prevent this jitter, the μC is put to sleep. When an interrupt wakes the processor from sleep, no instructions are executing, hence there is a repeatable, constant latency.

Note: The interrupt latency is actually a few cycles longer when waking from sleep, but that's not important here - what's important is the consistent latency.

The actual interrupt routine does nothing. It's empty. It's sole purpose is to wake the μC at the right time. It returns as soon as it's called.

The main loop is therefore as simple as:


    while( forever )
    {
        //go to sleep /*when the 64us interrupt wakes the uC, it will continue on the following code line*/
        //call current sync routine
        //call current render routine
    }

The 'current sync routine' is simply a function pointer to the appropriate sync routine.
The 'current render routine' is a function pointer to the appropriate render routine.

Serial Data Handling

Normally, interrupt driven serial handling would be preferred - when a character is received, an interrupt routine is magically called to process it. Unfortunately, introducing an interrupt that (essentially) fires at random points within scanlines would cause havoc with the carefully timed video signal generation.

That leaves polled reception as the only way. We need to regularly ask the processor if it's got a new character. Fortunately, there is a perfect place for polling - within the sync handling code that's called every 64us. That means that baud rates of 140k are possible (theoretically - more on this later).

Receiving the character isn't the whole story though - something must be done with it. A character processing routine must be called to actually do something.

That means that the following two things need to be done within every call to a sync handler:

Further details are given in the Serial Data and Control Sequence Handling sections.

Displaying Pixels

It's not just the initial sync pulse in a scanline that needs to have an accurate start point. The timing of the first pixel on a scanline must be accurate too. If not, vertical lines on the display won't be straight and we're back the the '80's VCR' look.

The problem is that character processing is carried out within the Sync routines. That processing takes take different times, depending on what function is needed doing. That makes it almost impossible to calculate how long it is before the first font pixel should be output.

To accurately place the first pixel on a line, the solution is to put the processor to sleep after the character processing is complete, and use another interrupt to wake the processor. Luckily, there is a spare 'output compare match' interrupt within the existing 64us timer. This 'Display Start' interrupt is configured to fire at 12.5us into the scanline. Any earlier than this and the slowest of serial processing will not have completed. Any later than this and there won't be time to fit all 38 characters into the scanline.

[ NTSC signals have a shorter back-porch than PAL, hence the 'visible portion' of the signal starts earlier - for our purposes, we want to start rendering from 11.5us into the scanline. This means that there was no longer quite enough time to process any serial data received. To claw back some time, the second 'sleep/wakeup' interrupt for 'display start' had to be abandoned. The overhead was just too much (e.g. it was taking too long to go to sleep and wake-up again). An alternative jitter-free timing method was found. The same 'output compare match' is used, but without the interrupt. Instead, the processor is put into a tight loop until the compare-match flag is set. It then performs a small timing adjustment to offset any jitter. The adjustment needed is determined by looking at the bottom couple of bits of the timer counter. ]