Text on TV

Display Line Rendering

A rendering routine (usually) displays a single slice of font data for each character in a row.

There are four different display line renderers:

A global variable, g_render_handler, holds a pointer to the currently selected rendering routine.

Note: There are no separate routines for double-height characters. When looking at a single scanline, double-height characters are rendered in exactly the same way as normal height characters. The only difference is in how often font-slice is changed. Normal characters change font slices every scanline ; Double height characters change font slices every-other scanline.

Which font-slice to use is pre-calculated at the end of the previous scanline.

Pre-calculations:

The routine that pre-calculates variables for the next scanline is common for all the font rendering routines. It ensures that the font slice base pointer is incremented (if necessary) and that the character pointer is reset back to the first character in the row. At the end of the 9th slice, it resets these pointers and also chooses the correct rendering routine for the next character row (wide, blank etc.).

Normal Font Renderer

Pseudocode is by far the simplest way of explaining how this renderer works:


    /* over-simplified version */
    for each column
    {
        // retrieve character from the display array
        // retrieve the characters one-byte-pixel-pattern from the font array
        // if this is the character at the cursor position, invert the pattern.

        // give the SPI module the new one-byte-pixel-pattern.
    }
    // call routine to pre-calculate variables for the next scanline

The pixels are shuffled onto the SIG_PIXEL pin using the hardware SPI module. This will shift the bits out 'in the background' whilst the program moves on to the next column. That means that we can send out pixel data as fast as the SPI module can.

The '9th bit' problem.

Unfortunately, there are some complications with using the SPI module.
Because the SPI interface is normally used for inter-chip communication, it insists on outputting a 9th 'idle' bit. This idle-bit is always high. That's a white pixel. That means that every 9th pixel will be white. Always. You can't even get around it by giving the SPI module the next byte 'early' - the SPI module just ignores your new byte. You can only output a byte every 18 clock cycles.

The problem with having every ninth pixel being white is that you get vertical white stripes over the text area of the screen.

There are two solutions to this 9th white bit:

The Easy Solution - Camouflage it:

The 'camouflage' solution was used during most of the development of this project.

Then a cunning second solution was found...

The Sneaky Solution - Control the pin:

This is an excellent solution - it means we don't need to invert the whole display. Having been used to testing with an inverted screen for a week or so, it's a breath of fresh air to see a normal white-on-black screen again!

It's still not perfect though. The 9th pixel would now always be black. That's not a problem for normal characters, but when it comes to the 'graphics' characters in code-page 437, they're designed to be right next to each other. Having a gap between them isn't the end of the world, but it's not ideal. Fortunately, we can choose to switch the pin to input or not. We are then in control of that 9th pixel. The 9th pixel must still be there if we use SPI, but we can now control it's colour.

We chose to control the 9th pixel by duplicating the 8th pixel (or to look at it another way - make the 8th pixel twice as wide as the others). For almost all non-graphics characters in the font, the 8th pixel is unused. Those few non-graphics characters that did use the 8th pixel have had their bitmaps 'adjusted'.

The pseudocode now becomes:


    for each column
    {
        // retrieve character from the display array
        // retrieve the characters one-byte-pixel-pattern from the font array
        // if this is the character at the cursor position, invert the pattern.

        // if the 8th bit of the previous pattern is 0, switch SIG_PIXEL pin to input
        // delay for 1 pixel
        // switch SIG_PIXEL pin to output

        // give the SPI module the new one-byte-pixel-pattern.
    }
    // call routine to pre-calculate variables for the next scanline

In order that the 'switch SIG_PIXEL pin to output' and the start of the new pixel pattern occur at the same time, the 'clock-phase' of the SPI output is set to '1', meaning the pixels are output at the 'leading edge' of the clock cycles. Without this, there is a one-cycle (half a pixel) gap between the pin being enabled and the pixel data starting.

The following diagram shows a sample of 9 scanlines, using the duplicate 9th bit...

9 scanlines showing the duplicated 9th bit

9 scanlines showing the duplicated 9th bit

It works nicely for normal characters
It works nicely for box-drawing and solid-fill graphics characters
It's not perfect for 'dithered' graphics characters, because the double-8th pixel breaks the dither pattern.
It's vastly better than an inversed screen.
It's just possible to do within the 18 clock cycles available

Double-width Font Renderer

This is almost identical to the normal font rendering routine, but for 2 differences:

All other details remain the same.

Blank Font Renderer

This routine renders scanlines where the Row Attribute indicates that it shouldn't be displayed. Technically, it could simply ensure the next scanline's pre-calculations are done and then return, but it actually closely mimics the normal font renderer by using the SPI module to output 38 'blank' characters. This is left-over from the original 'inversed display' version, but has been left in in case of future enhancements (e.g. an 'invert screen' option).

Non-display Renderer

This rendering routine doesn't do any rendering at all.

It is called for any scanline that doesn't contain character pixels - in other words, the scanlines outside of the 25 character rows.

Its purpose is to switch to other sync or render routines at the appropriate point through the frame/field. When the next scanline contains the vertical sync, it simply selects the 'Field Sync' sync routine. When the next scanline is the first with font pixels, it calls the pre-calculation routine and selects the appropriate font rendering routine (according to the row's attributes).