tvText
This library allows you to generate text display on a PAL TV using an ATmega168 running at 20MHz and two resistors to form a 2-bit DAC. The video signal generation is interrupt-driven, leaving some CPU time over for your own program.
The current specifications are as follows:
- Overall: 192×128 pixel resolution.
- 6×8 or 8×8 pixel character size.
- 32×16 or 24×16 characters, depending on current character size.
- Console-style text output routines with support for common command sequences (eg CR and LF) modelled on the BBC Micro's VDU.
- User-definable text viewports, allowing you to clear/scroll "windows" on the screen.
- Flashing cursor (variable refresh rate, runtime-selectable character, ability to be disabled).
- Flag to invert the entire screen (black on white as opposed to the default white on black).
The current version of the library, accompanying documentation and the source code for the demo program shown above can be downloaded from here.
Developer Manual
tvText is a library for use with Atmel AVR microcontrollers to provide text output on a television set using nothing more than two resistors and a 20MHz resonator.
It has been primarily developed for use with the ATmega168, but should be portable to other AVRs.
It outputs the picture signal on the most-significant bit of a user-defined I/O port. The remaining bits in the PORT register will be effectively filled with random data, but can be used as input pins (watch out for the internal pull-up resistors being enabled!) The sync signal is output on a single pin on one other I/O port – the rest of that port can be used as normal.
The source code is made available under the MIT licence. In short: do with it what you will.
Hardware
Two resistors are used to form a 2-bit DAC to generate the voltages required for composite video – 0V for sync, 0.3V for black and 1V for white. Assuming a 75Ω resistance in the television, the ideal resistor values are 450Ω and 900Ω – however, 470Ω and 1KΩ work just as well. The AVR needs to be run at 20MHz, so use an external 20MHz resonator and suitable load capacitors. Remember to set the fuses to use an external resonator and disable CKDIV8!
A simplified circuit diagram is shown above, showing the resonator and resistor DAC connections. The smaller resistor (450Ω) needs to be connected to the most significant pin on the TVTEXT_PICTURE_PORT (PD7 by default). The larger resistor (900Ω) needs to be connected to the pin defined as the sync pin by the TVTEXT_SYNC_PORT and TVTEXT_SYNC_BIT settings (PB0 by default).
Consult the data sheet for your AVR for more detailed instructions.
Software
This package contains the following files:
- tvtext/tvtext.c – Main library source code. Provides the high-level console functions, and should be added to your own projects.
- tvtext/tvtext.h – Header file for the library that should be #included into your own projects.
- tvtext/config.h – Various hardware and software configuration details.
- tvtext/driver.S – Low-level assembly driver that is responsible for generating the video signal.
- tvtext/font_6.h – A 6×8 pixel 256-character font that is loaded into the program memory.
- tvtext/font_8.h – An 8×8 pixel 256-character font that is loaded into the program memory.
- main.c – An example program that shows off some of the features of the library.
The various configuration options, functions and variables are discussed below.
Configuration
The config.h file can be modified to change the pins that are used to output the video signal.
For performance reasons, an entire I/O port is used to output the picture data (a 1 bit for white pixels and a 0 bit for black pixels). The picture data is always output on the most significant bit of this port. You can change this port by changing the TVTEXT_PICTURE_PORT and TVTEXT_PICTURE_DDR definitions (both must refer to the same port).
The pin used to generate synchronisation pulses can have any pin number within the port (configured with TVTEXT_SYNC_BIT), and may be any port other than the one used for picture data (configured with TVTEXT_SYNC_PORT and TVTEXT_SYNC_DDR).
Every scanline (row) of the generated video signal is output twice. Enabling the TVTEXT_SKIP_ALTERNATE_ROWS configuration switch will cause the driver to skip alternate rows (outputting a plain black scanline instead), only outputting each scanline once. This greatly reduces the amount of time the driver needs to spend outputting the video signal, at the expense of picture quality.
The width of characters can be controlled with the TVTEXT_CHARACTER_WIDTH setting. This can be 6 (for 6×8 characters in a 32×16 grid) or 8 (for 8×8 characters in a 24×16 grid). This setting will control the value of TVTEXT_BUFFER_WIDTH, which represents the width of the text buffer in characters.
Set TVTEXT_FONT to the name of a font file if you'd like to replace the default. This should be a file path in "quotation marks", relative to the tvtext directory.
Some TVs will crop some pixels from the left and right hand side of the image. You can avoid this by enabling TVTEXT_SQUASHED_HORIZONTALLY, which reduces the width of the fourth and seventh pixel (if applicable) column in every character to 4/5 the usual width. This isn't too noticable with a full screen of text (less noticable than text getting cropped from the sides of the display!) but can show up in certain graphical designs. For this reason, it is recommended that you enable it if you intend on using full screens of text, but leaving it disabled if you're using a more graphical output (e.g. a game) and keep away from these edges. In both instances, it is recommended that you provide the user with some way to set tvtext_offset_x to fine-tune the horizontal position of the display.
Variables
The functions of the variables are summarised below.
Variable | Type | Description | Default |
---|---|---|---|
tvtext_flags | uint8_t |
This variable stores a number of flags that control the appearance of the behaviour of the text console:
|
TVTEXT_VISIBLE, TVTEXT_AUTO_SCROLL, TVTEXT_CURSOR_ENABLED and TVTEXT_CURSOR_VISIBLE are set. |
tvtext_buffer | char[] | This array stores every character on the screen in left-to-right, top-to-bottom order. The address of any particular character on the screen can be calculated with row * 32 + column. | Filled with the clear character (tvtext_cleared). |
tvtext_cursor_column tvtext_cursor_row |
int8_t | These variables store the column and row that the text cursor resides in. If you change the cursor position manually, please ensure that it is within the text viewport before calling any of the text output functions – it is usually safer and easier to call tvtext_cursor_move() instead. | 0 |
tvtext_cursor | char | This is the character used to represent the flashing cursor (if enabled). | '_' |
tvtext_cleared | char | This is the character used to clear the viewport when tvtext_clear() is called or to fill in new rows or columns when the viewport is scrolled. | ' ' |
tvtext_viewport_left | uint8_t | These four variables define the edges of the current text viewport. All coordinates are inclusive – a left edge of 0 and a right edge of 31 results in a viewport 32 characters wide. If changing these values manually, please ensure that the cursor is moved inside the viewport before calling any of the text output functions – it is usually safer and easier to call tvtext_set_viewport() instead. | 0 |
tvtext_viewport_bottom | 15 | ||
tvtext_viewport_right | TVTEXT_BUFFER_WIDTH-1 | ||
tvtext_viewport_top | 0 | ||
tvtext_cursor_flash_period | uint8_t | The period (in frames) that it takes the flashing cursor to toggle between being shown and being hidden. A value of 50 would show the cursor for 50 frames (one second) then hide it for 50 frames (another second). | 16 |
tvtext_cursor_flash_timer | uint8_t | This timer counts down every frame. It starts at tvtext_cursor_flash_period, counts down to zero, then inverts the cursor state and restarts. | N/A |
tvtext_frame_counter | int16_t | This counter is incremented every time a frame is completed and the rasteriser enters vertical sync period. Feel free to modify it and use it for your own timing purposes. | 0 |
tvtext_offset_x | uint8_t |
This variable can be used to move the displayed text area left or right on the screen to some extent.
A larger value moves the display right, a smaller value moves it left. Each unit corresponds to 3/5 of a pixel's width. You must ensure that this value is greater than or equal to zero, and less than or equal to TVTEXT_OFFSET_X_MAX – if you exceed these values, the library will generate an invalid video signal that your TV will not be able to display correctly. It is recommended that you provide the user with some way to set this variable, to ensure that the text buffer is centred correctly on their TV and not have pixels from the left or right hand side cropped. |
TVTEXT_OFFSET_X_DEFAULT |
tvtext_offset_y | int16_t |
This variable can be used to move the displayed text area up and down on the screen.
A positive value moves the display down, a negative value moves it up. The active part of the TV display is 304 scanlines tall. Each row of the 192×128 pixel display is doubled vertically to fill two scanlines, making the displayed text area 256 scanlines tall. 32 scanlines above and 16 scanlines below this text area are left blank. To move the entire display down one 8 pixel row, you would need to set this variable to 16 due to the line doubling. |
0 |
Functions
The only function you need to call is tvtext_init() – you can output text by directly writing to the text buffer. Several other functions are provided to help you get text on the display more easily, however.
void tvtext_putc(char c);
This function writes a single character to the text buffer and advances the cursor.
If the cursor moves off the side of the viewport, it is advanced to the next line. If the cursor was previously on the bottom line of the viewport and TVTEXT_AUTO_SCROLL is enabled, the viewport scrolls up and the cursor is placed back on the bottom line. If the cursor was previously on the bottom line of the viewport and TVTEXT_AUTO_SCROLL is disabled, it wraps back around to the top of the viewport.
for (char c = 'A'; c <= 'Z'; ++c) { tvtext_putc(c); }
If the character value is in the range 0~31 or is 127, a special function is carried out. These functions aim to replicate those found on the BBC Micro and in subsequent versions of BBC BASIC.
Code | Function |
---|---|
0 | Nothing. This value is ignored. |
4 |
Enables auto-scrolling mode (sets the TVTEXT_AUTO_SCROLL bit in tvtext_flags). When the cursor moves below the bottom of the viewport the viewport is scrolled up and the cursor is moved back to the bottom line. When the cursor moves above the top of the viewport the viewport is scrolled down and the cursor is moved back to the top line. |
5 |
Disables auto-scrolling mode (clears the TVTEXT_AUTO_SCROLL bit in tvtext_flags). When the cursor moves below the bottom of the viewport it is moved back to the top line. When the cursor moves above the top of the viewport it is moved back to the bottom line. |
8 |
Moves the cursor left one character. If the cursor moved beyond the left edge of the viewport it is moved up a line and to the right edge of the viewport. |
9 |
Moves the cursor right one character. If the cursor moved beyond the right edge of the viewport it is moved down a line and to the left edge of the viewport. |
10 |
Moves the cursor down one line. If the cursor moved beyond the bottom edge of the viewport then either the viewport scrolls up and the cursor is moved to the bottom line or the cursor is moved to the top edge of the viewport, depending on the setting of TVTEXT_AUTO_SCROLL. |
11 |
Moves the cursor up one line. If the cursor moved beyond the top edge of the viewport then either the viewport scrolls down and the cursor is moved to the top line or the cursor is moved to the bottom edge of the viewport, depending on the setting of TVTEXT_AUTO_SCROLL. |
12 | The viewport is cleared. The character used to clear the viewport is controlled with the tvtext_cleared variable. |
13 | The text cursor is moved to the left edge of the display. |
23 |
After writing this character, you must write a further nine bytes of data. The action performed depends on the values you have written.
|
26 | This command resets the viewport to fill the entire screen and moves the cursor to its top left. |
27 | The character written immediately after this one goes directly to the screen. This allows you to print special characters in the 0~31 or 127 range. |
28 |
The next four bytes written to the screen are interpreted as the new left, bottom, right and top edges of the viewport respectively. If the cursor is outside the new viewport, it is moved to its top left – otherwise, it is left alone. |
30 | The cursor is moved to the top left of the current viewport. |
31 |
The next two bytes written to the screen are interpreted as the new column and row of the cursor respectively. The cursor will be clipped inside the current viewport. |
127 | The cursor is moved one character left. The character under the new cursor position is then cleared using the tvtext_cleared variable. |
void tvtext_puts(const char* s)
This function writes a NUL-terminated string held in SRAM to the display using the tvtext_putc() function.
tvtext_puts("Hello, world!");
void tvtext_puts_P(const char* s)
This function writes a NUL-terminated string stored in program memory to the display using the tvtext_putc() function.
tvtext_puts_P(PSTR("Hello, world!"));
This function is strongly recommended over tvtext_puts() for constant strings as it doesn't require a copy of the string to be stored in SRAM.
void tvtext_cursor_left(void)
Moves the cursor left one character.
If the cursor moved beyond the left edge of the viewport it is moved up a line and to the right edge of the viewport.
void tvtext_cursor_right(void)
Moves the cursor right one character.
If the cursor moved beyond the right edge of the viewport it is moved down a line and to the left edge of the viewport.
void tvtext_cursor_down(void)
Moves the cursor down one line.
If the cursor moved beyond the bottom edge of the viewport then either the viewport scrolls up and the cursor is moved to the bottom line or the cursor is moved to the top edge of the viewport, depending on the setting of TVTEXT_AUTO_SCROLL.
void tvtext_cursor_up(void)
Moves the cursor up one line.
If the cursor moved beyond the top edge of the viewport then either the viewport scrolls down and the cursor is moved to the top line or the cursor is moved to the bottom edge of the viewport, depending on the setting of TVTEXT_AUTO_SCROLL.
void tvtext_cursor_clear(void)
The viewport is cleared. The character used to clear the viewport is controlled with the tvtext_cleared variable.
void tvtext_cursor_scroll_right(void)
Scrolls the viewport contents right one character.
void tvtext_cursor_scroll_left(void)
Scrolls the viewport contents left one character.
void tvtext_cursor_scroll_down(void)
Scrolls the viewport contents down one line.
void tvtext_cursor_scroll_up(void)
Scrolls the viewport contents up one line.
void tvtext_reset_viewport_cursor_home(void)
Resets the viewport to fill the entire screen and moves the cursor to its top left.
void tvtext_set_viewport(int8_t left, int8_t bottom, int8_t right, int8_t top)
Sets the boundaries of the text viewport. If the cursor is outside the new viewport, it is moved to its top left – otherwise, it is left alone.
void tvtext_cursor_home()
The cursor is moved to the top left of the current viewport.
void tvtext_cursor_move(uint8_t column, uint8_t row)
Moves the cursor to a particular location on the screen. The cursor will be clipped inside the current viewport.
void tvtext_wait_vsync(void)
Waits for the rasteriser to enter the vsync period. During this period the library will be generating sequences that indicate that one frame is about to end and the next is about to begin. These vertical synchronisation sequences are fortunately quite simple and so the AVR has more time to spend running your program. Updating the text buffer between frames also reduces visual tearing, producing a smoother display. You may also use this function for timing purposes – at 50Hz, calling this function fifty times in a row will produce a one second delay.
void tvtext_cursor_reset_flash(void)
This function sets TVTEXT_CURSOR_VISIBLE and resets tvtext_cursor_flash_timer to tvtext_cursor_flash_period. It is automatically called whenever the cursor is moved.
uint8_t tvtext_get_font_row(char c, uint8_t row)
This function can be used to retrieve a row of pixels from a particular character in the font.
The returned value stores the first pixel in the most significant bit, the second pixel in the next most significant bit, the third in the next most significant bit after that and so on. A set bit indicates a white ("on") pixel.
tvtext_clear(); // Loop over every pixel in the character. for (uint8_t row = 0; row < 8; ++row) { // Fetch a row of pixels from the font for the '@' sign. uint8_t pixels = tvtext_get_font_row('@', row); for (uint8_t column = 0; column < 6; ++column) { // Is the bit set (on?) If so, draw it on the screen as a '*'. if (bit_is_set(pixels, 7 - column)) { tvtext_cursor_move(column, row); tvtext_putc('*'); } } }
References
The following links were extremely useful when writing this library.